diff --git a/.github/ISSUE_TEMPLATE/submit-recipe.yml b/.github/ISSUE_TEMPLATE/submit-recipe.yml
index c9b6b0d5aa..b2af2ae9e5 100644
--- a/.github/ISSUE_TEMPLATE/submit-recipe.yml
+++ b/.github/ISSUE_TEMPLATE/submit-recipe.yml
@@ -16,9 +16,7 @@ body:
πͺ **What Happens After?**
- If accepted, we'll publish your recipe to the [goose recipes cookbook](https://block.github.io/goose/recipes)
- - You'll receive OpenRouter **LLM API credits** as a thank you!
- Your GitHub handle will be displayed and linked on the recipe card
- - If you provide an email below, we'll email you your credits when your recipe is approved and merged.
- If the YAML has any issues, goose will comment with validation errors so you can fix and resubmit.
π§ͺ **Pro Tip:** You can test your recipe locally in your terminal with:
@@ -86,15 +84,6 @@ body:
validations:
required: true
- - type: input
- id: email
- attributes:
- label: Your Email (optional)
- description: If your recipe is approved, we'll email your LLM API credits here.
- placeholder: yourname@example.com
- validations:
- required: false
-
- type: markdown
attributes:
value: |
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index bc90ec22d9..b7cd0bdd4b 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -31,6 +31,3 @@ Before:
After:
-### Submitting a Recipe?
-
-**Email**:
diff --git a/.github/scripts/send_key.py b/.github/scripts/send_key.py
deleted file mode 100644
index 554873eadd..0000000000
--- a/.github/scripts/send_key.py
+++ /dev/null
@@ -1,229 +0,0 @@
-import os
-import requests
-import re
-import email_validator
-from sendgrid import SendGridAPIClient
-from sendgrid.helpers.mail import Mail
-from python_http_client.exceptions import HTTPError
-
-
-def main():
- # Environment variable validation
- required_envs = ["GITHUB_TOKEN", "GITHUB_SHA", "GITHUB_REPOSITORY", "PROVISIONING_API_KEY", "EMAIL_API_KEY"]
- missing = [env for env in required_envs if env not in os.environ]
- if missing:
- print(f"β Missing environment variables: {', '.join(missing)}")
- exit(2)
-
- GITHUB_TOKEN = os.environ["GITHUB_TOKEN"]
- GITHUB_SHA = os.environ["GITHUB_SHA"]
- REPO_NAME = os.environ["GITHUB_REPOSITORY"]
- PROVISIONING_API_KEY = os.environ["PROVISIONING_API_KEY"]
- SENDGRID_API_KEY = os.environ["EMAIL_API_KEY"]
-
- pr_number = get_pr_number_from_sha()
- pr_data = fetch_pr_body(pr_number, GITHUB_TOKEN, REPO_NAME)
- pr_body = pr_data.get("body", "")
-
- email = extract_email(pr_body, REPO_NAME, pr_number, GITHUB_TOKEN)
- print(f"π¬ Found email: {email}")
-
- try:
- api_key = provision_api_key(PROVISIONING_API_KEY)
- print("β
API key generated!")
-
- if not send_email(email, api_key, SENDGRID_API_KEY):
- print("β Email failed to send. Exiting without PR comment.")
- exit(2)
-
- comment_on_pr(GITHUB_TOKEN, REPO_NAME, pr_number, email)
-
- except Exception as err:
- print(f"β An error occurred: {err}")
- exit(2)
-
-def get_pr_number_from_sha():
- token = os.getenv("GITHUB_TOKEN")
- repo = os.getenv("GITHUB_REPOSITORY")
- sha = os.getenv("GITHUB_SHA")
-
- url = f"https://api.github.com/repos/{repo}/commits/{sha}/pulls"
- headers = {
- "Authorization": f"token {token}",
- "Accept": "application/vnd.github.groot-preview+json"
- }
-
- response = requests.get(url, headers=headers)
- response.raise_for_status()
-
- pr_data = response.json()
- if pr_data:
- return pr_data[0]["number"]
- else:
- raise Exception("No PR found for this SHA")
-
-def fetch_pr_body(pr_number, github_token, repo_full_name):
- print("π Fetching PR body...")
- pr_url = f"https://api.github.com/repos/{repo_full_name}/pulls/{pr_number}"
- try:
- pr_resp = requests.get(
- pr_url,
- headers={"Authorization": f"Bearer {github_token}"}
- )
- pr_resp.raise_for_status()
- except requests.exceptions.RequestException as e:
- print("β Failed to fetch PR body:", str(e))
- raise
- return pr_resp.json()
-
-def extract_email_from_text(text):
- email_match = re.search(r"\*\*Email\*\*:\s*([A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,})", text)
- if email_match:
- return email_match.group(1)
- email_match = re.search(r"[Ee]mail:\s*([A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,})", text)
- if email_match:
- return email_match.group(1)
- email_match = re.search(r"\b([A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,})\b", text)
- if email_match:
- return email_match.group(1)
- return None
-
-def fetch_pr_comments(repo_full_name, pr_number, github_token):
- comments_url = f"https://api.github.com/repos/{repo_full_name}/issues/{pr_number}/comments"
- try:
- comments_resp = requests.get(
- comments_url,
- headers={"Authorization": f"Bearer {github_token}"}
- )
- comments_resp.raise_for_status()
- return comments_resp.json()
- except requests.exceptions.RequestException as e:
- print(f"β οΈ Failed to fetch PR comments: {e}")
- return []
-
-def validate_email_address(email):
- try:
- valid_email = email_validator.validate_email(email)
- normalized_email = valid_email.email
- print(f"β
Email validation passed: {normalized_email}")
- return normalized_email
- except email_validator.EmailNotValidError as e:
- print(f"β Email validation failed: {e}")
- return None
-
-def extract_email(pr_body, repo_full_name, pr_number, github_token):
- print("π Searching for email in PR body...")
- email = extract_email_from_text(pr_body)
- if email:
- print(f"π§ Found email in PR body: {email}")
- validated_email = validate_email_address(email)
- if validated_email:
- return validated_email
- else:
- print("β οΈ Email in PR body is invalid, checking comments...")
-
- print("π No valid email found in PR body, checking comments...")
- comments = fetch_pr_comments(repo_full_name, pr_number, github_token)
- for comment in comments:
- comment_body = comment.get("body", "")
- email = extract_email_from_text(comment_body)
- if email:
- print(f"π§ Found email in comment by {comment.get('user', {}).get('login', 'unknown')}: {email}")
- validated_email = validate_email_address(email)
- if validated_email:
- return validated_email
- else:
- print("β οΈ Email in comment is invalid, continuing search...")
-
- print("β No valid email found in PR body or comments. Skipping key issuance.")
- exit(2)
-
-def provision_api_key(provisioning_api_key):
- print("π Creating OpenRouter key...")
- try:
- key_resp = requests.post(
- "https://openrouter.ai/api/v1/keys",
- headers={
- "Authorization": f"Bearer {provisioning_api_key}",
- "Content-Type": "application/json"
- },
- json={
- "name": "goose contributor",
- "label": "goose-cookbook",
- "limit": 10.0
- }
- )
- key_resp.raise_for_status()
- except requests.exceptions.RequestException as e:
- print("β Failed to provision API key:", str(e))
- raise
- key = key_resp.json().get("key")
- if not key:
- print("β API response did not include a key.")
- exit(2)
- return key
-
-def send_email(email, api_key, sendgrid_api_key):
- print("π€ Sending email via SendGrid...")
- try:
- sg = SendGridAPIClient(sendgrid_api_key)
- from_email = "goose team "
- subject = "π Your goose contributor API key"
- html_content = f"""
- Thank you for contributing to the goose recipe cookbook !
- π Here's your $10 OpenRouter API key :
- {api_key}
- To use this in goose (CLI or Desktop):
-
- Go to your Provider Settings
- Select OpenRouter from the provider list
- Paste your API key
-
- π Full setup instructions:
-
- https://block.github.io/goose/docs/getting-started/providers/#configure-provider
- Happy coding! β the goose team
- """
- message = Mail(
- from_email=from_email,
- to_emails=email,
- subject=subject,
- html_content=html_content
- )
- response = sg.send(message)
- print(f"β
Email sent successfully! Status code: {response.status_code}")
- if response.status_code >= 300:
- print(f"β οΈ Warning: Unexpected status code {response.status_code}")
- print(f"Response body: {response.body}")
- return False
- return True
-
- except HTTPError as e:
- print(f"β SendGrid HTTP error {e.status_code}: {e.body}")
- return False
- except Exception as e:
- print(f"β Unexpected error sending email: {type(e).__name__}: {e}")
- return False
-
-def comment_on_pr(github_token, repo_full_name, pr_number, email):
- print("π¬ Commenting on PR...")
- comment_url = f"https://api.github.com/repos/{repo_full_name}/issues/{pr_number}/comments"
- try:
- comment_resp = requests.post(
- comment_url,
- headers={
- "Authorization": f"Bearer {github_token}",
- "Accept": "application/vnd.github+json"
- },
- json={
- "body": f"β
$10 OpenRouter API key sent to `{email}`. Thanks for your contribution to the goose cookbook!"
- }
- )
- comment_resp.raise_for_status()
- print("β
Confirmation comment added to PR.")
- except requests.exceptions.RequestException as e:
- print("β Failed to comment on PR:", str(e))
- raise
-
-if __name__ == "__main__":
- main()
diff --git a/.github/workflows/goose-issue-solver.yml b/.github/workflows/goose-issue-solver.yml
new file mode 100644
index 0000000000..2ca1a22337
--- /dev/null
+++ b/.github/workflows/goose-issue-solver.yml
@@ -0,0 +1,200 @@
+name: goose Issue Solver
+
+on:
+ issue_comment:
+ types: [created]
+ workflow_dispatch:
+ inputs:
+ issue_number:
+ description: 'Issue number to solve'
+ required: true
+ type: string
+
+env:
+ GOOSE_RECIPE: |
+ version: "1.0.0"
+ title: "Solve GitHub Issue"
+ description: "Solve GitHub issue #${ISSUE_NUMBER}"
+
+ extensions:
+ - type: builtin
+ name: developer
+ - type: platform
+ name: todo
+
+ instructions: |
+ Principles:
+ - Extract all requirements before coding. Missing one means failure.
+ - Understand before changing. Research the code first.
+ - Follow existing patterns and AGENTS.md if it exists.
+ - Stop when requirements are met. Nothing more.
+ - Verify through deterministic means.
+ - Your context degrades. The TODO is your memory. Update it after each step.
+
+ prompt: |
+ Solve GitHub issue #${ISSUE_NUMBER}: ${ISSUE_TITLE}
+
+ The issue is saved at /tmp/issue.json
+
+ Trigger comment:
+ ${TRIGGER_COMMENT}
+
+ Write this to your TODO immediately and update as you progress:
+
+ ## Phase 1: Understand
+ - [ ] Read /tmp/issue.json
+ - [ ] Write all requirements (explicit + implicit) to /tmp/requirements.md
+ - [ ] Read AGENTS.md if it exists
+
+ ## Phase 2: Research
+ - [ ] Explore codebase with analyze and rg
+ - [ ] Identify files that need to change
+ - [ ] Update TODO with findings
+
+ ## Phase 3: Plan
+ - [ ] Decide on implementation approach
+ - [ ] For nontrivial issues, use subagents to evaluate architecture or implementation choices
+ - [ ] Update TODO with specific changes to make
+
+ ## Phase 4: Implement
+ - [ ] Implement minimal fix per /tmp/requirements.md
+ - [ ] Before adding anything, check: is it in the requirements? If not, don't.
+ - [ ] No .github/, no lock files, no secrets
+
+ ## Phase 5: Verify
+ - [ ] source bin/activate-hermit
+ - [ ] cargo check
+ - [ ] cargo test (affected crates)
+ - [ ] cargo fmt
+ - [ ] ./scripts/clippy-lint.sh
+ - [ ] Fix failures, retry up to 3 times
+
+ ## Phase 6: Confirm (MANDATORY)
+ - [ ] Reread /tmp/issue.json
+ - [ ] Reread /tmp/requirements.md
+ - [ ] Confirm all requirements met, nothing extra added
+ - [ ] Write a summary of changes to /tmp/issue_summary.txt
+
+ The Phase 6 "Reread" steps MUST appear in your updated TODO. Skipping them is failure.
+
+ Only write /tmp/issue_summary.txt if the fix is complete and verified.
+ Run `git add` for any new files required for the fix.
+ Do NOT commit. Leave changes in the working directory for the workflow to handle.
+
+permissions:
+ contents: write
+ pull-requests: write
+ issues: read
+
+concurrency:
+ group: goose-issue-${{ github.event.issue.number || github.event.inputs.issue_number }}
+ cancel-in-progress: false
+
+jobs:
+ solve-issue:
+ if: |
+ github.event_name == 'workflow_dispatch' ||
+ (!github.event.issue.pull_request &&
+ startsWith(github.event.comment.body, '/goose') &&
+ contains(fromJSON('["OWNER", "MEMBER"]'), github.event.comment.author_association))
+
+ runs-on: ubuntu-latest
+ timeout-minutes: 30
+
+ container:
+ image: ghcr.io/block/goose:latest
+ options: --user root
+ env:
+ GOOSE_PROVIDER: ${{ vars.GOOSE_PROVIDER || 'openai' }}
+ GOOSE_MODEL: ${{ vars.GOOSE_MODEL || 'gpt-5.1' }}
+ OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
+ HOME: /tmp/goose-home
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # pin@v4
+ with:
+ ref: main
+ fetch-depth: 1
+
+ - name: Install tools
+ run: |
+ apt-get update
+ apt-get install -y jq gettext curl build-essential pkg-config libssl-dev
+ curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg
+ echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | tee /etc/apt/sources.list.d/github-cli.list > /dev/null
+ apt-get update
+ apt-get install -y gh
+
+ - name: Get issue details (issue_comment)
+ if: github.event_name == 'issue_comment'
+ env:
+ ISSUE_JSON: ${{ toJSON(github.event.issue) }}
+ run: printenv ISSUE_JSON > /tmp/issue.json
+
+ - name: Get issue details (workflow_dispatch)
+ if: github.event_name == 'workflow_dispatch'
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: gh api /repos/${{ github.repository }}/issues/${{ github.event.inputs.issue_number }} > /tmp/issue.json
+
+ - name: Set issue outputs
+ id: issue
+ run: |
+ echo "number=$(jq -r '.number' /tmp/issue.json)" >> $GITHUB_OUTPUT
+
+ echo "title<> $GITHUB_OUTPUT
+ jq -r '.title' /tmp/issue.json >> $GITHUB_OUTPUT
+ echo "TITLE_EOF" >> $GITHUB_OUTPUT
+
+ - name: Run goose
+ id: goose
+ env:
+ ISSUE_NUMBER: ${{ steps.issue.outputs.number }}
+ ISSUE_TITLE: ${{ steps.issue.outputs.title }}
+ TRIGGER_COMMENT: ${{ github.event.comment.body || 'Triggered via workflow_dispatch' }}
+ run: |
+ mkdir -p $HOME/.local/share/goose/sessions
+ mkdir -p $HOME/.config/goose
+ git config --global --add safe.directory "$GITHUB_WORKSPACE"
+
+ echo "$GOOSE_RECIPE" | envsubst '$ISSUE_NUMBER $ISSUE_TITLE $TRIGGER_COMMENT' > /tmp/recipe.yaml
+
+ goose run --recipe /tmp/recipe.yaml
+
+ if [ -n "$(git status --porcelain)" ] && [ -f /tmp/issue_summary.txt ]; then
+ echo "has_changes=true" >> $GITHUB_OUTPUT
+ echo "summary<> $GITHUB_OUTPUT
+ cat /tmp/issue_summary.txt >> $GITHUB_OUTPUT
+ echo "SUMMARY_EOF" >> $GITHUB_OUTPUT
+ else
+ echo "has_changes=false" >> $GITHUB_OUTPUT
+ fi
+
+ - name: Verify no workflow changes
+ if: steps.goose.outputs.has_changes == 'true'
+ run: |
+ if git diff --name-only | grep -q "^\.github/"; then
+ echo "::error::Changes to .github/ are not allowed"
+ git checkout -- .github/
+ fi
+
+ - name: Create Pull Request
+ if: steps.goose.outputs.has_changes == 'true'
+ uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # pin@v7.0.8
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ commit-message: "fix: ${{ steps.issue.outputs.title }}"
+ title: "fix: ${{ steps.issue.outputs.title }}"
+ branch: goose/issue-${{ steps.issue.outputs.number }}
+ delete-branch: true
+ draft: true
+ labels: goose-generated
+ body: |
+ Closes #${{ steps.issue.outputs.number }}
+
+ ## Summary
+ ${{ steps.goose.outputs.summary || 'See commits for details.' }}
+
+ ---
+ *Generated by goose Issue Solver*
diff --git a/.github/workflows/send-api-key.yml b/.github/workflows/send-api-key.yml
deleted file mode 100644
index 5b1144acaa..0000000000
--- a/.github/workflows/send-api-key.yml
+++ /dev/null
@@ -1,71 +0,0 @@
-name: Send API Key on PR Merge
-
-on:
- pull_request_target:
- types: [closed]
- paths:
- - 'documentation/src/pages/recipes/data/recipes/**'
-
-permissions:
- contents: read
- issues: write
- pull-requests: write
-
-jobs:
- send-api-key:
- if: github.event.pull_request.merged == true
- runs-on: ubuntu-latest
-
- steps:
- - name: Checkout repo at merge commit
- uses: actions/checkout@v3
- with:
- ref: ${{ github.event.pull_request.merge_commit_sha }}
- fetch-depth: 0
-
- - name: Check if recipe files were added or modified in merged PR
- id: recipe_changes
- run: |
- set -e
- echo "π Checking if recipe files were added or modified in merged PR..."
-
- MERGE_COMMIT="${{ github.event.pull_request.merge_commit_sha }}"
- echo "Merge commit: $MERGE_COMMIT"
-
- # Get parent commit of the merge
- PARENT_COMMIT=$(git rev-parse "$MERGE_COMMIT^1")
- echo "Parent commit: $PARENT_COMMIT"
-
- # Get list of added or modified files in the PR (ignore deletions)
- CHANGED_FILES=$(git diff --name-only --diff-filter=AM "$PARENT_COMMIT" "$MERGE_COMMIT")
-
- echo "Files added/modified in merged PR:"
- echo "$CHANGED_FILES"
- echo ""
-
- # Detect recipe changes only
- if echo "$CHANGED_FILES" | grep -q "^documentation/src/pages/recipes/data/recipes/"; then
- echo "recipe_files_changed=true" >> "$GITHUB_OUTPUT"
- echo "β
Recipe files were added/modified in merged PR - proceeding with API key sending"
- else
- echo "recipe_files_changed=false" >> "$GITHUB_OUTPUT"
- echo "βΉοΈ No recipe files were added/modified in merged PR - skipping API key sending"
- fi
-
- - name: Set up Python
- if: steps.recipe_changes.outputs.recipe_files_changed == 'true'
- uses: actions/setup-python@v4
- with:
- python-version: '3.11'
-
- - name: Install dependencies and run email script
- if: steps.recipe_changes.outputs.recipe_files_changed == 'true'
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- GITHUB_SHA: ${{ github.sha }}
- GITHUB_REPOSITORY: ${{ github.repository }}
- PROVISIONING_API_KEY: ${{ secrets.PROVISIONING_API_KEY }}
- EMAIL_API_KEY: ${{ secrets.SENDGRID_API_KEY }}
- run: |
- pip install requests sendgrid email-validator
- python .github/scripts/send_key.py
diff --git a/.github/workflows/validate-recipe-pr.yml b/.github/workflows/validate-recipe-pr.yml
deleted file mode 100644
index 04529c7f63..0000000000
--- a/.github/workflows/validate-recipe-pr.yml
+++ /dev/null
@@ -1,265 +0,0 @@
-name: Validate Recipe PR
-
-on:
- pull_request_target:
- types: [opened, synchronize, reopened]
- paths:
- - 'documentation/src/pages/recipes/data/recipes/**'
-
-permissions:
- contents: read
- pull-requests: write
- issues: write
-
-jobs:
- validate-recipe:
- runs-on: ubuntu-latest
-
- env:
- PROVIDER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
-
- steps:
- - name: Checkout PR
- uses: actions/checkout@v4
- with:
- ref: ${{ github.event.pull_request.head.sha }}
- fetch-depth: 0
-
- - name: Set up Node.js
- uses: actions/setup-node@v3
- with:
- node-version: '20'
-
- - name: Install and Configure goose
- run: |
- mkdir -p /home/runner/.local/bin
- curl -fsSL https://github.com/block/goose/releases/download/stable/download_cli.sh \
- | CONFIGURE=false INSTALL_PATH=/home/runner/.local/bin bash
- echo "/home/runner/.local/bin" >> $GITHUB_PATH
-
- mkdir -p ~/.config/goose
- cat < ~/.config/goose/config.yaml
- GOOSE_PROVIDER: openrouter
- GOOSE_MODEL: "anthropic/claude-sonnet-4"
- keyring: false
- EOF
-
- - name: Check if recipe files changed in this push
- id: recipe_changes
- run: |
- set -e
- echo "π Checking if recipe files were modified in this push..."
-
- # Get the list of changed files in this specific push (added/modified only, not deleted)
- if [ "${{ github.event_name }}" = "pull_request" ] && [ "${{ github.event.action }}" = "synchronize" ]; then
- # For synchronize events, check files changed since the previous commit
- echo "π Synchronize event - checking files changed since previous commit"
- CHANGED_FILES=$(git diff --name-only --diff-filter=AM ${{ github.event.before }}..${{ github.event.after }})
- else
- # For opened/reopened, check all files in the PR (compare PR head against base)
- echo "π PR opened/reopened - checking all files in PR"
- CHANGED_FILES=$(git diff --name-only --diff-filter=AM ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }})
- fi
-
- echo "Changed files in this push:"
- echo "$CHANGED_FILES"
- echo ""
-
- # Check if any recipe files were changed
- if echo "$CHANGED_FILES" | grep -q "^documentation/src/pages/recipes/data/recipes/"; then
- echo "recipe_files_changed=true" >> "$GITHUB_OUTPUT"
- echo "β
Recipe files were modified in this push - proceeding with validation"
- else
- echo "recipe_files_changed=false" >> "$GITHUB_OUTPUT"
- echo "βΉοΈ No recipe files were modified in this push - skipping validation"
- fi
-
- - name: Find recipe files in PR (new or modified)
- id: find_changed_recipes
- if: steps.recipe_changes.outputs.recipe_files_changed == 'true'
- run: |
- set -e
- echo "Looking for recipe files in PR (new or modified)..."
-
- # Get the list of changed/new files in this PR (added/modified only, not deleted)
- if [ "${{ github.event_name }}" = "pull_request" ] && [ "${{ github.event.action }}" = "synchronize" ]; then
- # For synchronize events, check files changed since the previous commit
- echo "π Synchronize event - checking files changed/added since previous commit"
- CHANGED_FILES=$(git diff --name-only --diff-filter=AM ${{ github.event.before }}..${{ github.event.after }})
- else
- # For opened/reopened, check all files in the PR (compare PR head against base)
- echo "π PR opened/reopened - checking all new/modified files in PR"
- CHANGED_FILES=$(git diff --name-only --diff-filter=AM ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }})
- fi
-
- # Filter for recipe files only that were changed or added
- RECIPE_FILES=$(echo "$CHANGED_FILES" | grep "^documentation/src/pages/recipes/data/recipes/" | grep -E "\.(yaml|yml)$" || true)
-
- if [ -z "$RECIPE_FILES" ]; then
- echo "No changed recipe files found in PR"
- echo "validation_status=no_files" >> $GITHUB_OUTPUT
- exit 1
- fi
-
- echo "Found changed recipe files:"
- echo "$RECIPE_FILES"
-
- # Save recipe file paths for validation step
- echo "$RECIPE_FILES" > /tmp/changed_recipe_files.txt
-
- - name: Validate changed recipe files
- id: validate
- if: steps.recipe_changes.outputs.recipe_files_changed == 'true'
- run: |
- set -e
- # Read the list of changed recipe files
- RECIPE_FILES=$(cat /tmp/changed_recipe_files.txt)
-
- ALL_VALID=true
- VALIDATION_OUTPUT=""
-
- # First pass: Basic YAML validation
- while IFS= read -r RECIPE_FILE; do
- if [ -f "$RECIPE_FILE" ]; then
- BASE_RECIPE_FILENAME=$(basename "$RECIPE_FILE")
- echo "π Validating: $BASE_RECIPE_FILENAME"
- if OUTPUT=$(goose recipe validate "$RECIPE_FILE" 2>&1); then
- echo "β
Valid: $BASE_RECIPE_FILENAME"
- VALIDATION_OUTPUT="${VALIDATION_OUTPUT}β
$BASE_RECIPE_FILENAME: VALID\n"
- else
- echo "β Invalid: $BASE_RECIPE_FILENAME"
- echo "$OUTPUT"
- VALIDATION_OUTPUT="${VALIDATION_OUTPUT}β $BASE_RECIPE_FILENAME: INVALID\n\`\`\`\n$OUTPUT\n\`\`\`\n"
- ALL_VALID=false
- fi
- fi
- done < /tmp/changed_recipe_files.txt
-
- # Second pass: Check for duplicate filenames
- if [ "$ALL_VALID" = true ]; then
- echo "π Checking for duplicate filenames..."
-
- # Check for duplicate filenames first
- SEEN_FILENAMES=""
- while IFS= read -r RECIPE_FILE; do
- if [ -f "$RECIPE_FILE" ]; then
- FILENAME=$(basename "$RECIPE_FILE" .yaml)
- FILENAME=$(basename "$FILENAME" .yml)
-
- echo "π Checking filename: '$FILENAME'"
-
- # Check if we've seen this filename before in this PR
- if echo "$SEEN_FILENAMES" | grep -q "^$FILENAME$"; then
- echo "β Duplicate filename '$FILENAME' found in this PR"
- VALIDATION_OUTPUT="${VALIDATION_OUTPUT}β Duplicate filename '$FILENAME' found in this PR\n"
- ALL_VALID=false
- else
- SEEN_FILENAMES="$SEEN_FILENAMES\n$FILENAME"
- fi
-
- # Check if this is a new file or an update to existing file
- # Get list of changed files in this PR compared to base branch
- CHANGED_FILES=$(git diff --name-only --diff-filter=AM ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }} | grep "^$RECIPE_FILE$" || true)
- EXISTING_FILES=$(find documentation/src/pages/recipes/data/recipes/ -name "$FILENAME.yaml" -o -name "$FILENAME.yml" | grep -v "^$RECIPE_FILE$" || true)
-
- if [ -n "$EXISTING_FILES" ] && [ -z "$CHANGED_FILES" ]; then
- # File exists in repo but is not being modified - this is a new duplicate
- echo "β Recipe filename '$FILENAME' already exists:"
- echo "$EXISTING_FILES"
- VALIDATION_OUTPUT="${VALIDATION_OUTPUT}β $RECIPE_FILE: Filename '$FILENAME' already exists in: $EXISTING_FILES\n"
- ALL_VALID=false
- elif [ -n "$EXISTING_FILES" ] && [ -n "$CHANGED_FILES" ]; then
- # File exists and is being modified - this is an update
- echo "β
Updating existing recipe: '$FILENAME'"
- else
- # File doesn't exist - this is a new recipe
- echo "β
New recipe filename '$FILENAME' is unique"
- fi
-
- echo "β
Filename '$FILENAME' validation complete"
- fi
- done < /tmp/changed_recipe_files.txt
- fi
-
- # Save validation output for use in comment
- echo "$VALIDATION_OUTPUT" > /tmp/validation_output.txt
-
- if [ "$ALL_VALID" = true ]; then
- echo "validation_status=valid" >> $GITHUB_OUTPUT
- else
- echo "validation_status=invalid" >> $GITHUB_OUTPUT
- fi
-
- - name: Comment validation results
- if: steps.recipe_changes.outputs.recipe_files_changed == 'true'
- uses: actions/github-script@v7
- with:
- github-token: ${{ secrets.GITHUB_TOKEN }}
- script: |
- const fs = require('fs');
- const status = '${{ steps.validate.outputs.validation_status }}';
-
- let comment;
- if (status === 'no_files') {
- comment = `β **Recipe Validation Failed**
-
- No recipe files found in the correct location!
-
- π **Please add your recipe to**: \`documentation/src/pages/recipes/data/recipes/your-recipe-id.yaml\`
-
- **Example**: If your recipe ID is \`web-scraper\`, create:
- \`documentation/src/pages/recipes/data/recipes/web-scraper.yaml\``;
- } else if (status === 'valid') {
- comment = `β
**Recipe Validation Passed**
-
- Your recipe(s) are valid and ready for review!
-
- π **Next Steps**:
- 1. Our team will review your recipe
- 2. If approved, we'll run a security scan
- 3. Once merged, you'll receive $10 in OpenRouter credits (if email provided)
-
- Thanks for contributing to the goose Recipe Cookbook! π`;
- } else {
- // Read validation details from file
- let validationDetails = '';
- try {
- validationDetails = fs.readFileSync('/tmp/validation_output.txt', 'utf8');
- } catch (e) {
- validationDetails = 'See workflow logs for details.';
- }
-
- comment = `β **Recipe Validation Failed**
-
- Please fix the validation errors and push your changes:
-
- ${validationDetails}
-
- π Check our [Recipe Guide](https://block.github.io/goose/recipes) for help with the correct format.`;
- }
-
- await github.rest.issues.createComment({
- owner: context.repo.owner,
- repo: context.repo.repo,
- issue_number: context.payload.pull_request.number,
- body: comment
- });
-
- - name: Set validation status
- if: always()
- run: |
- # Check if recipe files were changed in this PR
- if [ "${{ steps.recipe_changes.outputs.recipe_files_changed }}" = "false" ]; then
- # No recipe files were modified in this PR - validation skipped
- echo "βΉοΈ No recipe files in PR - validation skipped"
- exit 0
- fi
-
- VALIDATION_STATUS="${{ steps.validate.outputs.validation_status }}"
- if [ "$VALIDATION_STATUS" = "valid" ]; then
- echo "β
All recipes are valid"
- exit 0
- else
- echo "β Recipe validation failed"
- exit 1
- fi
\ No newline at end of file
diff --git a/Cargo.lock b/Cargo.lock
index 1e90f70e02..eaf35aae43 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1638,9 +1638,9 @@ dependencies = [
[[package]]
name = "convert_case"
-version = "0.7.1"
+version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7"
+checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9"
dependencies = [
"unicode-segmentation",
]
@@ -2128,23 +2128,55 @@ dependencies = [
]
[[package]]
-name = "derive_more"
-version = "2.0.1"
+name = "derive_builder"
+version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678"
+checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947"
+dependencies = [
+ "derive_builder_macro",
+]
+
+[[package]]
+name = "derive_builder_core"
+version = "0.20.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8"
+dependencies = [
+ "darling 0.20.10",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.99",
+]
+
+[[package]]
+name = "derive_builder_macro"
+version = "0.20.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c"
+dependencies = [
+ "derive_builder_core",
+ "syn 2.0.99",
+]
+
+[[package]]
+name = "derive_more"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10b768e943bed7bf2cab53df09f4bc34bfd217cdb57d971e769874c9a6710618"
dependencies = [
"derive_more-impl",
]
[[package]]
name = "derive_more-impl"
-version = "2.0.1"
+version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3"
+checksum = "6d286bfdaf75e988b4a78e013ecd79c581e06399ab53fbacd2d916c2f904f30b"
dependencies = [
- "convert_case 0.7.1",
+ "convert_case 0.10.0",
"proc-macro2",
"quote",
+ "rustc_version",
"syn 2.0.99",
]
@@ -2863,7 +2895,7 @@ dependencies = [
[[package]]
name = "goose"
-version = "1.15.0"
+version = "1.16.0"
dependencies = [
"ahash",
"anyhow",
@@ -2905,6 +2937,7 @@ dependencies = [
"opentelemetry-otlp",
"opentelemetry_sdk",
"paste",
+ "posthog-rs",
"rand 0.8.5",
"regex",
"reqwest 0.12.12",
@@ -2946,7 +2979,7 @@ dependencies = [
[[package]]
name = "goose-bench"
-version = "1.15.0"
+version = "1.16.0"
dependencies = [
"anyhow",
"async-trait",
@@ -2969,7 +3002,7 @@ dependencies = [
[[package]]
name = "goose-cli"
-version = "1.15.0"
+version = "1.16.0"
dependencies = [
"agent-client-protocol",
"anstream",
@@ -3023,7 +3056,7 @@ dependencies = [
[[package]]
name = "goose-client"
-version = "1.15.0"
+version = "1.16.0"
dependencies = [
"anyhow",
"eventsource-stream",
@@ -3040,7 +3073,7 @@ dependencies = [
[[package]]
name = "goose-mcp"
-version = "1.15.0"
+version = "1.16.0"
dependencies = [
"anyhow",
"async-trait",
@@ -3105,7 +3138,7 @@ dependencies = [
[[package]]
name = "goose-server"
-version = "1.15.0"
+version = "1.16.0"
dependencies = [
"anyhow",
"async-trait",
@@ -3124,6 +3157,7 @@ dependencies = [
"rand 0.9.2",
"reqwest 0.12.12",
"rmcp 0.9.1",
+ "rustls 0.23.31",
"schemars",
"serde",
"serde_json",
@@ -3149,7 +3183,7 @@ dependencies = [
[[package]]
name = "goose-test"
-version = "1.15.0"
+version = "1.16.0"
dependencies = [
"clap",
"serde_json",
@@ -3157,7 +3191,7 @@ dependencies = [
[[package]]
name = "goose-tui"
-version = "1.15.0"
+version = "1.16.0"
dependencies = [
"ansi-to-tui",
"anyhow",
@@ -3886,9 +3920,9 @@ dependencies = [
[[package]]
name = "instability"
-version = "0.3.9"
+version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "435d80800b936787d62688c927b6490e887c7ef5ff9ce922c6c6050fca75eb9a"
+checksum = "6778b0196eefee7df739db78758e5cf9b37412268bfa5650bfeed028aed20d9c"
dependencies = [
"darling 0.20.10",
"indoc",
@@ -4543,23 +4577,6 @@ dependencies = [
"rand 0.8.5",
]
-[[package]]
-name = "native-tls"
-version = "0.2.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e"
-dependencies = [
- "libc",
- "log",
- "openssl",
- "openssl-probe",
- "openssl-sys",
- "schannel",
- "security-framework 2.11.1",
- "security-framework-sys",
- "tempfile",
-]
-
[[package]]
name = "ndk-context"
version = "0.1.1"
@@ -5385,6 +5402,21 @@ dependencies = [
"portable-atomic",
]
+[[package]]
+name = "posthog-rs"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "610d236077c94c96194b0d2b40989bda4bf8faf60e15a0da6ef8ffd4c78f6c1c"
+dependencies = [
+ "chrono",
+ "derive_builder",
+ "reqwest 0.11.27",
+ "semver",
+ "serde",
+ "serde_json",
+ "uuid",
+]
+
[[package]]
name = "powerfmt"
version = "0.2.0"
@@ -5955,6 +5987,7 @@ dependencies = [
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
+ "webpki-roots 0.25.4",
"winreg 0.50.0",
]
@@ -6225,6 +6258,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc"
dependencies = [
"aws-lc-rs",
+ "log",
"once_cell",
"ring",
"rustls-pki-types",
@@ -7589,16 +7623,6 @@ dependencies = [
"syn 2.0.99",
]
-[[package]]
-name = "tokio-native-tls"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
-dependencies = [
- "native-tls",
- "tokio",
-]
-
[[package]]
name = "tokio-rustls"
version = "0.24.1"
@@ -7650,9 +7674,11 @@ checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857"
dependencies = [
"futures-util",
"log",
- "native-tls",
+ "rustls 0.23.31",
+ "rustls-native-certs 0.8.1",
+ "rustls-pki-types",
"tokio",
- "tokio-native-tls",
+ "tokio-rustls 0.26.2",
"tungstenite 0.28.0",
]
@@ -8043,8 +8069,9 @@ dependencies = [
"http 1.2.0",
"httparse",
"log",
- "native-tls",
"rand 0.9.2",
+ "rustls 0.23.31",
+ "rustls-pki-types",
"sha1",
"thiserror 2.0.12",
"utf-8",
@@ -8252,6 +8279,7 @@ checksum = "e0f540e3240398cce6128b64ba83fdbdd86129c16a3aa1a3a252efd66eb3d587"
dependencies = [
"getrandom 0.3.1",
"rand 0.9.2",
+ "serde",
]
[[package]]
diff --git a/Cargo.toml b/Cargo.toml
index d29d6e4fe0..1704a80917 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -4,7 +4,7 @@ resolver = "2"
[workspace.package]
edition = "2021"
-version = "1.15.0"
+version = "1.16.0"
authors = ["Block "]
license = "Apache-2.0"
repository = "https://github.com/block/goose"
diff --git a/GOVERNANCE.md b/GOVERNANCE.md
index a9e5770999..ee6a912c49 100644
--- a/GOVERNANCE.md
+++ b/GOVERNANCE.md
@@ -187,3 +187,13 @@ Core Maintainers and Maintainers are listed in the main goose repository's [MAIN
* **Quality**: Thoughtful review while avoiding bureaucracy
We believe this balance enables goose to remain innovative while building a strong, engaged community around the shared goal of creating the most hackable, user-controlled AI agent available.
+
+# General Project Policies
+
+Founded by Block, goose has been established as a Series of LF Projects, LLC. Policies applicable to goose and participants in the goose project, including guidelines on the usage of trademarks, are located at [https://lfprojects.org/policies/](https://lfprojects.org/policies/). Governance changes approved as per the provisions of this governance document must also be approved by LF Projects, LLC.
+
+goose participants acknowledge that the copyright in all new contributions will be retained by the copyright holder as independent works of authorship and that no contributor or copyright holder will be required to assign copyrights to the project.
+Except as described below, all code and specification contributions to the project must be made using the Apache License, Version 2.0 available at (the βProject Licenseβ).
+
+All outbound code and specifications will be made available under the Project License. The Core Maintainers may approve the use of an alternative open license or licenses for inbound or outbound contributions on an exception basis.
+All documentation (excluding specifications) will be made available under the Creative Commons Attribution 4.0 International license, available at: https://creativecommons.org/licenses/by/4.0.
diff --git a/crates/goose-bench/src/eval_suites/core/developer_image/image.rs b/crates/goose-bench/src/eval_suites/core/developer_image/image.rs
index 98fa73b2c5..a9178f43f6 100644
--- a/crates/goose-bench/src/eval_suites/core/developer_image/image.rs
+++ b/crates/goose-bench/src/eval_suites/core/developer_image/image.rs
@@ -68,7 +68,7 @@ impl Evaluation for DeveloperImage {
if let MessageContent::ToolResponse(tool_resp) = content {
if let Ok(result) = &tool_resp.tool_result {
// Check each item in the result list
- for item in result {
+ for item in &result.content {
if let Some(image) = item.as_image() {
// Image content already contains mime_type and data
if image.mime_type.starts_with("image/")
diff --git a/crates/goose-cli/src/commands/acp.rs b/crates/goose-cli/src/commands/acp.rs
index 8c9684a037..89242bc656 100644
--- a/crates/goose-cli/src/commands/acp.rs
+++ b/crates/goose-cli/src/commands/acp.rs
@@ -11,7 +11,7 @@ use goose::mcp_utils::ToolResult;
use goose::providers::create;
use goose::session::session_manager::SessionType;
use goose::session::SessionManager;
-use rmcp::model::{Content, RawContent, ResourceContents, Role};
+use rmcp::model::{CallToolResult, RawContent, ResourceContents, Role};
use std::collections::{HashMap, HashSet};
use std::fs;
use std::sync::Arc;
@@ -74,8 +74,8 @@ fn extract_tool_locations(
.and_then(|c| c.as_str());
// Extract line numbers from the response content
- if let Ok(content_items) = &tool_response.tool_result {
- for content in content_items {
+ if let Ok(result) = &tool_response.tool_result {
+ for content in &result.content {
if let RawContent::Text(text_content) = &content.raw {
let text = &text_content.text;
@@ -491,9 +491,10 @@ impl GooseAcpAgent {
}
/// Build tool call content from tool result
-fn build_tool_call_content(tool_result: &ToolResult>) -> Vec {
+fn build_tool_call_content(tool_result: &ToolResult) -> Vec {
match tool_result {
- Ok(content_items) => content_items
+ Ok(result) => result
+ .content
.iter()
.filter_map(|content| match &content.raw {
RawContent::Text(val) => Some(ToolCallContent::Content {
diff --git a/crates/goose-cli/src/commands/configure.rs b/crates/goose-cli/src/commands/configure.rs
index e50dd6f09f..b6ae23df26 100644
--- a/crates/goose-cli/src/commands/configure.rs
+++ b/crates/goose-cli/src/commands/configure.rs
@@ -576,7 +576,7 @@ pub async fn configure_provider_dialog() -> anyhow::Result {
let models_res = {
let temp_model_config = ModelConfig::new(&provider_meta.default_model)?;
let temp_provider = create(provider_name, temp_model_config).await?;
- temp_provider.fetch_supported_models().await
+ temp_provider.fetch_recommended_models().await
};
spin.stop(style("Model fetch complete").green());
@@ -1891,7 +1891,7 @@ fn add_provider() -> anyhow::Result<()> {
.mask('βͺ')
.interact()?;
- let models_input: String = cliclack::input("Available models (seperate with commas):")
+ let models_input: String = cliclack::input("Available models (separate with commas):")
.placeholder("model-a, model-b, model-c")
.validate(|input: &String| {
if input.trim().is_empty() {
diff --git a/crates/goose-cli/src/scenario_tests/recordings/anthropic/weather_tool.json b/crates/goose-cli/src/scenario_tests/recordings/anthropic/weather_tool.json
index 1a128963eb..68ce092dce 100644
--- a/crates/goose-cli/src/scenario_tests/recordings/anthropic/weather_tool.json
+++ b/crates/goose-cli/src/scenario_tests/recordings/anthropic/weather_tool.json
@@ -1,12 +1,12 @@
{
- "74a3cc715c4c65dcae4ecec084d962947e4ce3cf02a8b639ee23b080e88bf6ec": {
+ "1b90ffd4b16eab7aef1c24e9b61f6b6cc6f0d9b04e8a4f4344454a45b0793284": {
"input": {
"system": "You are a general-purpose AI agent called goose, created by Block, the parent company of Square, CashApp, and Tidal.\ngoose is being developed as an open-source software project.\n\ngoose uses LLM providers with tool calling capability. You can be used with different language models (gpt-4o,\nclaude-sonnet-4, o1, llama-3.2, deepseek-r1, etc).\nThese models have varying knowledge cut-off dates depending on when they were trained, but typically it's between 5-10\nmonths prior to the current date.\n\n# Extensions\n\nExtensions allow other applications to provide context to goose. Extensions connect goose to different data sources and\ntools.\nYou are capable of dynamically plugging into new extensions and learning how to use them. You solve higher level\nproblems using the tools in these extensions, and can interact with multiple at once.\n\nIf the Extension Manager extension is enabled, you can use the search_available_extensions tool to discover additional\nextensions that can help with your task. To enable or disable extensions, use the manage_extensions tool with the\nextension_name. You should only enable extensions found from the search_available_extensions tool.\nIf Extension Manager is not available, you can only work with currently enabled extensions and cannot dynamically load\nnew ones.\n\nBecause you dynamically load extensions, your conversation history may refer\nto interactions with extensions that are not currently active. The currently\nactive extensions are below. Each of these extensions provides tools that are\nin your tool specification.\n\n\n## weather_extension\n\n\n\n\n\n\n# Response Guidelines\n\n- Use Markdown formatting for all responses.\n- Follow best practices for Markdown, including:\n - Using headers for organization.\n - Bullet points for lists.\n - Links formatted correctly, either as linked text (e.g., [this is linked text](https://example.com)) or automatic\n links using angle brackets (e.g., ).\n- For code examples, use fenced code blocks by placing triple backticks (` ``` `) before and after the code. Include the\n language identifier after the opening backticks (e.g., ` ```python `) to enable syntax highlighting.\n- Ensure clarity, conciseness, and proper formatting to enhance readability and usability.",
"messages": [
{
- "id": "msg_20251124_8_0",
+ "id": "msg_20251211_27_0",
"role": "user",
- "created": 1763989805,
+ "created": 1765495263,
"content": [
{
"type": "text",
@@ -19,13 +19,13 @@
}
},
{
- "id": "msg_b4135f6c-9a29-4904-a59f-632ea849ef5a",
+ "id": "msg_9076da8a-a3b1-4cfc-b5d9-0ffc2a7879c9",
"role": "assistant",
- "created": 1763989808,
+ "created": 1765495266,
"content": [
{
"type": "toolRequest",
- "id": "toolu_01VxSzmLcPK7UNNE4ms8XRa6",
+ "id": "toolu_01CDkt9pjh8oC9C1984uhvuG",
"toolCall": {
"status": "success",
"value": {
@@ -43,21 +43,23 @@
}
},
{
- "id": "msg_253b4067-0c08-4807-8b36-23b576f138b5",
+ "id": "msg_d7b3db5e-0402-42a8-937c-2ccf68a3e4e0",
"role": "user",
- "created": 1763989808,
+ "created": 1765495266,
"content": [
{
"type": "toolResponse",
- "id": "toolu_01VxSzmLcPK7UNNE4ms8XRa6",
+ "id": "toolu_01CDkt9pjh8oC9C1984uhvuG",
"toolResult": {
"status": "success",
- "value": [
- {
- "type": "text",
- "text": "The weather in Berlin, Germany is cloudy and 18Β°C"
- }
- ]
+ "value": {
+ "content": [
+ {
+ "type": "text",
+ "text": "The weather in Berlin, Germany is cloudy and 18Β°C"
+ }
+ ]
+ }
}
}
],
@@ -305,11 +307,11 @@
"message": {
"id": null,
"role": "assistant",
- "created": 1763989811,
+ "created": 1765495269,
"content": [
{
"type": "text",
- "text": "The weather in Berlin, Germany is currently **cloudy** with a temperature of **18Β°C** (64Β°F)."
+ "text": "# Weather in Berlin, Germany\n\nThe current weather in Berlin, Germany is:\n- **Conditions**: Cloudy\n- **Temperature**: 18Β°C (64Β°F)\n\nIt's a mild day with overcast conditions in Berlin!"
}
],
"metadata": {
@@ -321,301 +323,8 @@
"model": "claude-sonnet-4-20250514",
"usage": {
"input_tokens": 2406,
- "output_tokens": 29,
- "total_tokens": 2435
- }
- }
- }
- },
- "1bc400a528c54b25f4f1f609481e98e44222b3deaf7eee2c9e640e6345c73861": {
- "input": {
- "system": "You are a general-purpose AI agent called goose, created by Block, the parent company of Square, CashApp, and Tidal.\ngoose is being developed as an open-source software project.\n\ngoose uses LLM providers with tool calling capability. You can be used with different language models (gpt-4o,\nclaude-sonnet-4, o1, llama-3.2, deepseek-r1, etc).\nThese models have varying knowledge cut-off dates depending on when they were trained, but typically it's between 5-10\nmonths prior to the current date.\n\n# Extensions\n\nExtensions allow other applications to provide context to goose. Extensions connect goose to different data sources and\ntools.\nYou are capable of dynamically plugging into new extensions and learning how to use them. You solve higher level\nproblems using the tools in these extensions, and can interact with multiple at once.\n\nIf the Extension Manager extension is enabled, you can use the search_available_extensions tool to discover additional\nextensions that can help with your task. To enable or disable extensions, use the manage_extensions tool with the\nextension_name. You should only enable extensions found from the search_available_extensions tool.\nIf Extension Manager is not available, you can only work with currently enabled extensions and cannot dynamically load\nnew ones.\n\nBecause you dynamically load extensions, your conversation history may refer\nto interactions with extensions that are not currently active. The currently\nactive extensions are below. Each of these extensions provides tools that are\nin your tool specification.\n\n\n## weather_extension\n\n\n\n\n\n\n# Response Guidelines\n\n- Use Markdown formatting for all responses.\n- Follow best practices for Markdown, including:\n - Using headers for organization.\n - Bullet points for lists.\n - Links formatted correctly, either as linked text (e.g., [this is linked text](https://example.com)) or automatic\n links using angle brackets (e.g., ).\n- For code examples, use fenced code blocks by placing triple backticks (` ``` `) before and after the code. Include the\n language identifier after the opening backticks (e.g., ` ```python `) to enable syntax highlighting.\n- Ensure clarity, conciseness, and proper formatting to enhance readability and usability.",
- "messages": [
- {
- "id": "msg_20251124_8_0",
- "role": "user",
- "created": 1763989805,
- "content": [
- {
- "type": "text",
- "text": "tell me what the weather is in Berlin, Germany"
- }
- ],
- "metadata": {
- "userVisible": true,
- "agentVisible": true
- }
- }
- ],
- "tools": [
- {
- "name": "dynamic_task__create_task",
- "description": "Create tasks with instructions or prompt. For simple tasks, only include the instructions field. Extensions control: omit field = use all current extensions; empty array [] = no extensions; array with names = only those extensions. Specify extensions as shortnames (the prefixes for your tools). Specify return_last_only as true and have your subagent summarize its work in its last message to conserve your own context. Optional: title, description, extensions, settings, retry, response schema, activities. Arrays for multiple tasks.",
- "inputSchema": {
- "$defs": {
- "TaskParameter": {
- "description": "Parameters for a single task",
- "properties": {
- "activities": {
- "items": {
- "type": "string"
- },
- "type": [
- "array",
- "null"
- ]
- },
- "description": {
- "type": [
- "string",
- "null"
- ]
- },
- "extensions": {
- "items": {
- "additionalProperties": true,
- "type": "object"
- },
- "type": [
- "array",
- "null"
- ]
- },
- "instructions": {
- "type": [
- "string",
- "null"
- ]
- },
- "parameters": {
- "items": {
- "additionalProperties": true,
- "type": "object"
- },
- "type": [
- "array",
- "null"
- ]
- },
- "prompt": {
- "type": [
- "string",
- "null"
- ]
- },
- "response": {
- "additionalProperties": true,
- "type": [
- "object",
- "null"
- ]
- },
- "retry": {
- "additionalProperties": true,
- "type": [
- "object",
- "null"
- ]
- },
- "return_last_only": {
- "description": "If true, return only the last message from the subagent (default: false, returns full conversation)",
- "type": [
- "boolean",
- "null"
- ]
- },
- "settings": {
- "additionalProperties": true,
- "type": [
- "object",
- "null"
- ]
- },
- "title": {
- "type": [
- "string",
- "null"
- ]
- }
- },
- "type": "object"
- }
- },
- "$schema": "https://json-schema.org/draft/2020-12/schema",
- "properties": {
- "execution_mode": {
- "description": "How to execute multiple tasks (default: parallel for multiple tasks, sequential for single task)",
- "type": [
- "string",
- "null"
- ]
- },
- "task_parameters": {
- "description": "Array of tasks. Each task must have either 'instructions' OR 'prompt' field (at least one is required).",
- "items": {
- "$ref": "#/$defs/TaskParameter"
- },
- "minItems": 1,
- "type": "array"
- }
- },
- "required": [
- "task_parameters"
- ],
- "title": "CreateDynamicTaskParams",
- "type": "object"
- },
- "annotations": {
- "title": "Create Dynamic Tasks",
- "readOnlyHint": false,
- "destructiveHint": false,
- "idempotentHint": false,
- "openWorldHint": true
- }
- },
- {
- "name": "platform__manage_schedule",
- "description": "Manage scheduled recipe execution for this goose instance.\n\nActions:\n- \"list\": List all scheduled jobs\n- \"create\": Create a new scheduled job from a recipe file\n- \"run_now\": Execute a scheduled job immediately \n- \"pause\": Pause a scheduled job\n- \"unpause\": Resume a paused job\n- \"delete\": Remove a scheduled job\n- \"kill\": Terminate a currently running job\n- \"inspect\": Get details about a running job\n- \"sessions\": List execution history for a job\n- \"session_content\": Get the full content (messages) of a specific session\n",
- "inputSchema": {
- "properties": {
- "action": {
- "enum": [
- "list",
- "create",
- "run_now",
- "pause",
- "unpause",
- "delete",
- "kill",
- "inspect",
- "sessions",
- "session_content"
- ],
- "type": "string"
- },
- "cron_expression": {
- "description": "A cron expression for create action. Supports both 5-field (minute hour day month weekday) and 6-field (second minute hour day month weekday) formats. 5-field expressions are automatically converted to 6-field by prepending '0' for seconds.",
- "type": "string"
- },
- "job_id": {
- "description": "Job identifier for operations on existing jobs",
- "type": "string"
- },
- "limit": {
- "default": 50,
- "description": "Limit for sessions list",
- "type": "integer"
- },
- "recipe_path": {
- "description": "Path to recipe file for create action",
- "type": "string"
- },
- "session_id": {
- "description": "Session identifier for session_content action",
- "type": "string"
- }
- },
- "required": [
- "action"
- ],
- "type": "object"
- },
- "annotations": {
- "title": "Manage scheduled recipes",
- "readOnlyHint": false,
- "destructiveHint": true,
- "idempotentHint": false,
- "openWorldHint": false
- }
- },
- {
- "name": "subagent__execute_task",
- "description": "Only use the subagent__execute_task tool when you execute sub recipe task or dynamic task.\n EXECUTION STRATEGY DECISION:\n 1. If the tasks are created with execution_mode, use the execution_mode.\n 2. Execute tasks sequentially unless user explicitly requests parallel execution. PARALLEL: User uses keywords like 'parallel', 'simultaneously', 'at the same time', 'concurrently'\n\n IMPLEMENTATION:\n - Sequential execution: Call this tool multiple times, passing exactly ONE task per call\n - Parallel execution: Call this tool once, passing an ARRAY of all tasks\n\n EXAMPLES:\n User Intent Based:\n - User: 'get weather and tell me a joke' β Sequential (2 separate tool calls, 1 task each)\n - User: 'get weather and joke in parallel' β Parallel (1 tool call with array of 2 tasks)\n - User: 'run these simultaneously' β Parallel (1 tool call with task array)\n - User: 'do task A then task B' β Sequential (2 separate tool calls)",
- "inputSchema": {
- "properties": {
- "execution_mode": {
- "default": "sequential",
- "description": "Execution strategy for multiple tasks. Use 'sequential' (default) unless user explicitly requests parallel execution with words like 'parallel', 'simultaneously', 'at the same time', or 'concurrently'.",
- "enum": [
- "sequential",
- "parallel"
- ],
- "type": "string"
- },
- "task_ids": {
- "items": {
- "description": "Unique identifier for the task",
- "type": "string"
- },
- "type": "array"
- }
- },
- "required": [
- "task_ids"
- ],
- "type": "object"
- },
- "annotations": {
- "title": "Run tasks in parallel",
- "readOnlyHint": false,
- "destructiveHint": true,
- "idempotentHint": false,
- "openWorldHint": true
- }
- },
- {
- "name": "weather_extension__get_weather",
- "description": "Get the weather for a location",
- "inputSchema": {
- "properties": {
- "location": {
- "description": "The city and state, e.g. San Francisco, CA",
- "type": "string"
- }
- },
- "required": [
- "location"
- ],
- "type": "object"
- }
- }
- ]
- },
- "output": {
- "message": {
- "id": null,
- "role": "assistant",
- "created": 1763989808,
- "content": [
- {
- "type": "text",
- "text": "I'll get the current weather information for Berlin, Germany."
- },
- {
- "type": "toolRequest",
- "id": "toolu_01VxSzmLcPK7UNNE4ms8XRa6",
- "toolCall": {
- "status": "success",
- "value": {
- "name": "weather_extension__get_weather",
- "arguments": {
- "location": "Berlin, Germany"
- }
- }
- }
- }
- ],
- "metadata": {
- "userVisible": true,
- "agentVisible": true
- }
- },
- "usage": {
- "model": "claude-sonnet-4-20250514",
- "usage": {
- "input_tokens": 2320,
- "output_tokens": 72,
- "total_tokens": 2392
+ "output_tokens": 55,
+ "total_tokens": 2461
}
}
}
@@ -627,7 +336,7 @@
{
"id": null,
"role": "user",
- "created": 1763989805,
+ "created": 1765495263,
"content": [
{
"type": "text",
@@ -646,7 +355,7 @@
"message": {
"id": null,
"role": "assistant",
- "created": 1763989810,
+ "created": 1765495265,
"content": [
{
"type": "text",
@@ -667,5 +376,298 @@
}
}
}
+ },
+ "1bc400a528c54b25f4f1f609481e98e44222b3deaf7eee2c9e640e6345c73861": {
+ "input": {
+ "system": "You are a general-purpose AI agent called goose, created by Block, the parent company of Square, CashApp, and Tidal.\ngoose is being developed as an open-source software project.\n\ngoose uses LLM providers with tool calling capability. You can be used with different language models (gpt-4o,\nclaude-sonnet-4, o1, llama-3.2, deepseek-r1, etc).\nThese models have varying knowledge cut-off dates depending on when they were trained, but typically it's between 5-10\nmonths prior to the current date.\n\n# Extensions\n\nExtensions allow other applications to provide context to goose. Extensions connect goose to different data sources and\ntools.\nYou are capable of dynamically plugging into new extensions and learning how to use them. You solve higher level\nproblems using the tools in these extensions, and can interact with multiple at once.\n\nIf the Extension Manager extension is enabled, you can use the search_available_extensions tool to discover additional\nextensions that can help with your task. To enable or disable extensions, use the manage_extensions tool with the\nextension_name. You should only enable extensions found from the search_available_extensions tool.\nIf Extension Manager is not available, you can only work with currently enabled extensions and cannot dynamically load\nnew ones.\n\nBecause you dynamically load extensions, your conversation history may refer\nto interactions with extensions that are not currently active. The currently\nactive extensions are below. Each of these extensions provides tools that are\nin your tool specification.\n\n\n## weather_extension\n\n\n\n\n\n\n# Response Guidelines\n\n- Use Markdown formatting for all responses.\n- Follow best practices for Markdown, including:\n - Using headers for organization.\n - Bullet points for lists.\n - Links formatted correctly, either as linked text (e.g., [this is linked text](https://example.com)) or automatic\n links using angle brackets (e.g., ).\n- For code examples, use fenced code blocks by placing triple backticks (` ``` `) before and after the code. Include the\n language identifier after the opening backticks (e.g., ` ```python `) to enable syntax highlighting.\n- Ensure clarity, conciseness, and proper formatting to enhance readability and usability.",
+ "messages": [
+ {
+ "id": "msg_20251211_27_0",
+ "role": "user",
+ "created": 1765495263,
+ "content": [
+ {
+ "type": "text",
+ "text": "tell me what the weather is in Berlin, Germany"
+ }
+ ],
+ "metadata": {
+ "userVisible": true,
+ "agentVisible": true
+ }
+ }
+ ],
+ "tools": [
+ {
+ "name": "dynamic_task__create_task",
+ "description": "Create tasks with instructions or prompt. For simple tasks, only include the instructions field. Extensions control: omit field = use all current extensions; empty array [] = no extensions; array with names = only those extensions. Specify extensions as shortnames (the prefixes for your tools). Specify return_last_only as true and have your subagent summarize its work in its last message to conserve your own context. Optional: title, description, extensions, settings, retry, response schema, activities. Arrays for multiple tasks.",
+ "inputSchema": {
+ "$defs": {
+ "TaskParameter": {
+ "description": "Parameters for a single task",
+ "properties": {
+ "activities": {
+ "items": {
+ "type": "string"
+ },
+ "type": [
+ "array",
+ "null"
+ ]
+ },
+ "description": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "extensions": {
+ "items": {
+ "additionalProperties": true,
+ "type": "object"
+ },
+ "type": [
+ "array",
+ "null"
+ ]
+ },
+ "instructions": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "parameters": {
+ "items": {
+ "additionalProperties": true,
+ "type": "object"
+ },
+ "type": [
+ "array",
+ "null"
+ ]
+ },
+ "prompt": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "response": {
+ "additionalProperties": true,
+ "type": [
+ "object",
+ "null"
+ ]
+ },
+ "retry": {
+ "additionalProperties": true,
+ "type": [
+ "object",
+ "null"
+ ]
+ },
+ "return_last_only": {
+ "description": "If true, return only the last message from the subagent (default: false, returns full conversation)",
+ "type": [
+ "boolean",
+ "null"
+ ]
+ },
+ "settings": {
+ "additionalProperties": true,
+ "type": [
+ "object",
+ "null"
+ ]
+ },
+ "title": {
+ "type": [
+ "string",
+ "null"
+ ]
+ }
+ },
+ "type": "object"
+ }
+ },
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "properties": {
+ "execution_mode": {
+ "description": "How to execute multiple tasks (default: parallel for multiple tasks, sequential for single task)",
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "task_parameters": {
+ "description": "Array of tasks. Each task must have either 'instructions' OR 'prompt' field (at least one is required).",
+ "items": {
+ "$ref": "#/$defs/TaskParameter"
+ },
+ "minItems": 1,
+ "type": "array"
+ }
+ },
+ "required": [
+ "task_parameters"
+ ],
+ "title": "CreateDynamicTaskParams",
+ "type": "object"
+ },
+ "annotations": {
+ "title": "Create Dynamic Tasks",
+ "readOnlyHint": false,
+ "destructiveHint": false,
+ "idempotentHint": false,
+ "openWorldHint": true
+ }
+ },
+ {
+ "name": "platform__manage_schedule",
+ "description": "Manage scheduled recipe execution for this goose instance.\n\nActions:\n- \"list\": List all scheduled jobs\n- \"create\": Create a new scheduled job from a recipe file\n- \"run_now\": Execute a scheduled job immediately \n- \"pause\": Pause a scheduled job\n- \"unpause\": Resume a paused job\n- \"delete\": Remove a scheduled job\n- \"kill\": Terminate a currently running job\n- \"inspect\": Get details about a running job\n- \"sessions\": List execution history for a job\n- \"session_content\": Get the full content (messages) of a specific session\n",
+ "inputSchema": {
+ "properties": {
+ "action": {
+ "enum": [
+ "list",
+ "create",
+ "run_now",
+ "pause",
+ "unpause",
+ "delete",
+ "kill",
+ "inspect",
+ "sessions",
+ "session_content"
+ ],
+ "type": "string"
+ },
+ "cron_expression": {
+ "description": "A cron expression for create action. Supports both 5-field (minute hour day month weekday) and 6-field (second minute hour day month weekday) formats. 5-field expressions are automatically converted to 6-field by prepending '0' for seconds.",
+ "type": "string"
+ },
+ "job_id": {
+ "description": "Job identifier for operations on existing jobs",
+ "type": "string"
+ },
+ "limit": {
+ "default": 50,
+ "description": "Limit for sessions list",
+ "type": "integer"
+ },
+ "recipe_path": {
+ "description": "Path to recipe file for create action",
+ "type": "string"
+ },
+ "session_id": {
+ "description": "Session identifier for session_content action",
+ "type": "string"
+ }
+ },
+ "required": [
+ "action"
+ ],
+ "type": "object"
+ },
+ "annotations": {
+ "title": "Manage scheduled recipes",
+ "readOnlyHint": false,
+ "destructiveHint": true,
+ "idempotentHint": false,
+ "openWorldHint": false
+ }
+ },
+ {
+ "name": "subagent__execute_task",
+ "description": "Only use the subagent__execute_task tool when you execute sub recipe task or dynamic task.\n EXECUTION STRATEGY DECISION:\n 1. If the tasks are created with execution_mode, use the execution_mode.\n 2. Execute tasks sequentially unless user explicitly requests parallel execution. PARALLEL: User uses keywords like 'parallel', 'simultaneously', 'at the same time', 'concurrently'\n\n IMPLEMENTATION:\n - Sequential execution: Call this tool multiple times, passing exactly ONE task per call\n - Parallel execution: Call this tool once, passing an ARRAY of all tasks\n\n EXAMPLES:\n User Intent Based:\n - User: 'get weather and tell me a joke' β Sequential (2 separate tool calls, 1 task each)\n - User: 'get weather and joke in parallel' β Parallel (1 tool call with array of 2 tasks)\n - User: 'run these simultaneously' β Parallel (1 tool call with task array)\n - User: 'do task A then task B' β Sequential (2 separate tool calls)",
+ "inputSchema": {
+ "properties": {
+ "execution_mode": {
+ "default": "sequential",
+ "description": "Execution strategy for multiple tasks. Use 'sequential' (default) unless user explicitly requests parallel execution with words like 'parallel', 'simultaneously', 'at the same time', or 'concurrently'.",
+ "enum": [
+ "sequential",
+ "parallel"
+ ],
+ "type": "string"
+ },
+ "task_ids": {
+ "items": {
+ "description": "Unique identifier for the task",
+ "type": "string"
+ },
+ "type": "array"
+ }
+ },
+ "required": [
+ "task_ids"
+ ],
+ "type": "object"
+ },
+ "annotations": {
+ "title": "Run tasks in parallel",
+ "readOnlyHint": false,
+ "destructiveHint": true,
+ "idempotentHint": false,
+ "openWorldHint": true
+ }
+ },
+ {
+ "name": "weather_extension__get_weather",
+ "description": "Get the weather for a location",
+ "inputSchema": {
+ "properties": {
+ "location": {
+ "description": "The city and state, e.g. San Francisco, CA",
+ "type": "string"
+ }
+ },
+ "required": [
+ "location"
+ ],
+ "type": "object"
+ }
+ }
+ ]
+ },
+ "output": {
+ "message": {
+ "id": null,
+ "role": "assistant",
+ "created": 1765495266,
+ "content": [
+ {
+ "type": "text",
+ "text": "I'll get the current weather information for Berlin, Germany for you."
+ },
+ {
+ "type": "toolRequest",
+ "id": "toolu_01CDkt9pjh8oC9C1984uhvuG",
+ "toolCall": {
+ "status": "success",
+ "value": {
+ "name": "weather_extension__get_weather",
+ "arguments": {
+ "location": "Berlin, Germany"
+ }
+ }
+ }
+ }
+ ],
+ "metadata": {
+ "userVisible": true,
+ "agentVisible": true
+ }
+ },
+ "usage": {
+ "model": "claude-sonnet-4-20250514",
+ "usage": {
+ "input_tokens": 2320,
+ "output_tokens": 74,
+ "total_tokens": 2394
+ }
+ }
+ }
}
}
\ No newline at end of file
diff --git a/crates/goose-cli/src/scenario_tests/recordings/azure_openai/weather_tool.json b/crates/goose-cli/src/scenario_tests/recordings/azure_openai/weather_tool.json
index fffc7b746b..105749bcdf 100644
--- a/crates/goose-cli/src/scenario_tests/recordings/azure_openai/weather_tool.json
+++ b/crates/goose-cli/src/scenario_tests/recordings/azure_openai/weather_tool.json
@@ -1,12 +1,60 @@
{
+ "8b1b8633232ca390cb3ff37a48daf74168b58f13e2943efcb17957129ee34d1a": {
+ "input": {
+ "system": "Reply with only a description in four words or less",
+ "messages": [
+ {
+ "id": null,
+ "role": "user",
+ "created": 1765495269,
+ "content": [
+ {
+ "type": "text",
+ "text": "Here are the first few user messages:\ntell me what the weather is in Berlin, Germany\n\nBased on the conversation so far, provide a concise description of this session in 4 words or less. This will be used for finding the session later in a UI with limited space - reply *ONLY* with the description"
+ }
+ ],
+ "metadata": {
+ "userVisible": true,
+ "agentVisible": true
+ }
+ }
+ ],
+ "tools": []
+ },
+ "output": {
+ "message": {
+ "id": null,
+ "role": "assistant",
+ "created": 1765495270,
+ "content": [
+ {
+ "type": "text",
+ "text": "Weather inquiry for Berlin"
+ }
+ ],
+ "metadata": {
+ "userVisible": true,
+ "agentVisible": true
+ }
+ },
+ "usage": {
+ "model": "gpt-4o-mini-2024-07-18",
+ "usage": {
+ "input_tokens": 84,
+ "output_tokens": 5,
+ "total_tokens": 89
+ }
+ }
+ }
+ },
"1bc400a528c54b25f4f1f609481e98e44222b3deaf7eee2c9e640e6345c73861": {
"input": {
"system": "You are a general-purpose AI agent called goose, created by Block, the parent company of Square, CashApp, and Tidal.\ngoose is being developed as an open-source software project.\n\ngoose uses LLM providers with tool calling capability. You can be used with different language models (gpt-4o,\nclaude-sonnet-4, o1, llama-3.2, deepseek-r1, etc).\nThese models have varying knowledge cut-off dates depending on when they were trained, but typically it's between 5-10\nmonths prior to the current date.\n\n# Extensions\n\nExtensions allow other applications to provide context to goose. Extensions connect goose to different data sources and\ntools.\nYou are capable of dynamically plugging into new extensions and learning how to use them. You solve higher level\nproblems using the tools in these extensions, and can interact with multiple at once.\n\nIf the Extension Manager extension is enabled, you can use the search_available_extensions tool to discover additional\nextensions that can help with your task. To enable or disable extensions, use the manage_extensions tool with the\nextension_name. You should only enable extensions found from the search_available_extensions tool.\nIf Extension Manager is not available, you can only work with currently enabled extensions and cannot dynamically load\nnew ones.\n\nBecause you dynamically load extensions, your conversation history may refer\nto interactions with extensions that are not currently active. The currently\nactive extensions are below. Each of these extensions provides tools that are\nin your tool specification.\n\n\n## weather_extension\n\n\n\n\n\n\n# Response Guidelines\n\n- Use Markdown formatting for all responses.\n- Follow best practices for Markdown, including:\n - Using headers for organization.\n - Bullet points for lists.\n - Links formatted correctly, either as linked text (e.g., [this is linked text](https://example.com)) or automatic\n links using angle brackets (e.g., ).\n- For code examples, use fenced code blocks by placing triple backticks (` ``` `) before and after the code. Include the\n language identifier after the opening backticks (e.g., ` ```python `) to enable syntax highlighting.\n- Ensure clarity, conciseness, and proper formatting to enhance readability and usability.",
"messages": [
{
- "id": "msg_20251124_9_0",
+ "id": "msg_20251211_28_0",
"role": "user",
- "created": 1763989812,
+ "created": 1765495269,
"content": [
{
"type": "text",
@@ -257,11 +305,11 @@
"message": {
"id": null,
"role": "assistant",
- "created": 1763989813,
+ "created": 1765495270,
"content": [
{
"type": "toolRequest",
- "id": "call_5IgyNY33nTougqsNFhizypew",
+ "id": "call_JJok3ApyTL54ohjLX8tTYVHC",
"toolCall": {
"status": "success",
"value": {
@@ -288,14 +336,14 @@
}
}
},
- "2c07b9a6570269fef4ae329889ce5a5243d7b09b52165580be701b67b8b95818": {
+ "e020c157caa13bad743162d7079a04ddafca2d075469124a0162c5562a2cf21b": {
"input": {
"system": "You are a general-purpose AI agent called goose, created by Block, the parent company of Square, CashApp, and Tidal.\ngoose is being developed as an open-source software project.\n\ngoose uses LLM providers with tool calling capability. You can be used with different language models (gpt-4o,\nclaude-sonnet-4, o1, llama-3.2, deepseek-r1, etc).\nThese models have varying knowledge cut-off dates depending on when they were trained, but typically it's between 5-10\nmonths prior to the current date.\n\n# Extensions\n\nExtensions allow other applications to provide context to goose. Extensions connect goose to different data sources and\ntools.\nYou are capable of dynamically plugging into new extensions and learning how to use them. You solve higher level\nproblems using the tools in these extensions, and can interact with multiple at once.\n\nIf the Extension Manager extension is enabled, you can use the search_available_extensions tool to discover additional\nextensions that can help with your task. To enable or disable extensions, use the manage_extensions tool with the\nextension_name. You should only enable extensions found from the search_available_extensions tool.\nIf Extension Manager is not available, you can only work with currently enabled extensions and cannot dynamically load\nnew ones.\n\nBecause you dynamically load extensions, your conversation history may refer\nto interactions with extensions that are not currently active. The currently\nactive extensions are below. Each of these extensions provides tools that are\nin your tool specification.\n\n\n## weather_extension\n\n\n\n\n\n\n# Response Guidelines\n\n- Use Markdown formatting for all responses.\n- Follow best practices for Markdown, including:\n - Using headers for organization.\n - Bullet points for lists.\n - Links formatted correctly, either as linked text (e.g., [this is linked text](https://example.com)) or automatic\n links using angle brackets (e.g., ).\n- For code examples, use fenced code blocks by placing triple backticks (` ``` `) before and after the code. Include the\n language identifier after the opening backticks (e.g., ` ```python `) to enable syntax highlighting.\n- Ensure clarity, conciseness, and proper formatting to enhance readability and usability.",
"messages": [
{
- "id": "msg_20251124_9_0",
+ "id": "msg_20251211_28_0",
"role": "user",
- "created": 1763989812,
+ "created": 1765495269,
"content": [
{
"type": "text",
@@ -308,13 +356,13 @@
}
},
{
- "id": "msg_b87436fe-d710-4fcd-b912-a69ab4ccfe7e",
+ "id": "msg_1d08355f-6d2d-413e-a3b5-787d635f9c6e",
"role": "assistant",
- "created": 1763989813,
+ "created": 1765495270,
"content": [
{
"type": "toolRequest",
- "id": "call_5IgyNY33nTougqsNFhizypew",
+ "id": "call_JJok3ApyTL54ohjLX8tTYVHC",
"toolCall": {
"status": "success",
"value": {
@@ -332,21 +380,23 @@
}
},
{
- "id": "msg_9cf859cf-4adc-4464-aba4-1d4ccf9d7d05",
+ "id": "msg_929b0769-72dd-46f8-9027-31d1c09d4f08",
"role": "user",
- "created": 1763989813,
+ "created": 1765495270,
"content": [
{
"type": "toolResponse",
- "id": "call_5IgyNY33nTougqsNFhizypew",
+ "id": "call_JJok3ApyTL54ohjLX8tTYVHC",
"toolResult": {
"status": "success",
- "value": [
- {
- "type": "text",
- "text": "The weather in Berlin, Germany is cloudy and 18Β°C"
- }
- ]
+ "value": {
+ "content": [
+ {
+ "type": "text",
+ "text": "The weather in Berlin, Germany is cloudy and 18Β°C"
+ }
+ ]
+ }
}
}
],
@@ -594,11 +644,11 @@
"message": {
"id": null,
"role": "assistant",
- "created": 1763989814,
+ "created": 1765495271,
"content": [
{
"type": "text",
- "text": "### Current Weather in Berlin, Germany\n\n- **Condition:** Cloudy\n- **Temperature:** 18Β°C"
+ "text": "### Current Weather in Berlin, Germany\n\n- **Condition:** Cloudy\n- **Temperature:** 18Β°C\n\nIf you need more information or a forecast, let me know!"
}
],
"metadata": {
@@ -610,56 +660,8 @@
"model": "gpt-4o-mini-2024-07-18",
"usage": {
"input_tokens": 1534,
- "output_tokens": 24,
- "total_tokens": 1558
- }
- }
- }
- },
- "8b1b8633232ca390cb3ff37a48daf74168b58f13e2943efcb17957129ee34d1a": {
- "input": {
- "system": "Reply with only a description in four words or less",
- "messages": [
- {
- "id": null,
- "role": "user",
- "created": 1763989812,
- "content": [
- {
- "type": "text",
- "text": "Here are the first few user messages:\ntell me what the weather is in Berlin, Germany\n\nBased on the conversation so far, provide a concise description of this session in 4 words or less. This will be used for finding the session later in a UI with limited space - reply *ONLY* with the description"
- }
- ],
- "metadata": {
- "userVisible": true,
- "agentVisible": true
- }
- }
- ],
- "tools": []
- },
- "output": {
- "message": {
- "id": null,
- "role": "assistant",
- "created": 1763989813,
- "content": [
- {
- "type": "text",
- "text": "Berlin weather inquiry"
- }
- ],
- "metadata": {
- "userVisible": true,
- "agentVisible": true
- }
- },
- "usage": {
- "model": "gpt-4o-mini-2024-07-18",
- "usage": {
- "input_tokens": 84,
- "output_tokens": 4,
- "total_tokens": 88
+ "output_tokens": 38,
+ "total_tokens": 1572
}
}
}
diff --git a/crates/goose-cli/src/scenario_tests/recordings/groq/weather_tool.json b/crates/goose-cli/src/scenario_tests/recordings/groq/weather_tool.json
index 7a65421844..d32272fdac 100644
--- a/crates/goose-cli/src/scenario_tests/recordings/groq/weather_tool.json
+++ b/crates/goose-cli/src/scenario_tests/recordings/groq/weather_tool.json
@@ -1,301 +1,12 @@
{
- "1bc400a528c54b25f4f1f609481e98e44222b3deaf7eee2c9e640e6345c73861": {
+ "075d1e65f3a25bbdfc21aee19a1119b43fd4241f8643e646c9eb928a431975d4": {
"input": {
"system": "You are a general-purpose AI agent called goose, created by Block, the parent company of Square, CashApp, and Tidal.\ngoose is being developed as an open-source software project.\n\ngoose uses LLM providers with tool calling capability. You can be used with different language models (gpt-4o,\nclaude-sonnet-4, o1, llama-3.2, deepseek-r1, etc).\nThese models have varying knowledge cut-off dates depending on when they were trained, but typically it's between 5-10\nmonths prior to the current date.\n\n# Extensions\n\nExtensions allow other applications to provide context to goose. Extensions connect goose to different data sources and\ntools.\nYou are capable of dynamically plugging into new extensions and learning how to use them. You solve higher level\nproblems using the tools in these extensions, and can interact with multiple at once.\n\nIf the Extension Manager extension is enabled, you can use the search_available_extensions tool to discover additional\nextensions that can help with your task. To enable or disable extensions, use the manage_extensions tool with the\nextension_name. You should only enable extensions found from the search_available_extensions tool.\nIf Extension Manager is not available, you can only work with currently enabled extensions and cannot dynamically load\nnew ones.\n\nBecause you dynamically load extensions, your conversation history may refer\nto interactions with extensions that are not currently active. The currently\nactive extensions are below. Each of these extensions provides tools that are\nin your tool specification.\n\n\n## weather_extension\n\n\n\n\n\n\n# Response Guidelines\n\n- Use Markdown formatting for all responses.\n- Follow best practices for Markdown, including:\n - Using headers for organization.\n - Bullet points for lists.\n - Links formatted correctly, either as linked text (e.g., [this is linked text](https://example.com)) or automatic\n links using angle brackets (e.g., ).\n- For code examples, use fenced code blocks by placing triple backticks (` ``` `) before and after the code. Include the\n language identifier after the opening backticks (e.g., ` ```python `) to enable syntax highlighting.\n- Ensure clarity, conciseness, and proper formatting to enhance readability and usability.",
"messages": [
{
- "id": "msg_20251124_10_0",
+ "id": "msg_20251211_40_0",
"role": "user",
- "created": 1763989814,
- "content": [
- {
- "type": "text",
- "text": "tell me what the weather is in Berlin, Germany"
- }
- ],
- "metadata": {
- "userVisible": true,
- "agentVisible": true
- }
- }
- ],
- "tools": [
- {
- "name": "dynamic_task__create_task",
- "description": "Create tasks with instructions or prompt. For simple tasks, only include the instructions field. Extensions control: omit field = use all current extensions; empty array [] = no extensions; array with names = only those extensions. Specify extensions as shortnames (the prefixes for your tools). Specify return_last_only as true and have your subagent summarize its work in its last message to conserve your own context. Optional: title, description, extensions, settings, retry, response schema, activities. Arrays for multiple tasks.",
- "inputSchema": {
- "$defs": {
- "TaskParameter": {
- "description": "Parameters for a single task",
- "properties": {
- "activities": {
- "items": {
- "type": "string"
- },
- "type": [
- "array",
- "null"
- ]
- },
- "description": {
- "type": [
- "string",
- "null"
- ]
- },
- "extensions": {
- "items": {
- "additionalProperties": true,
- "type": "object"
- },
- "type": [
- "array",
- "null"
- ]
- },
- "instructions": {
- "type": [
- "string",
- "null"
- ]
- },
- "parameters": {
- "items": {
- "additionalProperties": true,
- "type": "object"
- },
- "type": [
- "array",
- "null"
- ]
- },
- "prompt": {
- "type": [
- "string",
- "null"
- ]
- },
- "response": {
- "additionalProperties": true,
- "type": [
- "object",
- "null"
- ]
- },
- "retry": {
- "additionalProperties": true,
- "type": [
- "object",
- "null"
- ]
- },
- "return_last_only": {
- "description": "If true, return only the last message from the subagent (default: false, returns full conversation)",
- "type": [
- "boolean",
- "null"
- ]
- },
- "settings": {
- "additionalProperties": true,
- "type": [
- "object",
- "null"
- ]
- },
- "title": {
- "type": [
- "string",
- "null"
- ]
- }
- },
- "type": "object"
- }
- },
- "$schema": "https://json-schema.org/draft/2020-12/schema",
- "properties": {
- "execution_mode": {
- "description": "How to execute multiple tasks (default: parallel for multiple tasks, sequential for single task)",
- "type": [
- "string",
- "null"
- ]
- },
- "task_parameters": {
- "description": "Array of tasks. Each task must have either 'instructions' OR 'prompt' field (at least one is required).",
- "items": {
- "$ref": "#/$defs/TaskParameter"
- },
- "minItems": 1,
- "type": "array"
- }
- },
- "required": [
- "task_parameters"
- ],
- "title": "CreateDynamicTaskParams",
- "type": "object"
- },
- "annotations": {
- "title": "Create Dynamic Tasks",
- "readOnlyHint": false,
- "destructiveHint": false,
- "idempotentHint": false,
- "openWorldHint": true
- }
- },
- {
- "name": "platform__manage_schedule",
- "description": "Manage scheduled recipe execution for this goose instance.\n\nActions:\n- \"list\": List all scheduled jobs\n- \"create\": Create a new scheduled job from a recipe file\n- \"run_now\": Execute a scheduled job immediately \n- \"pause\": Pause a scheduled job\n- \"unpause\": Resume a paused job\n- \"delete\": Remove a scheduled job\n- \"kill\": Terminate a currently running job\n- \"inspect\": Get details about a running job\n- \"sessions\": List execution history for a job\n- \"session_content\": Get the full content (messages) of a specific session\n",
- "inputSchema": {
- "properties": {
- "action": {
- "enum": [
- "list",
- "create",
- "run_now",
- "pause",
- "unpause",
- "delete",
- "kill",
- "inspect",
- "sessions",
- "session_content"
- ],
- "type": "string"
- },
- "cron_expression": {
- "description": "A cron expression for create action. Supports both 5-field (minute hour day month weekday) and 6-field (second minute hour day month weekday) formats. 5-field expressions are automatically converted to 6-field by prepending '0' for seconds.",
- "type": "string"
- },
- "job_id": {
- "description": "Job identifier for operations on existing jobs",
- "type": "string"
- },
- "limit": {
- "default": 50,
- "description": "Limit for sessions list",
- "type": "integer"
- },
- "recipe_path": {
- "description": "Path to recipe file for create action",
- "type": "string"
- },
- "session_id": {
- "description": "Session identifier for session_content action",
- "type": "string"
- }
- },
- "required": [
- "action"
- ],
- "type": "object"
- },
- "annotations": {
- "title": "Manage scheduled recipes",
- "readOnlyHint": false,
- "destructiveHint": true,
- "idempotentHint": false,
- "openWorldHint": false
- }
- },
- {
- "name": "subagent__execute_task",
- "description": "Only use the subagent__execute_task tool when you execute sub recipe task or dynamic task.\n EXECUTION STRATEGY DECISION:\n 1. If the tasks are created with execution_mode, use the execution_mode.\n 2. Execute tasks sequentially unless user explicitly requests parallel execution. PARALLEL: User uses keywords like 'parallel', 'simultaneously', 'at the same time', 'concurrently'\n\n IMPLEMENTATION:\n - Sequential execution: Call this tool multiple times, passing exactly ONE task per call\n - Parallel execution: Call this tool once, passing an ARRAY of all tasks\n\n EXAMPLES:\n User Intent Based:\n - User: 'get weather and tell me a joke' β Sequential (2 separate tool calls, 1 task each)\n - User: 'get weather and joke in parallel' β Parallel (1 tool call with array of 2 tasks)\n - User: 'run these simultaneously' β Parallel (1 tool call with task array)\n - User: 'do task A then task B' β Sequential (2 separate tool calls)",
- "inputSchema": {
- "properties": {
- "execution_mode": {
- "default": "sequential",
- "description": "Execution strategy for multiple tasks. Use 'sequential' (default) unless user explicitly requests parallel execution with words like 'parallel', 'simultaneously', 'at the same time', or 'concurrently'.",
- "enum": [
- "sequential",
- "parallel"
- ],
- "type": "string"
- },
- "task_ids": {
- "items": {
- "description": "Unique identifier for the task",
- "type": "string"
- },
- "type": "array"
- }
- },
- "required": [
- "task_ids"
- ],
- "type": "object"
- },
- "annotations": {
- "title": "Run tasks in parallel",
- "readOnlyHint": false,
- "destructiveHint": true,
- "idempotentHint": false,
- "openWorldHint": true
- }
- },
- {
- "name": "weather_extension__get_weather",
- "description": "Get the weather for a location",
- "inputSchema": {
- "properties": {
- "location": {
- "description": "The city and state, e.g. San Francisco, CA",
- "type": "string"
- }
- },
- "required": [
- "location"
- ],
- "type": "object"
- }
- }
- ]
- },
- "output": {
- "message": {
- "id": null,
- "role": "assistant",
- "created": 1763989814,
- "content": [
- {
- "type": "toolRequest",
- "id": "4kpg6v08d",
- "toolCall": {
- "status": "success",
- "value": {
- "name": "weather_extension__get_weather",
- "arguments": {
- "location": "Berlin, Germany"
- }
- }
- }
- }
- ],
- "metadata": {
- "userVisible": true,
- "agentVisible": true
- }
- },
- "usage": {
- "model": "llama-3.3-70b-versatile",
- "usage": {
- "input_tokens": 2258,
- "output_tokens": 20,
- "total_tokens": 2278
- }
- }
- }
- },
- "165258e98e56cdcccdc49bd70ecd2b081b41d35ddb3559ed3bc7b1e420290bed": {
- "input": {
- "system": "You are a general-purpose AI agent called goose, created by Block, the parent company of Square, CashApp, and Tidal.\ngoose is being developed as an open-source software project.\n\ngoose uses LLM providers with tool calling capability. You can be used with different language models (gpt-4o,\nclaude-sonnet-4, o1, llama-3.2, deepseek-r1, etc).\nThese models have varying knowledge cut-off dates depending on when they were trained, but typically it's between 5-10\nmonths prior to the current date.\n\n# Extensions\n\nExtensions allow other applications to provide context to goose. Extensions connect goose to different data sources and\ntools.\nYou are capable of dynamically plugging into new extensions and learning how to use them. You solve higher level\nproblems using the tools in these extensions, and can interact with multiple at once.\n\nIf the Extension Manager extension is enabled, you can use the search_available_extensions tool to discover additional\nextensions that can help with your task. To enable or disable extensions, use the manage_extensions tool with the\nextension_name. You should only enable extensions found from the search_available_extensions tool.\nIf Extension Manager is not available, you can only work with currently enabled extensions and cannot dynamically load\nnew ones.\n\nBecause you dynamically load extensions, your conversation history may refer\nto interactions with extensions that are not currently active. The currently\nactive extensions are below. Each of these extensions provides tools that are\nin your tool specification.\n\n\n## weather_extension\n\n\n\n\n\n\n# Response Guidelines\n\n- Use Markdown formatting for all responses.\n- Follow best practices for Markdown, including:\n - Using headers for organization.\n - Bullet points for lists.\n - Links formatted correctly, either as linked text (e.g., [this is linked text](https://example.com)) or automatic\n links using angle brackets (e.g., ).\n- For code examples, use fenced code blocks by placing triple backticks (` ``` `) before and after the code. Include the\n language identifier after the opening backticks (e.g., ` ```python `) to enable syntax highlighting.\n- Ensure clarity, conciseness, and proper formatting to enhance readability and usability.",
- "messages": [
- {
- "id": "msg_20251124_10_0",
- "role": "user",
- "created": 1763989814,
+ "created": 1765495334,
"content": [
{
"type": "text",
@@ -308,13 +19,13 @@
}
},
{
- "id": "msg_9d25f6e9-6d02-4f41-8710-325795e87fbc",
+ "id": "msg_6147fd68-5a38-4528-9924-d4dbaf5e7f44",
"role": "assistant",
- "created": 1763989814,
+ "created": 1765495335,
"content": [
{
"type": "toolRequest",
- "id": "4kpg6v08d",
+ "id": "zemx6n94z",
"toolCall": {
"status": "success",
"value": {
@@ -332,21 +43,23 @@
}
},
{
- "id": "msg_513af900-f4fe-4ed0-aed8-7ffb4c8c384d",
+ "id": "msg_0fac763c-62c3-4a33-a15b-27d605c5b81d",
"role": "user",
- "created": 1763989814,
+ "created": 1765495335,
"content": [
{
"type": "toolResponse",
- "id": "4kpg6v08d",
+ "id": "zemx6n94z",
"toolResult": {
"status": "success",
- "value": [
- {
- "type": "text",
- "text": "The weather in Berlin, Germany is cloudy and 18Β°C"
- }
- ]
+ "value": {
+ "content": [
+ {
+ "type": "text",
+ "text": "The weather in Berlin, Germany is cloudy and 18Β°C"
+ }
+ ]
+ }
}
}
],
@@ -594,7 +307,7 @@
"message": {
"id": null,
"role": "assistant",
- "created": 1763989815,
+ "created": 1765495335,
"content": [
{
"type": "text",
@@ -623,7 +336,7 @@
{
"id": null,
"role": "user",
- "created": 1763989814,
+ "created": 1765495334,
"content": [
{
"type": "text",
@@ -642,11 +355,11 @@
"message": {
"id": null,
"role": "assistant",
- "created": 1763989814,
+ "created": 1765495334,
"content": [
{
"type": "text",
- "text": "Berlin weather query"
+ "text": "Berlin weather"
}
],
"metadata": {
@@ -658,8 +371,297 @@
"model": "llama-3.3-70b-versatile",
"usage": {
"input_tokens": 108,
- "output_tokens": 4,
- "total_tokens": 112
+ "output_tokens": 3,
+ "total_tokens": 111
+ }
+ }
+ }
+ },
+ "1bc400a528c54b25f4f1f609481e98e44222b3deaf7eee2c9e640e6345c73861": {
+ "input": {
+ "system": "You are a general-purpose AI agent called goose, created by Block, the parent company of Square, CashApp, and Tidal.\ngoose is being developed as an open-source software project.\n\ngoose uses LLM providers with tool calling capability. You can be used with different language models (gpt-4o,\nclaude-sonnet-4, o1, llama-3.2, deepseek-r1, etc).\nThese models have varying knowledge cut-off dates depending on when they were trained, but typically it's between 5-10\nmonths prior to the current date.\n\n# Extensions\n\nExtensions allow other applications to provide context to goose. Extensions connect goose to different data sources and\ntools.\nYou are capable of dynamically plugging into new extensions and learning how to use them. You solve higher level\nproblems using the tools in these extensions, and can interact with multiple at once.\n\nIf the Extension Manager extension is enabled, you can use the search_available_extensions tool to discover additional\nextensions that can help with your task. To enable or disable extensions, use the manage_extensions tool with the\nextension_name. You should only enable extensions found from the search_available_extensions tool.\nIf Extension Manager is not available, you can only work with currently enabled extensions and cannot dynamically load\nnew ones.\n\nBecause you dynamically load extensions, your conversation history may refer\nto interactions with extensions that are not currently active. The currently\nactive extensions are below. Each of these extensions provides tools that are\nin your tool specification.\n\n\n## weather_extension\n\n\n\n\n\n\n# Response Guidelines\n\n- Use Markdown formatting for all responses.\n- Follow best practices for Markdown, including:\n - Using headers for organization.\n - Bullet points for lists.\n - Links formatted correctly, either as linked text (e.g., [this is linked text](https://example.com)) or automatic\n links using angle brackets (e.g., ).\n- For code examples, use fenced code blocks by placing triple backticks (` ``` `) before and after the code. Include the\n language identifier after the opening backticks (e.g., ` ```python `) to enable syntax highlighting.\n- Ensure clarity, conciseness, and proper formatting to enhance readability and usability.",
+ "messages": [
+ {
+ "id": "msg_20251211_40_0",
+ "role": "user",
+ "created": 1765495334,
+ "content": [
+ {
+ "type": "text",
+ "text": "tell me what the weather is in Berlin, Germany"
+ }
+ ],
+ "metadata": {
+ "userVisible": true,
+ "agentVisible": true
+ }
+ }
+ ],
+ "tools": [
+ {
+ "name": "dynamic_task__create_task",
+ "description": "Create tasks with instructions or prompt. For simple tasks, only include the instructions field. Extensions control: omit field = use all current extensions; empty array [] = no extensions; array with names = only those extensions. Specify extensions as shortnames (the prefixes for your tools). Specify return_last_only as true and have your subagent summarize its work in its last message to conserve your own context. Optional: title, description, extensions, settings, retry, response schema, activities. Arrays for multiple tasks.",
+ "inputSchema": {
+ "$defs": {
+ "TaskParameter": {
+ "description": "Parameters for a single task",
+ "properties": {
+ "activities": {
+ "items": {
+ "type": "string"
+ },
+ "type": [
+ "array",
+ "null"
+ ]
+ },
+ "description": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "extensions": {
+ "items": {
+ "additionalProperties": true,
+ "type": "object"
+ },
+ "type": [
+ "array",
+ "null"
+ ]
+ },
+ "instructions": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "parameters": {
+ "items": {
+ "additionalProperties": true,
+ "type": "object"
+ },
+ "type": [
+ "array",
+ "null"
+ ]
+ },
+ "prompt": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "response": {
+ "additionalProperties": true,
+ "type": [
+ "object",
+ "null"
+ ]
+ },
+ "retry": {
+ "additionalProperties": true,
+ "type": [
+ "object",
+ "null"
+ ]
+ },
+ "return_last_only": {
+ "description": "If true, return only the last message from the subagent (default: false, returns full conversation)",
+ "type": [
+ "boolean",
+ "null"
+ ]
+ },
+ "settings": {
+ "additionalProperties": true,
+ "type": [
+ "object",
+ "null"
+ ]
+ },
+ "title": {
+ "type": [
+ "string",
+ "null"
+ ]
+ }
+ },
+ "type": "object"
+ }
+ },
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "properties": {
+ "execution_mode": {
+ "description": "How to execute multiple tasks (default: parallel for multiple tasks, sequential for single task)",
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "task_parameters": {
+ "description": "Array of tasks. Each task must have either 'instructions' OR 'prompt' field (at least one is required).",
+ "items": {
+ "$ref": "#/$defs/TaskParameter"
+ },
+ "minItems": 1,
+ "type": "array"
+ }
+ },
+ "required": [
+ "task_parameters"
+ ],
+ "title": "CreateDynamicTaskParams",
+ "type": "object"
+ },
+ "annotations": {
+ "title": "Create Dynamic Tasks",
+ "readOnlyHint": false,
+ "destructiveHint": false,
+ "idempotentHint": false,
+ "openWorldHint": true
+ }
+ },
+ {
+ "name": "platform__manage_schedule",
+ "description": "Manage scheduled recipe execution for this goose instance.\n\nActions:\n- \"list\": List all scheduled jobs\n- \"create\": Create a new scheduled job from a recipe file\n- \"run_now\": Execute a scheduled job immediately \n- \"pause\": Pause a scheduled job\n- \"unpause\": Resume a paused job\n- \"delete\": Remove a scheduled job\n- \"kill\": Terminate a currently running job\n- \"inspect\": Get details about a running job\n- \"sessions\": List execution history for a job\n- \"session_content\": Get the full content (messages) of a specific session\n",
+ "inputSchema": {
+ "properties": {
+ "action": {
+ "enum": [
+ "list",
+ "create",
+ "run_now",
+ "pause",
+ "unpause",
+ "delete",
+ "kill",
+ "inspect",
+ "sessions",
+ "session_content"
+ ],
+ "type": "string"
+ },
+ "cron_expression": {
+ "description": "A cron expression for create action. Supports both 5-field (minute hour day month weekday) and 6-field (second minute hour day month weekday) formats. 5-field expressions are automatically converted to 6-field by prepending '0' for seconds.",
+ "type": "string"
+ },
+ "job_id": {
+ "description": "Job identifier for operations on existing jobs",
+ "type": "string"
+ },
+ "limit": {
+ "default": 50,
+ "description": "Limit for sessions list",
+ "type": "integer"
+ },
+ "recipe_path": {
+ "description": "Path to recipe file for create action",
+ "type": "string"
+ },
+ "session_id": {
+ "description": "Session identifier for session_content action",
+ "type": "string"
+ }
+ },
+ "required": [
+ "action"
+ ],
+ "type": "object"
+ },
+ "annotations": {
+ "title": "Manage scheduled recipes",
+ "readOnlyHint": false,
+ "destructiveHint": true,
+ "idempotentHint": false,
+ "openWorldHint": false
+ }
+ },
+ {
+ "name": "subagent__execute_task",
+ "description": "Only use the subagent__execute_task tool when you execute sub recipe task or dynamic task.\n EXECUTION STRATEGY DECISION:\n 1. If the tasks are created with execution_mode, use the execution_mode.\n 2. Execute tasks sequentially unless user explicitly requests parallel execution. PARALLEL: User uses keywords like 'parallel', 'simultaneously', 'at the same time', 'concurrently'\n\n IMPLEMENTATION:\n - Sequential execution: Call this tool multiple times, passing exactly ONE task per call\n - Parallel execution: Call this tool once, passing an ARRAY of all tasks\n\n EXAMPLES:\n User Intent Based:\n - User: 'get weather and tell me a joke' β Sequential (2 separate tool calls, 1 task each)\n - User: 'get weather and joke in parallel' β Parallel (1 tool call with array of 2 tasks)\n - User: 'run these simultaneously' β Parallel (1 tool call with task array)\n - User: 'do task A then task B' β Sequential (2 separate tool calls)",
+ "inputSchema": {
+ "properties": {
+ "execution_mode": {
+ "default": "sequential",
+ "description": "Execution strategy for multiple tasks. Use 'sequential' (default) unless user explicitly requests parallel execution with words like 'parallel', 'simultaneously', 'at the same time', or 'concurrently'.",
+ "enum": [
+ "sequential",
+ "parallel"
+ ],
+ "type": "string"
+ },
+ "task_ids": {
+ "items": {
+ "description": "Unique identifier for the task",
+ "type": "string"
+ },
+ "type": "array"
+ }
+ },
+ "required": [
+ "task_ids"
+ ],
+ "type": "object"
+ },
+ "annotations": {
+ "title": "Run tasks in parallel",
+ "readOnlyHint": false,
+ "destructiveHint": true,
+ "idempotentHint": false,
+ "openWorldHint": true
+ }
+ },
+ {
+ "name": "weather_extension__get_weather",
+ "description": "Get the weather for a location",
+ "inputSchema": {
+ "properties": {
+ "location": {
+ "description": "The city and state, e.g. San Francisco, CA",
+ "type": "string"
+ }
+ },
+ "required": [
+ "location"
+ ],
+ "type": "object"
+ }
+ }
+ ]
+ },
+ "output": {
+ "message": {
+ "id": null,
+ "role": "assistant",
+ "created": 1765495335,
+ "content": [
+ {
+ "type": "toolRequest",
+ "id": "zemx6n94z",
+ "toolCall": {
+ "status": "success",
+ "value": {
+ "name": "weather_extension__get_weather",
+ "arguments": {
+ "location": "Berlin, Germany"
+ }
+ }
+ }
+ }
+ ],
+ "metadata": {
+ "userVisible": true,
+ "agentVisible": true
+ }
+ },
+ "usage": {
+ "model": "llama-3.3-70b-versatile",
+ "usage": {
+ "input_tokens": 2258,
+ "output_tokens": 20,
+ "total_tokens": 2278
}
}
}
diff --git a/crates/goose-cli/src/scenario_tests/recordings/openai/weather_tool.json b/crates/goose-cli/src/scenario_tests/recordings/openai/weather_tool.json
index 195be6d270..10dbf8c397 100644
--- a/crates/goose-cli/src/scenario_tests/recordings/openai/weather_tool.json
+++ b/crates/goose-cli/src/scenario_tests/recordings/openai/weather_tool.json
@@ -4,9 +4,9 @@
"system": "You are a general-purpose AI agent called goose, created by Block, the parent company of Square, CashApp, and Tidal.\ngoose is being developed as an open-source software project.\n\ngoose uses LLM providers with tool calling capability. You can be used with different language models (gpt-4o,\nclaude-sonnet-4, o1, llama-3.2, deepseek-r1, etc).\nThese models have varying knowledge cut-off dates depending on when they were trained, but typically it's between 5-10\nmonths prior to the current date.\n\n# Extensions\n\nExtensions allow other applications to provide context to goose. Extensions connect goose to different data sources and\ntools.\nYou are capable of dynamically plugging into new extensions and learning how to use them. You solve higher level\nproblems using the tools in these extensions, and can interact with multiple at once.\n\nIf the Extension Manager extension is enabled, you can use the search_available_extensions tool to discover additional\nextensions that can help with your task. To enable or disable extensions, use the manage_extensions tool with the\nextension_name. You should only enable extensions found from the search_available_extensions tool.\nIf Extension Manager is not available, you can only work with currently enabled extensions and cannot dynamically load\nnew ones.\n\nBecause you dynamically load extensions, your conversation history may refer\nto interactions with extensions that are not currently active. The currently\nactive extensions are below. Each of these extensions provides tools that are\nin your tool specification.\n\n\n## weather_extension\n\n\n\n\n\n\n# Response Guidelines\n\n- Use Markdown formatting for all responses.\n- Follow best practices for Markdown, including:\n - Using headers for organization.\n - Bullet points for lists.\n - Links formatted correctly, either as linked text (e.g., [this is linked text](https://example.com)) or automatic\n links using angle brackets (e.g., ).\n- For code examples, use fenced code blocks by placing triple backticks (` ``` `) before and after the code. Include the\n language identifier after the opening backticks (e.g., ` ```python `) to enable syntax highlighting.\n- Ensure clarity, conciseness, and proper formatting to enhance readability and usability.",
"messages": [
{
- "id": "msg_20251124_7_0",
+ "id": "msg_20251211_26_0",
"role": "user",
- "created": 1763989801,
+ "created": 1765495261,
"content": [
{
"type": "text",
@@ -257,11 +257,11 @@
"message": {
"id": null,
"role": "assistant",
- "created": 1763989803,
+ "created": 1765495262,
"content": [
{
"type": "toolRequest",
- "id": "call_GmIN2vOnjDg6MSVWWFuZ4FlV",
+ "id": "call_sVLij6GavUkUc5Ri4VeDdgmN",
"toolCall": {
"status": "success",
"value": {
@@ -288,62 +288,14 @@
}
}
},
- "8b1b8633232ca390cb3ff37a48daf74168b58f13e2943efcb17957129ee34d1a": {
- "input": {
- "system": "Reply with only a description in four words or less",
- "messages": [
- {
- "id": null,
- "role": "user",
- "created": 1763989801,
- "content": [
- {
- "type": "text",
- "text": "Here are the first few user messages:\ntell me what the weather is in Berlin, Germany\n\nBased on the conversation so far, provide a concise description of this session in 4 words or less. This will be used for finding the session later in a UI with limited space - reply *ONLY* with the description"
- }
- ],
- "metadata": {
- "userVisible": true,
- "agentVisible": true
- }
- }
- ],
- "tools": []
- },
- "output": {
- "message": {
- "id": null,
- "role": "assistant",
- "created": 1763989804,
- "content": [
- {
- "type": "text",
- "text": "Berlin weather inquiry"
- }
- ],
- "metadata": {
- "userVisible": true,
- "agentVisible": true
- }
- },
- "usage": {
- "model": "gpt-4o-2024-08-06",
- "usage": {
- "input_tokens": 84,
- "output_tokens": 3,
- "total_tokens": 87
- }
- }
- }
- },
- "98726e7698e93241dfcba182bdca3a7d0640f0f538f086f4981dca6689b7ab5a": {
+ "950a6b5ec929abdd9e7cd337d2ea80c07631c23c6d64edac31988e18c6207e92": {
"input": {
"system": "You are a general-purpose AI agent called goose, created by Block, the parent company of Square, CashApp, and Tidal.\ngoose is being developed as an open-source software project.\n\ngoose uses LLM providers with tool calling capability. You can be used with different language models (gpt-4o,\nclaude-sonnet-4, o1, llama-3.2, deepseek-r1, etc).\nThese models have varying knowledge cut-off dates depending on when they were trained, but typically it's between 5-10\nmonths prior to the current date.\n\n# Extensions\n\nExtensions allow other applications to provide context to goose. Extensions connect goose to different data sources and\ntools.\nYou are capable of dynamically plugging into new extensions and learning how to use them. You solve higher level\nproblems using the tools in these extensions, and can interact with multiple at once.\n\nIf the Extension Manager extension is enabled, you can use the search_available_extensions tool to discover additional\nextensions that can help with your task. To enable or disable extensions, use the manage_extensions tool with the\nextension_name. You should only enable extensions found from the search_available_extensions tool.\nIf Extension Manager is not available, you can only work with currently enabled extensions and cannot dynamically load\nnew ones.\n\nBecause you dynamically load extensions, your conversation history may refer\nto interactions with extensions that are not currently active. The currently\nactive extensions are below. Each of these extensions provides tools that are\nin your tool specification.\n\n\n## weather_extension\n\n\n\n\n\n\n# Response Guidelines\n\n- Use Markdown formatting for all responses.\n- Follow best practices for Markdown, including:\n - Using headers for organization.\n - Bullet points for lists.\n - Links formatted correctly, either as linked text (e.g., [this is linked text](https://example.com)) or automatic\n links using angle brackets (e.g., ).\n- For code examples, use fenced code blocks by placing triple backticks (` ``` `) before and after the code. Include the\n language identifier after the opening backticks (e.g., ` ```python `) to enable syntax highlighting.\n- Ensure clarity, conciseness, and proper formatting to enhance readability and usability.",
"messages": [
{
- "id": "msg_20251124_7_0",
+ "id": "msg_20251211_26_0",
"role": "user",
- "created": 1763989801,
+ "created": 1765495261,
"content": [
{
"type": "text",
@@ -356,13 +308,13 @@
}
},
{
- "id": "msg_9a0fb3d7-d681-40b9-ab58-bb1cbe03e4b0",
+ "id": "msg_899ef38b-1440-47f7-a942-1b2e58c8835c",
"role": "assistant",
- "created": 1763989803,
+ "created": 1765495262,
"content": [
{
"type": "toolRequest",
- "id": "call_GmIN2vOnjDg6MSVWWFuZ4FlV",
+ "id": "call_sVLij6GavUkUc5Ri4VeDdgmN",
"toolCall": {
"status": "success",
"value": {
@@ -380,21 +332,23 @@
}
},
{
- "id": "msg_51ae1986-b095-4163-8d42-004ff1a28c59",
+ "id": "msg_2f8ddf2e-dedb-4b99-9549-826796292622",
"role": "user",
- "created": 1763989803,
+ "created": 1765495262,
"content": [
{
"type": "toolResponse",
- "id": "call_GmIN2vOnjDg6MSVWWFuZ4FlV",
+ "id": "call_sVLij6GavUkUc5Ri4VeDdgmN",
"toolResult": {
"status": "success",
- "value": [
- {
- "type": "text",
- "text": "The weather in Berlin, Germany is cloudy and 18Β°C"
- }
- ]
+ "value": {
+ "content": [
+ {
+ "type": "text",
+ "text": "The weather in Berlin, Germany is cloudy and 18Β°C"
+ }
+ ]
+ }
}
}
],
@@ -642,7 +596,7 @@
"message": {
"id": null,
"role": "assistant",
- "created": 1763989805,
+ "created": 1765495263,
"content": [
{
"type": "text",
@@ -663,5 +617,53 @@
}
}
}
+ },
+ "8b1b8633232ca390cb3ff37a48daf74168b58f13e2943efcb17957129ee34d1a": {
+ "input": {
+ "system": "Reply with only a description in four words or less",
+ "messages": [
+ {
+ "id": null,
+ "role": "user",
+ "created": 1765495261,
+ "content": [
+ {
+ "type": "text",
+ "text": "Here are the first few user messages:\ntell me what the weather is in Berlin, Germany\n\nBased on the conversation so far, provide a concise description of this session in 4 words or less. This will be used for finding the session later in a UI with limited space - reply *ONLY* with the description"
+ }
+ ],
+ "metadata": {
+ "userVisible": true,
+ "agentVisible": true
+ }
+ }
+ ],
+ "tools": []
+ },
+ "output": {
+ "message": {
+ "id": null,
+ "role": "assistant",
+ "created": 1765495262,
+ "content": [
+ {
+ "type": "text",
+ "text": "Berlin weather inquiry"
+ }
+ ],
+ "metadata": {
+ "userVisible": true,
+ "agentVisible": true
+ }
+ },
+ "usage": {
+ "model": "gpt-4o-2024-08-06",
+ "usage": {
+ "input_tokens": 84,
+ "output_tokens": 3,
+ "total_tokens": 87
+ }
+ }
+ }
}
}
\ No newline at end of file
diff --git a/crates/goose-cli/src/session/builder.rs b/crates/goose-cli/src/session/builder.rs
index 8c9fc96613..947e2d3349 100644
--- a/crates/goose-cli/src/session/builder.rs
+++ b/crates/goose-cli/src/session/builder.rs
@@ -248,6 +248,8 @@ pub struct SessionSettings {
}
pub async fn build_session(session_config: SessionBuilderConfig) -> CliSession {
+ goose::posthog::set_session_context("cli", session_config.resume);
+
let config = Config::global();
let (saved_provider, saved_model_config) = if session_config.resume {
diff --git a/crates/goose-cli/src/session/export.rs b/crates/goose-cli/src/session/export.rs
index 61e537d60c..7246913606 100644
--- a/crates/goose-cli/src/session/export.rs
+++ b/crates/goose-cli/src/session/export.rs
@@ -221,12 +221,12 @@ pub fn tool_response_to_markdown(resp: &ToolResponse, export_all_content: bool)
md.push_str("#### Tool Response:\n");
match &resp.tool_result {
- Ok(contents) => {
- if contents.is_empty() {
+ Ok(result) => {
+ if result.content.is_empty() {
md.push_str("*No textual output from tool.*\n");
}
- for content in contents {
+ for content in &result.content {
if !export_all_content {
if let Some(audience) = content.audience() {
if !audience.contains(&Role::Assistant) {
@@ -581,7 +581,12 @@ mod tests {
};
let tool_response = ToolResponse {
id: "test-id".to_string(),
- tool_result: Ok(vec![Content::text(text_content.raw.text)]),
+ tool_result: Ok(rmcp::model::CallToolResult {
+ content: vec![Content::text(text_content.raw.text)],
+ structured_content: None,
+ is_error: Some(false),
+ meta: None,
+ }),
};
let result = tool_response_to_markdown(&tool_response, true);
@@ -601,7 +606,12 @@ mod tests {
};
let tool_response = ToolResponse {
id: "test-id".to_string(),
- tool_result: Ok(vec![Content::text(text_content.raw.text)]),
+ tool_result: Ok(rmcp::model::CallToolResult {
+ content: vec![Content::text(text_content.raw.text)],
+ structured_content: None,
+ is_error: Some(false),
+ meta: None,
+ }),
};
let result = tool_response_to_markdown(&tool_response, true);
@@ -707,7 +717,12 @@ if __name__ == "__main__":
};
let tool_response = ToolResponse {
id: "shell-cat".to_string(),
- tool_result: Ok(vec![Content::text(text_content.raw.text)]),
+ tool_result: Ok(rmcp::model::CallToolResult {
+ content: vec![Content::text(text_content.raw.text)],
+ structured_content: None,
+ is_error: Some(false),
+ meta: None,
+ }),
};
let request_result = tool_request_to_markdown(&tool_request, true);
@@ -748,7 +763,12 @@ if __name__ == "__main__":
};
let tool_response = ToolResponse {
id: "git-status".to_string(),
- tool_result: Ok(vec![Content::text(text_content.raw.text)]),
+ tool_result: Ok(rmcp::model::CallToolResult {
+ content: vec![Content::text(text_content.raw.text)],
+ structured_content: None,
+ is_error: Some(false),
+ meta: None,
+ }),
};
let request_result = tool_request_to_markdown(&tool_request, true);
@@ -797,7 +817,12 @@ warning: unused variable `x`
};
let tool_response = ToolResponse {
id: "cargo-build".to_string(),
- tool_result: Ok(vec![Content::text(text_content.raw.text)]),
+ tool_result: Ok(rmcp::model::CallToolResult {
+ content: vec![Content::text(text_content.raw.text)],
+ structured_content: None,
+ is_error: Some(false),
+ meta: None,
+ }),
};
let response_result = tool_response_to_markdown(&tool_response, true);
@@ -844,7 +869,12 @@ warning: unused variable `x`
};
let tool_response = ToolResponse {
id: "curl-api".to_string(),
- tool_result: Ok(vec![Content::text(text_content.raw.text)]),
+ tool_result: Ok(rmcp::model::CallToolResult {
+ content: vec![Content::text(text_content.raw.text)],
+ structured_content: None,
+ is_error: Some(false),
+ meta: None,
+ }),
};
let response_result = tool_response_to_markdown(&tool_response, true);
@@ -880,7 +910,12 @@ warning: unused variable `x`
};
let tool_response = ToolResponse {
id: "editor-write".to_string(),
- tool_result: Ok(vec![Content::text(text_content.raw.text)]),
+ tool_result: Ok(rmcp::model::CallToolResult {
+ content: vec![Content::text(text_content.raw.text)],
+ structured_content: None,
+ is_error: Some(false),
+ meta: None,
+ }),
};
let request_result = tool_request_to_markdown(&tool_request, true);
@@ -937,7 +972,12 @@ def process_data(data: List[Dict]) -> List[Dict]:
};
let tool_response = ToolResponse {
id: "editor-view".to_string(),
- tool_result: Ok(vec![Content::text(text_content.raw.text)]),
+ tool_result: Ok(rmcp::model::CallToolResult {
+ content: vec![Content::text(text_content.raw.text)],
+ structured_content: None,
+ is_error: Some(false),
+ meta: None,
+ }),
};
let response_result = tool_response_to_markdown(&tool_response, true);
@@ -974,7 +1014,12 @@ Command failed with exit code 2"#;
};
let tool_response = ToolResponse {
id: "shell-error".to_string(),
- tool_result: Ok(vec![Content::text(text_content.raw.text)]),
+ tool_result: Ok(rmcp::model::CallToolResult {
+ content: vec![Content::text(text_content.raw.text)],
+ structured_content: None,
+ is_error: Some(false),
+ meta: None,
+ }),
};
let response_result = tool_response_to_markdown(&tool_response, true);
@@ -1014,7 +1059,12 @@ Command failed with exit code 2"#;
};
let tool_response = ToolResponse {
id: "script-exec".to_string(),
- tool_result: Ok(vec![Content::text(text_content.raw.text)]),
+ tool_result: Ok(rmcp::model::CallToolResult {
+ content: vec![Content::text(text_content.raw.text)],
+ structured_content: None,
+ is_error: Some(false),
+ meta: None,
+ }),
};
let request_result = tool_request_to_markdown(&tool_request, true);
@@ -1061,7 +1111,12 @@ drwx------ 3 user staff 96 Dec 6 16:20 com.apple.launchd.abc
};
let tool_response = ToolResponse {
id: "multi-cmd".to_string(),
- tool_result: Ok(vec![Content::text(text_content.raw.text)]),
+ tool_result: Ok(rmcp::model::CallToolResult {
+ content: vec![Content::text(text_content.raw.text)],
+ structured_content: None,
+ is_error: Some(false),
+ meta: None,
+ }),
};
let request_result = tool_request_to_markdown(&_tool_request, true);
@@ -1104,7 +1159,12 @@ src/middleware.rs:12:async fn auth_middleware(req: Request, next: Next) -> Resul
};
let tool_response = ToolResponse {
id: "grep-search".to_string(),
- tool_result: Ok(vec![Content::text(text_content.raw.text)]),
+ tool_result: Ok(rmcp::model::CallToolResult {
+ content: vec![Content::text(text_content.raw.text)],
+ structured_content: None,
+ is_error: Some(false),
+ meta: None,
+ }),
};
let request_result = tool_request_to_markdown(&tool_request, true);
@@ -1144,7 +1204,12 @@ src/middleware.rs:12:async fn auth_middleware(req: Request, next: Next) -> Resul
};
let tool_response = ToolResponse {
id: "json-test".to_string(),
- tool_result: Ok(vec![Content::text(text_content.raw.text)]),
+ tool_result: Ok(rmcp::model::CallToolResult {
+ content: vec![Content::text(text_content.raw.text)],
+ structured_content: None,
+ is_error: Some(false),
+ meta: None,
+ }),
};
let response_result = tool_response_to_markdown(&tool_response, true);
@@ -1185,7 +1250,12 @@ found 0 vulnerabilities"#;
};
let tool_response = ToolResponse {
id: "npm-install".to_string(),
- tool_result: Ok(vec![Content::text(text_content.raw.text)]),
+ tool_result: Ok(rmcp::model::CallToolResult {
+ content: vec![Content::text(text_content.raw.text)],
+ structured_content: None,
+ is_error: Some(false),
+ meta: None,
+ }),
};
let request_result = tool_request_to_markdown(&tool_request, true);
diff --git a/crates/goose-cli/src/session/output.rs b/crates/goose-cli/src/session/output.rs
index 642f78e7a2..a5ef298311 100644
--- a/crates/goose-cli/src/session/output.rs
+++ b/crates/goose-cli/src/session/output.rs
@@ -285,8 +285,8 @@ fn render_tool_response(resp: &ToolResponse, theme: Theme, debug: bool) {
let config = Config::global();
match &resp.tool_result {
- Ok(contents) => {
- for content in contents {
+ Ok(result) => {
+ for content in &result.content {
if let Some(audience) = content.audience() {
if !audience.contains(&rmcp::model::Role::User) {
continue;
diff --git a/crates/goose-server/Cargo.toml b/crates/goose-server/Cargo.toml
index cb586eef15..fedd693aae 100644
--- a/crates/goose-server/Cargo.toml
+++ b/crates/goose-server/Cargo.toml
@@ -39,12 +39,13 @@ reqwest = { version = "0.12.9", features = ["json", "rustls-tls", "blocking", "m
tokio-util = "0.7.15"
uuid = { version = "1.11", features = ["v4"] }
serde_path_to_error = "0.1.20"
-tokio-tungstenite = { version = "0.28.0", features = ["native-tls"] }
+tokio-tungstenite = { version = "0.28.0", features = ["rustls-tls-native-roots"] }
url = "2.5.7"
rand = "0.9.2"
hex = "0.4.3"
socket2 = "0.6.1"
fs2 = "0.4.3"
+rustls = { version = "0.23", features = ["ring"] }
[target.'cfg(windows)'.dependencies]
winreg = { version = "0.55.0" }
diff --git a/crates/goose-server/src/openapi.rs b/crates/goose-server/src/openapi.rs
index 2ff1f1da62..0468bdf1e2 100644
--- a/crates/goose-server/src/openapi.rs
+++ b/crates/goose-server/src/openapi.rs
@@ -354,6 +354,8 @@ derive_utoipa!(Icon as IconSchema);
super::routes::agent::start_agent,
super::routes::agent::resume_agent,
super::routes::agent::get_tools,
+ super::routes::agent::read_resource,
+ super::routes::agent::call_tool,
super::routes::agent::update_from_session,
super::routes::agent::agent_add_extension,
super::routes::agent::agent_remove_extension,
@@ -514,6 +516,10 @@ derive_utoipa!(Icon as IconSchema);
goose::agents::types::SuccessCheck,
super::routes::agent::UpdateProviderRequest,
super::routes::agent::GetToolsQuery,
+ super::routes::agent::ReadResourceRequest,
+ super::routes::agent::ReadResourceResponse,
+ super::routes::agent::CallToolRequest,
+ super::routes::agent::CallToolResponse,
super::routes::agent::UpdateRouterToolSelectorRequest,
super::routes::agent::StartAgentRequest,
super::routes::agent::ResumeAgentRequest,
diff --git a/crates/goose-server/src/routes/agent.rs b/crates/goose-server/src/routes/agent.rs
index ba23213dbc..1f1758dfdf 100644
--- a/crates/goose-server/src/routes/agent.rs
+++ b/crates/goose-server/src/routes/agent.rs
@@ -32,6 +32,7 @@ use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::atomic::Ordering;
use std::sync::Arc;
+use tokio_util::sync::CancellationToken;
use tracing::{error, warn};
#[derive(Deserialize, utoipa::ToSchema)]
@@ -91,6 +92,18 @@ pub struct RemoveExtensionRequest {
pub session_id: String,
}
+#[derive(Deserialize, utoipa::ToSchema)]
+pub struct ReadResourceRequest {
+ session_id: String,
+ extension_name: String,
+ uri: String,
+}
+
+#[derive(Serialize, Deserialize, utoipa::ToSchema)]
+pub struct ReadResourceResponse {
+ html: String,
+}
+
#[utoipa::path(
post,
path = "/agent/start",
@@ -106,6 +119,8 @@ async fn start_agent(
State(state): State>,
Json(payload): Json,
) -> Result, ErrorResponse> {
+ goose::posthog::set_session_context("desktop", false);
+
let StartAgentRequest {
working_dir,
recipe,
@@ -198,6 +213,8 @@ async fn resume_agent(
State(state): State>,
Json(payload): Json,
) -> Result, ErrorResponse> {
+ goose::posthog::set_session_context("desktop", true);
+
let session = SessionManager::get_session(&payload.session_id, true)
.await
.map_err(|err| {
@@ -481,7 +498,7 @@ async fn update_router_tool_selector(
.update_router_tool_selector(None, Some(true))
.await
.map_err(|e| {
- tracing::error!("Failed to update tool selection strategy: {}", e);
+ error!("Failed to update tool selection strategy: {}", e);
StatusCode::INTERNAL_SERVER_ERROR
})?;
@@ -575,6 +592,39 @@ pub struct CallToolResponse {
pub is_error: bool,
}
+#[utoipa::path(
+ post,
+ path = "/agent/read_resource",
+ request_body = ReadResourceRequest,
+ responses(
+ (status = 200, description = "Resource read successfully", body = ReadResourceResponse),
+ (status = 401, description = "Unauthorized - invalid secret key"),
+ (status = 424, description = "Agent not initialized"),
+ (status = 404, description = "Resource not found"),
+ (status = 500, description = "Internal server error")
+ )
+)]
+async fn read_resource(
+ State(state): State>,
+ Json(payload): Json,
+) -> Result, StatusCode> {
+ let agent = state
+ .get_agent_for_route(payload.session_id.clone())
+ .await?;
+
+ let html = agent
+ .extension_manager
+ .read_ui_resource(
+ &payload.uri,
+ &payload.extension_name,
+ CancellationToken::default(),
+ )
+ .await
+ .map_err(|_e| StatusCode::INTERNAL_SERVER_ERROR)?;
+
+ Ok(Json(ReadResourceResponse { html }))
+}
+
#[utoipa::path(
post,
path = "/agent/call_tool",
@@ -620,15 +670,16 @@ async fn call_tool(
Ok(tool_result) => {
let content_result = tool_result.result.await;
match content_result {
- Ok(content) => {
- let output = content
+ Ok(call_tool_result) => {
+ let output = call_tool_result
+ .content
.iter()
.filter_map(|c| c.as_text().map(|t| t.text.to_string()))
.collect::>()
.join("\n");
Ok(Json(CallToolResponse {
output,
- is_error: false,
+ is_error: call_tool_result.is_error.unwrap_or(false),
}))
}
Err(error_data) => Ok(Json(CallToolResponse {
@@ -649,6 +700,8 @@ pub fn routes(state: Arc) -> Router {
.route("/agent/start", post(start_agent))
.route("/agent/resume", post(resume_agent))
.route("/agent/tools", get(get_tools))
+ .route("/agent/read_resource", post(read_resource))
+ .route("/agent/call_tool", post(call_tool))
.route("/agent/update_provider", post(update_agent_provider))
.route(
"/agent/update_router_tool_selector",
@@ -658,6 +711,5 @@ pub fn routes(state: Arc) -> Router {
.route("/agent/add_extension", post(agent_add_extension))
.route("/agent/remove_extension", post(agent_remove_extension))
.route("/agent/stop", post(stop_agent))
- .route("/agent/call_tool", post(call_tool))
.with_state(state)
}
diff --git a/crates/goose-server/src/routes/config_management.rs b/crates/goose-server/src/routes/config_management.rs
index 711ba9d9d2..fc0d83a624 100644
--- a/crates/goose-server/src/routes/config_management.rs
+++ b/crates/goose-server/src/routes/config_management.rs
@@ -392,7 +392,9 @@ pub async fn get_provider_models(
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
- match provider.fetch_supported_models().await {
+ let models_result = provider.fetch_recommended_models().await;
+
+ match models_result {
Ok(Some(models)) => Ok(Json(models)),
Ok(None) => Ok(Json(Vec::new())),
Err(provider_error) => {
diff --git a/crates/goose-server/src/routes/errors.rs b/crates/goose-server/src/routes/errors.rs
index c265482d05..fb6ab9b8be 100644
--- a/crates/goose-server/src/routes/errors.rs
+++ b/crates/goose-server/src/routes/errors.rs
@@ -3,6 +3,7 @@ use axum::{
response::{IntoResponse, Response},
Json,
};
+use goose::config::ConfigError;
use serde::Serialize;
use utoipa::ToSchema;
@@ -37,3 +38,9 @@ impl From for ErrorResponse {
Self::internal(err.to_string())
}
}
+
+impl From for ErrorResponse {
+ fn from(err: ConfigError) -> Self {
+ Self::internal(err.to_string())
+ }
+}
diff --git a/crates/goose-server/src/tunnel/lapstone.rs b/crates/goose-server/src/tunnel/lapstone.rs
index 8449abc426..c824a39afd 100644
--- a/crates/goose-server/src/tunnel/lapstone.rs
+++ b/crates/goose-server/src/tunnel/lapstone.rs
@@ -477,6 +477,8 @@ async fn run_single_connection(
server_secret: String,
restart_tx: mpsc::Sender<()>,
) {
+ let _ = rustls::crypto::ring::default_provider().install_default();
+
let worker_url = get_worker_url();
let ws_url = worker_url
.replace("https://", "wss://")
diff --git a/crates/goose-tui/src/components/chat.rs b/crates/goose-tui/src/components/chat.rs
index d2733b7f52..e483952705 100644
--- a/crates/goose-tui/src/components/chat.rs
+++ b/crates/goose-tui/src/components/chat.rs
@@ -165,8 +165,8 @@ impl ChatComponent {
&tool_name, &tool_args, width, color, items, map, msg_idx,
);
- if let Ok(contents) = &resp.tool_result {
- Self::render_tool_response_body(contents, width, items, map, msg_idx);
+ if let Ok(call_tool_result) = &resp.tool_result {
+ Self::render_tool_response_body(&call_tool_result.content, width, items, map, msg_idx);
}
let footer = format!("β°{:β {
- for content in contents {
+ Ok(call_tool_result) => {
+ for content in &call_tool_result.content {
if let Some(audience) = content.audience() {
if !audience.contains(&rmcp::model::Role::User) {
continue;
diff --git a/crates/goose-tui/src/utils/message_format.rs b/crates/goose-tui/src/utils/message_format.rs
index d28872a1cc..a0d5f37936 100644
--- a/crates/goose-tui/src/utils/message_format.rs
+++ b/crates/goose-tui/src/utils/message_format.rs
@@ -21,8 +21,8 @@ pub fn message_to_plain_text(message: &Message) -> String {
}
MessageContent::ToolResponse(resp) => {
lines.push("Tool Output:".to_string());
- if let Ok(contents) = &resp.tool_result {
- for content in contents {
+ if let Ok(call_tool_result) = &resp.tool_result {
+ for content in &call_tool_result.content {
if let Some(audience) = content.audience() {
if !audience.contains(&rmcp::model::Role::User) {
continue;
diff --git a/crates/goose/Cargo.toml b/crates/goose/Cargo.toml
index b56c1ee617..454761d81a 100644
--- a/crates/goose/Cargo.toml
+++ b/crates/goose/Cargo.toml
@@ -111,6 +111,7 @@ shellexpand = "3.1.1"
indexmap = "2.12.0"
ignore = "0.4.25"
which = "8.0.0"
+posthog-rs = "0.3.7"
[target.'cfg(target_os = "windows")'.dependencies]
diff --git a/crates/goose/canonical_mapping_report.json b/crates/goose/canonical_mapping_report.json
new file mode 100644
index 0000000000..79c03c3b9e
--- /dev/null
+++ b/crates/goose/canonical_mapping_report.json
@@ -0,0 +1,4413 @@
+{
+ "timestamp": "2025-11-12T18:31:21.379526+00:00",
+ "unmapped_models": [
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-01-20-06-09-37"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-02-03-22-57-10"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-02-03-23-17-02"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-02-03-23-39-19"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-02-04-00-06-28"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-02-04-00-26-44"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-02-04-01-04-45"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-02-04-01-25-59"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-02-04-01-35-31"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-02-04-01-49-59"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-02-04-01-52-25"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-02-04-01-53-11"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-02-04-01-54-49"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-02-04-01-56-59"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-02-04-01-58-41"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-02-04-02-00-26"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-02-04-02-02-17"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-02-04-02-03-39"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-02-04-02-05-16"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-02-04-02-06-44"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-02-04-02-08-18"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-02-22-05-09-46"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-02-22-05-27-21"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-02-27-17-40-56"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-02-27-19-20-20"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-02-27-19-35-21"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-02-27-21-26-06"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-03-01-19-58-17"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-03-02-11-40-22"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-03-02-16-01-19"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-03-06-11-43-24"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-04-05-18-59-44"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-06-14-21-43-36"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square:ada-fine-tuned-pb-books-2023-06-10-01-41-30"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square:get-classify-test-1-2023-03-01-22-20-54"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square:get-classify-test-2-2023-03-01-23-00-30"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square:latha-2023-06-07-06-56-00"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square:mcc-from-items-2023-03-09-23-20-33"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square:mcc-from-items-2023-03-10-02-02-01"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square:sq-interview-rubrics-2023-03-01-23-25-00"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square:test-2023-06-07-07-05-04"
+ },
+ {
+ "provider": "openai",
+ "model": "babbage-002"
+ },
+ {
+ "provider": "openai",
+ "model": "babbage:ft-square-2023-02-28-14-48-38"
+ },
+ {
+ "provider": "openai",
+ "model": "codex-mini-latest"
+ },
+ {
+ "provider": "openai",
+ "model": "computer-use-preview"
+ },
+ {
+ "provider": "openai",
+ "model": "computer-use-preview-2025-03-11"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-01-04-00-17-01"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-02-03-20-54-03"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-02-03-22-00-22"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-02-03-23-12-33"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-02-03-23-38-15"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-02-03-23-50-25"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-02-03-23-56-35"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-02-04-00-10-16"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-02-04-00-30-47"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-02-04-01-10-58"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-02-14-03-10-35"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-02-14-05-13-05"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-02-24-12-25-24"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-02-24-13-42-34"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-02-24-16-41-35"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-02-24-18-12-55"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-02-27-22-00-27"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-02-28-12-28-40"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-02-28-16-08-29"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-02-28-23-14-48"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-03-02-01-48-30"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-03-02-13-12-20"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-03-02-16-17-24"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-03-06-14-16-22"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-03-09-04-37-07"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-04-05-19-33-31"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-04-05-23-44-20"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-05-01-19-30-51"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-05-02-16-19-15"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-06-08-20-11-11"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-06-12-14-33-09"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-06-14-22-55-22"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-06-14-23-23-37"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-06-14-23-25-31"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-06-14-23-37-23"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-06-15-14-24-56"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-06-15-15-16-31"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-06-15-15-21-41"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square:andrewbyers-iso-dates-2-2023-06-09-19-23-52"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square:andrewbyers-iso-dates-2023-06-09-16-22-54"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square:bartosz-2023-06-15-16-50-00"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square:bartosz-2023-06-15-17-40-30"
+ },
+ {
+ "provider": "openai",
+ "model": "dall-e-2"
+ },
+ {
+ "provider": "openai",
+ "model": "dall-e-3"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci-002"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-02-03-01-31-46"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-02-03-01-35-06"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-02-03-01-38-32"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-02-03-19-52-40"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-02-03-19-56-06"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-02-03-19-58-46"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-02-03-20-02-03"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-02-03-20-05-47"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-02-03-20-11-53"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-02-03-20-15-38"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-02-24-18-22-49"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-02-27-19-06-04"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-02-27-20-03-10"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-02-27-20-43-03"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-02-27-21-15-51"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-02-27-21-56-57"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-02-27-23-00-36"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-02-28-15-35-39"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-02-28-22-02-29"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-03-01-01-34-16"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-03-01-04-50-27"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-03-01-21-52-35"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-03-01-23-53-42"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-03-02-06-29-26"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-03-02-07-43-23"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-03-02-08-33-27"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-03-02-09-27-04"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-03-02-19-42-43"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-03-02-23-34-00"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-03-02-23-40-51"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-03-02-23-46-02"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-03-02-23-54-35"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-03-03-01-26-16"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-03-06-07-40-58"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-05-02-17-37-42"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-06-03-01-27-58"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-06-03-02-41-38"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-06-04-20-17-25"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-07-31-06-04-37"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square:conversational-ordering-2023-03-01-03-35-26"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square:conversational-ordering-2023-03-01-19-34-59"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square:martech-fox-2023-07-03-12-08-51"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square:ppg-1-2023-03-03-22-44-53"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square:ppg-2-2023-03-04-00-09-06"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square:product-descriptions-1-2023-02-18-02-01-38"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square:product-descriptions-2023-02-18-01-37-53"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square:sq-interview-rubrics-2023-03-01-06-12-57"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square:test-2023-06-02-21-46-09"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square:test-2023-06-07-07-18-50"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square:testmodel-2023-06-09-06-33-46"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:babbage-002:square::979OeECc"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:babbage-002:square::979X87hI"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:babbage-002:square::979tUAKY"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:babbage-002:square::9BaZ6utW"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:babbage-002:square::9BaZ962b:ckpt-step-418"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:babbage-002:square::9BaZ9TI9:ckpt-step-1254"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:babbage-002:square::9BaZ9yXw:ckpt-step-836"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:babbage-002:square::9f7y6Fq3:ckpt-step-132"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:babbage-002:square::9f7y6d94:ckpt-step-66"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:babbage-002:square::9f7y6tDo"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:davinci-002:square::8IHIFNF8"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:davinci-002:square::979av0Ca"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::9BdO7p4k"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::9BdO9dyB:ckpt-step-418"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::9BdOAZhy:ckpt-step-1254"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::9BdOAzyd:ckpt-step-836"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::9BdjnxSh"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::9Bdjq2HN:ckpt-step-836"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::9BdjqEL2:ckpt-step-418"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::9BdjqWL2:ckpt-step-1254"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::9CBYL14U"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::9CBYOIYz:ckpt-step-531"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::9CBYOe1X:ckpt-step-1062"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::9CBYPJjH:ckpt-step-1593"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::9dWw13R5:ckpt-step-531"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::9dWw21NB:ckpt-step-1062"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::9dWw2Jmx"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::9w2wKEHK:ckpt-step-1404"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::9w2wKRUk"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::9w2wKfxt:ckpt-step-702"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::9wDsxF0M:ckpt-step-702"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::9wDsxJU5"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::9wDsxcuJ:ckpt-step-1404"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::9wLTi98n:ckpt-step-702"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::9wLTiHPk:ckpt-step-1404"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::9wLTiuf3"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::A1oa308p:ckpt-step-768"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::A1oa4UVH:ckpt-step-1536"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::A1oa4fY2"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::A21LQ3B7:ckpt-step-1536"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::A21LQjft:ckpt-step-768"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::A21LRYaU"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::A3F6X0tu:ckpt-step-906"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::A3F6XYh7:ckpt-step-1812"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::A3F6YGI4"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::A3F7A46A:ckpt-step-906"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::A3F7AHaO:ckpt-step-1812"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::A3F7AoD8"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::BDIOxNBa"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::BDIOxtLv:ckpt-step-499"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::BDIOxz40:ckpt-step-998"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::BDIbgGdy"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::BDM8mQGM"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::BDQoyuDs"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::BPmNgoWc:ckpt-step-80"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::BPmNj3MY:ckpt-step-90"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::BPmNkBwY"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square:regex-generator:BV3pHd69"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square:regex-generator:BV3pHfFP:ckpt-step-608"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square:regex-generator:BV3pHz34:ckpt-step-304"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0613:square::7xkXhz2F"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0613:square:recipe-ner:8m9bUTJB"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-1106:square::8INlSFiD"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-1106:square::8IittuHW"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-1106:square::8VQq2h9i"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-1106:square:presale-prod-update:A1VCz1Ng:ckpt-step-772"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-1106:square:presale-prod-update:A1VCz1dF"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-1106:square:presale-prod-update:A1VCzSam:ckpt-step-1544"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-1106:square:tarkin-fmac:9esUGGea:ckpt-step-1522"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-1106:square:tarkin-fmac:9esUGnko"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-1106:square:tarkin-fmac:9f8il0PV"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-1106:square:tarkin-fmac:9f8iljLm:ckpt-step-1611"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-1106:square:tarkin-fmac:9f9O9vmz:ckpt-step-751"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-1106:square:tarkin-fmac:9f9OA72b:ckpt-step-1502"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-1106:square:tarkin-fmac:9f9OAsBX"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-1106:square:tarkin-fmac:9fAO711i:ckpt-step-753"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-1106:square:tarkin-fmac:9fAO71NV"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-1106:square:tarkin-fmac:9fAO7nhv:ckpt-step-1506"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-1106:square:tarkin-fmac:9fB61NQG"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-1106:square:tarkin-fmac:9fB61ZZn:ckpt-step-1520"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-1106:square:tarkin-fmac:9fB61cPW:ckpt-step-760"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-1106:square:tarkin-fmac:9fCbI5U9"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-1106:square:tarkin-fmac:9fCbICqK:ckpt-step-761"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-1106:square:tarkin-pm-35:9djnKK85:ckpt-step-1533"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-1106:square:tarkin-pm-35:9djnL0G0"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::9BpIi1ah"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::9BpIlVui:ckpt-step-418"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::9BpIle5X:ckpt-step-836"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::9BpImGbJ:ckpt-step-1254"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::9CERwrnZ"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::9CERyuVp:ckpt-step-531"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::9CERz6l4:ckpt-step-1593"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::9CERzjIk:ckpt-step-1062"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::9dWi6cve:ckpt-step-531"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::9dWi7AH4"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::9dWi7d0k:ckpt-step-1062"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::9wGGcJwE:ckpt-step-1404"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::9wGGceZ1:ckpt-step-702"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::9wGGcsCI"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::9wLlEcBS:ckpt-step-702"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::9wLlFM71"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::9wLlFtmK:ckpt-step-1404"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::9wP6wXmY:ckpt-step-702"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::9wP6xJ7K"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::9wP6xUW9:ckpt-step-1404"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::9yR6Pmck:ckpt-step-702"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::9yR6Q01x"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::9yR6QRCI:ckpt-step-1404"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::9ySoC7vZ"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::9ySoC9RL:ckpt-step-1482"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::9ySoCHH9:ckpt-step-741"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::A1os36ZY:ckpt-step-768"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::A1os3SzM:ckpt-step-1536"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::A1os4RF0"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square:g11n-helper:ALxGZf5Y:ckpt-step-54"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square:g11n-helper:ALxGaifK:ckpt-step-72"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square:g11n-helper:ALxGasbI"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square:presale-prod-update:A1VkM3KW:ckpt-step-772"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square:presale-prod-update:A1VkMPxN:ckpt-step-1544"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square:presale-prod-update:A1VkMugi"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square:tarkin:9a8eE6A4:ckpt-step-763"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square:tarkin:9a8eEWdX"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square:tarkin:9a8eEkGS:ckpt-step-1526"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square:tarkin:9aDzHMdR:ckpt-step-3054"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square:tarkin:9aDzHdca:ckpt-step-1527"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square:tarkin:9aDzI3c1"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square:tarkin:9ckg06KE:ckpt-step-70636"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square:tarkin:9ckg06yx:ckpt-step-35318"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square:tarkin:9ckg1W41"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4.1-2025-04-14:square:hotel-sys-design:Bn8TRXx7:ckpt-step-77"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4.1-2025-04-14:square:hotel-sys-design:Bn8TSnvc"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4.1-2025-04-14:square:hotel-sys-design:Bn8TSuqu:ckpt-step-88"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4.1-2025-04-14:square:picasso-tbl:Be4ohJ5c:ckpt-step-555"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4.1-2025-04-14:square:picasso-tbl:Be4okWcV"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4.1-2025-04-14:square:picasso-tbl:Be4okcLm:ckpt-step-1110"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4.1-mini-2025-04-14:square::BmTIcrto:ckpt-step-666"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4.1-mini-2025-04-14:square::BmTIfHgD"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4.1-mini-2025-04-14:square::BmTIfv3N:ckpt-step-1332"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4.1-mini-2025-04-14:square::BwjlIHbe"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4.1-mini-2025-04-14:square::BwjlIMor:ckpt-step-1018"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4.1-nano-2025-04-14:square:qliao-plathelp-v1:CRNAywx8"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square::9eTWHHtG"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square::9eTWHI2d:ckpt-step-80"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square::9eTWHWR7:ckpt-step-90"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square::9eoDA1QR:ckpt-step-32"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square::9eoDARki"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square::9eoDAkYo:ckpt-step-64"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square::9eqA79i0:ckpt-step-59"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square::9eqA8TBe:ckpt-step-118"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square::9eqA8pCn"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square::9hgDsAHH:ckpt-step-66"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square::9hgDsHOi:ckpt-step-132"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square::9hgDtDpP"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square::9yPneKZF:ckpt-step-702"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square::9yPnf26m:ckpt-step-1404"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square::9yPnfZ3w"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:pfi-golang:9smel7hl:ckpt-step-434"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:pfi-golang:9smelXOJ"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:pfi-golang:9smeltpg:ckpt-step-217"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:pfi:9sjuwq38:ckpt-step-505"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:pfi:9sjux561"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:pfi:9sjuxLbk:ckpt-step-1010"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-fma:9e48jPvC:ckpt-step-1510"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-fma:9e48kfSc"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-fma:9e4FyHOk:ckpt-step-528"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-fma:9e4FzikM:ckpt-step-1056"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-fma:9e4Fzzq8"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-fma:9e6FlhBM"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-fma:9e6Flnjr:ckpt-step-1526"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-fma:9e8EgHNk"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-fma:9e8Egd2H:ckpt-step-1526"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-fma:9ePrc2Qr"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-fma:9ePrcUgW:ckpt-step-1524"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-fmac:9eSm4CmX:ckpt-step-1513"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-fmac:9eSm4Nvi"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-fmac:9eVTYdgL:ckpt-step-1513"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-fmac:9eVTZNWy"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-fmac:9ellv805:ckpt-step-812"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-fmac:9ellvsEx"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-fmar:9gCyNcoZ:ckpt-step-753"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-fmar:9gCyOII4"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-fmar:9gCyOTcP:ckpt-step-1506"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-fmar:9gEX8El2:ckpt-step-1552"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-fmar:9gEX9ft2"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-fmas:9eq84If3"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-fmas:9eq84hU9:ckpt-step-1522"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-full-meal:9e2Qax41:ckpt-step-528"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-full-meal:9e2Qb1sn"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-full-meal:9e2QbHP1:ckpt-step-1056"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-piece-meal:9dgu0Q1g:ckpt-step-1533"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-piece-meal:9dgu1KXd"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-pm-4o:9dkScYcs"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-pm-4o:9dkScysk:ckpt-step-1533"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin:9dfz3A8x"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:test-ft:9e4bJAtq:ckpt-step-27"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:test-ft:9e4bKBv0:ckpt-step-54"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:test-ft:9e4bKlvw"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:thunderbird:9bd8MQGp:ckpt-step-88"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:thunderbird:9bd8NB8K"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:thunderbird:9bd8NX77:ckpt-step-132"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square:12-21-24:Ah0F4uLP:ckpt-step-185"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square:12-21-24:Ah0FNFHQ:ckpt-step-370"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square:12-21-24:Ah0FNFS1"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::9yOpXJbd:ckpt-step-702"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::9yOpXVGv"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::9yOpXmyQ:ckpt-step-1404"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::9yWRL773:ckpt-step-1482"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::9yWRLeLB:ckpt-step-741"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::9yWRM8Zn"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::A1pIt632"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::A1pItfDC:ckpt-step-1536"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::A1pItofx:ckpt-step-768"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::A218TwmT:ckpt-step-768"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::A218UZjU:ckpt-step-1536"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::A218UzZf"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::A3FCYhcv:ckpt-step-906"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::A3FCYpNh:ckpt-step-1812"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::A3FCZe3h"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::A6lkYKvg:ckpt-step-80"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::A6lkYTPi:ckpt-step-90"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::A6lkZ4nA"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ADYnVcKn:ckpt-step-153"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ADYnWDpr:ckpt-step-306"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ADYnWF7g"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ADcZL13Y:ckpt-step-149"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ADcZLJjk:ckpt-step-298"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ADcZMaZB"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ADdWL6S7:ckpt-step-149"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ADdWLGiA:ckpt-step-298"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ADdWLpNL"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ADv4G9ED:ckpt-step-310"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ADv4GfJI"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ADv4GslV:ckpt-step-155"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AFvffbd6:ckpt-step-60"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AFvfgOvb"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AFvfgYxS:ckpt-step-75"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AIrPDXh4:ckpt-step-70"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AIrPE1Jn:ckpt-step-84"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AIrPFDOA"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AIrRI7Py"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AIrRIbun:ckpt-step-70"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AIrRIz3n:ckpt-step-84"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AJPMFYjn:ckpt-step-70"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AJPMSMwR:ckpt-step-84"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AJPMTSQg"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ALDZQz9s:ckpt-step-57"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ALDZbB0b:ckpt-step-57"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ALDZlhsT:ckpt-step-57"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ALDZrh8z:ckpt-step-76"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ALDZrhRU:ckpt-step-76"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ALDZrhWY:ckpt-step-76"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ALDZs4af"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ALDZsBZ7"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ALDZsiMB"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ALelRGKV:ckpt-step-75"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ALelRQjh:ckpt-step-50"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ALelSl5l"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ALfXG00U:ckpt-step-26"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ALfXHUyM"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ALfXHdnT:ckpt-step-52"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AM3Kk6bJ:ckpt-step-57"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AM3Km10T:ckpt-step-114"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AM3KmncS"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AMHzrhbX:ckpt-step-58"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AMI02GZM:ckpt-step-58"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AMI0CTR7:ckpt-step-58"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AMI0MEJb:ckpt-step-116"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AMI0MZIn:ckpt-step-116"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AMI0MbSE:ckpt-step-116"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AMI0NAKb:ckpt-step-116"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AMI0NGtf"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AMI0NZqn:ckpt-step-58"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AMI0NfHv"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AMI0Nkxa"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AMI0NxXJ"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AMgDbCpJ:ckpt-step-61"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AMgDmR8i:ckpt-step-61"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AMgDwgwF:ckpt-step-61"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AMgE01SL"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AMgE0Zqi"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AMgE0dya:ckpt-step-122"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AMgE0l6B"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AMgE0lmy:ckpt-step-122"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AMgE0x9A:ckpt-step-122"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANBhCPM4:ckpt-step-71"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANBhNQrL:ckpt-step-71"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANBhXvS0:ckpt-step-71"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANBheCpk:ckpt-step-142"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANBheHqF:ckpt-step-142"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANBhelmB:ckpt-step-142"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANBhfHg0"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANBhfP3K"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANBhfWTj"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANR81FlE:ckpt-step-71"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANR8340Y"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANR83UMp:ckpt-step-142"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANWxC4R0:ckpt-step-76"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANWxMc5r:ckpt-step-76"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANWxWkuX:ckpt-step-76"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANWxb3ul"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANWxbR1m:ckpt-step-152"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANWxbZ7u:ckpt-step-152"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANWxbueA:ckpt-step-152"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANWxbx9d"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANWxcbir"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANnTNQfB:ckpt-step-76"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANnTXwXd:ckpt-step-76"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANnTeBFW:ckpt-step-152"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANnTeWRz"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANnTedmK:ckpt-step-152"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANnTenB6"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANq2D506:ckpt-step-76"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANq2OJoT:ckpt-step-76"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANq2Ykg1:ckpt-step-76"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANq2f35h:ckpt-step-152"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANq2fWSn:ckpt-step-152"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANq2fkPg:ckpt-step-152"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANq2g1fc"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANq2g4RI"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANq2gT5D"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::BDcdETt3"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::BDgw3aRR:ckpt-step-770"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::BDgwAYDm"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square:presale-prod-update:A1VLdLFo:ckpt-step-772"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square:presale-prod-update:A1VLdpp9:ckpt-step-1544"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square:presale-prod-update:A1VLef0R"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square:uip-1-3-2025-733pm:Aln8wDEd:ckpt-step-57"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square:uip-1-3-2025-733pm:Aln99EJs:ckpt-step-76"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square:uip-1-3-2025-733pm:Aln99po1"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square:uip-1-3-2025:AljIGSIY:ckpt-step-114"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square:uip-1-3-2025:AljIUDTx:ckpt-step-228"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square:uip-1-3-2025:AljIUw1E"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square:uip-4o-beta1:AU2gb0ZW:ckpt-step-99"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square:uip-4o-beta1:AU2glfI3:ckpt-step-99"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square:uip-4o-beta1:AU2goBN9:ckpt-step-198"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square:uip-4o-beta1:AU2goRoo:ckpt-step-198"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square:uip-4o-beta1:AU2gpUkI"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square:uip-4o-beta1:AU2gpkQO"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square:uipath:AStGEJCN:ckpt-step-140"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square:uipath:AStGEPHu"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square:uipath:AStGErTc:ckpt-step-70"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square::9yQURl9e:ckpt-step-702"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square::9yQUS8UO"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square::9yQUSboN:ckpt-step-1404"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square::9yS9rcIf:ckpt-step-741"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square::9yS9sWBR:ckpt-step-1482"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square::9yS9sZgG"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square::9yYfNBYp"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square::9yYfNYZR:ckpt-step-741"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square::9yYfNa27:ckpt-step-1482"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square::A1o0y9c3:ckpt-step-1536"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square::A1o0yYU0"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square::A1o0yoGv:ckpt-step-768"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square::A20kHEh1:ckpt-step-1536"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square::A20kHL5C"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square::A20kHQH6:ckpt-step-768"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square::A3Ew9LPO:ckpt-step-1812"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square::A3Ew9Pfa:ckpt-step-906"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square::A3EwAWC6"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square::C9PyMpaX:ckpt-step-926"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square::C9PyNU6h"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square:merchant-mappings-v1:BVLby6lj:ckpt-step-80"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square:merchant-mappings-v1:BVLc6ocQ:ckpt-step-90"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square:merchant-mappings-v1:BVLc6zzK"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square:merchant-tokens:BUsD3Vq9"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square:presale-prod-update:A1V43nWF:ckpt-step-772"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square:presale-prod-update:A1V44IqT"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square:presale-prod-update:A1V44Vjp:ckpt-step-1544"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square:ql-plathelp-241:CRPREgy9:ckpt-step-241"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square:ql-plathelp-241:CRPRFEus:ckpt-step-482"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square:ql-plathelp-241:CRPRGYXt"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square:support-250:Ao0TXJT1:ckpt-step-250"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square:support-250:Ao0TYWiG"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square:support-250:Ao0TYf9o:ckpt-step-500"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square:support-50:AnziWXoE:ckpt-step-50"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square:support-50:AnziY3TM"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square:support-50:AnziYbeg:ckpt-step-100"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square:tarkin:9ozEIZnh:ckpt-step-1751"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square:tarkin:9ozEMo2r"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square:tarkin:9qecJUHK:ckpt-step-2223"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square:tarkin:9qecJuVm"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square:tarkin:9qvs9txe:ckpt-step-2241"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square:tarkin:9qvsAbS8"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square:tarkin:9rJTBC8M:ckpt-step-2241"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square:tarkin:9rJTCzuI"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-4o-mini-audio-preview"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-4o-mini-audio-preview-2024-12-17"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-4o-mini-realtime-preview"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-4o-mini-realtime-preview-2024-12-17"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-4o-mini-transcribe"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-4o-mini-tts"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-4o-realtime-preview"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-4o-realtime-preview-2024-10-01"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-4o-realtime-preview-2024-12-17"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-4o-realtime-preview-2025-06-03"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-4o-transcribe"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-4o-transcribe-diarize"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-5-chat-latest"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-5-search-api"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-5-search-api-2025-10-14"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-audio"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-audio-2025-08-28"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-audio-mini"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-audio-mini-2025-10-06"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-image-1"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-image-1-mini"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-realtime"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-realtime-2025-08-28"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-realtime-mini"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-realtime-mini-2025-10-06"
+ },
+ {
+ "provider": "openai",
+ "model": "o1-mini"
+ },
+ {
+ "provider": "openai",
+ "model": "o1-mini-2024-09-12"
+ },
+ {
+ "provider": "openai",
+ "model": "omni-moderation-2024-09-26"
+ },
+ {
+ "provider": "openai",
+ "model": "omni-moderation-latest"
+ },
+ {
+ "provider": "openai",
+ "model": "sora-2"
+ },
+ {
+ "provider": "openai",
+ "model": "sora-2-pro"
+ },
+ {
+ "provider": "openai",
+ "model": "text-embedding-3-large"
+ },
+ {
+ "provider": "openai",
+ "model": "text-embedding-3-small"
+ },
+ {
+ "provider": "openai",
+ "model": "text-embedding-ada-002"
+ },
+ {
+ "provider": "openai",
+ "model": "tts-1"
+ },
+ {
+ "provider": "openai",
+ "model": "tts-1-1106"
+ },
+ {
+ "provider": "openai",
+ "model": "tts-1-hd"
+ },
+ {
+ "provider": "openai",
+ "model": "tts-1-hd-1106"
+ },
+ {
+ "provider": "openai",
+ "model": "whisper-1"
+ },
+ {
+ "provider": "openrouter",
+ "model": "ai21/jamba-large-1.7"
+ },
+ {
+ "provider": "openrouter",
+ "model": "ai21/jamba-mini-1.7"
+ },
+ {
+ "provider": "openrouter",
+ "model": "alibaba/tongyi-deepresearch-30b-a3b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "alibaba/tongyi-deepresearch-30b-a3b:free"
+ },
+ {
+ "provider": "openrouter",
+ "model": "amazon/nova-lite-v1"
+ },
+ {
+ "provider": "openrouter",
+ "model": "amazon/nova-micro-v1"
+ },
+ {
+ "provider": "openrouter",
+ "model": "amazon/nova-premier-v1"
+ },
+ {
+ "provider": "openrouter",
+ "model": "amazon/nova-pro-v1"
+ },
+ {
+ "provider": "openrouter",
+ "model": "arcee-ai/virtuoso-large"
+ },
+ {
+ "provider": "openrouter",
+ "model": "baidu/ernie-4.5-21b-a3b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "baidu/ernie-4.5-vl-28b-a3b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "cohere/command-r-08-2024"
+ },
+ {
+ "provider": "openrouter",
+ "model": "cohere/command-r-plus-08-2024"
+ },
+ {
+ "provider": "openrouter",
+ "model": "deepcogito/cogito-v2-preview-llama-109b-moe"
+ },
+ {
+ "provider": "openrouter",
+ "model": "deepcogito/cogito-v2-preview-llama-405b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "deepcogito/cogito-v2-preview-llama-70b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "deepseek/deepseek-chat"
+ },
+ {
+ "provider": "openrouter",
+ "model": "deepseek/deepseek-chat-v3-0324"
+ },
+ {
+ "provider": "openrouter",
+ "model": "deepseek/deepseek-chat-v3-0324:free"
+ },
+ {
+ "provider": "openrouter",
+ "model": "deepseek/deepseek-chat-v3.1"
+ },
+ {
+ "provider": "openrouter",
+ "model": "deepseek/deepseek-r1"
+ },
+ {
+ "provider": "openrouter",
+ "model": "deepseek/deepseek-r1-0528"
+ },
+ {
+ "provider": "openrouter",
+ "model": "deepseek/deepseek-r1-distill-llama-70b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "deepseek/deepseek-v3.1-terminus"
+ },
+ {
+ "provider": "openrouter",
+ "model": "deepseek/deepseek-v3.1-terminus:exacto"
+ },
+ {
+ "provider": "openrouter",
+ "model": "deepseek/deepseek-v3.2-exp"
+ },
+ {
+ "provider": "openrouter",
+ "model": "google/gemini-2.0-flash-exp:free"
+ },
+ {
+ "provider": "openrouter",
+ "model": "inception/mercury"
+ },
+ {
+ "provider": "openrouter",
+ "model": "inception/mercury-coder"
+ },
+ {
+ "provider": "openrouter",
+ "model": "inclusionai/ling-1t"
+ },
+ {
+ "provider": "openrouter",
+ "model": "inclusionai/ring-1t"
+ },
+ {
+ "provider": "openrouter",
+ "model": "kwaipilot/kat-coder-pro:free"
+ },
+ {
+ "provider": "openrouter",
+ "model": "meituan/longcat-flash-chat:free"
+ },
+ {
+ "provider": "openrouter",
+ "model": "meta-llama/llama-3-70b-instruct"
+ },
+ {
+ "provider": "openrouter",
+ "model": "meta-llama/llama-3-8b-instruct"
+ },
+ {
+ "provider": "openrouter",
+ "model": "meta-llama/llama-3.1-405b-instruct"
+ },
+ {
+ "provider": "openrouter",
+ "model": "meta-llama/llama-3.1-70b-instruct"
+ },
+ {
+ "provider": "openrouter",
+ "model": "meta-llama/llama-3.1-8b-instruct"
+ },
+ {
+ "provider": "openrouter",
+ "model": "meta-llama/llama-3.2-3b-instruct"
+ },
+ {
+ "provider": "openrouter",
+ "model": "meta-llama/llama-3.3-70b-instruct"
+ },
+ {
+ "provider": "openrouter",
+ "model": "meta-llama/llama-3.3-70b-instruct:free"
+ },
+ {
+ "provider": "openrouter",
+ "model": "meta-llama/llama-3.3-8b-instruct:free"
+ },
+ {
+ "provider": "openrouter",
+ "model": "meta-llama/llama-4-maverick"
+ },
+ {
+ "provider": "openrouter",
+ "model": "meta-llama/llama-4-maverick:free"
+ },
+ {
+ "provider": "openrouter",
+ "model": "meta-llama/llama-4-scout"
+ },
+ {
+ "provider": "openrouter",
+ "model": "meta-llama/llama-4-scout:free"
+ },
+ {
+ "provider": "openrouter",
+ "model": "microsoft/phi-3-medium-128k-instruct"
+ },
+ {
+ "provider": "openrouter",
+ "model": "microsoft/phi-3-mini-128k-instruct"
+ },
+ {
+ "provider": "openrouter",
+ "model": "microsoft/phi-3.5-mini-128k-instruct"
+ },
+ {
+ "provider": "openrouter",
+ "model": "minimax/minimax-m1"
+ },
+ {
+ "provider": "openrouter",
+ "model": "minimax/minimax-m2"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/codestral-2501"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/codestral-2508"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/devstral-medium"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/devstral-small"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/devstral-small-2505"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/magistral-medium-2506"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/magistral-medium-2506:thinking"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/magistral-small-2506"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/ministral-3b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/ministral-8b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/mistral-7b-instruct"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/mistral-7b-instruct-v0.1"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/mistral-7b-instruct:free"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/mistral-large"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/mistral-large-2407"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/mistral-large-2411"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/mistral-medium-3"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/mistral-medium-3.1"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/mistral-nemo"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/mistral-saba"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/mistral-small"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/mistral-small-24b-instruct-2501"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/mistral-small-3.1-24b-instruct"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/mistral-small-3.1-24b-instruct:free"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/mistral-small-3.2-24b-instruct"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/mistral-small-3.2-24b-instruct:free"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/mistral-tiny"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/mixtral-8x22b-instruct"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/mixtral-8x7b-instruct"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/pixtral-12b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/pixtral-large-2411"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/voxtral-small-24b-2507"
+ },
+ {
+ "provider": "openrouter",
+ "model": "moonshotai/kimi-k2"
+ },
+ {
+ "provider": "openrouter",
+ "model": "moonshotai/kimi-k2-0905"
+ },
+ {
+ "provider": "openrouter",
+ "model": "moonshotai/kimi-k2-0905:exacto"
+ },
+ {
+ "provider": "openrouter",
+ "model": "moonshotai/kimi-k2-thinking"
+ },
+ {
+ "provider": "openrouter",
+ "model": "nousresearch/deephermes-3-mistral-24b-preview"
+ },
+ {
+ "provider": "openrouter",
+ "model": "nousresearch/hermes-3-llama-3.1-70b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "nousresearch/hermes-4-405b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "nousresearch/hermes-4-70b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "nvidia/llama-3.1-nemotron-70b-instruct"
+ },
+ {
+ "provider": "openrouter",
+ "model": "nvidia/llama-3.3-nemotron-super-49b-v1.5"
+ },
+ {
+ "provider": "openrouter",
+ "model": "nvidia/nemotron-nano-12b-v2-vl:free"
+ },
+ {
+ "provider": "openrouter",
+ "model": "nvidia/nemotron-nano-9b-v2"
+ },
+ {
+ "provider": "openrouter",
+ "model": "nvidia/nemotron-nano-9b-v2:free"
+ },
+ {
+ "provider": "openrouter",
+ "model": "openai/gpt-oss-20b:free"
+ },
+ {
+ "provider": "openrouter",
+ "model": "openrouter/polaris-alpha"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen-2.5-72b-instruct"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen-2.5-7b-instruct"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen-max"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen-plus"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen-plus-2025-07-28"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen-plus-2025-07-28:thinking"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen-turbo"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen-vl-max"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen3-14b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen3-235b-a22b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen3-235b-a22b-2507"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen3-235b-a22b-thinking-2507"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen3-235b-a22b:free"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen3-30b-a3b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen3-30b-a3b-instruct-2507"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen3-30b-a3b-thinking-2507"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen3-32b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen3-4b:free"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen3-8b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen3-coder"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen3-coder-30b-a3b-instruct"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen3-coder-flash"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen3-coder-plus"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen3-coder:exacto"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen3-coder:free"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen3-max"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen3-next-80b-a3b-instruct"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen3-next-80b-a3b-thinking"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen3-vl-235b-a22b-instruct"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen3-vl-235b-a22b-thinking"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen3-vl-30b-a3b-instruct"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen3-vl-30b-a3b-thinking"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen3-vl-8b-instruct"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen3-vl-8b-thinking"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwq-32b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "sao10k/l3-euryale-70b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "sao10k/l3.1-euryale-70b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "stepfun-ai/step3"
+ },
+ {
+ "provider": "openrouter",
+ "model": "thedrummer/rocinante-12b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "thedrummer/unslopnemo-12b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "tngtech/deepseek-r1t2-chimera"
+ },
+ {
+ "provider": "openrouter",
+ "model": "x-ai/grok-3"
+ },
+ {
+ "provider": "openrouter",
+ "model": "x-ai/grok-3-beta"
+ },
+ {
+ "provider": "openrouter",
+ "model": "x-ai/grok-3-mini"
+ },
+ {
+ "provider": "openrouter",
+ "model": "x-ai/grok-3-mini-beta"
+ },
+ {
+ "provider": "openrouter",
+ "model": "x-ai/grok-4"
+ },
+ {
+ "provider": "openrouter",
+ "model": "x-ai/grok-4-fast"
+ },
+ {
+ "provider": "openrouter",
+ "model": "x-ai/grok-code-fast-1"
+ },
+ {
+ "provider": "openrouter",
+ "model": "z-ai/glm-4-32b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "z-ai/glm-4.5"
+ },
+ {
+ "provider": "openrouter",
+ "model": "z-ai/glm-4.5-air"
+ },
+ {
+ "provider": "openrouter",
+ "model": "z-ai/glm-4.5-air:free"
+ },
+ {
+ "provider": "openrouter",
+ "model": "z-ai/glm-4.5v"
+ },
+ {
+ "provider": "openrouter",
+ "model": "z-ai/glm-4.6"
+ },
+ {
+ "provider": "openrouter",
+ "model": "z-ai/glm-4.6:exacto"
+ },
+ {
+ "provider": "databricks",
+ "model": "baxen-migration-demo"
+ },
+ {
+ "provider": "databricks",
+ "model": "big-hack"
+ },
+ {
+ "provider": "databricks",
+ "model": "case_history_hackweek"
+ },
+ {
+ "provider": "databricks",
+ "model": "case-history-checker"
+ },
+ {
+ "provider": "databricks",
+ "model": "claude-3-5-haiku"
+ },
+ {
+ "provider": "databricks",
+ "model": "claude-3-5-sonnet"
+ },
+ {
+ "provider": "databricks",
+ "model": "claude-3-5-sonnet-2"
+ },
+ {
+ "provider": "databricks",
+ "model": "claude-3-7-sonnet"
+ },
+ {
+ "provider": "databricks",
+ "model": "claude-4"
+ },
+ {
+ "provider": "databricks",
+ "model": "claude-4-opus"
+ },
+ {
+ "provider": "databricks",
+ "model": "claude-haiku"
+ },
+ {
+ "provider": "databricks",
+ "model": "claude-opus"
+ },
+ {
+ "provider": "databricks",
+ "model": "claude-sonnet"
+ },
+ {
+ "provider": "databricks",
+ "model": "cmg-test-iris"
+ },
+ {
+ "provider": "databricks",
+ "model": "code-review-gpt-5"
+ },
+ {
+ "provider": "databricks",
+ "model": "code-review-gpt-5-mini"
+ },
+ {
+ "provider": "databricks",
+ "model": "codellama-7b-hf-ift"
+ },
+ {
+ "provider": "databricks",
+ "model": "column-mapping-model-endpoint"
+ },
+ {
+ "provider": "databricks",
+ "model": "column-mapping-model-endpoint-v2"
+ },
+ {
+ "provider": "databricks",
+ "model": "dummy-model-ml-gp-endpoint"
+ },
+ {
+ "provider": "databricks",
+ "model": "e5-large-v2"
+ },
+ {
+ "provider": "databricks",
+ "model": "gemini-1-5-flash"
+ },
+ {
+ "provider": "databricks",
+ "model": "gemini-1-5-pro"
+ },
+ {
+ "provider": "databricks",
+ "model": "gemini-2-0-flash"
+ },
+ {
+ "provider": "databricks",
+ "model": "gemini-2-5-flash"
+ },
+ {
+ "provider": "databricks",
+ "model": "gemini-2-5-flash-latest"
+ },
+ {
+ "provider": "databricks",
+ "model": "gemini-2-5-pro"
+ },
+ {
+ "provider": "databricks",
+ "model": "gemini-2-5-pro-exp"
+ },
+ {
+ "provider": "databricks",
+ "model": "gemini-flash-lite-latest"
+ },
+ {
+ "provider": "databricks",
+ "model": "gemini-pro"
+ },
+ {
+ "provider": "databricks",
+ "model": "goose"
+ },
+ {
+ "provider": "databricks",
+ "model": "goose-claude-3-5-sonnet"
+ },
+ {
+ "provider": "databricks",
+ "model": "goose-claude-3-7-sonnet"
+ },
+ {
+ "provider": "databricks",
+ "model": "goose-claude-4-5-sonnet"
+ },
+ {
+ "provider": "databricks",
+ "model": "goose-claude-4-opus"
+ },
+ {
+ "provider": "databricks",
+ "model": "goose-claude-4-sonnet"
+ },
+ {
+ "provider": "databricks",
+ "model": "goose-claude-4-sonnet-bedrock"
+ },
+ {
+ "provider": "databricks",
+ "model": "goose-gemini-2-5-pro"
+ },
+ {
+ "provider": "databricks",
+ "model": "goose-gpt-4-1"
+ },
+ {
+ "provider": "databricks",
+ "model": "goose-gpt-4o"
+ },
+ {
+ "provider": "databricks",
+ "model": "goose-gpt-5"
+ },
+ {
+ "provider": "databricks",
+ "model": "goose-gpt-oss"
+ },
+ {
+ "provider": "databricks",
+ "model": "goose-o1"
+ },
+ {
+ "provider": "databricks",
+ "model": "goose-o3"
+ },
+ {
+ "provider": "databricks",
+ "model": "goose-o4-mini"
+ },
+ {
+ "provider": "databricks",
+ "model": "gpt-3-5-turbo"
+ },
+ {
+ "provider": "databricks",
+ "model": "gpt-3-5-turbo-0125"
+ },
+ {
+ "provider": "databricks",
+ "model": "gpt-3-5-turbo-16k"
+ },
+ {
+ "provider": "databricks",
+ "model": "gpt-3-5-turbo-instruct"
+ },
+ {
+ "provider": "databricks",
+ "model": "gpt-4"
+ },
+ {
+ "provider": "databricks",
+ "model": "gpt-4-0125-preview"
+ },
+ {
+ "provider": "databricks",
+ "model": "gpt-4-1-2025-04-14"
+ },
+ {
+ "provider": "databricks",
+ "model": "gpt-4-1-mini"
+ },
+ {
+ "provider": "databricks",
+ "model": "gpt-4-1-nano"
+ },
+ {
+ "provider": "databricks",
+ "model": "gpt-4-turbo"
+ },
+ {
+ "provider": "databricks",
+ "model": "gpt-4-turbo-2024-04-09"
+ },
+ {
+ "provider": "databricks",
+ "model": "gpt-4-vision-preview"
+ },
+ {
+ "provider": "databricks",
+ "model": "gpt-4o"
+ },
+ {
+ "provider": "databricks",
+ "model": "gpt-4o-2024-05-13"
+ },
+ {
+ "provider": "databricks",
+ "model": "gpt-4o-2024-11-20"
+ },
+ {
+ "provider": "databricks",
+ "model": "gpt-4o-mini"
+ },
+ {
+ "provider": "databricks",
+ "model": "gpt-4o-mini-2024-07-18"
+ },
+ {
+ "provider": "databricks",
+ "model": "gpt-5"
+ },
+ {
+ "provider": "databricks",
+ "model": "gpt-5-mini-high"
+ },
+ {
+ "provider": "databricks",
+ "model": "gpt-5-nano"
+ },
+ {
+ "provider": "databricks",
+ "model": "gpt-vision"
+ },
+ {
+ "provider": "databricks",
+ "model": "hackweek-snowflake-gpt-query-generator"
+ },
+ {
+ "provider": "databricks",
+ "model": "headless-goose"
+ },
+ {
+ "provider": "databricks",
+ "model": "headless-goose-claude-4-sonnet"
+ },
+ {
+ "provider": "databricks",
+ "model": "headless-goose-o3-mini"
+ },
+ {
+ "provider": "databricks",
+ "model": "icg-poc"
+ },
+ {
+ "provider": "databricks",
+ "model": "invoice_parser_test"
+ },
+ {
+ "provider": "databricks",
+ "model": "jina-reranker-v1-turbo-en"
+ },
+ {
+ "provider": "databricks",
+ "model": "kgoose-cashapp-claude-4-sonnet"
+ },
+ {
+ "provider": "databricks",
+ "model": "kgoose-cashapp-claude-sonnet-4-5"
+ },
+ {
+ "provider": "databricks",
+ "model": "kgoose-claude-4-sonnet"
+ },
+ {
+ "provider": "databricks",
+ "model": "kgoose-claude-haiku-4-5"
+ },
+ {
+ "provider": "databricks",
+ "model": "kgoose-claude-sonnet-4-5"
+ },
+ {
+ "provider": "databricks",
+ "model": "kgoose-gemini-2-5-flash"
+ },
+ {
+ "provider": "databricks",
+ "model": "kgoose-gpt-4-1"
+ },
+ {
+ "provider": "databricks",
+ "model": "kgoose-gpt-4-1-mini"
+ },
+ {
+ "provider": "databricks",
+ "model": "kgoose-gpt-4-1-nano"
+ },
+ {
+ "provider": "databricks",
+ "model": "kgoose-gpt-4o"
+ },
+ {
+ "provider": "databricks",
+ "model": "kgoose-gpt-5"
+ },
+ {
+ "provider": "databricks",
+ "model": "kgoose-gpt-5-mini"
+ },
+ {
+ "provider": "databricks",
+ "model": "kgoose-gpt-5-nano"
+ },
+ {
+ "provider": "databricks",
+ "model": "kgoose-o3"
+ },
+ {
+ "provider": "databricks",
+ "model": "kgoose-o4-mini"
+ },
+ {
+ "provider": "databricks",
+ "model": "korhan-openai-test"
+ },
+ {
+ "provider": "databricks",
+ "model": "korhan-openai-wrapper"
+ },
+ {
+ "provider": "databricks",
+ "model": "moderation"
+ },
+ {
+ "provider": "databricks",
+ "model": "ng-tools-claude-haiku-3-5"
+ },
+ {
+ "provider": "databricks",
+ "model": "ng-tools-claude-opus-4"
+ },
+ {
+ "provider": "databricks",
+ "model": "ng-tools-claude-opus-4-1"
+ },
+ {
+ "provider": "databricks",
+ "model": "ng-tools-claude-sonnet-3-7"
+ },
+ {
+ "provider": "databricks",
+ "model": "ng-tools-claude-sonnet-4"
+ },
+ {
+ "provider": "databricks",
+ "model": "ng-tools-gpt-5-nano"
+ },
+ {
+ "provider": "databricks",
+ "model": "o1"
+ },
+ {
+ "provider": "databricks",
+ "model": "o1-2024-12-17"
+ },
+ {
+ "provider": "databricks",
+ "model": "o1-mini"
+ },
+ {
+ "provider": "databricks",
+ "model": "o1-preview"
+ },
+ {
+ "provider": "databricks",
+ "model": "o3"
+ },
+ {
+ "provider": "databricks",
+ "model": "o3-cdd-autopilot"
+ },
+ {
+ "provider": "databricks",
+ "model": "o3-mini"
+ },
+ {
+ "provider": "databricks",
+ "model": "optimized-llama2-7b"
+ },
+ {
+ "provider": "databricks",
+ "model": "opus-mt-en-es"
+ },
+ {
+ "provider": "databricks",
+ "model": "opus-mt-en-fr"
+ },
+ {
+ "provider": "databricks",
+ "model": "opus-mt-en-ja"
+ },
+ {
+ "provider": "databricks",
+ "model": "opus-mt-es-en"
+ },
+ {
+ "provider": "databricks",
+ "model": "opus-mt-fr-en"
+ },
+ {
+ "provider": "databricks",
+ "model": "opus-mt-ja-en"
+ },
+ {
+ "provider": "databricks",
+ "model": "p2p-device-recovery-classify"
+ },
+ {
+ "provider": "databricks",
+ "model": "picasso_embeddings"
+ },
+ {
+ "provider": "databricks",
+ "model": "prime_model"
+ },
+ {
+ "provider": "databricks",
+ "model": "raml-claude-sonnet-4-5"
+ },
+ {
+ "provider": "databricks",
+ "model": "reportiq_selector_1"
+ },
+ {
+ "provider": "databricks",
+ "model": "reportiq_selector_md_file"
+ },
+ {
+ "provider": "databricks",
+ "model": "snowflake-gpt-query-generator-v3"
+ },
+ {
+ "provider": "databricks",
+ "model": "sq-bank-statement-classifier"
+ },
+ {
+ "provider": "databricks",
+ "model": "sq-bank-statement-parser"
+ },
+ {
+ "provider": "databricks",
+ "model": "support-article-intent-mapping"
+ },
+ {
+ "provider": "databricks",
+ "model": "text-embedding-3-large"
+ },
+ {
+ "provider": "databricks",
+ "model": "text-embedding-3-small"
+ },
+ {
+ "provider": "databricks",
+ "model": "text-embedding-ada-002"
+ },
+ {
+ "provider": "databricks",
+ "model": "databricks-gpt-5"
+ },
+ {
+ "provider": "databricks",
+ "model": "databricks-gemini-2-5-flash"
+ },
+ {
+ "provider": "databricks",
+ "model": "databricks-claude-sonnet-4-5"
+ },
+ {
+ "provider": "databricks",
+ "model": "databricks-gpt-oss-120b"
+ },
+ {
+ "provider": "databricks",
+ "model": "databricks-gpt-5-mini"
+ },
+ {
+ "provider": "databricks",
+ "model": "databricks-gpt-5-nano"
+ },
+ {
+ "provider": "databricks",
+ "model": "databricks-gemini-2-5-pro"
+ },
+ {
+ "provider": "databricks",
+ "model": "databricks-gpt-oss-20b"
+ },
+ {
+ "provider": "databricks",
+ "model": "databricks-llama-4-maverick"
+ },
+ {
+ "provider": "databricks",
+ "model": "databricks-gemma-3-12b"
+ },
+ {
+ "provider": "databricks",
+ "model": "databricks-meta-llama-3-1-8b-instruct"
+ },
+ {
+ "provider": "databricks",
+ "model": "databricks-meta-llama-3-3-70b-instruct"
+ },
+ {
+ "provider": "databricks",
+ "model": "databricks-claude-opus-4-1"
+ },
+ {
+ "provider": "databricks",
+ "model": "databricks-claude-sonnet-4"
+ },
+ {
+ "provider": "databricks",
+ "model": "databricks-claude-3-7-sonnet"
+ },
+ {
+ "provider": "databricks",
+ "model": "databricks-gte-large-en"
+ },
+ {
+ "provider": "databricks",
+ "model": "databricks-bge-large-en"
+ },
+ {
+ "provider": "databricks",
+ "model": "databricks-meta-llama-3-1-405b-instruct"
+ },
+ {
+ "provider": "databricks",
+ "model": "databricks-claude-opus-4"
+ },
+ {
+ "provider": "google",
+ "model": "aqa"
+ },
+ {
+ "provider": "google",
+ "model": "embedding-001"
+ },
+ {
+ "provider": "google",
+ "model": "embedding-gecko-001"
+ },
+ {
+ "provider": "google",
+ "model": "gemini-2.0-flash-exp-image-generation"
+ },
+ {
+ "provider": "google",
+ "model": "gemini-2.0-flash-preview-image-generation"
+ },
+ {
+ "provider": "google",
+ "model": "gemini-2.0-flash-thinking-exp"
+ },
+ {
+ "provider": "google",
+ "model": "gemini-2.0-flash-thinking-exp-01-21"
+ },
+ {
+ "provider": "google",
+ "model": "gemini-2.0-flash-thinking-exp-1219"
+ },
+ {
+ "provider": "google",
+ "model": "gemini-2.0-pro-exp"
+ },
+ {
+ "provider": "google",
+ "model": "gemini-2.0-pro-exp-02-05"
+ },
+ {
+ "provider": "google",
+ "model": "gemini-2.5-computer-use-preview-10-2025"
+ },
+ {
+ "provider": "google",
+ "model": "gemini-2.5-flash-preview-tts"
+ },
+ {
+ "provider": "google",
+ "model": "gemini-2.5-pro-preview-tts"
+ },
+ {
+ "provider": "google",
+ "model": "gemini-embedding-001"
+ },
+ {
+ "provider": "google",
+ "model": "gemini-embedding-exp"
+ },
+ {
+ "provider": "google",
+ "model": "gemini-embedding-exp-03-07"
+ },
+ {
+ "provider": "google",
+ "model": "gemini-exp-1206"
+ },
+ {
+ "provider": "google",
+ "model": "gemini-flash-latest"
+ },
+ {
+ "provider": "google",
+ "model": "gemini-flash-lite-latest"
+ },
+ {
+ "provider": "google",
+ "model": "gemini-pro-latest"
+ },
+ {
+ "provider": "google",
+ "model": "gemini-robotics-er-1.5-preview"
+ },
+ {
+ "provider": "google",
+ "model": "gemma-3-1b-it"
+ },
+ {
+ "provider": "google",
+ "model": "gemma-3n-e2b-it"
+ },
+ {
+ "provider": "google",
+ "model": "imagen-4.0-generate-preview-06-06"
+ },
+ {
+ "provider": "google",
+ "model": "imagen-4.0-ultra-generate-preview-06-06"
+ },
+ {
+ "provider": "google",
+ "model": "learnlm-2.0-flash-experimental"
+ },
+ {
+ "provider": "google",
+ "model": "text-embedding-004"
+ }
+ ],
+ "all_mappings": {
+ "openrouter": [
+ {
+ "provider_model": "anthropic/claude-3-haiku",
+ "canonical_model": "anthropic/claude-3-haiku",
+ "verified": true
+ },
+ {
+ "provider_model": "anthropic/claude-3-opus",
+ "canonical_model": "anthropic/claude-3-opus",
+ "verified": true
+ },
+ {
+ "provider_model": "anthropic/claude-3.5-haiku",
+ "canonical_model": "anthropic/claude-3.5-haiku",
+ "verified": true
+ },
+ {
+ "provider_model": "anthropic/claude-3.5-haiku-20241022",
+ "canonical_model": "anthropic/claude-3.5-haiku",
+ "verified": true
+ },
+ {
+ "provider_model": "anthropic/claude-3.5-sonnet",
+ "canonical_model": "anthropic/claude-3.5-sonnet",
+ "verified": true
+ },
+ {
+ "provider_model": "anthropic/claude-3.5-sonnet-20240620",
+ "canonical_model": "anthropic/claude-3.5-sonnet",
+ "verified": true
+ },
+ {
+ "provider_model": "anthropic/claude-3.7-sonnet",
+ "canonical_model": "anthropic/claude-3.7-sonnet",
+ "verified": true
+ },
+ {
+ "provider_model": "anthropic/claude-3.7-sonnet:thinking",
+ "canonical_model": "anthropic/claude-3.7-sonnet:thinking",
+ "verified": true
+ },
+ {
+ "provider_model": "anthropic/claude-haiku-4.5",
+ "canonical_model": "anthropic/claude-haiku",
+ "verified": true
+ },
+ {
+ "provider_model": "anthropic/claude-opus-4",
+ "canonical_model": "anthropic/claude-opus",
+ "verified": true
+ },
+ {
+ "provider_model": "anthropic/claude-opus-4.1",
+ "canonical_model": "anthropic/claude-opus",
+ "verified": true
+ },
+ {
+ "provider_model": "anthropic/claude-sonnet-4",
+ "canonical_model": "anthropic/claude-sonnet",
+ "verified": true
+ },
+ {
+ "provider_model": "anthropic/claude-sonnet-4.5",
+ "canonical_model": "anthropic/claude-sonnet",
+ "verified": true
+ },
+ {
+ "provider_model": "google/gemini-2.0-flash-001",
+ "canonical_model": "google/gemini-2.0-flash",
+ "verified": true
+ },
+ {
+ "provider_model": "google/gemini-2.0-flash-lite-001",
+ "canonical_model": "google/gemini-2.0-flash-lite",
+ "verified": true
+ },
+ {
+ "provider_model": "google/gemini-2.5-flash",
+ "canonical_model": "google/gemini-2.5-flash",
+ "verified": true
+ },
+ {
+ "provider_model": "google/gemini-2.5-flash-lite",
+ "canonical_model": "google/gemini-2.5-flash-lite",
+ "verified": true
+ },
+ {
+ "provider_model": "google/gemini-2.5-flash-lite-preview-06-17",
+ "canonical_model": "google/gemini-2.5-flash-lite",
+ "verified": true
+ },
+ {
+ "provider_model": "google/gemini-2.5-flash-lite-preview-09-2025",
+ "canonical_model": "google/gemini-2.5-flash-lite",
+ "verified": true
+ },
+ {
+ "provider_model": "google/gemini-2.5-flash-preview-09-2025",
+ "canonical_model": "google/gemini-2.5-flash",
+ "verified": true
+ },
+ {
+ "provider_model": "google/gemini-2.5-pro",
+ "canonical_model": "google/gemini-2.5-pro",
+ "verified": true
+ },
+ {
+ "provider_model": "google/gemini-2.5-pro-preview",
+ "canonical_model": "google/gemini-2.5-pro",
+ "verified": true
+ },
+ {
+ "provider_model": "google/gemini-2.5-pro-preview-05-06",
+ "canonical_model": "google/gemini-2.5-pro",
+ "verified": true
+ },
+ {
+ "provider_model": "google/gemma-3-27b-it",
+ "canonical_model": "google/gemma-3-27b-it",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/codex-mini",
+ "canonical_model": "openai/codex-mini",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-3.5-turbo",
+ "canonical_model": "openai/gpt-3.5-turbo",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-3.5-turbo-0613",
+ "canonical_model": "openai/gpt-3.5-turbo",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-3.5-turbo-16k",
+ "canonical_model": "openai/gpt-3.5-turbo-16k",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-4",
+ "canonical_model": "openai/gpt",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-4-0314",
+ "canonical_model": "openai/gpt",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-4-1106-preview",
+ "canonical_model": "openai/gpt",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-4-turbo",
+ "canonical_model": "openai/gpt-4-turbo",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-4-turbo-preview",
+ "canonical_model": "openai/gpt-4-turbo",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-4.1",
+ "canonical_model": "openai/gpt",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-4.1-mini",
+ "canonical_model": "openai/gpt-4.1-mini",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-4.1-nano",
+ "canonical_model": "openai/gpt-4.1-nano",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-4o",
+ "canonical_model": "openai/gpt-4o",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-4o-2024-05-13",
+ "canonical_model": "openai/gpt-4o",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-4o-2024-08-06",
+ "canonical_model": "openai/gpt-4o",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-4o-2024-11-20",
+ "canonical_model": "openai/gpt-4o",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-4o-audio-preview",
+ "canonical_model": "openai/gpt-4o-audio",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-4o-mini",
+ "canonical_model": "openai/gpt-4o-mini",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-4o-mini-2024-07-18",
+ "canonical_model": "openai/gpt-4o-mini",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-4o:extended",
+ "canonical_model": "openai/gpt-4o:extended",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-5",
+ "canonical_model": "openai/gpt",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-5-codex",
+ "canonical_model": "openai/gpt-5-codex",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-5-image",
+ "canonical_model": "openai/gpt-5-image",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-5-image-mini",
+ "canonical_model": "openai/gpt-5-image-mini",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-5-mini",
+ "canonical_model": "openai/gpt-5-mini",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-5-nano",
+ "canonical_model": "openai/gpt-5-nano",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-5-pro",
+ "canonical_model": "openai/gpt-5-pro",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-oss-120b",
+ "canonical_model": "openai/gpt-oss-120b",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-oss-120b:exacto",
+ "canonical_model": "openai/gpt-oss-120b:exacto",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-oss-20b",
+ "canonical_model": "openai/gpt-oss-20b",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-oss-safeguard-20b",
+ "canonical_model": "openai/gpt-oss-safeguard-20b",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/o1",
+ "canonical_model": "openai/o1",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/o3",
+ "canonical_model": "openai/o3",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/o3-deep-research",
+ "canonical_model": "openai/o3-deep-research",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/o3-mini",
+ "canonical_model": "openai/o3-mini",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/o3-mini-high",
+ "canonical_model": "openai/o3-mini-high",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/o3-pro",
+ "canonical_model": "openai/o3-pro",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/o4-mini",
+ "canonical_model": "openai/o4-mini",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/o4-mini-deep-research",
+ "canonical_model": "openai/o4-mini-deep-research",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/o4-mini-high",
+ "canonical_model": "openai/o4-mini-high",
+ "verified": true
+ }
+ ],
+ "anthropic": [
+ {
+ "provider_model": "claude-3-5-haiku-20241022",
+ "canonical_model": "anthropic/claude-3.5-haiku",
+ "verified": true
+ },
+ {
+ "provider_model": "claude-3-7-sonnet-20250219",
+ "canonical_model": "anthropic/claude-3.7-sonnet",
+ "verified": true
+ },
+ {
+ "provider_model": "claude-3-haiku-20240307",
+ "canonical_model": "anthropic/claude-3-haiku",
+ "verified": true
+ },
+ {
+ "provider_model": "claude-3-opus-20240229",
+ "canonical_model": "anthropic/claude-3-opus",
+ "verified": true
+ },
+ {
+ "provider_model": "claude-haiku-4-5-20251001",
+ "canonical_model": "anthropic/claude-haiku",
+ "verified": true
+ },
+ {
+ "provider_model": "claude-opus-4-1-20250805",
+ "canonical_model": "anthropic/claude-opus",
+ "verified": true
+ },
+ {
+ "provider_model": "claude-opus-4-20250514",
+ "canonical_model": "anthropic/claude-opus",
+ "verified": true
+ },
+ {
+ "provider_model": "claude-sonnet-4-20250514",
+ "canonical_model": "anthropic/claude-sonnet",
+ "verified": true
+ },
+ {
+ "provider_model": "claude-sonnet-4-5-20250929",
+ "canonical_model": "anthropic/claude-sonnet",
+ "verified": true
+ }
+ ],
+ "openai": [
+ {
+ "provider_model": "chatgpt-4o-latest",
+ "canonical_model": "openai/chatgpt-4o-latest",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-3.5-turbo",
+ "canonical_model": "openai/gpt-3.5-turbo",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-3.5-turbo-0125",
+ "canonical_model": "openai/gpt-3.5-turbo",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-3.5-turbo-1106",
+ "canonical_model": "openai/gpt-3.5-turbo",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-3.5-turbo-16k",
+ "canonical_model": "openai/gpt-3.5-turbo-16k",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-3.5-turbo-instruct",
+ "canonical_model": "openai/gpt-3.5-turbo-instruct",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-3.5-turbo-instruct-0914",
+ "canonical_model": "openai/gpt-3.5-turbo-instruct",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4",
+ "canonical_model": "openai/gpt",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4-0125-preview",
+ "canonical_model": "openai/gpt",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4-0314",
+ "canonical_model": "openai/gpt",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4-0613",
+ "canonical_model": "openai/gpt",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4-1106-preview",
+ "canonical_model": "openai/gpt",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4-turbo",
+ "canonical_model": "openai/gpt-4-turbo",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4-turbo-2024-04-09",
+ "canonical_model": "openai/gpt-4-turbo",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4-turbo-preview",
+ "canonical_model": "openai/gpt-4-turbo",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4.1",
+ "canonical_model": "openai/gpt",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4.1-2025-04-14",
+ "canonical_model": "openai/gpt",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4.1-mini",
+ "canonical_model": "openai/gpt-4.1-mini",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4.1-mini-2025-04-14",
+ "canonical_model": "openai/gpt-4.1-mini",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4.1-nano",
+ "canonical_model": "openai/gpt-4.1-nano",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4.1-nano-2025-04-14",
+ "canonical_model": "openai/gpt-4.1-nano",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4o",
+ "canonical_model": "openai/gpt-4o",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4o-2024-05-13",
+ "canonical_model": "openai/gpt-4o",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4o-2024-08-06",
+ "canonical_model": "openai/gpt-4o",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4o-2024-11-20",
+ "canonical_model": "openai/gpt-4o",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4o-audio-preview",
+ "canonical_model": "openai/gpt-4o-audio",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4o-audio-preview-2024-10-01",
+ "canonical_model": "openai/gpt-4o-audio",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4o-audio-preview-2024-12-17",
+ "canonical_model": "openai/gpt-4o-audio",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4o-audio-preview-2025-06-03",
+ "canonical_model": "openai/gpt-4o-audio",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4o-mini",
+ "canonical_model": "openai/gpt-4o-mini",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4o-mini-2024-07-18",
+ "canonical_model": "openai/gpt-4o-mini",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4o-mini-search-preview",
+ "canonical_model": "openai/gpt-4o-mini-search",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4o-mini-search-preview-2025-03-11",
+ "canonical_model": "openai/gpt-4o-mini-search",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4o-search-preview",
+ "canonical_model": "openai/gpt-4o-search",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4o-search-preview-2025-03-11",
+ "canonical_model": "openai/gpt-4o-search",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-5",
+ "canonical_model": "openai/gpt",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-5-2025-08-07",
+ "canonical_model": "openai/gpt",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-5-codex",
+ "canonical_model": "openai/gpt-5-codex",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-5-mini",
+ "canonical_model": "openai/gpt-5-mini",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-5-mini-2025-08-07",
+ "canonical_model": "openai/gpt-5-mini",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-5-nano",
+ "canonical_model": "openai/gpt-5-nano",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-5-nano-2025-08-07",
+ "canonical_model": "openai/gpt-5-nano",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-5-pro",
+ "canonical_model": "openai/gpt-5-pro",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-5-pro-2025-10-06",
+ "canonical_model": "openai/gpt-5-pro",
+ "verified": true
+ },
+ {
+ "provider_model": "o1",
+ "canonical_model": "openai/o1",
+ "verified": true
+ },
+ {
+ "provider_model": "o1-2024-12-17",
+ "canonical_model": "openai/o1",
+ "verified": true
+ },
+ {
+ "provider_model": "o1-pro",
+ "canonical_model": "openai/o1-pro",
+ "verified": true
+ },
+ {
+ "provider_model": "o1-pro-2025-03-19",
+ "canonical_model": "openai/o1-pro",
+ "verified": true
+ },
+ {
+ "provider_model": "o3",
+ "canonical_model": "openai/o3",
+ "verified": true
+ },
+ {
+ "provider_model": "o3-2025-04-16",
+ "canonical_model": "openai/o3",
+ "verified": true
+ },
+ {
+ "provider_model": "o3-deep-research",
+ "canonical_model": "openai/o3-deep-research",
+ "verified": true
+ },
+ {
+ "provider_model": "o3-deep-research-2025-06-26",
+ "canonical_model": "openai/o3-deep-research",
+ "verified": true
+ },
+ {
+ "provider_model": "o3-mini",
+ "canonical_model": "openai/o3-mini",
+ "verified": true
+ },
+ {
+ "provider_model": "o3-mini-2025-01-31",
+ "canonical_model": "openai/o3-mini",
+ "verified": true
+ },
+ {
+ "provider_model": "o3-pro",
+ "canonical_model": "openai/o3-pro",
+ "verified": true
+ },
+ {
+ "provider_model": "o3-pro-2025-06-10",
+ "canonical_model": "openai/o3-pro",
+ "verified": true
+ },
+ {
+ "provider_model": "o4-mini",
+ "canonical_model": "openai/o4-mini",
+ "verified": true
+ },
+ {
+ "provider_model": "o4-mini-2025-04-16",
+ "canonical_model": "openai/o4-mini",
+ "verified": true
+ },
+ {
+ "provider_model": "o4-mini-deep-research",
+ "canonical_model": "openai/o4-mini-deep-research",
+ "verified": true
+ },
+ {
+ "provider_model": "o4-mini-deep-research-2025-06-26",
+ "canonical_model": "openai/o4-mini-deep-research",
+ "verified": true
+ }
+ ],
+ "google": [
+ {
+ "provider_model": "gemini-2.0-flash",
+ "canonical_model": "google/gemini-2.0-flash",
+ "verified": true
+ },
+ {
+ "provider_model": "gemini-2.0-flash-001",
+ "canonical_model": "google/gemini-2.0-flash",
+ "verified": true
+ },
+ {
+ "provider_model": "gemini-2.0-flash-exp",
+ "canonical_model": "google/gemini-2.0-flash",
+ "verified": true
+ },
+ {
+ "provider_model": "gemini-2.0-flash-lite",
+ "canonical_model": "google/gemini-2.0-flash-lite",
+ "verified": true
+ },
+ {
+ "provider_model": "gemini-2.0-flash-lite-001",
+ "canonical_model": "google/gemini-2.0-flash-lite",
+ "verified": true
+ },
+ {
+ "provider_model": "gemini-2.0-flash-lite-preview",
+ "canonical_model": "google/gemini-2.0-flash-lite",
+ "verified": true
+ },
+ {
+ "provider_model": "gemini-2.0-flash-lite-preview-02-05",
+ "canonical_model": "google/gemini-2.0-flash-lite",
+ "verified": true
+ },
+ {
+ "provider_model": "gemini-2.5-flash",
+ "canonical_model": "google/gemini-2.5-flash",
+ "verified": true
+ },
+ {
+ "provider_model": "gemini-2.5-flash-image",
+ "canonical_model": "google/gemini-2.5-flash-image",
+ "verified": true
+ },
+ {
+ "provider_model": "gemini-2.5-flash-image-preview",
+ "canonical_model": "google/gemini-2.5-flash-image",
+ "verified": true
+ },
+ {
+ "provider_model": "gemini-2.5-flash-lite",
+ "canonical_model": "google/gemini-2.5-flash-lite",
+ "verified": true
+ },
+ {
+ "provider_model": "gemini-2.5-flash-lite-preview-06-17",
+ "canonical_model": "google/gemini-2.5-flash-lite",
+ "verified": true
+ },
+ {
+ "provider_model": "gemini-2.5-flash-lite-preview-09-2025",
+ "canonical_model": "google/gemini-2.5-flash-lite",
+ "verified": true
+ },
+ {
+ "provider_model": "gemini-2.5-flash-preview-05-20",
+ "canonical_model": "google/gemini-2.5-flash",
+ "verified": true
+ },
+ {
+ "provider_model": "gemini-2.5-flash-preview-09-2025",
+ "canonical_model": "google/gemini-2.5-flash",
+ "verified": true
+ },
+ {
+ "provider_model": "gemini-2.5-pro",
+ "canonical_model": "google/gemini-2.5-pro",
+ "verified": true
+ },
+ {
+ "provider_model": "gemini-2.5-pro-preview-03-25",
+ "canonical_model": "google/gemini-2.5-pro",
+ "verified": true
+ },
+ {
+ "provider_model": "gemini-2.5-pro-preview-05-06",
+ "canonical_model": "google/gemini-2.5-pro",
+ "verified": true
+ },
+ {
+ "provider_model": "gemini-2.5-pro-preview-06-05",
+ "canonical_model": "google/gemini-2.5-pro",
+ "verified": true
+ },
+ {
+ "provider_model": "gemma-3-12b-it",
+ "canonical_model": "google/gemma-3-12b-it",
+ "verified": true
+ },
+ {
+ "provider_model": "gemma-3-27b-it",
+ "canonical_model": "google/gemma-3-27b-it",
+ "verified": true
+ },
+ {
+ "provider_model": "gemma-3-4b-it",
+ "canonical_model": "google/gemma-3-4b-it",
+ "verified": true
+ },
+ {
+ "provider_model": "gemma-3n-e4b-it",
+ "canonical_model": "google/gemma-3n-e4b-it",
+ "verified": true
+ }
+ ],
+ "databricks": [],
+ "tetrate": []
+ },
+ "model_counts": {
+ "openrouter": 217,
+ "anthropic": 9,
+ "openai": 625,
+ "tetrate": 0,
+ "databricks": 143,
+ "google": 50
+ },
+ "canonical_models_used": [
+ "openai/gpt-5-image-mini",
+ "google/gemini-2.5-flash-image",
+ "openai/gpt-4.1-mini",
+ "openai/o4-mini-high",
+ "google/gemini-2.5-flash-lite",
+ "openai/gpt-4-turbo",
+ "anthropic/claude-sonnet",
+ "openai/gpt-4o-mini",
+ "google/gemma-3-4b-it",
+ "openai/gpt-3.5-turbo-instruct",
+ "openai/gpt-3.5-turbo-16k",
+ "google/gemma-3n-e4b-it",
+ "anthropic/claude-3-opus",
+ "google/gemma-3-12b-it",
+ "openai/o3",
+ "openai/gpt-oss-safeguard-20b",
+ "anthropic/claude-3.5-haiku",
+ "openai/o1-pro",
+ "openai/o3-mini",
+ "openai/chatgpt-4o-latest",
+ "openai/gpt-3.5-turbo",
+ "anthropic/claude-3-haiku",
+ "openai/gpt",
+ "openai/gpt-5-codex",
+ "openai/o3-deep-research",
+ "anthropic/claude-3.7-sonnet:thinking",
+ "anthropic/claude-haiku",
+ "anthropic/claude-3.7-sonnet",
+ "openai/gpt-4.1-nano",
+ "openai/o4-mini",
+ "openai/o3-mini-high",
+ "openai/o4-mini-deep-research",
+ "openai/gpt-oss-120b",
+ "openai/gpt-4o-audio",
+ "openai/o3-pro",
+ "openai/gpt-5-image",
+ "google/gemini-2.5-flash",
+ "google/gemma-3-27b-it",
+ "openai/gpt-oss-20b",
+ "google/gemini-2.5-pro",
+ "anthropic/claude-3.5-sonnet",
+ "openai/o1",
+ "openai/gpt-oss-120b:exacto",
+ "openai/gpt-4o-mini-search",
+ "openai/gpt-5-pro",
+ "openai/gpt-4o",
+ "openai/codex-mini",
+ "google/gemini-2.0-flash-lite",
+ "anthropic/claude-opus",
+ "openai/gpt-4o:extended",
+ "google/gemini-2.0-flash",
+ "openai/gpt-5-mini",
+ "openai/gpt-5-nano",
+ "openai/gpt-4o-search"
+ ]
+}
\ No newline at end of file
diff --git a/crates/goose/examples/build_canonical_models.rs b/crates/goose/examples/build_canonical_models.rs
new file mode 100644
index 0000000000..36f563bd62
--- /dev/null
+++ b/crates/goose/examples/build_canonical_models.rs
@@ -0,0 +1,254 @@
+/// Build canonical models from OpenRouter API
+///
+/// This script fetches models from OpenRouter and converts them to canonical format.
+/// Usage:
+/// cargo run --example build_canonical_models
+///
+use anyhow::{Context, Result};
+use goose::providers::canonical::{
+ canonical_name, CanonicalModel, CanonicalModelRegistry, Pricing,
+};
+use serde_json::Value;
+use std::collections::HashMap;
+
+const OPENROUTER_API_URL: &str = "https://openrouter.ai/api/v1/models";
+const ALLOWED_PROVIDERS: &[&str] = &[
+ "anthropic",
+ "google",
+ "openai",
+ "meta-llama",
+ "mistralai",
+ "x-ai",
+ "deepseek",
+ "cohere",
+ "ai21",
+ "qwen",
+];
+
+#[tokio::main]
+async fn main() -> Result<()> {
+ println!("Fetching models from OpenRouter API...");
+
+ let client = reqwest::Client::new();
+ let response = client
+ .get(OPENROUTER_API_URL)
+ .header("User-Agent", "goose/canonical-builder")
+ .send()
+ .await
+ .context("Failed to fetch from OpenRouter API")?;
+
+ let json: Value = response
+ .json()
+ .await
+ .context("Failed to parse OpenRouter response")?;
+
+ let models = json["data"]
+ .as_array()
+ .context("Expected 'data' array in OpenRouter response")?
+ .clone();
+
+ println!("Processing {} models from OpenRouter...", models.len());
+
+ // First pass: Group models by canonical ID and track the one with shortest name
+ let mut canonical_groups: HashMap = HashMap::new();
+ let mut shortest_names: HashMap = HashMap::new();
+
+ for model in &models {
+ let id = model["id"].as_str().unwrap();
+ let name = model["name"].as_str().context("Model missing id field")?;
+
+ // Skip OpenRouter-specific pricing variants (:free, :nitro)
+ // Keep :extended since it has different context length
+ if id.contains(":free") || id.contains(":nitro") {
+ continue;
+ }
+
+ let canonical_id = canonical_name("openrouter", id);
+
+ let provider = canonical_id.split('/').next().unwrap_or("");
+ if !ALLOWED_PROVIDERS.contains(&provider) {
+ continue;
+ }
+
+ let prompt_cost = model
+ .get("pricing")
+ .and_then(|p| p.get("prompt"))
+ .and_then(|v| v.as_str())
+ .and_then(|s| s.parse::().ok())
+ .unwrap_or(0.0);
+
+ let completion_cost = model
+ .get("pricing")
+ .and_then(|p| p.get("completion"))
+ .and_then(|v| v.as_str())
+ .and_then(|s| s.parse::().ok())
+ .unwrap_or(0.0);
+
+ let has_paid_pricing = prompt_cost > 0.0 || completion_cost > 0.0;
+
+ if let Some(existing_model) = canonical_groups.get(&canonical_id) {
+ let existing_name = shortest_names.get(&canonical_id).unwrap();
+
+ let existing_prompt = existing_model
+ .get("pricing")
+ .and_then(|p| p.get("prompt"))
+ .and_then(|v| v.as_str())
+ .and_then(|s| s.parse::().ok())
+ .unwrap_or(0.0);
+
+ let existing_completion = existing_model
+ .get("pricing")
+ .and_then(|p| p.get("completion"))
+ .and_then(|v| v.as_str())
+ .and_then(|s| s.parse::().ok())
+ .unwrap_or(0.0);
+
+ let existing_has_paid = existing_prompt > 0.0 || existing_completion > 0.0;
+
+ let should_replace = if has_paid_pricing != existing_has_paid {
+ has_paid_pricing // Prefer the one with paid pricing
+ } else {
+ name.len() < existing_name.len() // Both same pricing tier, prefer shorter name
+ };
+
+ if should_replace {
+ println!(
+ " Updating {} from '{}' (paid: {}) to '{}' (paid: {})",
+ canonical_id,
+ existing_model["id"].as_str().unwrap(),
+ existing_has_paid,
+ id,
+ has_paid_pricing
+ );
+ shortest_names.insert(canonical_id.clone(), name.to_string());
+ canonical_groups.insert(canonical_id, model);
+ }
+ } else {
+ println!(
+ " Adding: {} (from {}, paid: {})",
+ canonical_id, id, has_paid_pricing
+ );
+ shortest_names.insert(canonical_id.clone(), name.to_string());
+ canonical_groups.insert(canonical_id, model);
+ }
+ }
+
+ // Filter out beta/preview variants if non-beta version exists
+ let beta_suffixes = ["-beta", "-preview", "-alpha"];
+ let mut to_remove = Vec::new();
+
+ for canonical_id in canonical_groups.keys() {
+ for suffix in &beta_suffixes {
+ if canonical_id.ends_with(suffix) {
+ // Check if non-beta version exists
+ let base_id = canonical_id.strip_suffix(suffix).unwrap();
+ if canonical_groups.contains_key(base_id) {
+ println!(
+ " Filtering out {} (non-beta version {} exists)",
+ canonical_id, base_id
+ );
+ to_remove.push(canonical_id.clone());
+ break;
+ }
+ }
+ }
+ }
+
+ for id in to_remove {
+ canonical_groups.remove(&id);
+ shortest_names.remove(&id);
+ }
+
+ // Second pass: Build the registry with the selected models
+ let mut registry = CanonicalModelRegistry::new();
+
+ for (canonical_id, model) in canonical_groups.iter() {
+ let name = shortest_names.get(canonical_id).unwrap();
+
+ let context_length = model["context_length"].as_u64().unwrap_or(128_000) as usize;
+
+ let max_completion_tokens = model
+ .get("top_provider")
+ .and_then(|tp| tp.get("max_completion_tokens"))
+ .and_then(|v| v.as_u64())
+ .map(|v| v as usize);
+
+ let input_modalities: Vec = model
+ .get("architecture")
+ .and_then(|arch| arch.get("input_modalities"))
+ .and_then(|v| v.as_array())
+ .map(|arr| {
+ arr.iter()
+ .filter_map(|v| v.as_str())
+ .map(|s| s.to_string())
+ .collect()
+ })
+ .unwrap_or_else(|| vec!["text".to_string()]);
+
+ let output_modalities: Vec = model
+ .get("architecture")
+ .and_then(|arch| arch.get("output_modalities"))
+ .and_then(|v| v.as_array())
+ .map(|arr| {
+ arr.iter()
+ .filter_map(|v| v.as_str())
+ .map(|s| s.to_string())
+ .collect()
+ })
+ .unwrap_or_else(|| vec!["text".to_string()]);
+
+ let supports_tools = model
+ .get("supported_parameters")
+ .and_then(|v| v.as_array())
+ .map(|params| params.iter().any(|param| param.as_str() == Some("tools")))
+ .unwrap_or(false);
+
+ let pricing_obj = model
+ .get("pricing")
+ .context("Model missing pricing field")?;
+ let pricing = Pricing {
+ prompt: pricing_obj
+ .get("prompt")
+ .and_then(|v| v.as_str())
+ .and_then(|s| s.parse().ok()),
+ completion: pricing_obj
+ .get("completion")
+ .and_then(|v| v.as_str())
+ .and_then(|s| s.parse().ok()),
+ request: pricing_obj
+ .get("request")
+ .and_then(|v| v.as_str())
+ .and_then(|s| s.parse().ok()),
+ image: pricing_obj
+ .get("image")
+ .and_then(|v| v.as_str())
+ .and_then(|s| s.parse().ok()),
+ };
+
+ let canonical_model = CanonicalModel {
+ id: canonical_id.clone(),
+ name: name.to_string(),
+ context_length,
+ max_completion_tokens,
+ input_modalities,
+ output_modalities,
+ supports_tools,
+ pricing,
+ };
+
+ registry.register(canonical_model);
+ }
+
+ use std::path::PathBuf;
+
+ let output_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
+ .join("src/providers/canonical/data/canonical_models.json");
+ registry.to_file(&output_path)?;
+ println!(
+ "\nβ Wrote {} models to {}",
+ registry.count(),
+ output_path.display()
+ );
+
+ Ok(())
+}
diff --git a/crates/goose/examples/canonical_model_checker.rs b/crates/goose/examples/canonical_model_checker.rs
new file mode 100644
index 0000000000..8c6cf530b8
--- /dev/null
+++ b/crates/goose/examples/canonical_model_checker.rs
@@ -0,0 +1,369 @@
+/// Canonical Model Checker
+///
+/// This script checks which models from top providers are properly mapped to canonical models.
+/// It maintains a lock file of mappings and detects changes between runs.
+///
+/// Outputs:
+/// - Models that are NOT mapped to canonical models
+/// - Full list of (provider, model) <-> canonical-model mappings
+/// - Diff report showing mapping changes since last run:
+/// * Changed mappings (model now maps to a different canonical model)
+/// * Added mappings (model gained a canonical mapping)
+/// * Removed mappings (model lost its canonical mapping)
+///
+/// Output File:
+/// - src/providers/canonical/data/canonical_mapping_report.json
+/// Contains full report with mapping data (acts as a lock file)
+///
+/// Usage:
+/// cargo run --example canonical_model_checker -- [--output custom_path.json]
+///
+use anyhow::{Context, Result};
+use goose::providers::{canonical::ModelMapping, create_with_named_model};
+use serde::{Deserialize, Serialize};
+use std::collections::{HashMap, HashSet};
+use std::path::PathBuf;
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
+struct ProviderModelPair {
+ provider: String,
+ model: String,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+struct MappingEntry {
+ provider: String,
+ model: String,
+ canonical: String,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+struct MappingReport {
+ /// Timestamp of this report
+ timestamp: String,
+
+ /// Models that are NOT mapped to canonical models
+ unmapped_models: Vec,
+
+ /// All mappings: (provider, model) -> canonical model
+ /// Stored per provider for backward compatibility
+ all_mappings: HashMap>,
+
+ /// Flat list of all mappings for easier comparison (lock file format)
+ mapped_models: Vec,
+
+ /// Total models checked per provider
+ model_counts: HashMap,
+
+ /// Canonical models referenced
+ canonical_models_used: HashSet,
+}
+
+impl MappingReport {
+ fn new() -> Self {
+ Self {
+ timestamp: chrono::Utc::now().to_rfc3339(),
+ unmapped_models: Vec::new(),
+ all_mappings: HashMap::new(),
+ mapped_models: Vec::new(),
+ model_counts: HashMap::new(),
+ canonical_models_used: HashSet::new(),
+ }
+ }
+
+ fn add_provider_results(
+ &mut self,
+ provider_name: &str,
+ fetched_models: Vec,
+ mappings: Vec,
+ ) {
+ let mapping_map: HashMap = mappings
+ .iter()
+ .map(|m| (m.provider_model.clone(), m.canonical_model.clone()))
+ .collect();
+
+ for model in &fetched_models {
+ if !mapping_map.contains_key(model) {
+ self.unmapped_models.push(ProviderModelPair {
+ provider: provider_name.to_string(),
+ model: model.clone(),
+ });
+ }
+ }
+
+ for (model, canonical) in &mapping_map {
+ self.canonical_models_used.insert(canonical.clone());
+ self.mapped_models.push(MappingEntry {
+ provider: provider_name.to_string(),
+ model: model.clone(),
+ canonical: canonical.clone(),
+ });
+ }
+
+ self.all_mappings
+ .insert(provider_name.to_string(), mappings);
+ self.model_counts
+ .insert(provider_name.to_string(), fetched_models.len());
+ }
+
+ fn print_summary(&self) {
+ println!("\n{}", "=".repeat(80));
+ println!("CANONICAL MODEL MAPPING REPORT");
+ println!("{}", "=".repeat(80));
+ println!("\nGenerated: {}\n", self.timestamp);
+
+ println!("Models Checked Per Provider:");
+ println!("{}", "-".repeat(80));
+ let mut providers: Vec<_> = self.model_counts.iter().collect();
+ providers.sort_by_key(|(name, _)| *name);
+ for (provider, count) in providers {
+ let mapped = self
+ .all_mappings
+ .get(provider)
+ .map(|m| m.len())
+ .unwrap_or(0);
+ let unmapped = count - mapped;
+ println!(
+ " {:<20} Total: {:>3} Mapped: {:>3} Unmapped: {:>3}",
+ provider, count, mapped, unmapped
+ );
+ }
+
+ println!("\n{}", "=".repeat(80));
+ println!("UNMAPPED MODELS ({})", self.unmapped_models.len());
+ println!("{}", "=".repeat(80));
+
+ if self.unmapped_models.is_empty() {
+ println!("β All models are mapped to canonical models!");
+ } else {
+ let mut unmapped_by_provider: HashMap<&str, Vec<&str>> = HashMap::new();
+ for pair in &self.unmapped_models {
+ unmapped_by_provider
+ .entry(pair.provider.as_str())
+ .or_default()
+ .push(pair.model.as_str());
+ }
+
+ let mut providers: Vec<_> = unmapped_by_provider.keys().collect();
+ providers.sort();
+
+ for provider in providers {
+ println!("\n{}:", provider);
+ let mut models = unmapped_by_provider[provider].to_vec();
+ models.sort();
+ for model in models {
+ println!(" - {}", model);
+ }
+ }
+ }
+
+ println!("\n{}", "=".repeat(80));
+ println!(
+ "CANONICAL MODELS REFERENCED ({})",
+ self.canonical_models_used.len()
+ );
+ println!("{}", "=".repeat(80));
+ if self.canonical_models_used.is_empty() {
+ println!(" (none yet)");
+ } else {
+ let mut canonical: Vec<_> = self.canonical_models_used.iter().collect();
+ canonical.sort();
+ for model in canonical {
+ println!(" - {}", model);
+ }
+ }
+
+ println!("\n{}", "=".repeat(80));
+ }
+
+ fn compare_with_previous(&self, previous: &MappingReport) {
+ println!("\n{}", "=".repeat(80));
+ println!("CHANGES SINCE PREVIOUS RUN");
+ println!("{}", "=".repeat(80));
+
+ let mut prev_map: HashMap<(String, String), String> = HashMap::new();
+ for entry in &previous.mapped_models {
+ prev_map.insert(
+ (entry.provider.clone(), entry.model.clone()),
+ entry.canonical.clone(),
+ );
+ }
+
+ let mut curr_map: HashMap<(String, String), String> = HashMap::new();
+ for entry in &self.mapped_models {
+ curr_map.insert(
+ (entry.provider.clone(), entry.model.clone()),
+ entry.canonical.clone(),
+ );
+ }
+
+ let mut changed_mappings = Vec::new();
+ let mut added_mappings = Vec::new();
+ let mut removed_mappings = Vec::new();
+
+ for (key @ (provider, model), canonical) in &curr_map {
+ match prev_map.get(key) {
+ Some(prev_canonical) if prev_canonical != canonical => {
+ changed_mappings.push((
+ provider.clone(),
+ model.clone(),
+ prev_canonical.clone(),
+ canonical.clone(),
+ ));
+ }
+ None => {
+ added_mappings.push((provider.clone(), model.clone(), canonical.clone()));
+ }
+ _ => {
+ // No change
+ }
+ }
+ }
+
+ for (key @ (provider, model), canonical) in &prev_map {
+ if !curr_map.contains_key(key) {
+ removed_mappings.push((provider.clone(), model.clone(), canonical.clone()));
+ }
+ }
+
+ if changed_mappings.is_empty() && added_mappings.is_empty() && removed_mappings.is_empty() {
+ println!("\nNo changes in model mappings.");
+ } else {
+ if !changed_mappings.is_empty() {
+ println!("\nβ Changed Mappings ({}):", changed_mappings.len());
+ println!(" (Models that now map to a different canonical model)");
+ for (provider, model, old_canonical, new_canonical) in changed_mappings {
+ println!(" {} / {}", provider, model);
+ println!(" WAS: {}", old_canonical);
+ println!(" NOW: {}", new_canonical);
+ }
+ }
+
+ if !added_mappings.is_empty() {
+ println!("\nβ Added Mappings ({}):", added_mappings.len());
+ println!(" (Models that gained a canonical mapping)");
+ for (provider, model, canonical) in added_mappings {
+ println!(" {} / {} -> {}", provider, model, canonical);
+ }
+ }
+
+ if !removed_mappings.is_empty() {
+ println!("\nβ Removed Mappings ({}):", removed_mappings.len());
+ println!(" (Models that lost their canonical mapping)");
+ for (provider, model, canonical) in removed_mappings {
+ println!(" {} / {} (was: {})", provider, model, canonical);
+ }
+ }
+ }
+
+ println!("\n{}", "=".repeat(80));
+ }
+
+ fn save_to_file(&self, path: &PathBuf) -> Result<()> {
+ let json = serde_json::to_string_pretty(self).context("Failed to serialize report")?;
+ std::fs::write(path, json).context("Failed to write report file")?;
+ Ok(())
+ }
+
+ fn load_from_file(path: &PathBuf) -> Result {
+ let content = std::fs::read_to_string(path).context("Failed to read report file")?;
+ let report: MappingReport =
+ serde_json::from_str(&content).context("Failed to parse report file")?;
+ Ok(report)
+ }
+}
+
+async fn check_provider(
+ provider_name: &str,
+ model_for_init: &str,
+) -> Result<(Vec, Vec)> {
+ println!("Checking provider: {}", provider_name);
+
+ let provider = match create_with_named_model(provider_name, model_for_init).await {
+ Ok(p) => p,
+ Err(e) => {
+ println!(" β Failed to create provider: {}", e);
+ println!(" This is expected if credentials are not configured.");
+ return Ok((Vec::new(), Vec::new()));
+ }
+ };
+
+ let fetched_models = match provider.fetch_supported_models().await {
+ Ok(Some(models)) => {
+ println!(" β Fetched {} models", models.len());
+ models
+ }
+ Ok(None) => {
+ println!(" β Provider does not support model listing");
+ Vec::new()
+ }
+ Err(e) => {
+ println!(" β Failed to fetch models: {}", e);
+ println!(" This is expected if credentials are not configured.");
+ Vec::new()
+ }
+ };
+
+ let mut mappings = Vec::new();
+ for model in &fetched_models {
+ match provider.map_to_canonical_model(model).await {
+ Ok(Some(canonical)) => {
+ mappings.push(ModelMapping::new(model.clone(), canonical));
+ }
+ Ok(None) => {
+ // No mapping found for this model
+ }
+ Err(e) => {
+ println!(" β Failed to map model '{}': {}", model, e);
+ }
+ }
+ }
+ println!(" β Found {} mappings", mappings.len());
+
+ Ok((fetched_models, mappings))
+}
+
+#[tokio::main]
+async fn main() -> Result<()> {
+ println!("Canonical Model Checker");
+ println!("Checking model mappings for top providers...\n");
+
+ // Define providers to check with their default models
+ let providers = vec![
+ ("anthropic", "claude-3-5-sonnet-20241022"),
+ ("openai", "gpt-4"),
+ ("openrouter", "anthropic/claude-3.5-sonnet"),
+ ("google", "gemini-1.5-pro-002"),
+ ("tetrate", "claude-3-5-sonnet-computer-use"),
+ ("xai", "grok-code-fast-1"),
+ ];
+
+ let mut report = MappingReport::new();
+
+ for (provider_name, default_model) in providers {
+ let (fetched, mappings) = check_provider(provider_name, default_model).await?;
+ report.add_provider_results(provider_name, fetched, mappings);
+ println!();
+ }
+
+ report.print_summary();
+
+ let args: Vec = std::env::args().collect();
+ let output_path = if args.len() > 2 && args[1] == "--output" {
+ PathBuf::from(&args[2])
+ } else {
+ PathBuf::from(env!("CARGO_MANIFEST_DIR"))
+ .join("src/providers/canonical/data/canonical_mapping_report.json")
+ };
+
+ if output_path.exists() {
+ if let Ok(previous) = MappingReport::load_from_file(&output_path) {
+ report.compare_with_previous(&previous);
+ }
+ }
+
+ report.save_to_file(&output_path)?;
+ println!("\nβ Report saved to: {}", output_path.display());
+
+ Ok(())
+}
diff --git a/crates/goose/examples/image_tool.rs b/crates/goose/examples/image_tool.rs
index 517bebd92d..f70e0a0073 100644
--- a/crates/goose/examples/image_tool.rs
+++ b/crates/goose/examples/image_tool.rs
@@ -37,8 +37,15 @@ async fn main() -> Result<()> {
arguments: Some(object!({"path": "./test_image.png"})),
}),
),
- Message::user()
- .with_tool_response("000", Ok(vec![Content::image(base64_image, "image/png")])),
+ Message::user().with_tool_response(
+ "000",
+ Ok(rmcp::model::CallToolResult {
+ content: vec![Content::image(base64_image, "image/png")],
+ structured_content: None,
+ is_error: Some(false),
+ meta: None,
+ }),
+ ),
];
// Get a response from the model about the image
diff --git a/crates/goose/src/agents/agent.rs b/crates/goose/src/agents/agent.rs
index be04c7100d..edfe875bc7 100644
--- a/crates/goose/src/agents/agent.rs
+++ b/crates/goose/src/agents/agent.rs
@@ -58,7 +58,7 @@ use crate::tool_monitor::RepetitionInspector;
use crate::utils::is_token_cancelled;
use regex::Regex;
use rmcp::model::{
- CallToolRequestParam, Content, ErrorCode, ErrorData, GetPromptResult, Prompt,
+ CallToolRequestParam, CallToolResult, Content, ErrorCode, ErrorData, GetPromptResult, Prompt,
ServerNotification, Tool,
};
use serde_json::Value;
@@ -100,7 +100,7 @@ pub struct Agent {
pub(super) prompt_manager: Mutex,
pub(super) confirmation_tx: mpsc::Sender<(String, PermissionConfirmation)>,
pub(super) confirmation_rx: Mutex>,
- pub(super) tool_result_tx: mpsc::Sender<(String, ToolResult>)>,
+ pub(super) tool_result_tx: mpsc::Sender<(String, ToolResult)>,
pub(super) tool_result_rx: ToolResultReceiver,
pub tool_route_manager: Arc,
@@ -128,7 +128,8 @@ pub enum ToolStreamItem {
Result(T),
}
-pub type ToolStream = Pin>>> + Send>>;
+pub type ToolStream =
+ Pin>> + Send>>;
// tool_stream combines a stream of ServerNotifications with a future representing the
// final result of the tool call. MCP notifications are not request-scoped, but
@@ -137,7 +138,7 @@ pub type ToolStream = Pin(rx: S, done: F) -> ToolStream
where
S: Stream- + Send + Unpin + 'static,
- F: Future
>> + Send + 'static,
+ F: Future> + Send + 'static,
{
Box::pin(async_stream::stream! {
tokio::pin!(done);
@@ -360,7 +361,12 @@ impl Agent {
let mut response = response_msg.lock().await;
*response = response.clone().with_tool_response(
request.id.clone(),
- Ok(vec![rmcp::model::Content::text(DECLINED_RESPONSE)]),
+ Ok(CallToolResult {
+ content: vec![rmcp::model::Content::text(DECLINED_RESPONSE)],
+ structured_content: None,
+ is_error: Some(true),
+ meta: None,
+ }),
);
}
}
@@ -454,7 +460,13 @@ impl Agent {
let result = self
.handle_schedule_management(arguments, request_id.clone())
.await;
- return (request_id, Ok(ToolCallResult::from(result)));
+ let wrapped_result = result.map(|content| CallToolResult {
+ content,
+ structured_content: None,
+ is_error: Some(false),
+ meta: None,
+ });
+ return (request_id, Ok(ToolCallResult::from(wrapped_result)));
}
if tool_call.name == FINAL_OUTPUT_TOOL_NAME {
@@ -1090,7 +1102,12 @@ impl Agent {
let mut response = response_msg.lock().await;
*response = response.clone().with_tool_response(
request.id.clone(),
- Ok(vec![Content::text(CHAT_MODE_TOOL_SKIPPED_RESPONSE)]),
+ Ok(CallToolResult {
+ content: vec![Content::text(CHAT_MODE_TOOL_SKIPPED_RESPONSE)],
+ structured_content: None,
+ is_error: Some(false),
+ meta: None,
+ }),
);
}
}
@@ -1221,7 +1238,8 @@ impl Agent {
no_tools_called = false;
}
}
- Err(ProviderError::ContextLengthExceeded(_error_msg)) => {
+ Err(ref provider_err @ ProviderError::ContextLengthExceeded(_)) => {
+ crate::posthog::emit_error(provider_err.telemetry_type());
yield AgentEvent::Message(
Message::assistant().with_system_notification(
SystemNotificationType::InlineMessage,
@@ -1255,11 +1273,12 @@ impl Agent {
}
}
}
- Err(e) => {
- error!("Error: {}", e);
+ Err(ref provider_err) => {
+ crate::posthog::emit_error(provider_err.telemetry_type());
+ error!("Error: {}", provider_err);
yield AgentEvent::Message(
Message::assistant().with_text(
- format!("Ran into this error: {e}.\n\nPlease retry if you think this is a transient or recoverable error.")
+ format!("Ran into this error: {provider_err}.\n\nPlease retry if you think this is a transient or recoverable error.")
)
);
break;
@@ -1419,7 +1438,7 @@ impl Agent {
Ok(plan_prompt)
}
- pub async fn handle_tool_result(&self, id: String, result: ToolResult>) {
+ pub async fn handle_tool_result(&self, id: String, result: ToolResult) {
if let Err(e) = self.tool_result_tx.send((id, result)).await {
error!("Failed to send tool result: {}", e);
}
diff --git a/crates/goose/src/agents/extension_manager.rs b/crates/goose/src/agents/extension_manager.rs
index 4384e9841e..2af90d66cb 100644
--- a/crates/goose/src/agents/extension_manager.rs
+++ b/crates/goose/src/agents/extension_manager.rs
@@ -41,8 +41,8 @@ use crate::oauth::oauth_flow;
use crate::prompt_template;
use crate::subprocess::configure_command_no_window;
use rmcp::model::{
- CallToolRequestParam, Content, ErrorCode, ErrorData, GetPromptResult, Prompt, ResourceContents,
- ServerInfo, Tool,
+ CallToolRequestParam, Content, ErrorCode, ErrorData, GetPromptResult, Prompt, RawContent,
+ Resource, ResourceContents, ServerInfo, Tool,
};
use rmcp::transport::auth::AuthClient;
use schemars::_private::NoSerialize;
@@ -758,6 +758,7 @@ impl ExtensionManager {
uri,
extension_name.unwrap(),
cancellation_token.clone(),
+ true,
)
.await?;
return Ok(result);
@@ -773,7 +774,12 @@ impl ExtensionManager {
for extension_name in extension_names {
let result = self
- .read_resource_from_extension(uri, &extension_name, cancellation_token.clone())
+ .read_resource_from_extension(
+ uri,
+ &extension_name,
+ cancellation_token.clone(),
+ true,
+ )
.await;
match result {
Ok(result) => return Ok(result),
@@ -807,6 +813,7 @@ impl ExtensionManager {
uri: &str,
extension_name: &str,
cancellation_token: CancellationToken,
+ format_with_uri: bool,
) -> Result, ErrorData> {
let available_extensions = self
.extensions
@@ -840,9 +847,12 @@ impl ExtensionManager {
let mut result = Vec::new();
for content in read_result.contents {
- // Only reading the text resource content; skipping the blob content cause it's too long
if let ResourceContents::TextResourceContents { text, .. } = content {
- let content_str = format!("{}\n\n{}", uri, text);
+ let content_str = if format_with_uri {
+ format!("{}\n\n{}", uri, text)
+ } else {
+ text
+ };
result.push(Content::text(content_str));
}
}
@@ -850,6 +860,65 @@ impl ExtensionManager {
Ok(result)
}
+ pub async fn get_ui_resources(&self) -> Result, ErrorData> {
+ let mut ui_resources = Vec::new();
+
+ let extensions_to_check: Vec<(String, McpClientBox)> = {
+ let extensions = self.extensions.lock().await;
+ extensions
+ .iter()
+ .map(|(name, ext)| (name.clone(), ext.get_client()))
+ .collect()
+ };
+
+ for (extension_name, client) in extensions_to_check {
+ let client_guard = client.lock().await;
+
+ match client_guard
+ .list_resources(None, CancellationToken::default())
+ .await
+ {
+ Ok(list_response) => {
+ for resource in list_response.resources {
+ if resource.uri.starts_with("ui://") {
+ ui_resources.push((extension_name.clone(), resource));
+ }
+ }
+ }
+ Err(e) => {
+ warn!("Failed to list resources for {}: {:?}", extension_name, e);
+ }
+ }
+ }
+
+ Ok(ui_resources)
+ }
+
+ pub async fn read_ui_resource(
+ &self,
+ uri: &str,
+ extension_name: &str,
+ cancellation_token: CancellationToken,
+ ) -> Result {
+ let contents = self
+ .read_resource_from_extension(uri, extension_name, cancellation_token, false)
+ .await?;
+
+ contents
+ .into_iter()
+ .find_map(|c| match c.raw {
+ RawContent::Text(text_content) => Some(text_content.text),
+ _ => None,
+ })
+ .ok_or_else(|| {
+ ErrorData::new(
+ ErrorCode::RESOURCE_NOT_FOUND,
+ format!("No text content in resource '{}'", uri),
+ None,
+ )
+ })
+ }
+
async fn list_resources_from_extension(
&self,
extension_name: &str,
@@ -936,7 +1005,6 @@ impl ExtensionManager {
}
}
- // Log any errors that occurred
if !errors.is_empty() {
tracing::error!(
errors = ?errors
@@ -998,7 +1066,6 @@ impl ExtensionManager {
client_guard
.call_tool(&tool_name, arguments, cancellation_token)
.await
- .map(|call| call.content)
.map_err(|e| match e {
ServiceError::McpError(error_data) => error_data,
_ => {
@@ -1077,7 +1144,6 @@ impl ExtensionManager {
}
}
- // Log any errors that occurred
if !errors.is_empty() {
tracing::debug!(
errors = ?errors
diff --git a/crates/goose/src/agents/final_output_tool.rs b/crates/goose/src/agents/final_output_tool.rs
index 439cde5157..0adea4db4a 100644
--- a/crates/goose/src/agents/final_output_tool.rs
+++ b/crates/goose/src/agents/final_output_tool.rs
@@ -123,9 +123,14 @@ impl FinalOutputTool {
match result {
Ok(parsed_value) => {
self.final_output = Some(Self::parsed_final_output_string(parsed_value));
- ToolCallResult::from(Ok(vec![Content::text(
- "Final output successfully collected.".to_string(),
- )]))
+ ToolCallResult::from(Ok(rmcp::model::CallToolResult {
+ content: vec![Content::text(
+ "Final output successfully collected.".to_string(),
+ )],
+ structured_content: None,
+ is_error: Some(false),
+ meta: None,
+ }))
}
Err(error) => ToolCallResult::from(Err(ErrorData {
code: ErrorCode::INVALID_PARAMS,
diff --git a/crates/goose/src/agents/large_response_handler.rs b/crates/goose/src/agents/large_response_handler.rs
index f45dc01abc..e1554ed4e2 100644
--- a/crates/goose/src/agents/large_response_handler.rs
+++ b/crates/goose/src/agents/large_response_handler.rs
@@ -1,5 +1,5 @@
use chrono::Utc;
-use rmcp::model::{Content, ErrorData};
+use rmcp::model::{CallToolResult, Content, ErrorData};
use std::fs::File;
use std::io::Write;
@@ -7,13 +7,13 @@ const LARGE_TEXT_THRESHOLD: usize = 200_000;
/// Process tool response and handle large text content
pub fn process_tool_response(
- response: Result, ErrorData>,
-) -> Result, ErrorData> {
+ response: Result,
+) -> Result {
match response {
- Ok(contents) => {
+ Ok(mut result) => {
let mut processed_contents = Vec::new();
- for content in contents {
+ for content in result.content {
match content.as_text() {
Some(text_content) => {
// Check if text exceeds threshold
@@ -51,7 +51,8 @@ pub fn process_tool_response(
}
}
- Ok(processed_contents)
+ result.content = processed_contents;
+ Ok(result)
}
Err(e) => Err(e),
}
@@ -89,14 +90,19 @@ mod tests {
let small_text = "This is a small text response";
let content = Content::text(small_text.to_string());
- let response = Ok(vec![content]);
+ let response = Ok(CallToolResult {
+ content: vec![content],
+ structured_content: None,
+ is_error: Some(false),
+ meta: None,
+ });
// Process the response
let processed = process_tool_response(response).unwrap();
// Verify the response is unchanged
- assert_eq!(processed.len(), 1);
- if let Some(text_content) = processed[0].as_text() {
+ assert_eq!(processed.content.len(), 1);
+ if let Some(text_content) = processed.content[0].as_text() {
assert_eq!(text_content.text, small_text);
} else {
panic!("Expected text content");
@@ -109,14 +115,19 @@ mod tests {
let large_text = "a".repeat(LARGE_TEXT_THRESHOLD + 1000);
let content = Content::text(large_text.clone());
- let response = Ok(vec![content]);
+ let response = Ok(CallToolResult {
+ content: vec![content],
+ structured_content: None,
+ is_error: Some(false),
+ meta: None,
+ });
// Process the response
let processed = process_tool_response(response).unwrap();
// Verify the response contains a message about the file
- assert_eq!(processed.len(), 1);
- if let Some(text_content) = processed[0].as_text() {
+ assert_eq!(processed.content.len(), 1);
+ if let Some(text_content) = processed.content[0].as_text() {
assert!(text_content
.text
.contains("The response returned from the tool call was larger"));
@@ -146,14 +157,19 @@ mod tests {
// Create an image content
let image_content = Content::image("base64data".to_string(), "image/png".to_string());
- let response = Ok(vec![image_content]);
+ let response = Ok(CallToolResult {
+ content: vec![image_content],
+ structured_content: None,
+ is_error: Some(false),
+ meta: None,
+ });
// Process the response
let processed = process_tool_response(response).unwrap();
// Verify the response is unchanged
- assert_eq!(processed.len(), 1);
- if let Some(img) = processed[0].as_image() {
+ assert_eq!(processed.content.len(), 1);
+ if let Some(img) = processed.content[0].as_image() {
assert_eq!(img.data, "base64data");
assert_eq!(img.mime_type, "image/png");
} else {
@@ -168,23 +184,28 @@ mod tests {
let large_text = Content::text("a".repeat(LARGE_TEXT_THRESHOLD + 1000));
let image = Content::image("image_data".to_string(), "image/jpeg".to_string());
- let response = Ok(vec![small_text, large_text, image]);
+ let response = Ok(CallToolResult {
+ content: vec![small_text, large_text, image],
+ structured_content: None,
+ is_error: Some(false),
+ meta: None,
+ });
// Process the response
let processed = process_tool_response(response).unwrap();
// Verify each item is handled correctly
- assert_eq!(processed.len(), 3);
+ assert_eq!(processed.content.len(), 3);
// First item should be unchanged small text
- if let Some(text_content) = processed[0].as_text() {
+ if let Some(text_content) = processed.content[0].as_text() {
assert_eq!(text_content.text, "Small text");
} else {
panic!("Expected text content");
}
// Second item should be a message about the file
- if let Some(text_content) = processed[1].as_text() {
+ if let Some(text_content) = processed.content[1].as_text() {
assert!(text_content
.text
.contains("The response returned from the tool call was larger"));
@@ -201,7 +222,7 @@ mod tests {
}
// Third item should be unchanged image
- if let Some(img) = processed[2].as_image() {
+ if let Some(img) = processed.content[2].as_image() {
assert_eq!(img.data, "image_data");
assert_eq!(img.mime_type, "image/jpeg");
} else {
@@ -217,7 +238,7 @@ mod tests {
message: Cow::from("Test error"),
data: None,
};
- let response: Result, ErrorData> = Err(error);
+ let response: Result = Err(error);
// Process the response
let processed = process_tool_response(response);
diff --git a/crates/goose/src/agents/moim.rs b/crates/goose/src/agents/moim.rs
index d920f3b7dc..97f273d524 100644
--- a/crates/goose/src/agents/moim.rs
+++ b/crates/goose/src/agents/moim.rs
@@ -106,7 +106,15 @@ mod tests {
arguments: None,
}),
),
- Message::user().with_tool_response("search_1", Ok(vec![])),
+ Message::user().with_tool_response(
+ "search_1",
+ Ok(rmcp::model::CallToolResult {
+ content: vec![],
+ structured_content: None,
+ is_error: Some(false),
+ meta: None,
+ }),
+ ),
Message::assistant()
.with_text("I need to search more")
.with_tool_request(
@@ -116,7 +124,15 @@ mod tests {
arguments: None,
}),
),
- Message::user().with_tool_response("search_2", Ok(vec![])),
+ Message::user().with_tool_response(
+ "search_2",
+ Ok(rmcp::model::CallToolResult {
+ content: vec![],
+ structured_content: None,
+ is_error: Some(false),
+ meta: None,
+ }),
+ ),
]);
let result = inject_moim(conv, &em).await;
diff --git a/crates/goose/src/agents/recipe_tools/dynamic_task_tools.rs b/crates/goose/src/agents/recipe_tools/dynamic_task_tools.rs
index d0dd14a6e9..66729d04dd 100644
--- a/crates/goose/src/agents/recipe_tools/dynamic_task_tools.rs
+++ b/crates/goose/src/agents/recipe_tools/dynamic_task_tools.rs
@@ -370,5 +370,10 @@ pub async fn create_dynamic_task(
};
tasks_manager.save_tasks(tasks).await;
- ToolCallResult::from(Ok(vec![Content::text(tasks_json)]))
+ ToolCallResult::from(Ok(rmcp::model::CallToolResult {
+ content: vec![Content::text(tasks_json)],
+ structured_content: None,
+ is_error: Some(false),
+ meta: None,
+ }))
}
diff --git a/crates/goose/src/agents/sub_recipe_manager.rs b/crates/goose/src/agents/sub_recipe_manager.rs
index c8603a0090..6e7d8820bb 100644
--- a/crates/goose/src/agents/sub_recipe_manager.rs
+++ b/crates/goose/src/agents/sub_recipe_manager.rs
@@ -63,7 +63,12 @@ impl SubRecipeManager {
.call_sub_recipe_tool(tool_name, params, tasks_manager, parent_working_dir)
.await;
match result {
- Ok(call_result) => ToolCallResult::from(Ok(call_result)),
+ Ok(content) => ToolCallResult::from(Ok(rmcp::model::CallToolResult {
+ content,
+ structured_content: None,
+ is_error: Some(false),
+ meta: None,
+ })),
Err(e) => ToolCallResult::from(Err(ErrorData {
code: ErrorCode::INTERNAL_ERROR,
message: Cow::from(e.to_string()),
diff --git a/crates/goose/src/agents/subagent_execution_tool/subagent_execute_task_tool.rs b/crates/goose/src/agents/subagent_execution_tool/subagent_execute_task_tool.rs
index b70f71d3f2..2fa463a4da 100644
--- a/crates/goose/src/agents/subagent_execution_tool/subagent_execute_task_tool.rs
+++ b/crates/goose/src/agents/subagent_execution_tool/subagent_execute_task_tool.rs
@@ -82,7 +82,12 @@ pub async fn run_tasks(
{
Ok(result) => {
let output = serde_json::to_string(&result).unwrap();
- Ok(vec![Content::text(output)])
+ Ok(rmcp::model::CallToolResult {
+ content: vec![Content::text(output)],
+ structured_content: None,
+ is_error: Some(false),
+ meta: None,
+ })
}
Err(e) => Err(ErrorData {
code: ErrorCode::INTERNAL_ERROR,
diff --git a/crates/goose/src/agents/subagent_handler.rs b/crates/goose/src/agents/subagent_handler.rs
index 2d6f7f8167..cd0834a12a 100644
--- a/crates/goose/src/agents/subagent_handler.rs
+++ b/crates/goose/src/agents/subagent_handler.rs
@@ -61,8 +61,9 @@ pub async fn run_complete_subagent_task(
tool_response,
) => {
// Extract text from tool response
- if let Ok(contents) = &tool_response.tool_result {
- let texts: Vec = contents
+ if let Ok(result) = &tool_response.tool_result {
+ let texts: Vec = result
+ .content
.iter()
.filter_map(|content| {
if let rmcp::model::RawContent::Text(raw_text_content) =
diff --git a/crates/goose/src/agents/tool_execution.rs b/crates/goose/src/agents/tool_execution.rs
index f7fdfa914a..144d3d11ff 100644
--- a/crates/goose/src/agents/tool_execution.rs
+++ b/crates/goose/src/agents/tool_execution.rs
@@ -16,12 +16,12 @@ use rmcp::model::{Content, ServerNotification};
// ToolCallResult combines the result of a tool call with an optional notification stream that
// can be used to receive notifications from the tool.
pub struct ToolCallResult {
- pub result: Box>> + Send + Unpin>,
+ pub result: Box> + Send + Unpin>,
pub notification_stream: Option + Send + Unpin>>,
}
-impl From>> for ToolCallResult {
- fn from(result: ToolResult>) -> Self {
+impl From> for ToolCallResult {
+ fn from(result: ToolResult) -> Self {
Self {
result: Box::new(futures::future::ready(result)),
notification_stream: None,
@@ -122,7 +122,12 @@ impl Agent {
let mut response = response_msg.lock().await;
*response = response.clone().with_tool_response(
request.id.clone(),
- Ok(vec![Content::text(DECLINED_RESPONSE)]),
+ Ok(rmcp::model::CallToolResult {
+ content: vec![Content::text(DECLINED_RESPONSE)],
+ structured_content: None,
+ is_error: Some(true),
+ meta: None,
+ }),
);
}
}
diff --git a/crates/goose/src/agents/tool_route_manager.rs b/crates/goose/src/agents/tool_route_manager.rs
index 6708060702..c757a173ae 100644
--- a/crates/goose/src/agents/tool_route_manager.rs
+++ b/crates/goose/src/agents/tool_route_manager.rs
@@ -38,9 +38,9 @@ impl ToolRouteManager {
pub async fn record_tool_requests(&self, requests: &[ToolRequest]) {
let selector = self.router_tool_selector.lock().await.clone();
- if let Some(selector) = selector {
- for request in requests {
- if let Ok(tool_call) = &request.tool_call {
+ for request in requests {
+ if let Ok(tool_call) = &request.tool_call {
+ if let Some(ref selector) = selector {
if let Err(e) = selector.record_tool_call(&tool_call.name).await {
error!("Failed to record tool call: {}", e);
}
@@ -56,7 +56,12 @@ impl ToolRouteManager {
let selector = self.router_tool_selector.lock().await.clone();
match selector.as_ref() {
Some(selector) => match selector.select_tools(arguments).await {
- Ok(tools) => Ok(ToolCallResult::from(Ok(tools))),
+ Ok(content) => Ok(ToolCallResult::from(Ok(rmcp::model::CallToolResult {
+ content,
+ structured_content: None,
+ is_error: Some(false),
+ meta: None,
+ }))),
Err(e) => Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!("Failed to select tools: {}", e),
diff --git a/crates/goose/src/agents/types.rs b/crates/goose/src/agents/types.rs
index 0f2902bf9e..13138a7fb7 100644
--- a/crates/goose/src/agents/types.rs
+++ b/crates/goose/src/agents/types.rs
@@ -1,13 +1,13 @@
use crate::mcp_utils::ToolResult;
use crate::providers::base::Provider;
-use rmcp::model::{Content, Tool};
+use rmcp::model::{CallToolResult, Tool};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tokio::sync::{mpsc, Mutex};
use utoipa::ToSchema;
/// Type alias for the tool result channel receiver
-pub type ToolResultReceiver = Arc>)>>>;
+pub type ToolResultReceiver = Arc)>>>;
// We use double Arc here to allow easy provider swaps while sharing concurrent access
pub type SharedProvider = Arc>>>;
diff --git a/crates/goose/src/context_mgmt/mod.rs b/crates/goose/src/context_mgmt/mod.rs
index da682a9d77..4e312a48fa 100644
--- a/crates/goose/src/context_mgmt/mod.rs
+++ b/crates/goose/src/context_mgmt/mod.rs
@@ -355,8 +355,9 @@ fn format_message_for_compacting(msg: &Message) -> String {
}
}
MessageContent::ToolResponse(res) => {
- if let Ok(contents) = &res.tool_result {
- let text_items: Vec = contents
+ if let Ok(result) = &res.tool_result {
+ let text_items: Vec = result
+ .content
.iter()
.filter_map(|content| {
content.as_text().map(|text_str| text_str.text.clone())
@@ -517,7 +518,12 @@ mod tests {
),
Message::user().with_tool_response(
"tool_0",
- Ok(vec![RawContent::text("hello, world").no_annotation()]),
+ Ok(rmcp::model::CallToolResult {
+ content: vec![RawContent::text("hello, world").no_annotation()],
+ structured_content: None,
+ is_error: Some(false),
+ meta: None,
+ }),
),
];
@@ -550,9 +556,12 @@ mod tests {
));
messages.push(Message::user().with_tool_response(
format!("tool_{}", i),
- Ok(vec![
- RawContent::text(format!("response{}", i)).no_annotation(),
- ]),
+ Ok(rmcp::model::CallToolResult {
+ content: vec![RawContent::text(format!("response{}", i)).no_annotation()],
+ structured_content: None,
+ is_error: Some(false),
+ meta: None,
+ }),
));
}
diff --git a/crates/goose/src/conversation/message.rs b/crates/goose/src/conversation/message.rs
index 19193f4a6c..0651ecb5dc 100644
--- a/crates/goose/src/conversation/message.rs
+++ b/crates/goose/src/conversation/message.rs
@@ -1,9 +1,9 @@
use crate::mcp_utils::ToolResult;
use chrono::Utc;
use rmcp::model::{
- AnnotateAble, CallToolRequestParam, Content, ImageContent, JsonObject, PromptMessage,
- PromptMessageContent, PromptMessageRole, RawContent, RawImageContent, RawTextContent,
- ResourceContents, Role, TextContent,
+ AnnotateAble, CallToolRequestParam, CallToolResult, Content, ImageContent, JsonObject,
+ PromptMessage, PromptMessageContent, PromptMessageRole, RawContent, RawImageContent,
+ RawTextContent, ResourceContents, Role, TextContent,
};
use serde::{Deserialize, Deserializer, Serialize};
use std::collections::HashSet;
@@ -86,9 +86,9 @@ impl ToolRequest {
#[derive(ToSchema)]
pub struct ToolResponse {
pub id: String,
- #[serde(with = "tool_result_serde")]
+ #[serde(with = "tool_result_serde::call_tool_result")]
#[schema(value_type = Object)]
- pub tool_result: ToolResult>,
+ pub tool_result: ToolResult,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
@@ -190,7 +190,7 @@ impl fmt::Display for MessageContent {
f,
"[ToolResponse: {}]",
match &r.tool_result {
- Ok(contents) => format!("{} content item(s)", contents.len()),
+ Ok(result) => format!("{} content item(s)", result.content.len()),
Err(e) => format!("Error: {e}"),
}
),
@@ -266,7 +266,7 @@ impl MessageContent {
})
}
- pub fn tool_response>(id: S, tool_result: ToolResult>) -> Self {
+ pub fn tool_response>(id: S, tool_result: ToolResult) -> Self {
MessageContent::ToolResponse(ToolResponse {
id: id.into(),
tool_result,
@@ -380,8 +380,9 @@ impl MessageContent {
pub fn as_tool_response_text(&self) -> Option {
if let Some(tool_response) = self.as_tool_response() {
- if let Ok(contents) = &tool_response.tool_result {
- let texts: Vec = contents
+ if let Ok(result) = &tool_response.tool_result {
+ let texts: Vec = result
+ .content
.iter()
.filter_map(|content| content.as_text().map(|t| t.text.to_string()))
.collect();
@@ -644,7 +645,7 @@ impl Message {
pub fn with_tool_response>(
self,
id: S,
- result: ToolResult>,
+ result: ToolResult,
) -> Self {
self.with_content(MessageContent::tool_response(id, result))
}
@@ -1302,4 +1303,87 @@ mod tests {
assert!(metadata.user_visible);
assert!(metadata.agent_visible);
}
+
+ #[test]
+ fn test_legacy_tool_response_deserialization() {
+ let legacy_json = r#"{
+ "role": "user",
+ "created": 1640995200,
+ "content": [{
+ "type": "toolResponse",
+ "id": "tool123",
+ "toolResult": {
+ "status": "success",
+ "value": [
+ {
+ "type": "text",
+ "text": "Tool output text"
+ }
+ ]
+ }
+ }],
+ "metadata": { "agentVisible": true, "userVisible": true }
+ }"#;
+
+ let message: Message = serde_json::from_str(legacy_json).unwrap();
+ assert_eq!(message.content.len(), 1);
+
+ if let MessageContent::ToolResponse(response) = &message.content[0] {
+ assert_eq!(response.id, "tool123");
+ if let Ok(result) = &response.tool_result {
+ assert_eq!(result.content.len(), 1);
+ assert_eq!(
+ result.content[0].as_text().unwrap().text,
+ "Tool output text"
+ );
+ } else {
+ panic!("Expected successful tool result");
+ }
+ } else {
+ panic!("Expected ToolResponse content");
+ }
+ }
+
+ #[test]
+ fn test_new_tool_response_deserialization() {
+ let new_json = r#"{
+ "role": "user",
+ "created": 1640995200,
+ "content": [{
+ "type": "toolResponse",
+ "id": "tool456",
+ "toolResult": {
+ "status": "success",
+ "value": {
+ "content": [
+ {
+ "type": "text",
+ "text": "New format output"
+ }
+ ],
+ "isError": false
+ }
+ }
+ }],
+ "metadata": { "agentVisible": true, "userVisible": true }
+ }"#;
+
+ let message: Message = serde_json::from_str(new_json).unwrap();
+ assert_eq!(message.content.len(), 1);
+
+ if let MessageContent::ToolResponse(response) = &message.content[0] {
+ assert_eq!(response.id, "tool456");
+ if let Ok(result) = &response.tool_result {
+ assert_eq!(result.content.len(), 1);
+ assert_eq!(
+ result.content[0].as_text().unwrap().text,
+ "New format output"
+ );
+ } else {
+ panic!("Expected successful tool result");
+ }
+ } else {
+ panic!("Expected ToolResponse content");
+ }
+ }
}
diff --git a/crates/goose/src/conversation/mod.rs b/crates/goose/src/conversation/mod.rs
index 14d9e5c281..524fbce39d 100644
--- a/crates/goose/src/conversation/mod.rs
+++ b/crates/goose/src/conversation/mod.rs
@@ -555,7 +555,15 @@ mod tests {
arguments: Some(object!({"query": "rust programming"})),
}),
),
- Message::user().with_tool_response("search_1", Ok(vec![])),
+ Message::user().with_tool_response(
+ "search_1",
+ Ok(rmcp::model::CallToolResult {
+ content: vec![],
+ structured_content: None,
+ is_error: Some(false),
+ meta: None,
+ }),
+ ),
Message::assistant().with_text("Based on the search results, here's what I found..."),
];
@@ -592,7 +600,15 @@ mod tests {
Message::user().with_text("Another user message"),
Message::assistant()
.with_text("Response")
- .with_tool_response("orphan_1", Ok(vec![])), // Wrong role
+ .with_tool_response(
+ "orphan_1",
+ Ok(rmcp::model::CallToolResult {
+ content: vec![],
+ structured_content: None,
+ is_error: Some(false),
+ meta: None,
+ }),
+ ), // Wrong role
Message::assistant().with_thinking("Let me think", "sig"),
Message::user()
.with_tool_request(
@@ -642,7 +658,15 @@ mod tests {
}),
),
Message::user(),
- Message::user().with_tool_response("wrong_id", Ok(vec![])),
+ Message::user().with_tool_response(
+ "wrong_id",
+ Ok(rmcp::model::CallToolResult {
+ content: vec![],
+ structured_content: None,
+ is_error: Some(false),
+ meta: None,
+ }),
+ ),
Message::assistant().with_tool_request(
"search_2",
Ok(CallToolRequestParam {
@@ -687,7 +711,12 @@ mod tests {
.with_tool_request("toolu_bdrk_01KgDYHs4fAodi22NqxRzmwx", Ok(CallToolRequestParam { name: "developer__shell".into(), arguments: Some(object!({"command": "wc slack.yaml"})) })),
Message::user()
- .with_tool_response("toolu_bdrk_01KgDYHs4fAodi22NqxRzmwx", Ok(vec![])),
+ .with_tool_response("toolu_bdrk_01KgDYHs4fAodi22NqxRzmwx", Ok(rmcp::model::CallToolResult {
+ content: vec![],
+ structured_content: None,
+ is_error: Some(false),
+ meta: None,
+ })),
Message::assistant()
.with_text("I ran `ls -la` in the current directory and found several files. Looking at the file sizes, I can see that both `slack.yaml` and `subrecipes.yaml` are 0 bytes (the smallest files). I ran a word count on `slack.yaml` which shows: **0 lines**, **0 words**, **0 characters**"),
@@ -718,7 +747,15 @@ mod tests {
arguments: Some(object!({})),
}),
),
- Message::user().with_tool_response("search_1", Ok(vec![])),
+ Message::user().with_tool_response(
+ "search_1",
+ Ok(rmcp::model::CallToolResult {
+ content: vec![],
+ structured_content: None,
+ is_error: Some(false),
+ meta: None,
+ }),
+ ),
Message::user().with_text("Thanks!"),
];
diff --git a/crates/goose/src/conversation/tool_result_serde.rs b/crates/goose/src/conversation/tool_result_serde.rs
index abbf9d6123..ef359e2801 100644
--- a/crates/goose/src/conversation/tool_result_serde.rs
+++ b/crates/goose/src/conversation/tool_result_serde.rs
@@ -25,13 +25,11 @@ where
}
}
-// For deserialization, let's use a simpler approach that works with the format we're serializing to
pub fn deserialize<'de, T, D>(deserializer: D) -> Result, D::Error>
where
T: Deserialize<'de>,
D: Deserializer<'de>,
{
- // Define a helper enum to handle the two possible formats
#[derive(Deserialize)]
#[serde(untagged)]
enum ResultFormat {
@@ -68,3 +66,79 @@ where
}
}
}
+
+pub mod call_tool_result {
+ use super::*;
+ use rmcp::model::{CallToolResult, Content};
+
+ pub fn serialize(
+ value: &ToolResult,
+ serializer: S,
+ ) -> Result
+ where
+ S: Serializer,
+ {
+ super::serialize(value, serializer)
+ }
+
+ pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error>
+ where
+ D: Deserializer<'de>,
+ {
+ #[derive(Deserialize)]
+ #[serde(untagged)]
+ enum ResultFormat {
+ NewSuccess {
+ status: String,
+ value: CallToolResult,
+ },
+ LegacySuccess {
+ status: String,
+ value: Vec,
+ },
+ Error {
+ status: String,
+ error: String,
+ },
+ }
+
+ let format = ResultFormat::deserialize(deserializer)?;
+
+ match format {
+ ResultFormat::NewSuccess { status, value } => {
+ if status == "success" {
+ Ok(Ok(value))
+ } else {
+ Err(serde::de::Error::custom(format!(
+ "Expected status 'success', got '{}'",
+ status
+ )))
+ }
+ }
+ ResultFormat::LegacySuccess { status, value } => {
+ if status == "success" {
+ Ok(Ok(CallToolResult::success(value)))
+ } else {
+ Err(serde::de::Error::custom(format!(
+ "Expected status 'success', got '{}'",
+ status
+ )))
+ }
+ }
+ ResultFormat::Error { status, error } => {
+ if status == "error" {
+ Ok(Err(ErrorData {
+ code: ErrorCode::INTERNAL_ERROR,
+ message: Cow::from(error),
+ data: None,
+ }))
+ } else {
+ Err(serde::de::Error::custom(format!(
+ "Expected status 'error', got '{}'",
+ status
+ )))
+ }
+ }
+ }
+ }
+}
diff --git a/crates/goose/src/lib.rs b/crates/goose/src/lib.rs
index 9c21c95dce..5e88df2373 100644
--- a/crates/goose/src/lib.rs
+++ b/crates/goose/src/lib.rs
@@ -10,6 +10,7 @@ pub mod mcp_utils;
pub mod model;
pub mod oauth;
pub mod permission;
+pub mod posthog;
pub mod prompt_template;
pub mod providers;
pub mod recipe;
diff --git a/crates/goose/src/permission/permission_store.rs b/crates/goose/src/permission/permission_store.rs
index 4dee06ff8a..fded07081c 100644
--- a/crates/goose/src/permission/permission_store.rs
+++ b/crates/goose/src/permission/permission_store.rs
@@ -14,7 +14,7 @@ pub struct ToolPermissionRecord {
allowed: bool,
context_hash: String, // Hash of the tool's arguments/context to differentiate similar calls
#[serde(skip_serializing_if = "Option::is_none")] // Don't serialize if None
- readable_context: Option, // Add this field
+ readable_context: Option,
timestamp: i64,
expiry: Option, // Optional expiry timestamp
}
diff --git a/crates/goose/src/posthog.rs b/crates/goose/src/posthog.rs
new file mode 100644
index 0000000000..54b5aa8f88
--- /dev/null
+++ b/crates/goose/src/posthog.rs
@@ -0,0 +1,303 @@
+//! PostHog telemetry - fires once per session creation.
+
+use crate::config::paths::Paths;
+use crate::config::{get_enabled_extensions, Config};
+use crate::session::SessionManager;
+use chrono::{DateTime, Utc};
+use once_cell::sync::Lazy;
+use serde::{Deserialize, Serialize};
+use std::fs;
+use std::sync::atomic::{AtomicBool, Ordering};
+use std::sync::Mutex;
+use uuid::Uuid;
+
+const POSTHOG_API_KEY: &str = "phc_RyX5CaY01VtZJCQyhSR5KFh6qimUy81YwxsEpotAftT";
+
+/// Config key for telemetry opt-out preference
+pub const TELEMETRY_ENABLED_KEY: &str = "GOOSE_TELEMETRY_ENABLED";
+
+static TELEMETRY_DISABLED_BY_ENV: Lazy = Lazy::new(|| {
+ std::env::var("GOOSE_TELEMETRY_OFF")
+ .map(|v| v == "1" || v.to_lowercase() == "true")
+ .unwrap_or(false)
+ .into()
+});
+
+/// Check if telemetry is enabled.
+///
+/// Returns false if:
+/// - GOOSE_TELEMETRY_OFF environment variable is set to "1" or "true"
+/// - GOOSE_TELEMETRY_ENABLED config value is set to false
+///
+/// Returns true otherwise (telemetry is opt-out, enabled by default)
+pub fn is_telemetry_enabled() -> bool {
+ if TELEMETRY_DISABLED_BY_ENV.load(Ordering::Relaxed) {
+ return false;
+ }
+
+ let config = Config::global();
+ config
+ .get_param::(TELEMETRY_ENABLED_KEY)
+ .unwrap_or(true)
+}
+
+// ============================================================================
+// Installation Tracking
+// ============================================================================
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+struct InstallationData {
+ installation_id: String,
+ first_seen: DateTime,
+ session_count: u32,
+}
+
+impl Default for InstallationData {
+ fn default() -> Self {
+ Self {
+ installation_id: Uuid::new_v4().to_string(),
+ first_seen: Utc::now(),
+ session_count: 0,
+ }
+ }
+}
+
+fn installation_file_path() -> std::path::PathBuf {
+ Paths::state_dir().join("telemetry_installation.json")
+}
+
+fn load_or_create_installation() -> InstallationData {
+ let path = installation_file_path();
+
+ if let Ok(contents) = fs::read_to_string(&path) {
+ if let Ok(data) = serde_json::from_str::(&contents) {
+ return data;
+ }
+ }
+
+ let data = InstallationData::default();
+ save_installation(&data);
+ data
+}
+
+fn save_installation(data: &InstallationData) {
+ let path = installation_file_path();
+
+ if let Some(parent) = path.parent() {
+ let _ = fs::create_dir_all(parent);
+ }
+
+ if let Ok(json) = serde_json::to_string_pretty(data) {
+ let _ = fs::write(path, json);
+ }
+}
+
+fn increment_session_count() -> InstallationData {
+ let mut data = load_or_create_installation();
+ data.session_count += 1;
+ save_installation(&data);
+ data
+}
+
+// ============================================================================
+// Platform Info
+// ============================================================================
+
+fn get_platform_version() -> Option {
+ #[cfg(target_os = "macos")]
+ {
+ std::process::Command::new("sw_vers")
+ .arg("-productVersion")
+ .output()
+ .ok()
+ .and_then(|o| String::from_utf8(o.stdout).ok())
+ .map(|s| s.trim().to_string())
+ }
+ #[cfg(target_os = "linux")]
+ {
+ fs::read_to_string("/etc/os-release")
+ .ok()
+ .and_then(|content| {
+ content
+ .lines()
+ .find(|line| line.starts_with("VERSION_ID="))
+ .map(|line| {
+ line.trim_start_matches("VERSION_ID=")
+ .trim_matches('"')
+ .to_string()
+ })
+ })
+ }
+ #[cfg(target_os = "windows")]
+ {
+ std::process::Command::new("cmd")
+ .args(["/C", "ver"])
+ .output()
+ .ok()
+ .and_then(|o| String::from_utf8(o.stdout).ok())
+ .map(|s| s.trim().to_string())
+ }
+ #[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
+ {
+ None
+ }
+}
+
+fn detect_install_method() -> String {
+ let exe_path = std::env::current_exe().ok();
+
+ if let Some(path) = exe_path {
+ let path_str = path.to_string_lossy().to_lowercase();
+
+ if path_str.contains("homebrew") || path_str.contains("/opt/homebrew") {
+ return "homebrew".to_string();
+ }
+ if path_str.contains(".cargo") {
+ return "cargo".to_string();
+ }
+ if path_str.contains("applications") || path_str.contains(".app") {
+ return "desktop".to_string();
+ }
+ }
+
+ if std::env::var("GOOSE_DESKTOP").is_ok() {
+ return "desktop".to_string();
+ }
+
+ "binary".to_string()
+}
+
+// ============================================================================
+// Session Context (set by CLI/Desktop at startup)
+// ============================================================================
+
+static SESSION_INTERFACE: Lazy>> = Lazy::new(|| Mutex::new(None));
+static SESSION_IS_RESUMED: AtomicBool = AtomicBool::new(false);
+
+pub fn set_session_context(interface: &str, is_resumed: bool) {
+ if let Ok(mut iface) = SESSION_INTERFACE.lock() {
+ *iface = Some(interface.to_string());
+ }
+ SESSION_IS_RESUMED.store(is_resumed, Ordering::Relaxed);
+}
+
+fn get_session_interface() -> String {
+ SESSION_INTERFACE
+ .lock()
+ .ok()
+ .and_then(|i| i.clone())
+ .unwrap_or_else(|| "unknown".to_string())
+}
+
+fn get_session_is_resumed() -> bool {
+ SESSION_IS_RESUMED.load(Ordering::Relaxed)
+}
+
+// ============================================================================
+// Telemetry Events
+// ============================================================================
+
+pub fn emit_session_started() {
+ if !is_telemetry_enabled() {
+ return;
+ }
+
+ let installation = increment_session_count();
+
+ tokio::spawn(async move {
+ let _ = send_session_event(&installation).await;
+ });
+}
+
+pub fn emit_error(error_type: &str) {
+ if !is_telemetry_enabled() {
+ return;
+ }
+
+ let installation = load_or_create_installation();
+ let error_type = error_type.to_string();
+
+ tokio::spawn(async move {
+ let _ = send_error_event(&installation, &error_type).await;
+ });
+}
+
+async fn send_error_event(installation: &InstallationData, error_type: &str) -> Result<(), String> {
+ let client = posthog_rs::client(POSTHOG_API_KEY).await;
+ let mut event = posthog_rs::Event::new("error", &installation.installation_id);
+
+ event.insert_prop("error_type", error_type).ok();
+ event.insert_prop("version", env!("CARGO_PKG_VERSION")).ok();
+ event.insert_prop("interface", get_session_interface()).ok();
+ event.insert_prop("os", std::env::consts::OS).ok();
+ event.insert_prop("arch", std::env::consts::ARCH).ok();
+
+ if let Some(platform_version) = get_platform_version() {
+ event.insert_prop("platform_version", platform_version).ok();
+ }
+
+ let config = Config::global();
+ if let Ok(provider) = config.get_param::("GOOSE_PROVIDER") {
+ event.insert_prop("provider", provider).ok();
+ }
+ if let Ok(model) = config.get_param::("GOOSE_MODEL") {
+ event.insert_prop("model", model).ok();
+ }
+
+ client.capture(event).await.map_err(|e| format!("{:?}", e))
+}
+
+async fn send_session_event(installation: &InstallationData) -> Result<(), String> {
+ let client = posthog_rs::client(POSTHOG_API_KEY).await;
+ let mut event = posthog_rs::Event::new("session_started", &installation.installation_id);
+
+ event.insert_prop("os", std::env::consts::OS).ok();
+ event.insert_prop("arch", std::env::consts::ARCH).ok();
+ event.insert_prop("version", env!("CARGO_PKG_VERSION")).ok();
+
+ if let Some(platform_version) = get_platform_version() {
+ event.insert_prop("platform_version", platform_version).ok();
+ }
+
+ event
+ .insert_prop("install_method", detect_install_method())
+ .ok();
+
+ event.insert_prop("interface", get_session_interface()).ok();
+
+ event
+ .insert_prop("is_resumed", get_session_is_resumed())
+ .ok();
+
+ event
+ .insert_prop("session_number", installation.session_count)
+ .ok();
+ let days_since_install = (Utc::now() - installation.first_seen).num_days();
+ event
+ .insert_prop("days_since_install", days_since_install)
+ .ok();
+
+ let config = Config::global();
+ if let Ok(provider) = config.get_param::("GOOSE_PROVIDER") {
+ event.insert_prop("provider", provider).ok();
+ }
+ if let Ok(model) = config.get_param::("GOOSE_MODEL") {
+ event.insert_prop("model", model).ok();
+ }
+
+ let extensions = get_enabled_extensions();
+ event.insert_prop("extensions_count", extensions.len()).ok();
+ let extension_names: Vec = extensions.iter().map(|e| e.name()).collect();
+ event.insert_prop("extensions", extension_names).ok();
+
+ if let Ok(insights) = SessionManager::get_insights().await {
+ event
+ .insert_prop("total_sessions", insights.total_sessions)
+ .ok();
+ event
+ .insert_prop("total_tokens", insights.total_tokens)
+ .ok();
+ }
+
+ client.capture(event).await.map_err(|e| format!("{:?}", e))
+}
diff --git a/crates/goose/src/providers/base.rs b/crates/goose/src/providers/base.rs
index e908cd88c9..4a84fff8e0 100644
--- a/crates/goose/src/providers/base.rs
+++ b/crates/goose/src/providers/base.rs
@@ -2,6 +2,7 @@ use anyhow::Result;
use futures::Stream;
use serde::{Deserialize, Serialize};
+use super::canonical::{map_to_canonical_model, CanonicalModelRegistry};
use super::errors::ProviderError;
use super::retry::RetryConfig;
use crate::config::base::ConfigValue;
@@ -415,6 +416,52 @@ pub trait Provider: Send + Sync {
Ok(None)
}
+ /// Fetch models filtered by canonical registry and usability
+ async fn fetch_recommended_models(&self) -> Result>, ProviderError> {
+ let all_models = match self.fetch_supported_models().await? {
+ Some(models) => models,
+ None => return Ok(None),
+ };
+
+ let registry = CanonicalModelRegistry::bundled().map_err(|e| {
+ ProviderError::ExecutionError(format!("Failed to load canonical registry: {}", e))
+ })?;
+
+ let provider_name = self.get_name();
+
+ let recommended_models: Vec = all_models
+ .iter()
+ .filter(|model| {
+ map_to_canonical_model(provider_name, model, registry)
+ .and_then(|canonical_id| registry.get(&canonical_id))
+ .map(|m| m.input_modalities.contains(&"text".to_string()))
+ .unwrap_or(false)
+ })
+ .cloned()
+ .collect();
+
+ if recommended_models.is_empty() {
+ Ok(Some(all_models))
+ } else {
+ Ok(Some(recommended_models))
+ }
+ }
+
+ async fn map_to_canonical_model(
+ &self,
+ provider_model: &str,
+ ) -> Result, ProviderError> {
+ let registry = CanonicalModelRegistry::bundled().map_err(|e| {
+ ProviderError::ExecutionError(format!("Failed to load canonical registry: {}", e))
+ })?;
+
+ Ok(map_to_canonical_model(
+ self.get_name(),
+ provider_model,
+ registry,
+ ))
+ }
+
fn supports_embeddings(&self) -> bool {
false
}
diff --git a/crates/goose/src/providers/canonical/README.md b/crates/goose/src/providers/canonical/README.md
new file mode 100644
index 0000000000..61bf29ccdd
--- /dev/null
+++ b/crates/goose/src/providers/canonical/README.md
@@ -0,0 +1,24 @@
+# Canonical Model System
+
+Provides a unified view of model metadata (pricing, capabilities, context limits) across different LLM providers.
+Normalizes provider-specific model names (e.g., `claude-3-5-sonnet-20241022`)
+to canonical IDs (e.g., `anthropic/claude-3.5-sonnet`).
+
+## Scripts
+
+### Build Canonical Models
+Fetches latest model metadata from OpenRouter and updates the registry:
+```bash
+cargo run --example build_canonical_models
+```
+Writes to: `src/providers/canonical/data/canonical_models.json`
+
+### Check Model Mappings
+Tests provider model mappings and tracks changes over time:
+```bash
+cargo run --example canonical_model_checker
+```
+- Reports unmapped models
+- Compares with previous runs (like a lock file)
+- Shows changed/added/removed mappings
+- Writes to: `src/providers/canonical/data/canonical_mapping_report.json`
diff --git a/crates/goose/src/providers/canonical/data/canonical_mapping_report.json b/crates/goose/src/providers/canonical/data/canonical_mapping_report.json
new file mode 100644
index 0000000000..f526b61646
--- /dev/null
+++ b/crates/goose/src/providers/canonical/data/canonical_mapping_report.json
@@ -0,0 +1,5352 @@
+{
+ "timestamp": "2025-12-11T15:48:25.968909+00:00",
+ "unmapped_models": [
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-01-20-06-09-37"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-02-03-22-57-10"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-02-03-23-17-02"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-02-03-23-39-19"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-02-04-00-06-28"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-02-04-00-26-44"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-02-04-01-04-45"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-02-04-01-25-59"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-02-04-01-35-31"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-02-04-01-49-59"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-02-04-01-52-25"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-02-04-01-53-11"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-02-04-01-54-49"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-02-04-01-56-59"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-02-04-01-58-41"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-02-04-02-00-26"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-02-04-02-02-17"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-02-04-02-03-39"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-02-04-02-05-16"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-02-04-02-06-44"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-02-04-02-08-18"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-02-22-05-09-46"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-02-22-05-27-21"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-02-27-17-40-56"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-02-27-19-20-20"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-02-27-19-35-21"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-02-27-21-26-06"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-03-01-19-58-17"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-03-02-11-40-22"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-03-02-16-01-19"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-03-06-11-43-24"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-04-05-18-59-44"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square-2023-06-14-21-43-36"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square:ada-fine-tuned-pb-books-2023-06-10-01-41-30"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square:get-classify-test-1-2023-03-01-22-20-54"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square:get-classify-test-2-2023-03-01-23-00-30"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square:latha-2023-06-07-06-56-00"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square:mcc-from-items-2023-03-09-23-20-33"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square:mcc-from-items-2023-03-10-02-02-01"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square:sq-interview-rubrics-2023-03-01-23-25-00"
+ },
+ {
+ "provider": "openai",
+ "model": "ada:ft-square:test-2023-06-07-07-05-04"
+ },
+ {
+ "provider": "openai",
+ "model": "babbage-002"
+ },
+ {
+ "provider": "openai",
+ "model": "babbage:ft-square-2023-02-28-14-48-38"
+ },
+ {
+ "provider": "openai",
+ "model": "computer-use-preview"
+ },
+ {
+ "provider": "openai",
+ "model": "computer-use-preview-2025-03-11"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-01-04-00-17-01"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-02-03-20-54-03"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-02-03-22-00-22"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-02-03-23-12-33"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-02-03-23-38-15"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-02-03-23-50-25"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-02-03-23-56-35"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-02-04-00-10-16"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-02-04-00-30-47"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-02-04-01-10-58"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-02-14-03-10-35"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-02-14-05-13-05"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-02-24-12-25-24"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-02-24-13-42-34"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-02-24-16-41-35"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-02-24-18-12-55"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-02-27-22-00-27"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-02-28-12-28-40"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-02-28-16-08-29"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-02-28-23-14-48"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-03-02-01-48-30"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-03-02-13-12-20"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-03-02-16-17-24"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-03-06-14-16-22"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-03-09-04-37-07"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-04-05-19-33-31"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-04-05-23-44-20"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-05-01-19-30-51"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-05-02-16-19-15"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-06-08-20-11-11"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-06-12-14-33-09"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-06-14-22-55-22"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-06-14-23-23-37"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-06-14-23-25-31"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-06-14-23-37-23"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-06-15-14-24-56"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-06-15-15-16-31"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square-2023-06-15-15-21-41"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square:andrewbyers-iso-dates-2-2023-06-09-19-23-52"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square:andrewbyers-iso-dates-2023-06-09-16-22-54"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square:bartosz-2023-06-15-16-50-00"
+ },
+ {
+ "provider": "openai",
+ "model": "curie:ft-square:bartosz-2023-06-15-17-40-30"
+ },
+ {
+ "provider": "openai",
+ "model": "dall-e-2"
+ },
+ {
+ "provider": "openai",
+ "model": "dall-e-3"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci-002"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-02-03-01-31-46"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-02-03-01-35-06"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-02-03-01-38-32"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-02-03-19-52-40"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-02-03-19-56-06"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-02-03-19-58-46"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-02-03-20-02-03"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-02-03-20-05-47"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-02-03-20-11-53"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-02-03-20-15-38"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-02-24-18-22-49"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-02-27-19-06-04"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-02-27-20-03-10"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-02-27-20-43-03"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-02-27-21-15-51"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-02-27-21-56-57"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-02-27-23-00-36"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-02-28-15-35-39"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-02-28-22-02-29"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-03-01-01-34-16"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-03-01-04-50-27"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-03-01-21-52-35"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-03-01-23-53-42"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-03-02-06-29-26"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-03-02-07-43-23"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-03-02-08-33-27"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-03-02-09-27-04"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-03-02-19-42-43"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-03-02-23-34-00"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-03-02-23-40-51"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-03-02-23-46-02"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-03-02-23-54-35"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-03-03-01-26-16"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-03-06-07-40-58"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-05-02-17-37-42"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-06-03-01-27-58"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-06-03-02-41-38"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-06-04-20-17-25"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square-2023-07-31-06-04-37"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square:conversational-ordering-2023-03-01-03-35-26"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square:conversational-ordering-2023-03-01-19-34-59"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square:martech-fox-2023-07-03-12-08-51"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square:ppg-1-2023-03-03-22-44-53"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square:ppg-2-2023-03-04-00-09-06"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square:product-descriptions-1-2023-02-18-02-01-38"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square:product-descriptions-2023-02-18-01-37-53"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square:sq-interview-rubrics-2023-03-01-06-12-57"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square:test-2023-06-02-21-46-09"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square:test-2023-06-07-07-18-50"
+ },
+ {
+ "provider": "openai",
+ "model": "davinci:ft-square:testmodel-2023-06-09-06-33-46"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:babbage-002:square::979OeECc"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:babbage-002:square::979X87hI"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:babbage-002:square::979tUAKY"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:babbage-002:square::9BaZ6utW"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:babbage-002:square::9BaZ962b:ckpt-step-418"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:babbage-002:square::9BaZ9TI9:ckpt-step-1254"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:babbage-002:square::9BaZ9yXw:ckpt-step-836"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:babbage-002:square::9f7y6Fq3:ckpt-step-132"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:babbage-002:square::9f7y6d94:ckpt-step-66"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:babbage-002:square::9f7y6tDo"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:davinci-002:square::8IHIFNF8"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:davinci-002:square::979av0Ca"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::9BdO7p4k"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::9BdO9dyB:ckpt-step-418"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::9BdOAZhy:ckpt-step-1254"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::9BdOAzyd:ckpt-step-836"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::9BdjnxSh"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::9Bdjq2HN:ckpt-step-836"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::9BdjqEL2:ckpt-step-418"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::9BdjqWL2:ckpt-step-1254"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::9CBYL14U"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::9CBYOIYz:ckpt-step-531"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::9CBYOe1X:ckpt-step-1062"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::9CBYPJjH:ckpt-step-1593"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::9dWw13R5:ckpt-step-531"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::9dWw21NB:ckpt-step-1062"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::9dWw2Jmx"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::9w2wKEHK:ckpt-step-1404"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::9w2wKRUk"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::9w2wKfxt:ckpt-step-702"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::9wDsxF0M:ckpt-step-702"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::9wDsxJU5"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::9wDsxcuJ:ckpt-step-1404"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::9wLTi98n:ckpt-step-702"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::9wLTiHPk:ckpt-step-1404"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::9wLTiuf3"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::A1oa308p:ckpt-step-768"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::A1oa4UVH:ckpt-step-1536"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::A1oa4fY2"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::A21LQ3B7:ckpt-step-1536"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::A21LQjft:ckpt-step-768"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::A21LRYaU"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::A3F6X0tu:ckpt-step-906"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::A3F6XYh7:ckpt-step-1812"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::A3F6YGI4"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::A3F7A46A:ckpt-step-906"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::A3F7AHaO:ckpt-step-1812"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::A3F7AoD8"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::BDIOxNBa"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::BDIOxtLv:ckpt-step-499"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::BDIOxz40:ckpt-step-998"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::BDIbgGdy"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::BDM8mQGM"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::BDQoyuDs"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::BPmNgoWc:ckpt-step-80"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::BPmNj3MY:ckpt-step-90"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square::BPmNkBwY"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square:regex-generator:BV3pHd69"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square:regex-generator:BV3pHfFP:ckpt-step-608"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0125:square:regex-generator:BV3pHz34:ckpt-step-304"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0613:square::7xkXhz2F"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-0613:square:recipe-ner:8m9bUTJB"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-1106:square::8INlSFiD"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-1106:square::8IittuHW"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-1106:square::8VQq2h9i"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-1106:square:presale-prod-update:A1VCz1Ng:ckpt-step-772"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-1106:square:presale-prod-update:A1VCz1dF"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-1106:square:presale-prod-update:A1VCzSam:ckpt-step-1544"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-1106:square:tarkin-fmac:9esUGGea:ckpt-step-1522"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-1106:square:tarkin-fmac:9esUGnko"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-1106:square:tarkin-fmac:9f8il0PV"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-1106:square:tarkin-fmac:9f8iljLm:ckpt-step-1611"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-1106:square:tarkin-fmac:9f9O9vmz:ckpt-step-751"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-1106:square:tarkin-fmac:9f9OA72b:ckpt-step-1502"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-1106:square:tarkin-fmac:9f9OAsBX"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-1106:square:tarkin-fmac:9fAO711i:ckpt-step-753"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-1106:square:tarkin-fmac:9fAO71NV"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-1106:square:tarkin-fmac:9fAO7nhv:ckpt-step-1506"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-1106:square:tarkin-fmac:9fB61NQG"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-1106:square:tarkin-fmac:9fB61ZZn:ckpt-step-1520"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-1106:square:tarkin-fmac:9fB61cPW:ckpt-step-760"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-1106:square:tarkin-fmac:9fCbI5U9"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-1106:square:tarkin-fmac:9fCbICqK:ckpt-step-761"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-1106:square:tarkin-pm-35:9djnKK85:ckpt-step-1533"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-3.5-turbo-1106:square:tarkin-pm-35:9djnL0G0"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::9BpIi1ah"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::9BpIlVui:ckpt-step-418"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::9BpIle5X:ckpt-step-836"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::9BpImGbJ:ckpt-step-1254"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::9CERwrnZ"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::9CERyuVp:ckpt-step-531"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::9CERz6l4:ckpt-step-1593"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::9CERzjIk:ckpt-step-1062"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::9dWi6cve:ckpt-step-531"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::9dWi7AH4"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::9dWi7d0k:ckpt-step-1062"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::9wGGcJwE:ckpt-step-1404"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::9wGGceZ1:ckpt-step-702"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::9wGGcsCI"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::9wLlEcBS:ckpt-step-702"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::9wLlFM71"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::9wLlFtmK:ckpt-step-1404"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::9wP6wXmY:ckpt-step-702"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::9wP6xJ7K"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::9wP6xUW9:ckpt-step-1404"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::9yR6Pmck:ckpt-step-702"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::9yR6Q01x"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::9yR6QRCI:ckpt-step-1404"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::9ySoC7vZ"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::9ySoC9RL:ckpt-step-1482"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::9ySoCHH9:ckpt-step-741"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::A1os36ZY:ckpt-step-768"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::A1os3SzM:ckpt-step-1536"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square::A1os4RF0"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square:g11n-helper:ALxGZf5Y:ckpt-step-54"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square:g11n-helper:ALxGaifK:ckpt-step-72"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square:g11n-helper:ALxGasbI"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square:presale-prod-update:A1VkM3KW:ckpt-step-772"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square:presale-prod-update:A1VkMPxN:ckpt-step-1544"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square:presale-prod-update:A1VkMugi"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square:tarkin:9a8eE6A4:ckpt-step-763"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square:tarkin:9a8eEWdX"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square:tarkin:9a8eEkGS:ckpt-step-1526"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square:tarkin:9aDzHMdR:ckpt-step-3054"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square:tarkin:9aDzHdca:ckpt-step-1527"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square:tarkin:9aDzI3c1"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square:tarkin:9ckg06KE:ckpt-step-70636"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square:tarkin:9ckg06yx:ckpt-step-35318"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4-0613:square:tarkin:9ckg1W41"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4.1-2025-04-14:square:hotel-sys-design:Bn8TRXx7:ckpt-step-77"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4.1-2025-04-14:square:hotel-sys-design:Bn8TSnvc"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4.1-2025-04-14:square:hotel-sys-design:Bn8TSuqu:ckpt-step-88"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4.1-2025-04-14:square:picasso-tbl:Be4ohJ5c:ckpt-step-555"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4.1-2025-04-14:square:picasso-tbl:Be4okWcV"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4.1-2025-04-14:square:picasso-tbl:Be4okcLm:ckpt-step-1110"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4.1-mini-2025-04-14:square::BmTIcrto:ckpt-step-666"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4.1-mini-2025-04-14:square::BmTIfHgD"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4.1-mini-2025-04-14:square::BmTIfv3N:ckpt-step-1332"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4.1-mini-2025-04-14:square::BwjlIHbe"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4.1-mini-2025-04-14:square::BwjlIMor:ckpt-step-1018"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4.1-nano-2025-04-14:square:qliao-plathelp-v1:CRNAywx8"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square::9eTWHHtG"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square::9eTWHI2d:ckpt-step-80"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square::9eTWHWR7:ckpt-step-90"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square::9eoDA1QR:ckpt-step-32"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square::9eoDARki"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square::9eoDAkYo:ckpt-step-64"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square::9eqA79i0:ckpt-step-59"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square::9eqA8TBe:ckpt-step-118"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square::9eqA8pCn"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square::9hgDsAHH:ckpt-step-66"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square::9hgDsHOi:ckpt-step-132"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square::9hgDtDpP"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square::9yPneKZF:ckpt-step-702"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square::9yPnf26m:ckpt-step-1404"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square::9yPnfZ3w"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:pfi-golang:9smel7hl:ckpt-step-434"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:pfi-golang:9smelXOJ"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:pfi-golang:9smeltpg:ckpt-step-217"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:pfi:9sjuwq38:ckpt-step-505"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:pfi:9sjux561"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:pfi:9sjuxLbk:ckpt-step-1010"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-fma:9e48jPvC:ckpt-step-1510"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-fma:9e48kfSc"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-fma:9e4FyHOk:ckpt-step-528"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-fma:9e4FzikM:ckpt-step-1056"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-fma:9e4Fzzq8"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-fma:9e6FlhBM"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-fma:9e6Flnjr:ckpt-step-1526"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-fma:9e8EgHNk"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-fma:9e8Egd2H:ckpt-step-1526"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-fma:9ePrc2Qr"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-fma:9ePrcUgW:ckpt-step-1524"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-fmac:9eSm4CmX:ckpt-step-1513"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-fmac:9eSm4Nvi"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-fmac:9eVTYdgL:ckpt-step-1513"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-fmac:9eVTZNWy"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-fmac:9ellv805:ckpt-step-812"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-fmac:9ellvsEx"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-fmar:9gCyNcoZ:ckpt-step-753"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-fmar:9gCyOII4"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-fmar:9gCyOTcP:ckpt-step-1506"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-fmar:9gEX8El2:ckpt-step-1552"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-fmar:9gEX9ft2"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-fmas:9eq84If3"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-fmas:9eq84hU9:ckpt-step-1522"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-full-meal:9e2Qax41:ckpt-step-528"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-full-meal:9e2Qb1sn"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-full-meal:9e2QbHP1:ckpt-step-1056"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-piece-meal:9dgu0Q1g:ckpt-step-1533"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-piece-meal:9dgu1KXd"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-pm-4o:9dkScYcs"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin-pm-4o:9dkScysk:ckpt-step-1533"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:tarkin:9dfz3A8x"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:test-ft:9e4bJAtq:ckpt-step-27"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:test-ft:9e4bKBv0:ckpt-step-54"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:test-ft:9e4bKlvw"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:thunderbird:9bd8MQGp:ckpt-step-88"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:thunderbird:9bd8NB8K"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-05-13:square:thunderbird:9bd8NX77:ckpt-step-132"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square:12-21-24:Ah0F4uLP:ckpt-step-185"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square:12-21-24:Ah0FNFHQ:ckpt-step-370"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square:12-21-24:Ah0FNFS1"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::9yOpXJbd:ckpt-step-702"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::9yOpXVGv"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::9yOpXmyQ:ckpt-step-1404"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::9yWRL773:ckpt-step-1482"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::9yWRLeLB:ckpt-step-741"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::9yWRM8Zn"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::A1pIt632"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::A1pItfDC:ckpt-step-1536"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::A1pItofx:ckpt-step-768"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::A218TwmT:ckpt-step-768"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::A218UZjU:ckpt-step-1536"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::A218UzZf"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::A3FCYhcv:ckpt-step-906"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::A3FCYpNh:ckpt-step-1812"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::A3FCZe3h"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::A6lkYKvg:ckpt-step-80"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::A6lkYTPi:ckpt-step-90"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::A6lkZ4nA"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ADYnVcKn:ckpt-step-153"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ADYnWDpr:ckpt-step-306"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ADYnWF7g"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ADcZL13Y:ckpt-step-149"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ADcZLJjk:ckpt-step-298"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ADcZMaZB"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ADdWL6S7:ckpt-step-149"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ADdWLGiA:ckpt-step-298"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ADdWLpNL"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ADv4G9ED:ckpt-step-310"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ADv4GfJI"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ADv4GslV:ckpt-step-155"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AFvffbd6:ckpt-step-60"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AFvfgOvb"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AFvfgYxS:ckpt-step-75"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AIrPDXh4:ckpt-step-70"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AIrPE1Jn:ckpt-step-84"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AIrPFDOA"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AIrRI7Py"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AIrRIbun:ckpt-step-70"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AIrRIz3n:ckpt-step-84"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AJPMFYjn:ckpt-step-70"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AJPMSMwR:ckpt-step-84"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AJPMTSQg"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ALDZQz9s:ckpt-step-57"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ALDZbB0b:ckpt-step-57"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ALDZlhsT:ckpt-step-57"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ALDZrh8z:ckpt-step-76"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ALDZrhRU:ckpt-step-76"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ALDZrhWY:ckpt-step-76"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ALDZs4af"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ALDZsBZ7"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ALDZsiMB"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ALelRGKV:ckpt-step-75"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ALelRQjh:ckpt-step-50"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ALelSl5l"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ALfXG00U:ckpt-step-26"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ALfXHUyM"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ALfXHdnT:ckpt-step-52"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AM3Kk6bJ:ckpt-step-57"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AM3Km10T:ckpt-step-114"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AM3KmncS"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AMHzrhbX:ckpt-step-58"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AMI02GZM:ckpt-step-58"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AMI0CTR7:ckpt-step-58"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AMI0MEJb:ckpt-step-116"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AMI0MZIn:ckpt-step-116"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AMI0MbSE:ckpt-step-116"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AMI0NAKb:ckpt-step-116"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AMI0NGtf"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AMI0NZqn:ckpt-step-58"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AMI0NfHv"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AMI0Nkxa"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AMI0NxXJ"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AMgDbCpJ:ckpt-step-61"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AMgDmR8i:ckpt-step-61"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AMgDwgwF:ckpt-step-61"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AMgE01SL"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AMgE0Zqi"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AMgE0dya:ckpt-step-122"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AMgE0l6B"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AMgE0lmy:ckpt-step-122"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::AMgE0x9A:ckpt-step-122"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANBhCPM4:ckpt-step-71"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANBhNQrL:ckpt-step-71"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANBhXvS0:ckpt-step-71"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANBheCpk:ckpt-step-142"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANBheHqF:ckpt-step-142"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANBhelmB:ckpt-step-142"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANBhfHg0"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANBhfP3K"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANBhfWTj"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANR81FlE:ckpt-step-71"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANR8340Y"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANR83UMp:ckpt-step-142"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANWxC4R0:ckpt-step-76"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANWxMc5r:ckpt-step-76"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANWxWkuX:ckpt-step-76"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANWxb3ul"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANWxbR1m:ckpt-step-152"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANWxbZ7u:ckpt-step-152"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANWxbueA:ckpt-step-152"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANWxbx9d"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANWxcbir"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANnTNQfB:ckpt-step-76"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANnTXwXd:ckpt-step-76"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANnTeBFW:ckpt-step-152"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANnTeWRz"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANnTedmK:ckpt-step-152"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANnTenB6"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANq2D506:ckpt-step-76"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANq2OJoT:ckpt-step-76"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANq2Ykg1:ckpt-step-76"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANq2f35h:ckpt-step-152"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANq2fWSn:ckpt-step-152"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANq2fkPg:ckpt-step-152"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANq2g1fc"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANq2g4RI"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::ANq2gT5D"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::BDcdETt3"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::BDgw3aRR:ckpt-step-770"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::BDgwAYDm"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::Cl22v5JX:ckpt-step-50"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::Cl22ws6P"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square::Cl22ww03:ckpt-step-100"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square:presale-prod-update:A1VLdLFo:ckpt-step-772"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square:presale-prod-update:A1VLdpp9:ckpt-step-1544"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square:presale-prod-update:A1VLef0R"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square:uip-1-3-2025-733pm:Aln8wDEd:ckpt-step-57"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square:uip-1-3-2025-733pm:Aln99EJs:ckpt-step-76"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square:uip-1-3-2025-733pm:Aln99po1"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square:uip-1-3-2025:AljIGSIY:ckpt-step-114"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square:uip-1-3-2025:AljIUDTx:ckpt-step-228"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square:uip-1-3-2025:AljIUw1E"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square:uip-4o-beta1:AU2gb0ZW:ckpt-step-99"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square:uip-4o-beta1:AU2glfI3:ckpt-step-99"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square:uip-4o-beta1:AU2goBN9:ckpt-step-198"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square:uip-4o-beta1:AU2goRoo:ckpt-step-198"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square:uip-4o-beta1:AU2gpUkI"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square:uip-4o-beta1:AU2gpkQO"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square:uipath:AStGEJCN:ckpt-step-140"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square:uipath:AStGEPHu"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-2024-08-06:square:uipath:AStGErTc:ckpt-step-70"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square::9yQURl9e:ckpt-step-702"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square::9yQUS8UO"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square::9yQUSboN:ckpt-step-1404"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square::9yS9rcIf:ckpt-step-741"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square::9yS9sWBR:ckpt-step-1482"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square::9yS9sZgG"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square::9yYfNBYp"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square::9yYfNYZR:ckpt-step-741"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square::9yYfNa27:ckpt-step-1482"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square::A1o0y9c3:ckpt-step-1536"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square::A1o0yYU0"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square::A1o0yoGv:ckpt-step-768"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square::A20kHEh1:ckpt-step-1536"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square::A20kHL5C"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square::A20kHQH6:ckpt-step-768"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square::A3Ew9LPO:ckpt-step-1812"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square::A3Ew9Pfa:ckpt-step-906"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square::A3EwAWC6"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square::C9PyMpaX:ckpt-step-926"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square::C9PyNU6h"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square:ading-test:CjZH4S3Z:ckpt-step-10"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square:ading-test:CjZH51lQ:ckpt-step-20"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square:ading-test:CjZH5Os2"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square:merchant-mappings-v1:BVLby6lj:ckpt-step-80"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square:merchant-mappings-v1:BVLc6ocQ:ckpt-step-90"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square:merchant-mappings-v1:BVLc6zzK"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square:merchant-tokens:BUsD3Vq9"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square:presale-prod-update:A1V43nWF:ckpt-step-772"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square:presale-prod-update:A1V44IqT"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square:presale-prod-update:A1V44Vjp:ckpt-step-1544"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square:ql-plathelp-241:CRPREgy9:ckpt-step-241"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square:ql-plathelp-241:CRPRFEus:ckpt-step-482"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square:ql-plathelp-241:CRPRGYXt"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square:support-250:Ao0TXJT1:ckpt-step-250"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square:support-250:Ao0TYWiG"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square:support-250:Ao0TYf9o:ckpt-step-500"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square:support-50:AnziWXoE:ckpt-step-50"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square:support-50:AnziY3TM"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square:support-50:AnziYbeg:ckpt-step-100"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square:tarkin:9ozEIZnh:ckpt-step-1751"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square:tarkin:9ozEMo2r"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square:tarkin:9qecJUHK:ckpt-step-2223"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square:tarkin:9qecJuVm"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square:tarkin:9qvs9txe:ckpt-step-2241"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square:tarkin:9qvsAbS8"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square:tarkin:9rJTBC8M:ckpt-step-2241"
+ },
+ {
+ "provider": "openai",
+ "model": "ft:gpt-4o-mini-2024-07-18:square:tarkin:9rJTCzuI"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-4o-mini-audio-preview"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-4o-mini-audio-preview-2024-12-17"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-4o-mini-realtime-preview"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-4o-mini-realtime-preview-2024-12-17"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-4o-mini-transcribe"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-4o-mini-tts"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-4o-realtime-preview"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-4o-realtime-preview-2024-12-17"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-4o-realtime-preview-2025-06-03"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-4o-transcribe"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-4o-transcribe-diarize"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-5-search-api"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-5-search-api-2025-10-14"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-audio"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-audio-2025-08-28"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-audio-mini"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-audio-mini-2025-10-06"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-image-1"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-image-1-mini"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-realtime"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-realtime-2025-08-28"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-realtime-mini"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-realtime-mini-2025-10-06"
+ },
+ {
+ "provider": "openai",
+ "model": "omni-moderation-2024-09-26"
+ },
+ {
+ "provider": "openai",
+ "model": "omni-moderation-latest"
+ },
+ {
+ "provider": "openai",
+ "model": "sora-2"
+ },
+ {
+ "provider": "openai",
+ "model": "sora-2-pro"
+ },
+ {
+ "provider": "openai",
+ "model": "text-embedding-3-large"
+ },
+ {
+ "provider": "openai",
+ "model": "text-embedding-3-small"
+ },
+ {
+ "provider": "openai",
+ "model": "text-embedding-ada-002"
+ },
+ {
+ "provider": "openai",
+ "model": "tts-1"
+ },
+ {
+ "provider": "openai",
+ "model": "tts-1-1106"
+ },
+ {
+ "provider": "openai",
+ "model": "tts-1-hd"
+ },
+ {
+ "provider": "openai",
+ "model": "tts-1-hd-1106"
+ },
+ {
+ "provider": "openai",
+ "model": "whisper-1"
+ },
+ {
+ "provider": "openrouter",
+ "model": "alibaba/tongyi-deepresearch-30b-a3b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "alibaba/tongyi-deepresearch-30b-a3b:free"
+ },
+ {
+ "provider": "openrouter",
+ "model": "allenai/olmo-3-7b-instruct"
+ },
+ {
+ "provider": "openrouter",
+ "model": "amazon/nova-2-lite-v1"
+ },
+ {
+ "provider": "openrouter",
+ "model": "amazon/nova-2-lite-v1:free"
+ },
+ {
+ "provider": "openrouter",
+ "model": "amazon/nova-lite-v1"
+ },
+ {
+ "provider": "openrouter",
+ "model": "amazon/nova-micro-v1"
+ },
+ {
+ "provider": "openrouter",
+ "model": "amazon/nova-premier-v1"
+ },
+ {
+ "provider": "openrouter",
+ "model": "amazon/nova-pro-v1"
+ },
+ {
+ "provider": "openrouter",
+ "model": "arcee-ai/trinity-mini"
+ },
+ {
+ "provider": "openrouter",
+ "model": "arcee-ai/trinity-mini:free"
+ },
+ {
+ "provider": "openrouter",
+ "model": "arcee-ai/virtuoso-large"
+ },
+ {
+ "provider": "openrouter",
+ "model": "baidu/ernie-4.5-21b-a3b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "baidu/ernie-4.5-vl-28b-a3b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "deepcogito/cogito-v2-preview-llama-109b-moe"
+ },
+ {
+ "provider": "openrouter",
+ "model": "deepcogito/cogito-v2-preview-llama-405b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "deepcogito/cogito-v2-preview-llama-70b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "google/gemini-2.0-flash-exp:free"
+ },
+ {
+ "provider": "openrouter",
+ "model": "inception/mercury"
+ },
+ {
+ "provider": "openrouter",
+ "model": "inception/mercury-coder"
+ },
+ {
+ "provider": "openrouter",
+ "model": "kwaipilot/kat-coder-pro:free"
+ },
+ {
+ "provider": "openrouter",
+ "model": "meituan/longcat-flash-chat:free"
+ },
+ {
+ "provider": "openrouter",
+ "model": "meta-llama/llama-3.3-70b-instruct:free"
+ },
+ {
+ "provider": "openrouter",
+ "model": "microsoft/phi-3-medium-128k-instruct"
+ },
+ {
+ "provider": "openrouter",
+ "model": "microsoft/phi-3-mini-128k-instruct"
+ },
+ {
+ "provider": "openrouter",
+ "model": "microsoft/phi-3.5-mini-128k-instruct"
+ },
+ {
+ "provider": "openrouter",
+ "model": "minimax/minimax-m1"
+ },
+ {
+ "provider": "openrouter",
+ "model": "minimax/minimax-m2"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/devstral-2512"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/devstral-2512:free"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/mistral-7b-instruct:free"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/mistral-small-3.1-24b-instruct:free"
+ },
+ {
+ "provider": "openrouter",
+ "model": "moonshotai/kimi-k2"
+ },
+ {
+ "provider": "openrouter",
+ "model": "moonshotai/kimi-k2-0905"
+ },
+ {
+ "provider": "openrouter",
+ "model": "moonshotai/kimi-k2-0905:exacto"
+ },
+ {
+ "provider": "openrouter",
+ "model": "moonshotai/kimi-k2-thinking"
+ },
+ {
+ "provider": "openrouter",
+ "model": "nex-agi/deepseek-v3.1-nex-n1:free"
+ },
+ {
+ "provider": "openrouter",
+ "model": "nousresearch/deephermes-3-mistral-24b-preview"
+ },
+ {
+ "provider": "openrouter",
+ "model": "nousresearch/hermes-4-405b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "nousresearch/hermes-4-70b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "nvidia/llama-3.1-nemotron-70b-instruct"
+ },
+ {
+ "provider": "openrouter",
+ "model": "nvidia/llama-3.3-nemotron-super-49b-v1.5"
+ },
+ {
+ "provider": "openrouter",
+ "model": "nvidia/nemotron-nano-12b-v2-vl:free"
+ },
+ {
+ "provider": "openrouter",
+ "model": "nvidia/nemotron-nano-9b-v2"
+ },
+ {
+ "provider": "openrouter",
+ "model": "nvidia/nemotron-nano-9b-v2:free"
+ },
+ {
+ "provider": "openrouter",
+ "model": "openai/gpt-oss-120b:free"
+ },
+ {
+ "provider": "openrouter",
+ "model": "openai/gpt-oss-20b:free"
+ },
+ {
+ "provider": "openrouter",
+ "model": "prime-intellect/intellect-3"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen3-235b-a22b:free"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen3-4b:free"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen3-coder:free"
+ },
+ {
+ "provider": "openrouter",
+ "model": "relace/relace-search"
+ },
+ {
+ "provider": "openrouter",
+ "model": "sao10k/l3-euryale-70b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "sao10k/l3.1-euryale-70b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "stepfun-ai/step3"
+ },
+ {
+ "provider": "openrouter",
+ "model": "thedrummer/rocinante-12b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "thedrummer/unslopnemo-12b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "tngtech/deepseek-r1t2-chimera"
+ },
+ {
+ "provider": "openrouter",
+ "model": "tngtech/tng-r1t-chimera"
+ },
+ {
+ "provider": "openrouter",
+ "model": "tngtech/tng-r1t-chimera:free"
+ },
+ {
+ "provider": "openrouter",
+ "model": "x-ai/grok-3-beta"
+ },
+ {
+ "provider": "openrouter",
+ "model": "x-ai/grok-3-mini-beta"
+ },
+ {
+ "provider": "openrouter",
+ "model": "z-ai/glm-4-32b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "z-ai/glm-4.5"
+ },
+ {
+ "provider": "openrouter",
+ "model": "z-ai/glm-4.5-air"
+ },
+ {
+ "provider": "openrouter",
+ "model": "z-ai/glm-4.5-air:free"
+ },
+ {
+ "provider": "openrouter",
+ "model": "z-ai/glm-4.5v"
+ },
+ {
+ "provider": "openrouter",
+ "model": "z-ai/glm-4.6"
+ },
+ {
+ "provider": "openrouter",
+ "model": "z-ai/glm-4.6:exacto"
+ },
+ {
+ "provider": "openrouter",
+ "model": "z-ai/glm-4.6v"
+ },
+ {
+ "provider": "google",
+ "model": "aqa"
+ },
+ {
+ "provider": "google",
+ "model": "embedding-001"
+ },
+ {
+ "provider": "google",
+ "model": "embedding-gecko-001"
+ },
+ {
+ "provider": "google",
+ "model": "gemini-2.0-flash-exp-image-generation"
+ },
+ {
+ "provider": "google",
+ "model": "gemini-2.5-computer-use-preview-10-2025"
+ },
+ {
+ "provider": "google",
+ "model": "gemini-2.5-flash-native-audio-latest"
+ },
+ {
+ "provider": "google",
+ "model": "gemini-2.5-flash-preview-tts"
+ },
+ {
+ "provider": "google",
+ "model": "gemini-2.5-pro-preview-tts"
+ },
+ {
+ "provider": "google",
+ "model": "gemini-embedding-001"
+ },
+ {
+ "provider": "google",
+ "model": "gemini-embedding-exp"
+ },
+ {
+ "provider": "google",
+ "model": "gemini-embedding-exp-03-07"
+ },
+ {
+ "provider": "google",
+ "model": "gemini-exp-1206"
+ },
+ {
+ "provider": "google",
+ "model": "gemini-flash-latest"
+ },
+ {
+ "provider": "google",
+ "model": "gemini-flash-lite-latest"
+ },
+ {
+ "provider": "google",
+ "model": "gemini-pro-latest"
+ },
+ {
+ "provider": "google",
+ "model": "gemini-robotics-er-1.5-preview"
+ },
+ {
+ "provider": "google",
+ "model": "gemma-3-1b-it"
+ },
+ {
+ "provider": "google",
+ "model": "gemma-3n-e2b-it"
+ },
+ {
+ "provider": "google",
+ "model": "imagen-4.0-fast-generate-001"
+ },
+ {
+ "provider": "google",
+ "model": "imagen-4.0-generate-001"
+ },
+ {
+ "provider": "google",
+ "model": "imagen-4.0-generate-preview-06-06"
+ },
+ {
+ "provider": "google",
+ "model": "imagen-4.0-ultra-generate-001"
+ },
+ {
+ "provider": "google",
+ "model": "imagen-4.0-ultra-generate-preview-06-06"
+ },
+ {
+ "provider": "google",
+ "model": "nano-banana-pro-preview"
+ },
+ {
+ "provider": "google",
+ "model": "text-embedding-004"
+ },
+ {
+ "provider": "google",
+ "model": "veo-2.0-generate-001"
+ },
+ {
+ "provider": "google",
+ "model": "veo-3.0-fast-generate-001"
+ },
+ {
+ "provider": "google",
+ "model": "veo-3.0-generate-001"
+ },
+ {
+ "provider": "google",
+ "model": "veo-3.1-fast-generate-preview"
+ },
+ {
+ "provider": "google",
+ "model": "veo-3.1-generate-preview"
+ }
+ ],
+ "all_mappings": {
+ "xai": [],
+ "tetrate": [],
+ "google": [
+ {
+ "provider_model": "gemini-2.0-flash",
+ "canonical_model": "google/gemini-2.0-flash",
+ "verified": true
+ },
+ {
+ "provider_model": "gemini-2.0-flash-001",
+ "canonical_model": "google/gemini-2.0-flash",
+ "verified": true
+ },
+ {
+ "provider_model": "gemini-2.0-flash-exp",
+ "canonical_model": "google/gemini-2.0-flash",
+ "verified": true
+ },
+ {
+ "provider_model": "gemini-2.0-flash-lite",
+ "canonical_model": "google/gemini-2.0-flash-lite",
+ "verified": true
+ },
+ {
+ "provider_model": "gemini-2.0-flash-lite-001",
+ "canonical_model": "google/gemini-2.0-flash-lite",
+ "verified": true
+ },
+ {
+ "provider_model": "gemini-2.0-flash-lite-preview",
+ "canonical_model": "google/gemini-2.0-flash-lite",
+ "verified": true
+ },
+ {
+ "provider_model": "gemini-2.0-flash-lite-preview-02-05",
+ "canonical_model": "google/gemini-2.0-flash-lite",
+ "verified": true
+ },
+ {
+ "provider_model": "gemini-2.5-flash",
+ "canonical_model": "google/gemini-2.5-flash",
+ "verified": true
+ },
+ {
+ "provider_model": "gemini-2.5-flash-image",
+ "canonical_model": "google/gemini-2.5-flash-image",
+ "verified": true
+ },
+ {
+ "provider_model": "gemini-2.5-flash-image-preview",
+ "canonical_model": "google/gemini-2.5-flash-image",
+ "verified": true
+ },
+ {
+ "provider_model": "gemini-2.5-flash-lite",
+ "canonical_model": "google/gemini-2.5-flash-lite",
+ "verified": true
+ },
+ {
+ "provider_model": "gemini-2.5-flash-lite-preview-09-2025",
+ "canonical_model": "google/gemini-2.5-flash-lite",
+ "verified": true
+ },
+ {
+ "provider_model": "gemini-2.5-flash-preview-09-2025",
+ "canonical_model": "google/gemini-2.5-flash",
+ "verified": true
+ },
+ {
+ "provider_model": "gemini-2.5-pro",
+ "canonical_model": "google/gemini-2.5-pro",
+ "verified": true
+ },
+ {
+ "provider_model": "gemini-3-pro-image-preview",
+ "canonical_model": "google/gemini-3-pro-image",
+ "verified": true
+ },
+ {
+ "provider_model": "gemini-3-pro-preview",
+ "canonical_model": "google/gemini-3-pro",
+ "verified": true
+ },
+ {
+ "provider_model": "gemma-3-12b-it",
+ "canonical_model": "google/gemma-3-12b-it",
+ "verified": true
+ },
+ {
+ "provider_model": "gemma-3-27b-it",
+ "canonical_model": "google/gemma-3-27b-it",
+ "verified": true
+ },
+ {
+ "provider_model": "gemma-3-4b-it",
+ "canonical_model": "google/gemma-3-4b-it",
+ "verified": true
+ },
+ {
+ "provider_model": "gemma-3n-e4b-it",
+ "canonical_model": "google/gemma-3n-e4b-it",
+ "verified": true
+ }
+ ],
+ "anthropic": [
+ {
+ "provider_model": "claude-3-5-haiku-20241022",
+ "canonical_model": "anthropic/claude-3.5-haiku",
+ "verified": true
+ },
+ {
+ "provider_model": "claude-3-7-sonnet-20250219",
+ "canonical_model": "anthropic/claude-3.7-sonnet",
+ "verified": true
+ },
+ {
+ "provider_model": "claude-3-haiku-20240307",
+ "canonical_model": "anthropic/claude-3-haiku",
+ "verified": true
+ },
+ {
+ "provider_model": "claude-3-opus-20240229",
+ "canonical_model": "anthropic/claude-3-opus",
+ "verified": true
+ },
+ {
+ "provider_model": "claude-haiku-4-5-20251001",
+ "canonical_model": "anthropic/claude-haiku-4.5",
+ "verified": true
+ },
+ {
+ "provider_model": "claude-opus-4-1-20250805",
+ "canonical_model": "anthropic/claude-opus-4.1",
+ "verified": true
+ },
+ {
+ "provider_model": "claude-opus-4-20250514",
+ "canonical_model": "anthropic/claude-opus-4",
+ "verified": true
+ },
+ {
+ "provider_model": "claude-opus-4-5-20251101",
+ "canonical_model": "anthropic/claude-opus-4.5",
+ "verified": true
+ },
+ {
+ "provider_model": "claude-sonnet-4-20250514",
+ "canonical_model": "anthropic/claude-sonnet-4",
+ "verified": true
+ },
+ {
+ "provider_model": "claude-sonnet-4-5-20250929",
+ "canonical_model": "anthropic/claude-sonnet-4.5",
+ "verified": true
+ }
+ ],
+ "openrouter": [
+ {
+ "provider_model": "ai21/jamba-large-1.7",
+ "canonical_model": "ai21/jamba-large-1.7",
+ "verified": true
+ },
+ {
+ "provider_model": "ai21/jamba-mini-1.7",
+ "canonical_model": "ai21/jamba-mini-1.7",
+ "verified": true
+ },
+ {
+ "provider_model": "anthropic/claude-3-haiku",
+ "canonical_model": "anthropic/claude-3-haiku",
+ "verified": true
+ },
+ {
+ "provider_model": "anthropic/claude-3-opus",
+ "canonical_model": "anthropic/claude-3-opus",
+ "verified": true
+ },
+ {
+ "provider_model": "anthropic/claude-3.5-haiku",
+ "canonical_model": "anthropic/claude-3.5-haiku",
+ "verified": true
+ },
+ {
+ "provider_model": "anthropic/claude-3.5-haiku-20241022",
+ "canonical_model": "anthropic/claude-3.5-haiku",
+ "verified": true
+ },
+ {
+ "provider_model": "anthropic/claude-3.5-sonnet",
+ "canonical_model": "anthropic/claude-3.5-sonnet",
+ "verified": true
+ },
+ {
+ "provider_model": "anthropic/claude-3.7-sonnet",
+ "canonical_model": "anthropic/claude-3.7-sonnet",
+ "verified": true
+ },
+ {
+ "provider_model": "anthropic/claude-3.7-sonnet:thinking",
+ "canonical_model": "anthropic/claude-3.7-sonnet:thinking",
+ "verified": true
+ },
+ {
+ "provider_model": "anthropic/claude-haiku-4.5",
+ "canonical_model": "anthropic/claude-haiku-4.5",
+ "verified": true
+ },
+ {
+ "provider_model": "anthropic/claude-opus-4",
+ "canonical_model": "anthropic/claude-opus-4",
+ "verified": true
+ },
+ {
+ "provider_model": "anthropic/claude-opus-4.1",
+ "canonical_model": "anthropic/claude-opus-4.1",
+ "verified": true
+ },
+ {
+ "provider_model": "anthropic/claude-opus-4.5",
+ "canonical_model": "anthropic/claude-opus-4.5",
+ "verified": true
+ },
+ {
+ "provider_model": "anthropic/claude-sonnet-4",
+ "canonical_model": "anthropic/claude-sonnet-4",
+ "verified": true
+ },
+ {
+ "provider_model": "anthropic/claude-sonnet-4.5",
+ "canonical_model": "anthropic/claude-sonnet-4.5",
+ "verified": true
+ },
+ {
+ "provider_model": "cohere/command-r-08-2024",
+ "canonical_model": "cohere/command-r-08",
+ "verified": true
+ },
+ {
+ "provider_model": "cohere/command-r-plus-08-2024",
+ "canonical_model": "cohere/command-r-plus-08",
+ "verified": true
+ },
+ {
+ "provider_model": "deepseek/deepseek-chat",
+ "canonical_model": "deepseek/deepseek-chat",
+ "verified": true
+ },
+ {
+ "provider_model": "deepseek/deepseek-chat-v3-0324",
+ "canonical_model": "deepseek/deepseek-chat",
+ "verified": true
+ },
+ {
+ "provider_model": "deepseek/deepseek-chat-v3.1",
+ "canonical_model": "deepseek/deepseek-chat",
+ "verified": true
+ },
+ {
+ "provider_model": "deepseek/deepseek-r1",
+ "canonical_model": "deepseek/deepseek-r1",
+ "verified": true
+ },
+ {
+ "provider_model": "deepseek/deepseek-r1-0528",
+ "canonical_model": "deepseek/deepseek-r1",
+ "verified": true
+ },
+ {
+ "provider_model": "deepseek/deepseek-r1-distill-llama-70b",
+ "canonical_model": "deepseek/deepseek-r1-distill-llama-70b",
+ "verified": true
+ },
+ {
+ "provider_model": "deepseek/deepseek-v3.1-terminus",
+ "canonical_model": "deepseek/deepseek-v3.1-terminus",
+ "verified": true
+ },
+ {
+ "provider_model": "deepseek/deepseek-v3.1-terminus:exacto",
+ "canonical_model": "deepseek/deepseek-v3.1-terminus",
+ "verified": true
+ },
+ {
+ "provider_model": "deepseek/deepseek-v3.2",
+ "canonical_model": "deepseek/deepseek",
+ "verified": true
+ },
+ {
+ "provider_model": "deepseek/deepseek-v3.2-exp",
+ "canonical_model": "deepseek/deepseek",
+ "verified": true
+ },
+ {
+ "provider_model": "google/gemini-2.0-flash-001",
+ "canonical_model": "google/gemini-2.0-flash",
+ "verified": true
+ },
+ {
+ "provider_model": "google/gemini-2.0-flash-lite-001",
+ "canonical_model": "google/gemini-2.0-flash-lite",
+ "verified": true
+ },
+ {
+ "provider_model": "google/gemini-2.5-flash",
+ "canonical_model": "google/gemini-2.5-flash",
+ "verified": true
+ },
+ {
+ "provider_model": "google/gemini-2.5-flash-lite",
+ "canonical_model": "google/gemini-2.5-flash-lite",
+ "verified": true
+ },
+ {
+ "provider_model": "google/gemini-2.5-flash-lite-preview-09-2025",
+ "canonical_model": "google/gemini-2.5-flash-lite",
+ "verified": true
+ },
+ {
+ "provider_model": "google/gemini-2.5-flash-preview-09-2025",
+ "canonical_model": "google/gemini-2.5-flash",
+ "verified": true
+ },
+ {
+ "provider_model": "google/gemini-2.5-pro",
+ "canonical_model": "google/gemini-2.5-pro",
+ "verified": true
+ },
+ {
+ "provider_model": "google/gemini-2.5-pro-preview",
+ "canonical_model": "google/gemini-2.5-pro",
+ "verified": true
+ },
+ {
+ "provider_model": "google/gemini-2.5-pro-preview-05-06",
+ "canonical_model": "google/gemini-2.5-pro",
+ "verified": true
+ },
+ {
+ "provider_model": "google/gemini-3-pro-preview",
+ "canonical_model": "google/gemini-3-pro",
+ "verified": true
+ },
+ {
+ "provider_model": "google/gemma-3-27b-it",
+ "canonical_model": "google/gemma-3-27b-it",
+ "verified": true
+ },
+ {
+ "provider_model": "meta-llama/llama-3-70b-instruct",
+ "canonical_model": "meta-llama/llama-3-70b-instruct",
+ "verified": true
+ },
+ {
+ "provider_model": "meta-llama/llama-3-8b-instruct",
+ "canonical_model": "meta-llama/llama-3-8b-instruct",
+ "verified": true
+ },
+ {
+ "provider_model": "meta-llama/llama-3.1-405b-instruct",
+ "canonical_model": "meta-llama/llama-3.1-405b-instruct",
+ "verified": true
+ },
+ {
+ "provider_model": "meta-llama/llama-3.1-70b-instruct",
+ "canonical_model": "meta-llama/llama-3.1-70b-instruct",
+ "verified": true
+ },
+ {
+ "provider_model": "meta-llama/llama-3.1-8b-instruct",
+ "canonical_model": "meta-llama/llama-3.1-8b-instruct",
+ "verified": true
+ },
+ {
+ "provider_model": "meta-llama/llama-3.2-3b-instruct",
+ "canonical_model": "meta-llama/llama-3.2-3b-instruct",
+ "verified": true
+ },
+ {
+ "provider_model": "meta-llama/llama-3.3-70b-instruct",
+ "canonical_model": "meta-llama/llama-3.3-70b-instruct",
+ "verified": true
+ },
+ {
+ "provider_model": "meta-llama/llama-4-maverick",
+ "canonical_model": "meta-llama/llama-4-maverick",
+ "verified": true
+ },
+ {
+ "provider_model": "meta-llama/llama-4-scout",
+ "canonical_model": "meta-llama/llama-4-scout",
+ "verified": true
+ },
+ {
+ "provider_model": "mistralai/codestral-2508",
+ "canonical_model": "mistralai/codestral",
+ "verified": true
+ },
+ {
+ "provider_model": "mistralai/devstral-medium",
+ "canonical_model": "mistralai/devstral-medium",
+ "verified": true
+ },
+ {
+ "provider_model": "mistralai/devstral-small",
+ "canonical_model": "mistralai/devstral-small",
+ "verified": true
+ },
+ {
+ "provider_model": "mistralai/ministral-14b-2512",
+ "canonical_model": "mistralai/ministral-14b",
+ "verified": true
+ },
+ {
+ "provider_model": "mistralai/ministral-3b",
+ "canonical_model": "mistralai/ministral-3b",
+ "verified": true
+ },
+ {
+ "provider_model": "mistralai/ministral-3b-2512",
+ "canonical_model": "mistralai/ministral-3b",
+ "verified": true
+ },
+ {
+ "provider_model": "mistralai/ministral-8b",
+ "canonical_model": "mistralai/ministral-8b",
+ "verified": true
+ },
+ {
+ "provider_model": "mistralai/ministral-8b-2512",
+ "canonical_model": "mistralai/ministral-8b",
+ "verified": true
+ },
+ {
+ "provider_model": "mistralai/mistral-7b-instruct",
+ "canonical_model": "mistralai/mistral-7b-instruct",
+ "verified": true
+ },
+ {
+ "provider_model": "mistralai/mistral-large",
+ "canonical_model": "mistralai/mistral-large",
+ "verified": true
+ },
+ {
+ "provider_model": "mistralai/mistral-large-2407",
+ "canonical_model": "mistralai/mistral-large",
+ "verified": true
+ },
+ {
+ "provider_model": "mistralai/mistral-large-2411",
+ "canonical_model": "mistralai/mistral-large",
+ "verified": true
+ },
+ {
+ "provider_model": "mistralai/mistral-large-2512",
+ "canonical_model": "mistralai/mistral-large",
+ "verified": true
+ },
+ {
+ "provider_model": "mistralai/mistral-medium-3",
+ "canonical_model": "mistralai/mistral-medium-3",
+ "verified": true
+ },
+ {
+ "provider_model": "mistralai/mistral-medium-3.1",
+ "canonical_model": "mistralai/mistral-medium-3.1",
+ "verified": true
+ },
+ {
+ "provider_model": "mistralai/mistral-nemo",
+ "canonical_model": "mistralai/mistral-nemo",
+ "verified": true
+ },
+ {
+ "provider_model": "mistralai/mistral-saba",
+ "canonical_model": "mistralai/mistral-saba",
+ "verified": true
+ },
+ {
+ "provider_model": "mistralai/mistral-small-24b-instruct-2501",
+ "canonical_model": "mistralai/mistral-small-24b-instruct",
+ "verified": true
+ },
+ {
+ "provider_model": "mistralai/mistral-small-3.1-24b-instruct",
+ "canonical_model": "mistralai/mistral-small-3.1-24b-instruct",
+ "verified": true
+ },
+ {
+ "provider_model": "mistralai/mistral-small-3.2-24b-instruct",
+ "canonical_model": "mistralai/mistral-small-3.2-24b-instruct",
+ "verified": true
+ },
+ {
+ "provider_model": "mistralai/mistral-tiny",
+ "canonical_model": "mistralai/mistral-tiny",
+ "verified": true
+ },
+ {
+ "provider_model": "mistralai/mixtral-8x22b-instruct",
+ "canonical_model": "mistralai/mixtral-8x22b-instruct",
+ "verified": true
+ },
+ {
+ "provider_model": "mistralai/mixtral-8x7b-instruct",
+ "canonical_model": "mistralai/mixtral-8x7b-instruct",
+ "verified": true
+ },
+ {
+ "provider_model": "mistralai/pixtral-12b",
+ "canonical_model": "mistralai/pixtral-12b",
+ "verified": true
+ },
+ {
+ "provider_model": "mistralai/pixtral-large-2411",
+ "canonical_model": "mistralai/pixtral-large",
+ "verified": true
+ },
+ {
+ "provider_model": "mistralai/voxtral-small-24b-2507",
+ "canonical_model": "mistralai/voxtral-small-24b",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/codex-mini",
+ "canonical_model": "openai/codex-mini",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-3.5-turbo",
+ "canonical_model": "openai/gpt-3.5-turbo",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-3.5-turbo-0613",
+ "canonical_model": "openai/gpt-3.5-turbo",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-3.5-turbo-16k",
+ "canonical_model": "openai/gpt-3.5-turbo-16k",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-4",
+ "canonical_model": "openai/gpt-4",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-4-0314",
+ "canonical_model": "openai/gpt-4",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-4-1106-preview",
+ "canonical_model": "openai/gpt-4",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-4-turbo",
+ "canonical_model": "openai/gpt-4-turbo",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-4-turbo-preview",
+ "canonical_model": "openai/gpt-4-turbo",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-4.1",
+ "canonical_model": "openai/gpt-4.1",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-4.1-mini",
+ "canonical_model": "openai/gpt-4.1-mini",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-4.1-nano",
+ "canonical_model": "openai/gpt-4.1-nano",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-4o",
+ "canonical_model": "openai/gpt-4o",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-4o-2024-05-13",
+ "canonical_model": "openai/gpt-4o",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-4o-2024-08-06",
+ "canonical_model": "openai/gpt-4o",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-4o-2024-11-20",
+ "canonical_model": "openai/gpt-4o",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-4o-audio-preview",
+ "canonical_model": "openai/gpt-4o-audio",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-4o-mini",
+ "canonical_model": "openai/gpt-4o-mini",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-4o-mini-2024-07-18",
+ "canonical_model": "openai/gpt-4o-mini",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-4o:extended",
+ "canonical_model": "openai/gpt-4o:extended",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-5",
+ "canonical_model": "openai/gpt-5",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-5-codex",
+ "canonical_model": "openai/gpt-5-codex",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-5-image",
+ "canonical_model": "openai/gpt-5-image",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-5-image-mini",
+ "canonical_model": "openai/gpt-5-image-mini",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-5-mini",
+ "canonical_model": "openai/gpt-5-mini",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-5-nano",
+ "canonical_model": "openai/gpt-5-nano",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-5-pro",
+ "canonical_model": "openai/gpt-5-pro",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-5.1",
+ "canonical_model": "openai/gpt-5.1",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-5.1-chat",
+ "canonical_model": "openai/gpt-5.1-chat",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-5.1-codex",
+ "canonical_model": "openai/gpt-5.1-codex",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-5.1-codex-max",
+ "canonical_model": "openai/gpt-5.1-codex-max",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-5.1-codex-mini",
+ "canonical_model": "openai/gpt-5.1-codex-mini",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-oss-120b",
+ "canonical_model": "openai/gpt-oss-120b",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-oss-120b:exacto",
+ "canonical_model": "openai/gpt-oss-120b",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-oss-20b",
+ "canonical_model": "openai/gpt-oss-20b",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/gpt-oss-safeguard-20b",
+ "canonical_model": "openai/gpt-oss-safeguard-20b",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/o1",
+ "canonical_model": "openai/o1",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/o3",
+ "canonical_model": "openai/o3",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/o3-deep-research",
+ "canonical_model": "openai/o3-deep-research",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/o3-mini",
+ "canonical_model": "openai/o3-mini",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/o3-mini-high",
+ "canonical_model": "openai/o3-mini-high",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/o3-pro",
+ "canonical_model": "openai/o3-pro",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/o4-mini",
+ "canonical_model": "openai/o4-mini",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/o4-mini-deep-research",
+ "canonical_model": "openai/o4-mini-deep-research",
+ "verified": true
+ },
+ {
+ "provider_model": "openai/o4-mini-high",
+ "canonical_model": "openai/o4-mini-high",
+ "verified": true
+ },
+ {
+ "provider_model": "qwen/qwen-2.5-72b-instruct",
+ "canonical_model": "qwen/qwen-2.5-72b-instruct",
+ "verified": true
+ },
+ {
+ "provider_model": "qwen/qwen-max",
+ "canonical_model": "qwen/qwen-max",
+ "verified": true
+ },
+ {
+ "provider_model": "qwen/qwen-plus",
+ "canonical_model": "qwen/qwen-plus",
+ "verified": true
+ },
+ {
+ "provider_model": "qwen/qwen-plus-2025-07-28",
+ "canonical_model": "qwen/qwen-plus",
+ "verified": true
+ },
+ {
+ "provider_model": "qwen/qwen-plus-2025-07-28:thinking",
+ "canonical_model": "qwen/qwen-plus-2025-07-28:thinking",
+ "verified": true
+ },
+ {
+ "provider_model": "qwen/qwen-turbo",
+ "canonical_model": "qwen/qwen-turbo",
+ "verified": true
+ },
+ {
+ "provider_model": "qwen/qwen-vl-max",
+ "canonical_model": "qwen/qwen-vl-max",
+ "verified": true
+ },
+ {
+ "provider_model": "qwen/qwen3-14b",
+ "canonical_model": "qwen/qwen3-14b",
+ "verified": true
+ },
+ {
+ "provider_model": "qwen/qwen3-235b-a22b",
+ "canonical_model": "qwen/qwen3-235b-a22b",
+ "verified": true
+ },
+ {
+ "provider_model": "qwen/qwen3-235b-a22b-2507",
+ "canonical_model": "qwen/qwen3-235b-a22b",
+ "verified": true
+ },
+ {
+ "provider_model": "qwen/qwen3-235b-a22b-thinking-2507",
+ "canonical_model": "qwen/qwen3-235b-a22b-thinking",
+ "verified": true
+ },
+ {
+ "provider_model": "qwen/qwen3-30b-a3b",
+ "canonical_model": "qwen/qwen3-30b-a3b",
+ "verified": true
+ },
+ {
+ "provider_model": "qwen/qwen3-30b-a3b-instruct-2507",
+ "canonical_model": "qwen/qwen3-30b-a3b-instruct",
+ "verified": true
+ },
+ {
+ "provider_model": "qwen/qwen3-30b-a3b-thinking-2507",
+ "canonical_model": "qwen/qwen3-30b-a3b-thinking",
+ "verified": true
+ },
+ {
+ "provider_model": "qwen/qwen3-32b",
+ "canonical_model": "qwen/qwen3-32b",
+ "verified": true
+ },
+ {
+ "provider_model": "qwen/qwen3-8b",
+ "canonical_model": "qwen/qwen3-8b",
+ "verified": true
+ },
+ {
+ "provider_model": "qwen/qwen3-coder",
+ "canonical_model": "qwen/qwen3-coder",
+ "verified": true
+ },
+ {
+ "provider_model": "qwen/qwen3-coder-30b-a3b-instruct",
+ "canonical_model": "qwen/qwen3-coder-30b-a3b-instruct",
+ "verified": true
+ },
+ {
+ "provider_model": "qwen/qwen3-coder-flash",
+ "canonical_model": "qwen/qwen3-coder-flash",
+ "verified": true
+ },
+ {
+ "provider_model": "qwen/qwen3-coder-plus",
+ "canonical_model": "qwen/qwen3-coder-plus",
+ "verified": true
+ },
+ {
+ "provider_model": "qwen/qwen3-coder:exacto",
+ "canonical_model": "qwen/qwen3-coder",
+ "verified": true
+ },
+ {
+ "provider_model": "qwen/qwen3-max",
+ "canonical_model": "qwen/qwen3-max",
+ "verified": true
+ },
+ {
+ "provider_model": "qwen/qwen3-next-80b-a3b-instruct",
+ "canonical_model": "qwen/qwen3-next-80b-a3b-instruct",
+ "verified": true
+ },
+ {
+ "provider_model": "qwen/qwen3-next-80b-a3b-thinking",
+ "canonical_model": "qwen/qwen3-next-80b-a3b-thinking",
+ "verified": true
+ },
+ {
+ "provider_model": "qwen/qwen3-vl-235b-a22b-instruct",
+ "canonical_model": "qwen/qwen3-vl-235b-a22b-instruct",
+ "verified": true
+ },
+ {
+ "provider_model": "qwen/qwen3-vl-235b-a22b-thinking",
+ "canonical_model": "qwen/qwen3-vl-235b-a22b-thinking",
+ "verified": true
+ },
+ {
+ "provider_model": "qwen/qwen3-vl-30b-a3b-instruct",
+ "canonical_model": "qwen/qwen3-vl-30b-a3b-instruct",
+ "verified": true
+ },
+ {
+ "provider_model": "qwen/qwen3-vl-30b-a3b-thinking",
+ "canonical_model": "qwen/qwen3-vl-30b-a3b-thinking",
+ "verified": true
+ },
+ {
+ "provider_model": "qwen/qwen3-vl-8b-instruct",
+ "canonical_model": "qwen/qwen3-vl-8b-instruct",
+ "verified": true
+ },
+ {
+ "provider_model": "qwen/qwen3-vl-8b-thinking",
+ "canonical_model": "qwen/qwen3-vl-8b-thinking",
+ "verified": true
+ },
+ {
+ "provider_model": "qwen/qwq-32b",
+ "canonical_model": "qwen/qwq-32b",
+ "verified": true
+ },
+ {
+ "provider_model": "x-ai/grok-3",
+ "canonical_model": "x-ai/grok-3",
+ "verified": true
+ },
+ {
+ "provider_model": "x-ai/grok-3-mini",
+ "canonical_model": "x-ai/grok-3-mini",
+ "verified": true
+ },
+ {
+ "provider_model": "x-ai/grok-4",
+ "canonical_model": "x-ai/grok-4",
+ "verified": true
+ },
+ {
+ "provider_model": "x-ai/grok-4-fast",
+ "canonical_model": "x-ai/grok-4-fast",
+ "verified": true
+ },
+ {
+ "provider_model": "x-ai/grok-4.1-fast",
+ "canonical_model": "x-ai/grok-4.1-fast",
+ "verified": true
+ },
+ {
+ "provider_model": "x-ai/grok-code-fast-1",
+ "canonical_model": "x-ai/grok-code-fast-1",
+ "verified": true
+ }
+ ],
+ "openai": [
+ {
+ "provider_model": "chatgpt-4o-latest",
+ "canonical_model": "openai/chatgpt-4o",
+ "verified": true
+ },
+ {
+ "provider_model": "codex-mini-latest",
+ "canonical_model": "openai/codex-mini",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-3.5-turbo",
+ "canonical_model": "openai/gpt-3.5-turbo",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-3.5-turbo-0125",
+ "canonical_model": "openai/gpt-3.5-turbo",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-3.5-turbo-1106",
+ "canonical_model": "openai/gpt-3.5-turbo",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-3.5-turbo-16k",
+ "canonical_model": "openai/gpt-3.5-turbo-16k",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-3.5-turbo-instruct",
+ "canonical_model": "openai/gpt-3.5-turbo-instruct",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-3.5-turbo-instruct-0914",
+ "canonical_model": "openai/gpt-3.5-turbo-instruct",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4",
+ "canonical_model": "openai/gpt-4",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4-0125-preview",
+ "canonical_model": "openai/gpt-4",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4-0314",
+ "canonical_model": "openai/gpt-4",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4-0613",
+ "canonical_model": "openai/gpt-4",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4-1106-preview",
+ "canonical_model": "openai/gpt-4",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4-turbo",
+ "canonical_model": "openai/gpt-4-turbo",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4-turbo-2024-04-09",
+ "canonical_model": "openai/gpt-4-turbo",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4-turbo-preview",
+ "canonical_model": "openai/gpt-4-turbo",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4.1",
+ "canonical_model": "openai/gpt-4.1",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4.1-2025-04-14",
+ "canonical_model": "openai/gpt-4.1",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4.1-mini",
+ "canonical_model": "openai/gpt-4.1-mini",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4.1-mini-2025-04-14",
+ "canonical_model": "openai/gpt-4.1-mini",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4.1-nano",
+ "canonical_model": "openai/gpt-4.1-nano",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4.1-nano-2025-04-14",
+ "canonical_model": "openai/gpt-4.1-nano",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4o",
+ "canonical_model": "openai/gpt-4o",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4o-2024-05-13",
+ "canonical_model": "openai/gpt-4o",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4o-2024-08-06",
+ "canonical_model": "openai/gpt-4o",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4o-2024-11-20",
+ "canonical_model": "openai/gpt-4o",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4o-audio-preview",
+ "canonical_model": "openai/gpt-4o-audio",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4o-audio-preview-2024-12-17",
+ "canonical_model": "openai/gpt-4o-audio",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4o-audio-preview-2025-06-03",
+ "canonical_model": "openai/gpt-4o-audio",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4o-mini",
+ "canonical_model": "openai/gpt-4o-mini",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4o-mini-2024-07-18",
+ "canonical_model": "openai/gpt-4o-mini",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4o-mini-search-preview",
+ "canonical_model": "openai/gpt-4o-mini-search",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4o-mini-search-preview-2025-03-11",
+ "canonical_model": "openai/gpt-4o-mini-search",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4o-search-preview",
+ "canonical_model": "openai/gpt-4o-search",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-4o-search-preview-2025-03-11",
+ "canonical_model": "openai/gpt-4o-search",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-5",
+ "canonical_model": "openai/gpt-5",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-5-2025-08-07",
+ "canonical_model": "openai/gpt-5",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-5-chat-latest",
+ "canonical_model": "openai/gpt-5-chat",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-5-codex",
+ "canonical_model": "openai/gpt-5-codex",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-5-mini",
+ "canonical_model": "openai/gpt-5-mini",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-5-mini-2025-08-07",
+ "canonical_model": "openai/gpt-5-mini",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-5-nano",
+ "canonical_model": "openai/gpt-5-nano",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-5-nano-2025-08-07",
+ "canonical_model": "openai/gpt-5-nano",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-5-pro",
+ "canonical_model": "openai/gpt-5-pro",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-5-pro-2025-10-06",
+ "canonical_model": "openai/gpt-5-pro",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-5.1",
+ "canonical_model": "openai/gpt-5.1",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-5.1-2025-11-13",
+ "canonical_model": "openai/gpt-5.1",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-5.1-chat-latest",
+ "canonical_model": "openai/gpt-5.1-chat",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-5.1-codex",
+ "canonical_model": "openai/gpt-5.1-codex",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-5.1-codex-max",
+ "canonical_model": "openai/gpt-5.1-codex-max",
+ "verified": true
+ },
+ {
+ "provider_model": "gpt-5.1-codex-mini",
+ "canonical_model": "openai/gpt-5.1-codex-mini",
+ "verified": true
+ },
+ {
+ "provider_model": "o1",
+ "canonical_model": "openai/o1",
+ "verified": true
+ },
+ {
+ "provider_model": "o1-2024-12-17",
+ "canonical_model": "openai/o1",
+ "verified": true
+ },
+ {
+ "provider_model": "o1-pro",
+ "canonical_model": "openai/o1-pro",
+ "verified": true
+ },
+ {
+ "provider_model": "o1-pro-2025-03-19",
+ "canonical_model": "openai/o1-pro",
+ "verified": true
+ },
+ {
+ "provider_model": "o3",
+ "canonical_model": "openai/o3",
+ "verified": true
+ },
+ {
+ "provider_model": "o3-2025-04-16",
+ "canonical_model": "openai/o3",
+ "verified": true
+ },
+ {
+ "provider_model": "o3-deep-research",
+ "canonical_model": "openai/o3-deep-research",
+ "verified": true
+ },
+ {
+ "provider_model": "o3-deep-research-2025-06-26",
+ "canonical_model": "openai/o3-deep-research",
+ "verified": true
+ },
+ {
+ "provider_model": "o3-mini",
+ "canonical_model": "openai/o3-mini",
+ "verified": true
+ },
+ {
+ "provider_model": "o3-mini-2025-01-31",
+ "canonical_model": "openai/o3-mini",
+ "verified": true
+ },
+ {
+ "provider_model": "o3-pro",
+ "canonical_model": "openai/o3-pro",
+ "verified": true
+ },
+ {
+ "provider_model": "o3-pro-2025-06-10",
+ "canonical_model": "openai/o3-pro",
+ "verified": true
+ },
+ {
+ "provider_model": "o4-mini",
+ "canonical_model": "openai/o4-mini",
+ "verified": true
+ },
+ {
+ "provider_model": "o4-mini-2025-04-16",
+ "canonical_model": "openai/o4-mini",
+ "verified": true
+ },
+ {
+ "provider_model": "o4-mini-deep-research",
+ "canonical_model": "openai/o4-mini-deep-research",
+ "verified": true
+ },
+ {
+ "provider_model": "o4-mini-deep-research-2025-06-26",
+ "canonical_model": "openai/o4-mini-deep-research",
+ "verified": true
+ }
+ ]
+ },
+ "mapped_models": [
+ {
+ "provider": "anthropic",
+ "model": "claude-opus-4-20250514",
+ "canonical": "anthropic/claude-opus-4"
+ },
+ {
+ "provider": "anthropic",
+ "model": "claude-opus-4-1-20250805",
+ "canonical": "anthropic/claude-opus-4.1"
+ },
+ {
+ "provider": "anthropic",
+ "model": "claude-3-5-haiku-20241022",
+ "canonical": "anthropic/claude-3.5-haiku"
+ },
+ {
+ "provider": "anthropic",
+ "model": "claude-3-opus-20240229",
+ "canonical": "anthropic/claude-3-opus"
+ },
+ {
+ "provider": "anthropic",
+ "model": "claude-haiku-4-5-20251001",
+ "canonical": "anthropic/claude-haiku-4.5"
+ },
+ {
+ "provider": "anthropic",
+ "model": "claude-sonnet-4-5-20250929",
+ "canonical": "anthropic/claude-sonnet-4.5"
+ },
+ {
+ "provider": "anthropic",
+ "model": "claude-opus-4-5-20251101",
+ "canonical": "anthropic/claude-opus-4.5"
+ },
+ {
+ "provider": "anthropic",
+ "model": "claude-3-7-sonnet-20250219",
+ "canonical": "anthropic/claude-3.7-sonnet"
+ },
+ {
+ "provider": "anthropic",
+ "model": "claude-sonnet-4-20250514",
+ "canonical": "anthropic/claude-sonnet-4"
+ },
+ {
+ "provider": "anthropic",
+ "model": "claude-3-haiku-20240307",
+ "canonical": "anthropic/claude-3-haiku"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-5.1-chat-latest",
+ "canonical": "openai/gpt-5.1-chat"
+ },
+ {
+ "provider": "openai",
+ "model": "o4-mini-2025-04-16",
+ "canonical": "openai/o4-mini"
+ },
+ {
+ "provider": "openai",
+ "model": "o3-mini-2025-01-31",
+ "canonical": "openai/o3-mini"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-4.1-mini-2025-04-14",
+ "canonical": "openai/gpt-4.1-mini"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-5.1",
+ "canonical": "openai/gpt-5.1"
+ },
+ {
+ "provider": "openai",
+ "model": "o1-pro",
+ "canonical": "openai/o1-pro"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-4o",
+ "canonical": "openai/gpt-4o"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-5-pro",
+ "canonical": "openai/gpt-5-pro"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-4-0125-preview",
+ "canonical": "openai/gpt-4"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-4.1-2025-04-14",
+ "canonical": "openai/gpt-4.1"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-3.5-turbo-1106",
+ "canonical": "openai/gpt-3.5-turbo"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-4o-2024-11-20",
+ "canonical": "openai/gpt-4o"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-3.5-turbo-instruct-0914",
+ "canonical": "openai/gpt-3.5-turbo-instruct"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-5-nano",
+ "canonical": "openai/gpt-5-nano"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-4.1-nano",
+ "canonical": "openai/gpt-4.1-nano"
+ },
+ {
+ "provider": "openai",
+ "model": "o3-deep-research-2025-06-26",
+ "canonical": "openai/o3-deep-research"
+ },
+ {
+ "provider": "openai",
+ "model": "o1-pro-2025-03-19",
+ "canonical": "openai/o1-pro"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-4-1106-preview",
+ "canonical": "openai/gpt-4"
+ },
+ {
+ "provider": "openai",
+ "model": "codex-mini-latest",
+ "canonical": "openai/codex-mini"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-3.5-turbo-16k",
+ "canonical": "openai/gpt-3.5-turbo-16k"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-5.1-2025-11-13",
+ "canonical": "openai/gpt-5.1"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-4.1",
+ "canonical": "openai/gpt-4.1"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-4o-mini",
+ "canonical": "openai/gpt-4o-mini"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-5-chat-latest",
+ "canonical": "openai/gpt-5-chat"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-4o-search-preview",
+ "canonical": "openai/gpt-4o-search"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-4.1-nano-2025-04-14",
+ "canonical": "openai/gpt-4.1-nano"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-4o-audio-preview",
+ "canonical": "openai/gpt-4o-audio"
+ },
+ {
+ "provider": "openai",
+ "model": "o1",
+ "canonical": "openai/o1"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-5-mini",
+ "canonical": "openai/gpt-5-mini"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-5",
+ "canonical": "openai/gpt-5"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-4o-search-preview-2025-03-11",
+ "canonical": "openai/gpt-4o-search"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-3.5-turbo",
+ "canonical": "openai/gpt-3.5-turbo"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-3.5-turbo-instruct",
+ "canonical": "openai/gpt-3.5-turbo-instruct"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-5-codex",
+ "canonical": "openai/gpt-5-codex"
+ },
+ {
+ "provider": "openai",
+ "model": "o3",
+ "canonical": "openai/o3"
+ },
+ {
+ "provider": "openai",
+ "model": "o4-mini-deep-research-2025-06-26",
+ "canonical": "openai/o4-mini-deep-research"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-4o-2024-05-13",
+ "canonical": "openai/gpt-4o"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-4-turbo-2024-04-09",
+ "canonical": "openai/gpt-4-turbo"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-4o-audio-preview-2024-12-17",
+ "canonical": "openai/gpt-4o-audio"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-4",
+ "canonical": "openai/gpt-4"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-5-nano-2025-08-07",
+ "canonical": "openai/gpt-5-nano"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-4-turbo",
+ "canonical": "openai/gpt-4-turbo"
+ },
+ {
+ "provider": "openai",
+ "model": "o3-pro",
+ "canonical": "openai/o3-pro"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-4-turbo-preview",
+ "canonical": "openai/gpt-4-turbo"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-4o-2024-08-06",
+ "canonical": "openai/gpt-4o"
+ },
+ {
+ "provider": "openai",
+ "model": "o3-deep-research",
+ "canonical": "openai/o3-deep-research"
+ },
+ {
+ "provider": "openai",
+ "model": "o3-2025-04-16",
+ "canonical": "openai/o3"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-5.1-codex-mini",
+ "canonical": "openai/gpt-5.1-codex-mini"
+ },
+ {
+ "provider": "openai",
+ "model": "o4-mini-deep-research",
+ "canonical": "openai/o4-mini-deep-research"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-3.5-turbo-0125",
+ "canonical": "openai/gpt-3.5-turbo"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-4o-mini-search-preview-2025-03-11",
+ "canonical": "openai/gpt-4o-mini-search"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-4-0613",
+ "canonical": "openai/gpt-4"
+ },
+ {
+ "provider": "openai",
+ "model": "o3-mini",
+ "canonical": "openai/o3-mini"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-4o-mini-search-preview",
+ "canonical": "openai/gpt-4o-mini-search"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-5.1-codex",
+ "canonical": "openai/gpt-5.1-codex"
+ },
+ {
+ "provider": "openai",
+ "model": "o4-mini",
+ "canonical": "openai/o4-mini"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-5-2025-08-07",
+ "canonical": "openai/gpt-5"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-4-0314",
+ "canonical": "openai/gpt-4"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-4o-mini-2024-07-18",
+ "canonical": "openai/gpt-4o-mini"
+ },
+ {
+ "provider": "openai",
+ "model": "o3-pro-2025-06-10",
+ "canonical": "openai/o3-pro"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-5.1-codex-max",
+ "canonical": "openai/gpt-5.1-codex-max"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-5-pro-2025-10-06",
+ "canonical": "openai/gpt-5-pro"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-5-mini-2025-08-07",
+ "canonical": "openai/gpt-5-mini"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-4o-audio-preview-2025-06-03",
+ "canonical": "openai/gpt-4o-audio"
+ },
+ {
+ "provider": "openai",
+ "model": "gpt-4.1-mini",
+ "canonical": "openai/gpt-4.1-mini"
+ },
+ {
+ "provider": "openai",
+ "model": "chatgpt-4o-latest",
+ "canonical": "openai/chatgpt-4o"
+ },
+ {
+ "provider": "openai",
+ "model": "o1-2024-12-17",
+ "canonical": "openai/o1"
+ },
+ {
+ "provider": "openrouter",
+ "model": "openai/gpt-4o-2024-11-20",
+ "canonical": "openai/gpt-4o"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/mistral-large",
+ "canonical": "mistralai/mistral-large"
+ },
+ {
+ "provider": "openrouter",
+ "model": "google/gemini-2.5-pro",
+ "canonical": "google/gemini-2.5-pro"
+ },
+ {
+ "provider": "openrouter",
+ "model": "openai/gpt-5.1",
+ "canonical": "openai/gpt-5.1"
+ },
+ {
+ "provider": "openrouter",
+ "model": "openai/o4-mini-deep-research",
+ "canonical": "openai/o4-mini-deep-research"
+ },
+ {
+ "provider": "openrouter",
+ "model": "openai/gpt-4-1106-preview",
+ "canonical": "openai/gpt-4"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/ministral-3b-2512",
+ "canonical": "mistralai/ministral-3b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "openai/gpt-4o:extended",
+ "canonical": "openai/gpt-4o:extended"
+ },
+ {
+ "provider": "openrouter",
+ "model": "anthropic/claude-sonnet-4",
+ "canonical": "anthropic/claude-sonnet-4"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/ministral-3b",
+ "canonical": "mistralai/ministral-3b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/pixtral-12b",
+ "canonical": "mistralai/pixtral-12b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "ai21/jamba-large-1.7",
+ "canonical": "ai21/jamba-large-1.7"
+ },
+ {
+ "provider": "openrouter",
+ "model": "anthropic/claude-3-haiku",
+ "canonical": "anthropic/claude-3-haiku"
+ },
+ {
+ "provider": "openrouter",
+ "model": "deepseek/deepseek-r1-distill-llama-70b",
+ "canonical": "deepseek/deepseek-r1-distill-llama-70b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "openai/gpt-4o-2024-05-13",
+ "canonical": "openai/gpt-4o"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/mistral-medium-3.1",
+ "canonical": "mistralai/mistral-medium-3.1"
+ },
+ {
+ "provider": "openrouter",
+ "model": "meta-llama/llama-3-70b-instruct",
+ "canonical": "meta-llama/llama-3-70b-instruct"
+ },
+ {
+ "provider": "openrouter",
+ "model": "google/gemini-2.5-flash-lite-preview-09-2025",
+ "canonical": "google/gemini-2.5-flash-lite"
+ },
+ {
+ "provider": "openrouter",
+ "model": "openai/gpt-3.5-turbo-16k",
+ "canonical": "openai/gpt-3.5-turbo-16k"
+ },
+ {
+ "provider": "openrouter",
+ "model": "google/gemma-3-27b-it",
+ "canonical": "google/gemma-3-27b-it"
+ },
+ {
+ "provider": "openrouter",
+ "model": "deepseek/deepseek-v3.1-terminus:exacto",
+ "canonical": "deepseek/deepseek-v3.1-terminus"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/mistral-large-2411",
+ "canonical": "mistralai/mistral-large"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/devstral-medium",
+ "canonical": "mistralai/devstral-medium"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen3-vl-30b-a3b-thinking",
+ "canonical": "qwen/qwen3-vl-30b-a3b-thinking"
+ },
+ {
+ "provider": "openrouter",
+ "model": "x-ai/grok-4.1-fast",
+ "canonical": "x-ai/grok-4.1-fast"
+ },
+ {
+ "provider": "openrouter",
+ "model": "meta-llama/llama-3.1-405b-instruct",
+ "canonical": "meta-llama/llama-3.1-405b-instruct"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen3-30b-a3b",
+ "canonical": "qwen/qwen3-30b-a3b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen3-coder:exacto",
+ "canonical": "qwen/qwen3-coder"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen3-next-80b-a3b-instruct",
+ "canonical": "qwen/qwen3-next-80b-a3b-instruct"
+ },
+ {
+ "provider": "openrouter",
+ "model": "x-ai/grok-3",
+ "canonical": "x-ai/grok-3"
+ },
+ {
+ "provider": "openrouter",
+ "model": "google/gemini-2.0-flash-001",
+ "canonical": "google/gemini-2.0-flash"
+ },
+ {
+ "provider": "openrouter",
+ "model": "meta-llama/llama-4-maverick",
+ "canonical": "meta-llama/llama-4-maverick"
+ },
+ {
+ "provider": "openrouter",
+ "model": "anthropic/claude-3.5-sonnet",
+ "canonical": "anthropic/claude-3.5-sonnet"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/mistral-tiny",
+ "canonical": "mistralai/mistral-tiny"
+ },
+ {
+ "provider": "openrouter",
+ "model": "openai/gpt-5.1-codex",
+ "canonical": "openai/gpt-5.1-codex"
+ },
+ {
+ "provider": "openrouter",
+ "model": "openai/gpt-5-image-mini",
+ "canonical": "openai/gpt-5-image-mini"
+ },
+ {
+ "provider": "openrouter",
+ "model": "openai/gpt-5-pro",
+ "canonical": "openai/gpt-5-pro"
+ },
+ {
+ "provider": "openrouter",
+ "model": "openai/gpt-5.1-codex-max",
+ "canonical": "openai/gpt-5.1-codex-max"
+ },
+ {
+ "provider": "openrouter",
+ "model": "openai/gpt-4-0314",
+ "canonical": "openai/gpt-4"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen3-vl-8b-instruct",
+ "canonical": "qwen/qwen3-vl-8b-instruct"
+ },
+ {
+ "provider": "openrouter",
+ "model": "openai/o3",
+ "canonical": "openai/o3"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen3-235b-a22b",
+ "canonical": "qwen/qwen3-235b-a22b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "anthropic/claude-3.5-haiku",
+ "canonical": "anthropic/claude-3.5-haiku"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/ministral-8b-2512",
+ "canonical": "mistralai/ministral-8b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "google/gemini-2.5-flash",
+ "canonical": "google/gemini-2.5-flash"
+ },
+ {
+ "provider": "openrouter",
+ "model": "openai/gpt-4.1",
+ "canonical": "openai/gpt-4.1"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/voxtral-small-24b-2507",
+ "canonical": "mistralai/voxtral-small-24b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen3-coder",
+ "canonical": "qwen/qwen3-coder"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/ministral-8b",
+ "canonical": "mistralai/ministral-8b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/codestral-2508",
+ "canonical": "mistralai/codestral"
+ },
+ {
+ "provider": "openrouter",
+ "model": "meta-llama/llama-3.3-70b-instruct",
+ "canonical": "meta-llama/llama-3.3-70b-instruct"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen3-14b",
+ "canonical": "qwen/qwen3-14b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "openai/o4-mini",
+ "canonical": "openai/o4-mini"
+ },
+ {
+ "provider": "openrouter",
+ "model": "meta-llama/llama-3-8b-instruct",
+ "canonical": "meta-llama/llama-3-8b-instruct"
+ },
+ {
+ "provider": "openrouter",
+ "model": "openai/gpt-4o-2024-08-06",
+ "canonical": "openai/gpt-4o"
+ },
+ {
+ "provider": "openrouter",
+ "model": "openai/gpt-5",
+ "canonical": "openai/gpt-5"
+ },
+ {
+ "provider": "openrouter",
+ "model": "x-ai/grok-code-fast-1",
+ "canonical": "x-ai/grok-code-fast-1"
+ },
+ {
+ "provider": "openrouter",
+ "model": "anthropic/claude-opus-4",
+ "canonical": "anthropic/claude-opus-4"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen-vl-max",
+ "canonical": "qwen/qwen-vl-max"
+ },
+ {
+ "provider": "openrouter",
+ "model": "google/gemini-2.5-pro-preview-05-06",
+ "canonical": "google/gemini-2.5-pro"
+ },
+ {
+ "provider": "openrouter",
+ "model": "deepseek/deepseek-r1",
+ "canonical": "deepseek/deepseek-r1"
+ },
+ {
+ "provider": "openrouter",
+ "model": "openai/gpt-4-turbo",
+ "canonical": "openai/gpt-4-turbo"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen-max",
+ "canonical": "qwen/qwen-max"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/mistral-7b-instruct",
+ "canonical": "mistralai/mistral-7b-instruct"
+ },
+ {
+ "provider": "openrouter",
+ "model": "google/gemini-2.5-flash-lite",
+ "canonical": "google/gemini-2.5-flash-lite"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/mistral-large-2407",
+ "canonical": "mistralai/mistral-large"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/mistral-medium-3",
+ "canonical": "mistralai/mistral-medium-3"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen-2.5-72b-instruct",
+ "canonical": "qwen/qwen-2.5-72b-instruct"
+ },
+ {
+ "provider": "openrouter",
+ "model": "anthropic/claude-sonnet-4.5",
+ "canonical": "anthropic/claude-sonnet-4.5"
+ },
+ {
+ "provider": "openrouter",
+ "model": "openai/o3-mini",
+ "canonical": "openai/o3-mini"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/mistral-saba",
+ "canonical": "mistralai/mistral-saba"
+ },
+ {
+ "provider": "openrouter",
+ "model": "openai/gpt-5-nano",
+ "canonical": "openai/gpt-5-nano"
+ },
+ {
+ "provider": "openrouter",
+ "model": "openai/gpt-4.1-mini",
+ "canonical": "openai/gpt-4.1-mini"
+ },
+ {
+ "provider": "openrouter",
+ "model": "anthropic/claude-opus-4.5",
+ "canonical": "anthropic/claude-opus-4.5"
+ },
+ {
+ "provider": "openrouter",
+ "model": "x-ai/grok-4-fast",
+ "canonical": "x-ai/grok-4-fast"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/mixtral-8x7b-instruct",
+ "canonical": "mistralai/mixtral-8x7b-instruct"
+ },
+ {
+ "provider": "openrouter",
+ "model": "meta-llama/llama-3.2-3b-instruct",
+ "canonical": "meta-llama/llama-3.2-3b-instruct"
+ },
+ {
+ "provider": "openrouter",
+ "model": "openai/o4-mini-high",
+ "canonical": "openai/o4-mini-high"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen-plus-2025-07-28",
+ "canonical": "qwen/qwen-plus"
+ },
+ {
+ "provider": "openrouter",
+ "model": "openai/gpt-4o-audio-preview",
+ "canonical": "openai/gpt-4o-audio"
+ },
+ {
+ "provider": "openrouter",
+ "model": "google/gemini-2.5-pro-preview",
+ "canonical": "google/gemini-2.5-pro"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwq-32b",
+ "canonical": "qwen/qwq-32b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "meta-llama/llama-3.1-70b-instruct",
+ "canonical": "meta-llama/llama-3.1-70b-instruct"
+ },
+ {
+ "provider": "openrouter",
+ "model": "openai/gpt-4.1-nano",
+ "canonical": "openai/gpt-4.1-nano"
+ },
+ {
+ "provider": "openrouter",
+ "model": "anthropic/claude-3.7-sonnet:thinking",
+ "canonical": "anthropic/claude-3.7-sonnet:thinking"
+ },
+ {
+ "provider": "openrouter",
+ "model": "openai/o1",
+ "canonical": "openai/o1"
+ },
+ {
+ "provider": "openrouter",
+ "model": "cohere/command-r-08-2024",
+ "canonical": "cohere/command-r-08"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen-plus",
+ "canonical": "qwen/qwen-plus"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen3-235b-a22b-thinking-2507",
+ "canonical": "qwen/qwen3-235b-a22b-thinking"
+ },
+ {
+ "provider": "openrouter",
+ "model": "ai21/jamba-mini-1.7",
+ "canonical": "ai21/jamba-mini-1.7"
+ },
+ {
+ "provider": "openrouter",
+ "model": "x-ai/grok-3-mini",
+ "canonical": "x-ai/grok-3-mini"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen3-vl-235b-a22b-instruct",
+ "canonical": "qwen/qwen3-vl-235b-a22b-instruct"
+ },
+ {
+ "provider": "openrouter",
+ "model": "openai/gpt-oss-120b",
+ "canonical": "openai/gpt-oss-120b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "google/gemini-2.0-flash-lite-001",
+ "canonical": "google/gemini-2.0-flash-lite"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen3-coder-30b-a3b-instruct",
+ "canonical": "qwen/qwen3-coder-30b-a3b-instruct"
+ },
+ {
+ "provider": "openrouter",
+ "model": "openai/o3-mini-high",
+ "canonical": "openai/o3-mini-high"
+ },
+ {
+ "provider": "openrouter",
+ "model": "anthropic/claude-3.7-sonnet",
+ "canonical": "anthropic/claude-3.7-sonnet"
+ },
+ {
+ "provider": "openrouter",
+ "model": "deepseek/deepseek-v3.2-exp",
+ "canonical": "deepseek/deepseek"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen3-30b-a3b-thinking-2507",
+ "canonical": "qwen/qwen3-30b-a3b-thinking"
+ },
+ {
+ "provider": "openrouter",
+ "model": "deepseek/deepseek-chat-v3-0324",
+ "canonical": "deepseek/deepseek-chat"
+ },
+ {
+ "provider": "openrouter",
+ "model": "google/gemini-2.5-flash-preview-09-2025",
+ "canonical": "google/gemini-2.5-flash"
+ },
+ {
+ "provider": "openrouter",
+ "model": "openai/gpt-oss-safeguard-20b",
+ "canonical": "openai/gpt-oss-safeguard-20b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "openai/gpt-5-mini",
+ "canonical": "openai/gpt-5-mini"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen3-coder-plus",
+ "canonical": "qwen/qwen3-coder-plus"
+ },
+ {
+ "provider": "openrouter",
+ "model": "meta-llama/llama-3.1-8b-instruct",
+ "canonical": "meta-llama/llama-3.1-8b-instruct"
+ },
+ {
+ "provider": "openrouter",
+ "model": "openai/gpt-3.5-turbo",
+ "canonical": "openai/gpt-3.5-turbo"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/pixtral-large-2411",
+ "canonical": "mistralai/pixtral-large"
+ },
+ {
+ "provider": "openrouter",
+ "model": "deepseek/deepseek-v3.1-terminus",
+ "canonical": "deepseek/deepseek-v3.1-terminus"
+ },
+ {
+ "provider": "openrouter",
+ "model": "openai/gpt-4o-mini",
+ "canonical": "openai/gpt-4o-mini"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/mistral-nemo",
+ "canonical": "mistralai/mistral-nemo"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen3-next-80b-a3b-thinking",
+ "canonical": "qwen/qwen3-next-80b-a3b-thinking"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen3-vl-235b-a22b-thinking",
+ "canonical": "qwen/qwen3-vl-235b-a22b-thinking"
+ },
+ {
+ "provider": "openrouter",
+ "model": "openai/gpt-4o-mini-2024-07-18",
+ "canonical": "openai/gpt-4o-mini"
+ },
+ {
+ "provider": "openrouter",
+ "model": "deepseek/deepseek-r1-0528",
+ "canonical": "deepseek/deepseek-r1"
+ },
+ {
+ "provider": "openrouter",
+ "model": "google/gemini-3-pro-preview",
+ "canonical": "google/gemini-3-pro"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen3-8b",
+ "canonical": "qwen/qwen3-8b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "openai/o3-pro",
+ "canonical": "openai/o3-pro"
+ },
+ {
+ "provider": "openrouter",
+ "model": "x-ai/grok-4",
+ "canonical": "x-ai/grok-4"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/mixtral-8x22b-instruct",
+ "canonical": "mistralai/mixtral-8x22b-instruct"
+ },
+ {
+ "provider": "openrouter",
+ "model": "anthropic/claude-haiku-4.5",
+ "canonical": "anthropic/claude-haiku-4.5"
+ },
+ {
+ "provider": "openrouter",
+ "model": "deepseek/deepseek-chat",
+ "canonical": "deepseek/deepseek-chat"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/mistral-large-2512",
+ "canonical": "mistralai/mistral-large"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen-plus-2025-07-28:thinking",
+ "canonical": "qwen/qwen-plus-2025-07-28:thinking"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen-turbo",
+ "canonical": "qwen/qwen-turbo"
+ },
+ {
+ "provider": "openrouter",
+ "model": "openai/gpt-5.1-chat",
+ "canonical": "openai/gpt-5.1-chat"
+ },
+ {
+ "provider": "openrouter",
+ "model": "openai/gpt-5-image",
+ "canonical": "openai/gpt-5-image"
+ },
+ {
+ "provider": "openrouter",
+ "model": "openai/gpt-oss-20b",
+ "canonical": "openai/gpt-oss-20b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "openai/o3-deep-research",
+ "canonical": "openai/o3-deep-research"
+ },
+ {
+ "provider": "openrouter",
+ "model": "openai/gpt-5-codex",
+ "canonical": "openai/gpt-5-codex"
+ },
+ {
+ "provider": "openrouter",
+ "model": "openai/gpt-oss-120b:exacto",
+ "canonical": "openai/gpt-oss-120b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen3-235b-a22b-2507",
+ "canonical": "qwen/qwen3-235b-a22b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "openai/gpt-4",
+ "canonical": "openai/gpt-4"
+ },
+ {
+ "provider": "openrouter",
+ "model": "anthropic/claude-3-opus",
+ "canonical": "anthropic/claude-3-opus"
+ },
+ {
+ "provider": "openrouter",
+ "model": "meta-llama/llama-4-scout",
+ "canonical": "meta-llama/llama-4-scout"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/devstral-small",
+ "canonical": "mistralai/devstral-small"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/mistral-small-3.2-24b-instruct",
+ "canonical": "mistralai/mistral-small-3.2-24b-instruct"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/ministral-14b-2512",
+ "canonical": "mistralai/ministral-14b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/mistral-small-3.1-24b-instruct",
+ "canonical": "mistralai/mistral-small-3.1-24b-instruct"
+ },
+ {
+ "provider": "openrouter",
+ "model": "openai/gpt-4-turbo-preview",
+ "canonical": "openai/gpt-4-turbo"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen3-30b-a3b-instruct-2507",
+ "canonical": "qwen/qwen3-30b-a3b-instruct"
+ },
+ {
+ "provider": "openrouter",
+ "model": "anthropic/claude-opus-4.1",
+ "canonical": "anthropic/claude-opus-4.1"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen3-32b",
+ "canonical": "qwen/qwen3-32b"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen3-max",
+ "canonical": "qwen/qwen3-max"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen3-vl-8b-thinking",
+ "canonical": "qwen/qwen3-vl-8b-thinking"
+ },
+ {
+ "provider": "openrouter",
+ "model": "openai/codex-mini",
+ "canonical": "openai/codex-mini"
+ },
+ {
+ "provider": "openrouter",
+ "model": "openai/gpt-4o",
+ "canonical": "openai/gpt-4o"
+ },
+ {
+ "provider": "openrouter",
+ "model": "mistralai/mistral-small-24b-instruct-2501",
+ "canonical": "mistralai/mistral-small-24b-instruct"
+ },
+ {
+ "provider": "openrouter",
+ "model": "anthropic/claude-3.5-haiku-20241022",
+ "canonical": "anthropic/claude-3.5-haiku"
+ },
+ {
+ "provider": "openrouter",
+ "model": "deepseek/deepseek-chat-v3.1",
+ "canonical": "deepseek/deepseek-chat"
+ },
+ {
+ "provider": "openrouter",
+ "model": "openai/gpt-3.5-turbo-0613",
+ "canonical": "openai/gpt-3.5-turbo"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen3-coder-flash",
+ "canonical": "qwen/qwen3-coder-flash"
+ },
+ {
+ "provider": "openrouter",
+ "model": "openai/gpt-5.1-codex-mini",
+ "canonical": "openai/gpt-5.1-codex-mini"
+ },
+ {
+ "provider": "openrouter",
+ "model": "cohere/command-r-plus-08-2024",
+ "canonical": "cohere/command-r-plus-08"
+ },
+ {
+ "provider": "openrouter",
+ "model": "qwen/qwen3-vl-30b-a3b-instruct",
+ "canonical": "qwen/qwen3-vl-30b-a3b-instruct"
+ },
+ {
+ "provider": "openrouter",
+ "model": "deepseek/deepseek-v3.2",
+ "canonical": "deepseek/deepseek"
+ },
+ {
+ "provider": "google",
+ "model": "gemini-2.0-flash",
+ "canonical": "google/gemini-2.0-flash"
+ },
+ {
+ "provider": "google",
+ "model": "gemini-2.5-flash",
+ "canonical": "google/gemini-2.5-flash"
+ },
+ {
+ "provider": "google",
+ "model": "gemini-2.5-flash-preview-09-2025",
+ "canonical": "google/gemini-2.5-flash"
+ },
+ {
+ "provider": "google",
+ "model": "gemini-2.0-flash-001",
+ "canonical": "google/gemini-2.0-flash"
+ },
+ {
+ "provider": "google",
+ "model": "gemma-3n-e4b-it",
+ "canonical": "google/gemma-3n-e4b-it"
+ },
+ {
+ "provider": "google",
+ "model": "gemini-2.5-pro",
+ "canonical": "google/gemini-2.5-pro"
+ },
+ {
+ "provider": "google",
+ "model": "gemma-3-4b-it",
+ "canonical": "google/gemma-3-4b-it"
+ },
+ {
+ "provider": "google",
+ "model": "gemini-2.5-flash-image",
+ "canonical": "google/gemini-2.5-flash-image"
+ },
+ {
+ "provider": "google",
+ "model": "gemini-3-pro-image-preview",
+ "canonical": "google/gemini-3-pro-image"
+ },
+ {
+ "provider": "google",
+ "model": "gemini-2.0-flash-exp",
+ "canonical": "google/gemini-2.0-flash"
+ },
+ {
+ "provider": "google",
+ "model": "gemini-2.0-flash-lite-preview",
+ "canonical": "google/gemini-2.0-flash-lite"
+ },
+ {
+ "provider": "google",
+ "model": "gemini-2.5-flash-lite",
+ "canonical": "google/gemini-2.5-flash-lite"
+ },
+ {
+ "provider": "google",
+ "model": "gemini-2.5-flash-image-preview",
+ "canonical": "google/gemini-2.5-flash-image"
+ },
+ {
+ "provider": "google",
+ "model": "gemini-3-pro-preview",
+ "canonical": "google/gemini-3-pro"
+ },
+ {
+ "provider": "google",
+ "model": "gemini-2.0-flash-lite-preview-02-05",
+ "canonical": "google/gemini-2.0-flash-lite"
+ },
+ {
+ "provider": "google",
+ "model": "gemini-2.0-flash-lite-001",
+ "canonical": "google/gemini-2.0-flash-lite"
+ },
+ {
+ "provider": "google",
+ "model": "gemini-2.5-flash-lite-preview-09-2025",
+ "canonical": "google/gemini-2.5-flash-lite"
+ },
+ {
+ "provider": "google",
+ "model": "gemma-3-27b-it",
+ "canonical": "google/gemma-3-27b-it"
+ },
+ {
+ "provider": "google",
+ "model": "gemini-2.0-flash-lite",
+ "canonical": "google/gemini-2.0-flash-lite"
+ },
+ {
+ "provider": "google",
+ "model": "gemma-3-12b-it",
+ "canonical": "google/gemma-3-12b-it"
+ }
+ ],
+ "model_counts": {
+ "anthropic": 10,
+ "google": 50,
+ "tetrate": 0,
+ "xai": 0,
+ "openai": 633,
+ "openrouter": 225
+ },
+ "canonical_models_used": [
+ "mistralai/mistral-small-3.2-24b-instruct",
+ "meta-llama/llama-3-8b-instruct",
+ "mistralai/pixtral-12b",
+ "qwen/qwen3-coder-30b-a3b-instruct",
+ "anthropic/claude-opus-4",
+ "cohere/command-r-plus-08",
+ "google/gemini-2.5-flash-image",
+ "qwen/qwen3-next-80b-a3b-instruct",
+ "openai/gpt-4o-mini",
+ "cohere/command-r-08",
+ "qwen/qwen3-vl-235b-a22b-thinking",
+ "openai/gpt-5.1-codex-max",
+ "mistralai/mistral-large",
+ "google/gemma-3-27b-it",
+ "openai/gpt-oss-120b",
+ "openai/gpt-5",
+ "openai/gpt-5.1-chat",
+ "qwen/qwen3-vl-8b-thinking",
+ "openai/gpt-4o-audio",
+ "google/gemini-3-pro",
+ "openai/gpt-5.1-codex",
+ "openai/o3-pro",
+ "qwen/qwen3-coder-plus",
+ "openai/gpt-4",
+ "openai/gpt-5-image-mini",
+ "openai/gpt-3.5-turbo-instruct",
+ "anthropic/claude-3-opus",
+ "mistralai/ministral-8b",
+ "qwen/qwen3-30b-a3b-instruct",
+ "openai/gpt-oss-20b",
+ "x-ai/grok-code-fast-1",
+ "mistralai/mistral-saba",
+ "qwen/qwen3-vl-30b-a3b-instruct",
+ "openai/o3",
+ "mistralai/mistral-7b-instruct",
+ "mistralai/mistral-nemo",
+ "qwen/qwen3-next-80b-a3b-thinking",
+ "x-ai/grok-3",
+ "meta-llama/llama-4-maverick",
+ "qwen/qwen3-coder-flash",
+ "openai/gpt-oss-safeguard-20b",
+ "anthropic/claude-opus-4.5",
+ "x-ai/grok-4.1-fast",
+ "google/gemma-3n-e4b-it",
+ "mistralai/pixtral-large",
+ "qwen/qwen3-235b-a22b-thinking",
+ "openai/gpt-5-codex",
+ "anthropic/claude-3.7-sonnet:thinking",
+ "anthropic/claude-3.7-sonnet",
+ "meta-llama/llama-3.3-70b-instruct",
+ "meta-llama/llama-4-scout",
+ "openai/gpt-5-nano",
+ "openai/o3-mini",
+ "openai/o4-mini-deep-research",
+ "openai/gpt-4o",
+ "deepseek/deepseek-v3.1-terminus",
+ "mistralai/mixtral-8x7b-instruct",
+ "qwen/qwen-plus-2025-07-28:thinking",
+ "qwen/qwen-plus",
+ "mistralai/ministral-14b",
+ "openai/gpt-5-mini",
+ "meta-llama/llama-3.2-3b-instruct",
+ "openai/gpt-5.1",
+ "openai/gpt-5-pro",
+ "qwen/qwen3-vl-8b-instruct",
+ "google/gemini-2.5-flash-lite",
+ "qwen/qwen-vl-max",
+ "mistralai/devstral-medium",
+ "google/gemini-2.5-flash",
+ "meta-llama/llama-3-70b-instruct",
+ "mistralai/mistral-small-3.1-24b-instruct",
+ "qwen/qwen3-32b",
+ "openai/gpt-3.5-turbo-16k",
+ "openai/o3-deep-research",
+ "openai/o1-pro",
+ "meta-llama/llama-3.1-405b-instruct",
+ "anthropic/claude-sonnet-4",
+ "deepseek/deepseek",
+ "google/gemini-3-pro-image",
+ "ai21/jamba-mini-1.7",
+ "anthropic/claude-3.5-haiku",
+ "qwen/qwen-turbo",
+ "google/gemma-3-4b-it",
+ "google/gemini-2.5-pro",
+ "qwen/qwen3-30b-a3b",
+ "qwen/qwen-2.5-72b-instruct",
+ "google/gemma-3-12b-it",
+ "openai/gpt-4.1-mini",
+ "openai/gpt-4o-mini-search",
+ "x-ai/grok-4-fast",
+ "openai/gpt-4.1",
+ "openai/gpt-5.1-codex-mini",
+ "qwen/qwen3-max",
+ "openai/o4-mini",
+ "anthropic/claude-opus-4.1",
+ "mistralai/voxtral-small-24b",
+ "x-ai/grok-4",
+ "x-ai/grok-3-mini",
+ "qwen/qwen-max",
+ "anthropic/claude-sonnet-4.5",
+ "openai/gpt-4o-search",
+ "openai/gpt-5-chat",
+ "google/gemini-2.0-flash-lite",
+ "mistralai/mistral-small-24b-instruct",
+ "mistralai/mistral-medium-3",
+ "meta-llama/llama-3.1-8b-instruct",
+ "openai/gpt-4o:extended",
+ "openai/gpt-5-image",
+ "qwen/qwq-32b",
+ "deepseek/deepseek-chat",
+ "openai/o3-mini-high",
+ "mistralai/codestral",
+ "openai/codex-mini",
+ "deepseek/deepseek-r1",
+ "qwen/qwen3-30b-a3b-thinking",
+ "openai/o1",
+ "openai/gpt-4.1-nano",
+ "openai/chatgpt-4o",
+ "openai/o4-mini-high",
+ "openai/gpt-3.5-turbo",
+ "openai/gpt-4-turbo",
+ "anthropic/claude-3.5-sonnet",
+ "meta-llama/llama-3.1-70b-instruct",
+ "mistralai/mistral-tiny",
+ "qwen/qwen3-vl-235b-a22b-instruct",
+ "qwen/qwen3-8b",
+ "mistralai/devstral-small",
+ "anthropic/claude-haiku-4.5",
+ "deepseek/deepseek-r1-distill-llama-70b",
+ "qwen/qwen3-14b",
+ "mistralai/ministral-3b",
+ "qwen/qwen3-235b-a22b",
+ "mistralai/mistral-medium-3.1",
+ "anthropic/claude-3-haiku",
+ "ai21/jamba-large-1.7",
+ "google/gemini-2.0-flash",
+ "qwen/qwen3-vl-30b-a3b-thinking",
+ "qwen/qwen3-coder",
+ "mistralai/mixtral-8x22b-instruct"
+ ]
+}
\ No newline at end of file
diff --git a/crates/goose/src/providers/canonical/data/canonical_models.json b/crates/goose/src/providers/canonical/data/canonical_models.json
new file mode 100644
index 0000000000..94cdcfe523
--- /dev/null
+++ b/crates/goose/src/providers/canonical/data/canonical_models.json
@@ -0,0 +1,3257 @@
+[
+ {
+ "id": "ai21/jamba-large-1.7",
+ "name": "AI21: Jamba Large 1.7",
+ "context_length": 256000,
+ "max_completion_tokens": 4096,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 2e-6,
+ "completion": 8e-6,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "ai21/jamba-mini-1.7",
+ "name": "AI21: Jamba Mini 1.7",
+ "context_length": 256000,
+ "max_completion_tokens": 4096,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 2e-7,
+ "completion": 4e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "anthropic/claude-3-haiku",
+ "name": "Anthropic: Claude 3 Haiku",
+ "context_length": 200000,
+ "max_completion_tokens": 4096,
+ "input_modalities": [
+ "text",
+ "image"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 2.5e-7,
+ "completion": 1.25e-6,
+ "request": 0.0,
+ "image": 0.0004
+ }
+ },
+ {
+ "id": "anthropic/claude-3-opus",
+ "name": "Anthropic: Claude 3 Opus",
+ "context_length": 200000,
+ "max_completion_tokens": 4096,
+ "input_modalities": [
+ "text",
+ "image"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 0.000015,
+ "completion": 0.000075,
+ "request": 0.0,
+ "image": 0.024
+ }
+ },
+ {
+ "id": "anthropic/claude-3.5-haiku",
+ "name": "Anthropic: Claude 3.5 Haiku",
+ "context_length": 200000,
+ "max_completion_tokens": 8192,
+ "input_modalities": [
+ "text",
+ "image"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 8e-7,
+ "completion": 4e-6,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "anthropic/claude-3.5-sonnet",
+ "name": "Anthropic: Claude 3.5 Sonnet",
+ "context_length": 200000,
+ "max_completion_tokens": 8192,
+ "input_modalities": [
+ "text",
+ "image",
+ "file"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 6e-6,
+ "completion": 0.00003,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "anthropic/claude-3.7-sonnet",
+ "name": "Anthropic: Claude 3.7 Sonnet",
+ "context_length": 200000,
+ "max_completion_tokens": 64000,
+ "input_modalities": [
+ "text",
+ "image",
+ "file"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 3e-6,
+ "completion": 0.000015,
+ "request": 0.0,
+ "image": 0.0048
+ }
+ },
+ {
+ "id": "anthropic/claude-3.7-sonnet:thinking",
+ "name": "Anthropic: Claude 3.7 Sonnet (thinking)",
+ "context_length": 200000,
+ "max_completion_tokens": 64000,
+ "input_modalities": [
+ "text",
+ "image",
+ "file"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 3e-6,
+ "completion": 0.000015,
+ "request": 0.0,
+ "image": 0.0048
+ }
+ },
+ {
+ "id": "anthropic/claude-haiku-4.5",
+ "name": "Anthropic: Claude Haiku 4.5",
+ "context_length": 200000,
+ "max_completion_tokens": 64000,
+ "input_modalities": [
+ "image",
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 1e-6,
+ "completion": 5e-6,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "anthropic/claude-opus-4",
+ "name": "Anthropic: Claude Opus 4",
+ "context_length": 200000,
+ "max_completion_tokens": 32000,
+ "input_modalities": [
+ "image",
+ "text",
+ "file"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 0.000015,
+ "completion": 0.000075,
+ "request": 0.0,
+ "image": 0.024
+ }
+ },
+ {
+ "id": "anthropic/claude-opus-4.1",
+ "name": "Anthropic: Claude Opus 4.1",
+ "context_length": 200000,
+ "max_completion_tokens": 32000,
+ "input_modalities": [
+ "image",
+ "text",
+ "file"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 0.000015,
+ "completion": 0.000075,
+ "request": 0.0,
+ "image": 0.024
+ }
+ },
+ {
+ "id": "anthropic/claude-opus-4.5",
+ "name": "Anthropic: Claude Opus 4.5",
+ "context_length": 200000,
+ "max_completion_tokens": 32000,
+ "input_modalities": [
+ "file",
+ "image",
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 5e-6,
+ "completion": 0.000025,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "anthropic/claude-sonnet-4",
+ "name": "Anthropic: Claude Sonnet 4",
+ "context_length": 1000000,
+ "max_completion_tokens": 64000,
+ "input_modalities": [
+ "image",
+ "text",
+ "file"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 3e-6,
+ "completion": 0.000015,
+ "request": 0.0,
+ "image": 0.0048
+ }
+ },
+ {
+ "id": "anthropic/claude-sonnet-4.5",
+ "name": "Anthropic: Claude Sonnet 4.5",
+ "context_length": 1000000,
+ "max_completion_tokens": 64000,
+ "input_modalities": [
+ "text",
+ "image",
+ "file"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 3e-6,
+ "completion": 0.000015,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "cohere/command-a",
+ "name": "Cohere: Command A",
+ "context_length": 256000,
+ "max_completion_tokens": 8192,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": false,
+ "pricing": {
+ "prompt": 2.5e-6,
+ "completion": 0.00001,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "cohere/command-r-08",
+ "name": "Cohere: Command R (08-2024)",
+ "context_length": 128000,
+ "max_completion_tokens": 4000,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 1.5e-7,
+ "completion": 6e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "cohere/command-r-plus-08",
+ "name": "Cohere: Command R+ (08-2024)",
+ "context_length": 128000,
+ "max_completion_tokens": 4000,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 2.5e-6,
+ "completion": 0.00001,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "cohere/command-r7b-12",
+ "name": "Cohere: Command R7B (12-2024)",
+ "context_length": 128000,
+ "max_completion_tokens": 4000,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": false,
+ "pricing": {
+ "prompt": 3.75e-8,
+ "completion": 1.5e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "deepseek/deepseek",
+ "name": "DeepSeek: DeepSeek V3.2",
+ "context_length": 163840,
+ "max_completion_tokens": 163840,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 2.5e-7,
+ "completion": 3.8e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "deepseek/deepseek-chat",
+ "name": "DeepSeek: DeepSeek V3",
+ "context_length": 163840,
+ "max_completion_tokens": 163840,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 3e-7,
+ "completion": 1.2e-6,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "deepseek/deepseek-prover",
+ "name": "DeepSeek: DeepSeek Prover V2",
+ "context_length": 163840,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": false,
+ "pricing": {
+ "prompt": 5e-7,
+ "completion": 2.18e-6,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "deepseek/deepseek-r1",
+ "name": "DeepSeek: R1",
+ "context_length": 163840,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 3e-7,
+ "completion": 1.2e-6,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "deepseek/deepseek-r1-0528-qwen3-8b",
+ "name": "DeepSeek: DeepSeek R1 0528 Qwen3 8B",
+ "context_length": 32768,
+ "max_completion_tokens": 32768,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": false,
+ "pricing": {
+ "prompt": 2e-8,
+ "completion": 1e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "deepseek/deepseek-r1-distill-llama-70b",
+ "name": "DeepSeek: R1 Distill Llama 70B",
+ "context_length": 131072,
+ "max_completion_tokens": 131072,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 3e-8,
+ "completion": 1.3e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "deepseek/deepseek-r1-distill-qwen-14b",
+ "name": "DeepSeek: R1 Distill Qwen 14B",
+ "context_length": 32768,
+ "max_completion_tokens": 16384,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": false,
+ "pricing": {
+ "prompt": 1.2e-7,
+ "completion": 1.2e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "deepseek/deepseek-r1-distill-qwen-32b",
+ "name": "DeepSeek: R1 Distill Qwen 32B",
+ "context_length": 64000,
+ "max_completion_tokens": 32000,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": false,
+ "pricing": {
+ "prompt": 2.4e-7,
+ "completion": 2.4e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "deepseek/deepseek-v3.1-terminus",
+ "name": "DeepSeek: DeepSeek V3.1 Terminus",
+ "context_length": 163840,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 2.1e-7,
+ "completion": 7.9e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "deepseek/deepseek-v3.2-speciale",
+ "name": "DeepSeek: DeepSeek V3.2 Speciale",
+ "context_length": 163840,
+ "max_completion_tokens": 65536,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": false,
+ "pricing": {
+ "prompt": 2.7e-7,
+ "completion": 4.1e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "google/gemini-2.0-flash",
+ "name": "Google: Gemini 2.0 Flash",
+ "context_length": 1048576,
+ "max_completion_tokens": 8192,
+ "input_modalities": [
+ "text",
+ "image",
+ "file",
+ "audio",
+ "video"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 1e-7,
+ "completion": 4e-7,
+ "request": 0.0,
+ "image": 0.0000258
+ }
+ },
+ {
+ "id": "google/gemini-2.0-flash-lite",
+ "name": "Google: Gemini 2.0 Flash Lite",
+ "context_length": 1048576,
+ "max_completion_tokens": 8192,
+ "input_modalities": [
+ "text",
+ "image",
+ "file",
+ "audio",
+ "video"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 7.5e-8,
+ "completion": 3e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "google/gemini-2.5-flash",
+ "name": "Google: Gemini 2.5 Flash",
+ "context_length": 1048576,
+ "max_completion_tokens": 65535,
+ "input_modalities": [
+ "file",
+ "image",
+ "text",
+ "audio",
+ "video"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 3e-7,
+ "completion": 2.5e-6,
+ "request": 0.0,
+ "image": 0.001238
+ }
+ },
+ {
+ "id": "google/gemini-2.5-flash-image",
+ "name": "Google: Gemini 2.5 Flash Image (Nano Banana)",
+ "context_length": 32768,
+ "max_completion_tokens": 32768,
+ "input_modalities": [
+ "image",
+ "text"
+ ],
+ "output_modalities": [
+ "image",
+ "text"
+ ],
+ "supports_tools": false,
+ "pricing": {
+ "prompt": 3e-7,
+ "completion": 2.5e-6,
+ "request": 0.0,
+ "image": 0.001238
+ }
+ },
+ {
+ "id": "google/gemini-2.5-flash-lite",
+ "name": "Google: Gemini 2.5 Flash Lite",
+ "context_length": 1048576,
+ "max_completion_tokens": 65535,
+ "input_modalities": [
+ "text",
+ "image",
+ "file",
+ "audio",
+ "video"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 1e-7,
+ "completion": 4e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "google/gemini-2.5-pro",
+ "name": "Google: Gemini 2.5 Pro",
+ "context_length": 1048576,
+ "max_completion_tokens": 65536,
+ "input_modalities": [
+ "text",
+ "image",
+ "file",
+ "audio",
+ "video"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 1.25e-6,
+ "completion": 0.00001,
+ "request": 0.0,
+ "image": 0.00516
+ }
+ },
+ {
+ "id": "google/gemini-3-pro",
+ "name": "Google: Gemini 3 Pro Preview",
+ "context_length": 1048576,
+ "max_completion_tokens": 65536,
+ "input_modalities": [
+ "text",
+ "image",
+ "file",
+ "audio",
+ "video"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 2e-6,
+ "completion": 0.000012,
+ "request": 0.0,
+ "image": 0.008256
+ }
+ },
+ {
+ "id": "google/gemini-3-pro-image",
+ "name": "Google: Nano Banana Pro (Gemini 3 Pro Image Preview)",
+ "context_length": 65536,
+ "max_completion_tokens": 32768,
+ "input_modalities": [
+ "image",
+ "text"
+ ],
+ "output_modalities": [
+ "image",
+ "text"
+ ],
+ "supports_tools": false,
+ "pricing": {
+ "prompt": 2e-6,
+ "completion": 0.000012,
+ "request": 0.0,
+ "image": 0.067
+ }
+ },
+ {
+ "id": "google/gemma-2-27b-it",
+ "name": "Google: Gemma 2 27B",
+ "context_length": 8192,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": false,
+ "pricing": {
+ "prompt": 6.5e-7,
+ "completion": 6.5e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "google/gemma-2-9b-it",
+ "name": "Google: Gemma 2 9B",
+ "context_length": 8192,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": false,
+ "pricing": {
+ "prompt": 3e-8,
+ "completion": 9e-8,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "google/gemma-3-12b-it",
+ "name": "Google: Gemma 3 12B",
+ "context_length": 131072,
+ "max_completion_tokens": 131072,
+ "input_modalities": [
+ "text",
+ "image"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": false,
+ "pricing": {
+ "prompt": 3e-8,
+ "completion": 1e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "google/gemma-3-27b-it",
+ "name": "Google: Gemma 3 27B",
+ "context_length": 96000,
+ "max_completion_tokens": 96000,
+ "input_modalities": [
+ "text",
+ "image"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 4e-8,
+ "completion": 1.5e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "google/gemma-3-4b-it",
+ "name": "Google: Gemma 3 4B",
+ "context_length": 96000,
+ "input_modalities": [
+ "text",
+ "image"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": false,
+ "pricing": {
+ "prompt": 1.703012e-8,
+ "completion": 6.81536e-8,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "google/gemma-3n-e4b-it",
+ "name": "Google: Gemma 3n 4B",
+ "context_length": 32768,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": false,
+ "pricing": {
+ "prompt": 2e-8,
+ "completion": 4e-8,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "meta-llama/llama-3-70b-instruct",
+ "name": "Meta: Llama 3 70B Instruct",
+ "context_length": 8192,
+ "max_completion_tokens": 16384,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 3e-7,
+ "completion": 4e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "meta-llama/llama-3-8b-instruct",
+ "name": "Meta: Llama 3 8B Instruct",
+ "context_length": 8192,
+ "max_completion_tokens": 16384,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 3e-8,
+ "completion": 6e-8,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "meta-llama/llama-3.1-405b",
+ "name": "Meta: Llama 3.1 405B (base)",
+ "context_length": 32768,
+ "max_completion_tokens": 32768,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": false,
+ "pricing": {
+ "prompt": 4e-6,
+ "completion": 4e-6,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "meta-llama/llama-3.1-405b-instruct",
+ "name": "Meta: Llama 3.1 405B Instruct",
+ "context_length": 130815,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 3.5e-6,
+ "completion": 3.5e-6,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "meta-llama/llama-3.1-70b-instruct",
+ "name": "Meta: Llama 3.1 70B Instruct",
+ "context_length": 131072,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 4e-7,
+ "completion": 4e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "meta-llama/llama-3.1-8b-instruct",
+ "name": "Meta: Llama 3.1 8B Instruct",
+ "context_length": 131072,
+ "max_completion_tokens": 16384,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 2e-8,
+ "completion": 3e-8,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "meta-llama/llama-3.2-11b-vision-instruct",
+ "name": "Meta: Llama 3.2 11B Vision Instruct",
+ "context_length": 131072,
+ "max_completion_tokens": 16384,
+ "input_modalities": [
+ "text",
+ "image"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": false,
+ "pricing": {
+ "prompt": 4.9e-8,
+ "completion": 4.9e-8,
+ "request": 0.0,
+ "image": 0.00007948
+ }
+ },
+ {
+ "id": "meta-llama/llama-3.2-1b-instruct",
+ "name": "Meta: Llama 3.2 1B Instruct",
+ "context_length": 60000,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": false,
+ "pricing": {
+ "prompt": 2.7e-8,
+ "completion": 2e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "meta-llama/llama-3.2-3b-instruct",
+ "name": "Meta: Llama 3.2 3B Instruct",
+ "context_length": 131072,
+ "max_completion_tokens": 16384,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 2e-8,
+ "completion": 2e-8,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "meta-llama/llama-3.2-90b-vision-instruct",
+ "name": "Meta: Llama 3.2 90B Vision Instruct",
+ "context_length": 32768,
+ "max_completion_tokens": 16384,
+ "input_modalities": [
+ "text",
+ "image"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": false,
+ "pricing": {
+ "prompt": 3.5e-7,
+ "completion": 4e-7,
+ "request": 0.0,
+ "image": 0.0005058
+ }
+ },
+ {
+ "id": "meta-llama/llama-3.3-70b-instruct",
+ "name": "Meta: Llama 3.3 70B Instruct",
+ "context_length": 131072,
+ "max_completion_tokens": 16384,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 1e-7,
+ "completion": 3.2e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "meta-llama/llama-4-maverick",
+ "name": "Meta: Llama 4 Maverick",
+ "context_length": 1048576,
+ "max_completion_tokens": 16384,
+ "input_modalities": [
+ "text",
+ "image"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 1.5e-7,
+ "completion": 6e-7,
+ "request": 0.0,
+ "image": 0.0006684
+ }
+ },
+ {
+ "id": "meta-llama/llama-4-scout",
+ "name": "Meta: Llama 4 Scout",
+ "context_length": 327680,
+ "max_completion_tokens": 16384,
+ "input_modalities": [
+ "text",
+ "image"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 8e-8,
+ "completion": 3e-7,
+ "request": 0.0,
+ "image": 0.0003342
+ }
+ },
+ {
+ "id": "meta-llama/llama-guard-2-8b",
+ "name": "Meta: LlamaGuard 2 8B",
+ "context_length": 8192,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": false,
+ "pricing": {
+ "prompt": 2e-7,
+ "completion": 2e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "meta-llama/llama-guard-3-8b",
+ "name": "Llama Guard 3 8B",
+ "context_length": 131072,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": false,
+ "pricing": {
+ "prompt": 2e-8,
+ "completion": 6e-8,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "meta-llama/llama-guard-4-12b",
+ "name": "Meta: Llama Guard 4 12B",
+ "context_length": 163840,
+ "input_modalities": [
+ "image",
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": false,
+ "pricing": {
+ "prompt": 1.8e-7,
+ "completion": 1.8e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "mistralai/codestral",
+ "name": "Mistral: Codestral 2508",
+ "context_length": 256000,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 3e-7,
+ "completion": 9e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "mistralai/devstral",
+ "name": "Mistral: Devstral 2 2512",
+ "context_length": 262144,
+ "max_completion_tokens": 65536,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 1.5e-7,
+ "completion": 6e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "mistralai/devstral-medium",
+ "name": "Mistral: Devstral Medium",
+ "context_length": 131072,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 4e-7,
+ "completion": 2e-6,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "mistralai/devstral-small",
+ "name": "Mistral: Devstral Small 1.1",
+ "context_length": 128000,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 7e-8,
+ "completion": 2.8e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "mistralai/ministral-14b",
+ "name": "Mistral: Ministral 3 14B 2512",
+ "context_length": 262144,
+ "input_modalities": [
+ "text",
+ "image"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 2e-7,
+ "completion": 2e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "mistralai/ministral-3b",
+ "name": "Mistral: Ministral 3B",
+ "context_length": 131072,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 4e-8,
+ "completion": 4e-8,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "mistralai/ministral-8b",
+ "name": "Mistral: Ministral 8B",
+ "context_length": 131072,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 1e-7,
+ "completion": 1e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "mistralai/mistral-7b-instruct",
+ "name": "Mistral: Mistral 7B Instruct",
+ "context_length": 32768,
+ "max_completion_tokens": 16384,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 2.8e-8,
+ "completion": 5.4e-8,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "mistralai/mistral-large",
+ "name": "Mistral Large",
+ "context_length": 128000,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 2e-6,
+ "completion": 6e-6,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "mistralai/mistral-medium-3",
+ "name": "Mistral: Mistral Medium 3",
+ "context_length": 131072,
+ "input_modalities": [
+ "text",
+ "image"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 4e-7,
+ "completion": 2e-6,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "mistralai/mistral-medium-3.1",
+ "name": "Mistral: Mistral Medium 3.1",
+ "context_length": 131072,
+ "input_modalities": [
+ "text",
+ "image"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 4e-7,
+ "completion": 2e-6,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "mistralai/mistral-nemo",
+ "name": "Mistral: Mistral Nemo",
+ "context_length": 131072,
+ "max_completion_tokens": 16384,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 2e-8,
+ "completion": 4e-8,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "mistralai/mistral-saba",
+ "name": "Mistral: Saba",
+ "context_length": 32768,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 2e-7,
+ "completion": 6e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "mistralai/mistral-small-24b-instruct",
+ "name": "Mistral: Mistral Small 3",
+ "context_length": 32768,
+ "max_completion_tokens": 32768,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 3e-8,
+ "completion": 1.1e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "mistralai/mistral-small-3.1-24b-instruct",
+ "name": "Mistral: Mistral Small 3.1 24B",
+ "context_length": 131072,
+ "max_completion_tokens": 131072,
+ "input_modalities": [
+ "text",
+ "image"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 3e-8,
+ "completion": 1.1e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "mistralai/mistral-small-3.2-24b-instruct",
+ "name": "Mistral: Mistral Small 3.2 24B",
+ "context_length": 131072,
+ "max_completion_tokens": 131072,
+ "input_modalities": [
+ "image",
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 6e-8,
+ "completion": 1.8e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "mistralai/mistral-tiny",
+ "name": "Mistral Tiny",
+ "context_length": 32768,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 2.5e-7,
+ "completion": 2.5e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "mistralai/mixtral-8x22b-instruct",
+ "name": "Mistral: Mixtral 8x22B Instruct",
+ "context_length": 65536,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 2e-6,
+ "completion": 6e-6,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "mistralai/mixtral-8x7b-instruct",
+ "name": "Mistral: Mixtral 8x7B Instruct",
+ "context_length": 32768,
+ "max_completion_tokens": 16384,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 5.4e-7,
+ "completion": 5.4e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "mistralai/pixtral-12b",
+ "name": "Mistral: Pixtral 12B",
+ "context_length": 32768,
+ "input_modalities": [
+ "text",
+ "image"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 1e-7,
+ "completion": 1e-7,
+ "request": 0.0,
+ "image": 0.0001445
+ }
+ },
+ {
+ "id": "mistralai/pixtral-large",
+ "name": "Mistral: Pixtral Large 2411",
+ "context_length": 131072,
+ "input_modalities": [
+ "text",
+ "image"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 2e-6,
+ "completion": 6e-6,
+ "request": 0.0,
+ "image": 0.002888
+ }
+ },
+ {
+ "id": "mistralai/voxtral-small-24b",
+ "name": "Mistral: Voxtral Small 24B 2507",
+ "context_length": 32000,
+ "input_modalities": [
+ "text",
+ "audio"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 1e-7,
+ "completion": 3e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "openai/chatgpt-4o",
+ "name": "OpenAI: ChatGPT-4o",
+ "context_length": 128000,
+ "max_completion_tokens": 16384,
+ "input_modalities": [
+ "text",
+ "image"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": false,
+ "pricing": {
+ "prompt": 5e-6,
+ "completion": 0.000015,
+ "request": 0.0,
+ "image": 0.007225
+ }
+ },
+ {
+ "id": "openai/codex-mini",
+ "name": "OpenAI: Codex Mini",
+ "context_length": 200000,
+ "max_completion_tokens": 100000,
+ "input_modalities": [
+ "image",
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 1.5e-6,
+ "completion": 6e-6,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "openai/gpt-3.5-turbo",
+ "name": "OpenAI: GPT-3.5 Turbo",
+ "context_length": 16385,
+ "max_completion_tokens": 4096,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 5e-7,
+ "completion": 1.5e-6,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "openai/gpt-3.5-turbo-16k",
+ "name": "OpenAI: GPT-3.5 Turbo 16k",
+ "context_length": 16385,
+ "max_completion_tokens": 4096,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 3e-6,
+ "completion": 4e-6,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "openai/gpt-3.5-turbo-instruct",
+ "name": "OpenAI: GPT-3.5 Turbo Instruct",
+ "context_length": 4095,
+ "max_completion_tokens": 4096,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": false,
+ "pricing": {
+ "prompt": 1.5e-6,
+ "completion": 2e-6,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "openai/gpt-4",
+ "name": "OpenAI: GPT-4",
+ "context_length": 8191,
+ "max_completion_tokens": 4096,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 0.00003,
+ "completion": 0.00006,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "openai/gpt-4-turbo",
+ "name": "OpenAI: GPT-4 Turbo",
+ "context_length": 128000,
+ "max_completion_tokens": 4096,
+ "input_modalities": [
+ "text",
+ "image"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 0.00001,
+ "completion": 0.00003,
+ "request": 0.0,
+ "image": 0.01445
+ }
+ },
+ {
+ "id": "openai/gpt-4.1",
+ "name": "OpenAI: GPT-4.1",
+ "context_length": 1047576,
+ "max_completion_tokens": 32768,
+ "input_modalities": [
+ "image",
+ "text",
+ "file"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 2e-6,
+ "completion": 8e-6,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "openai/gpt-4.1-mini",
+ "name": "OpenAI: GPT-4.1 Mini",
+ "context_length": 1047576,
+ "max_completion_tokens": 32768,
+ "input_modalities": [
+ "image",
+ "text",
+ "file"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 4e-7,
+ "completion": 1.6e-6,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "openai/gpt-4.1-nano",
+ "name": "OpenAI: GPT-4.1 Nano",
+ "context_length": 1047576,
+ "max_completion_tokens": 32768,
+ "input_modalities": [
+ "image",
+ "text",
+ "file"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 1e-7,
+ "completion": 4e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "openai/gpt-4o",
+ "name": "OpenAI: GPT-4o",
+ "context_length": 128000,
+ "max_completion_tokens": 16384,
+ "input_modalities": [
+ "text",
+ "image",
+ "file"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 2.5e-6,
+ "completion": 0.00001,
+ "request": 0.0,
+ "image": 0.003613
+ }
+ },
+ {
+ "id": "openai/gpt-4o-audio",
+ "name": "OpenAI: GPT-4o Audio",
+ "context_length": 128000,
+ "max_completion_tokens": 16384,
+ "input_modalities": [
+ "audio",
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 2.5e-6,
+ "completion": 0.00001,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "openai/gpt-4o-mini",
+ "name": "OpenAI: GPT-4o-mini",
+ "context_length": 128000,
+ "max_completion_tokens": 16384,
+ "input_modalities": [
+ "text",
+ "image",
+ "file"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 1.5e-7,
+ "completion": 6e-7,
+ "request": 0.0,
+ "image": 0.000217
+ }
+ },
+ {
+ "id": "openai/gpt-4o-mini-search",
+ "name": "OpenAI: GPT-4o-mini Search Preview",
+ "context_length": 128000,
+ "max_completion_tokens": 16384,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": false,
+ "pricing": {
+ "prompt": 1.5e-7,
+ "completion": 6e-7,
+ "request": 0.0275,
+ "image": 0.000217
+ }
+ },
+ {
+ "id": "openai/gpt-4o-search",
+ "name": "OpenAI: GPT-4o Search Preview",
+ "context_length": 128000,
+ "max_completion_tokens": 16384,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": false,
+ "pricing": {
+ "prompt": 2.5e-6,
+ "completion": 0.00001,
+ "request": 0.035,
+ "image": 0.003613
+ }
+ },
+ {
+ "id": "openai/gpt-4o:extended",
+ "name": "OpenAI: GPT-4o (extended)",
+ "context_length": 128000,
+ "max_completion_tokens": 64000,
+ "input_modalities": [
+ "text",
+ "image",
+ "file"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 6e-6,
+ "completion": 0.000018,
+ "request": 0.0,
+ "image": 0.007225
+ }
+ },
+ {
+ "id": "openai/gpt-5",
+ "name": "OpenAI: GPT-5",
+ "context_length": 400000,
+ "max_completion_tokens": 128000,
+ "input_modalities": [
+ "text",
+ "image",
+ "file"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 1.25e-6,
+ "completion": 0.00001,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "openai/gpt-5-chat",
+ "name": "OpenAI: GPT-5 Chat",
+ "context_length": 128000,
+ "max_completion_tokens": 16384,
+ "input_modalities": [
+ "file",
+ "image",
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": false,
+ "pricing": {
+ "prompt": 1.25e-6,
+ "completion": 0.00001,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "openai/gpt-5-codex",
+ "name": "OpenAI: GPT-5 Codex",
+ "context_length": 400000,
+ "max_completion_tokens": 128000,
+ "input_modalities": [
+ "text",
+ "image"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 1.25e-6,
+ "completion": 0.00001,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "openai/gpt-5-image",
+ "name": "OpenAI: GPT-5 Image",
+ "context_length": 400000,
+ "max_completion_tokens": 128000,
+ "input_modalities": [
+ "image",
+ "text",
+ "file"
+ ],
+ "output_modalities": [
+ "image",
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 0.00001,
+ "completion": 0.00001,
+ "request": 0.0,
+ "image": 0.00001
+ }
+ },
+ {
+ "id": "openai/gpt-5-image-mini",
+ "name": "OpenAI: GPT-5 Image Mini",
+ "context_length": 400000,
+ "max_completion_tokens": 128000,
+ "input_modalities": [
+ "file",
+ "image",
+ "text"
+ ],
+ "output_modalities": [
+ "image",
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 2.5e-6,
+ "completion": 2e-6,
+ "request": 0.0,
+ "image": 2.5e-6
+ }
+ },
+ {
+ "id": "openai/gpt-5-mini",
+ "name": "OpenAI: GPT-5 Mini",
+ "context_length": 400000,
+ "max_completion_tokens": 128000,
+ "input_modalities": [
+ "text",
+ "image",
+ "file"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 2.5e-7,
+ "completion": 2e-6,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "openai/gpt-5-nano",
+ "name": "OpenAI: GPT-5 Nano",
+ "context_length": 400000,
+ "max_completion_tokens": 128000,
+ "input_modalities": [
+ "text",
+ "image",
+ "file"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 5e-8,
+ "completion": 4e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "openai/gpt-5-pro",
+ "name": "OpenAI: GPT-5 Pro",
+ "context_length": 400000,
+ "max_completion_tokens": 128000,
+ "input_modalities": [
+ "image",
+ "text",
+ "file"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 0.000015,
+ "completion": 0.00012,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "openai/gpt-5.1",
+ "name": "OpenAI: GPT-5.1",
+ "context_length": 400000,
+ "max_completion_tokens": 128000,
+ "input_modalities": [
+ "image",
+ "text",
+ "file"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 1.25e-6,
+ "completion": 0.00001,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "openai/gpt-5.1-chat",
+ "name": "OpenAI: GPT-5.1 Chat",
+ "context_length": 128000,
+ "max_completion_tokens": 16384,
+ "input_modalities": [
+ "file",
+ "image",
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 1.25e-6,
+ "completion": 0.00001,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "openai/gpt-5.1-codex",
+ "name": "OpenAI: GPT-5.1-Codex",
+ "context_length": 400000,
+ "max_completion_tokens": 128000,
+ "input_modalities": [
+ "text",
+ "image"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 1.25e-6,
+ "completion": 0.00001,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "openai/gpt-5.1-codex-max",
+ "name": "OpenAI: GPT-5.1-Codex-Max",
+ "context_length": 400000,
+ "max_completion_tokens": 128000,
+ "input_modalities": [
+ "text",
+ "image"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 1.25e-6,
+ "completion": 0.00001,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "openai/gpt-5.1-codex-mini",
+ "name": "OpenAI: GPT-5.1-Codex-Mini",
+ "context_length": 400000,
+ "max_completion_tokens": 100000,
+ "input_modalities": [
+ "image",
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 2.5e-7,
+ "completion": 2e-6,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "openai/gpt-5.2",
+ "name": "OpenAI: GPT-5.2",
+ "context_length": 400000,
+ "max_completion_tokens": 128000,
+ "input_modalities": [
+ "file",
+ "image",
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 1.75e-6,
+ "completion": 0.000014,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "openai/gpt-5.2-chat",
+ "name": "OpenAI: GPT-5.2 Chat",
+ "context_length": 128000,
+ "max_completion_tokens": 16384,
+ "input_modalities": [
+ "file",
+ "image",
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 1.75e-6,
+ "completion": 0.000014,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "openai/gpt-5.2-pro",
+ "name": "OpenAI: GPT-5.2 Pro",
+ "context_length": 400000,
+ "max_completion_tokens": 128000,
+ "input_modalities": [
+ "image",
+ "text",
+ "file"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 0.000021,
+ "completion": 0.000168,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "openai/gpt-oss-120b",
+ "name": "OpenAI: gpt-oss-120b",
+ "context_length": 131072,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 3.9e-8,
+ "completion": 1.9e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "openai/gpt-oss-20b",
+ "name": "OpenAI: gpt-oss-20b",
+ "context_length": 131072,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 3e-8,
+ "completion": 1.4e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "openai/gpt-oss-safeguard-20b",
+ "name": "OpenAI: gpt-oss-safeguard-20b",
+ "context_length": 131072,
+ "max_completion_tokens": 65536,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 7.5e-8,
+ "completion": 3e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "openai/o1",
+ "name": "OpenAI: o1",
+ "context_length": 200000,
+ "max_completion_tokens": 100000,
+ "input_modalities": [
+ "text",
+ "image",
+ "file"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 0.000015,
+ "completion": 0.00006,
+ "request": 0.0,
+ "image": 0.021675
+ }
+ },
+ {
+ "id": "openai/o1-pro",
+ "name": "OpenAI: o1-pro",
+ "context_length": 200000,
+ "max_completion_tokens": 100000,
+ "input_modalities": [
+ "text",
+ "image",
+ "file"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": false,
+ "pricing": {
+ "prompt": 0.00015,
+ "completion": 0.0006,
+ "request": 0.0,
+ "image": 0.21675
+ }
+ },
+ {
+ "id": "openai/o3",
+ "name": "OpenAI: o3",
+ "context_length": 200000,
+ "max_completion_tokens": 100000,
+ "input_modalities": [
+ "image",
+ "text",
+ "file"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 2e-6,
+ "completion": 8e-6,
+ "request": 0.0,
+ "image": 0.00153
+ }
+ },
+ {
+ "id": "openai/o3-deep-research",
+ "name": "OpenAI: o3 Deep Research",
+ "context_length": 200000,
+ "max_completion_tokens": 100000,
+ "input_modalities": [
+ "image",
+ "text",
+ "file"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 0.00001,
+ "completion": 0.00004,
+ "request": 0.0,
+ "image": 0.00765
+ }
+ },
+ {
+ "id": "openai/o3-mini",
+ "name": "OpenAI: o3 Mini",
+ "context_length": 200000,
+ "max_completion_tokens": 100000,
+ "input_modalities": [
+ "text",
+ "file"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 1.1e-6,
+ "completion": 4.4e-6,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "openai/o3-mini-high",
+ "name": "OpenAI: o3 Mini High",
+ "context_length": 200000,
+ "max_completion_tokens": 100000,
+ "input_modalities": [
+ "text",
+ "file"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 1.1e-6,
+ "completion": 4.4e-6,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "openai/o3-pro",
+ "name": "OpenAI: o3 Pro",
+ "context_length": 200000,
+ "max_completion_tokens": 100000,
+ "input_modalities": [
+ "text",
+ "file",
+ "image"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 0.00002,
+ "completion": 0.00008,
+ "request": 0.0,
+ "image": 0.0153
+ }
+ },
+ {
+ "id": "openai/o4-mini",
+ "name": "OpenAI: o4 Mini",
+ "context_length": 200000,
+ "max_completion_tokens": 100000,
+ "input_modalities": [
+ "image",
+ "text",
+ "file"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 1.1e-6,
+ "completion": 4.4e-6,
+ "request": 0.0,
+ "image": 0.0008415
+ }
+ },
+ {
+ "id": "openai/o4-mini-deep-research",
+ "name": "OpenAI: o4 Mini Deep Research",
+ "context_length": 200000,
+ "max_completion_tokens": 100000,
+ "input_modalities": [
+ "file",
+ "image",
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 2e-6,
+ "completion": 8e-6,
+ "request": 0.0,
+ "image": 0.00153
+ }
+ },
+ {
+ "id": "openai/o4-mini-high",
+ "name": "OpenAI: o4 Mini High",
+ "context_length": 200000,
+ "max_completion_tokens": 100000,
+ "input_modalities": [
+ "image",
+ "text",
+ "file"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 1.1e-6,
+ "completion": 4.4e-6,
+ "request": 0.0,
+ "image": 0.0008415
+ }
+ },
+ {
+ "id": "qwen/qwen-2.5-72b-instruct",
+ "name": "Qwen2.5 72B Instruct",
+ "context_length": 32768,
+ "max_completion_tokens": 32768,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 7e-8,
+ "completion": 2.6e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "qwen/qwen-2.5-7b-instruct",
+ "name": "Qwen: Qwen2.5 7B Instruct",
+ "context_length": 32768,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": false,
+ "pricing": {
+ "prompt": 4e-8,
+ "completion": 1e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "qwen/qwen-2.5-coder-32b-instruct",
+ "name": "Qwen2.5 Coder 32B Instruct",
+ "context_length": 32768,
+ "max_completion_tokens": 32768,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": false,
+ "pricing": {
+ "prompt": 3e-8,
+ "completion": 1.1e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "qwen/qwen-2.5-vl-7b-instruct",
+ "name": "Qwen: Qwen2.5-VL 7B Instruct",
+ "context_length": 32768,
+ "input_modalities": [
+ "text",
+ "image"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": false,
+ "pricing": {
+ "prompt": 2e-7,
+ "completion": 2e-7,
+ "request": 0.0,
+ "image": 0.0001445
+ }
+ },
+ {
+ "id": "qwen/qwen-max",
+ "name": "Qwen: Qwen-Max ",
+ "context_length": 32768,
+ "max_completion_tokens": 8192,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 1.6e-6,
+ "completion": 6.4e-6,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "qwen/qwen-plus",
+ "name": "Qwen: Qwen-Plus",
+ "context_length": 131072,
+ "max_completion_tokens": 8192,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 4e-7,
+ "completion": 1.2e-6,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "qwen/qwen-plus-2025-07-28:thinking",
+ "name": "Qwen: Qwen Plus 0728 (thinking)",
+ "context_length": 1000000,
+ "max_completion_tokens": 32768,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 4e-7,
+ "completion": 4e-6,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "qwen/qwen-turbo",
+ "name": "Qwen: Qwen-Turbo",
+ "context_length": 1000000,
+ "max_completion_tokens": 8192,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 5e-8,
+ "completion": 2e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "qwen/qwen-vl-max",
+ "name": "Qwen: Qwen VL Max",
+ "context_length": 131072,
+ "max_completion_tokens": 8192,
+ "input_modalities": [
+ "text",
+ "image"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 8e-7,
+ "completion": 3.2e-6,
+ "request": 0.0,
+ "image": 0.001024
+ }
+ },
+ {
+ "id": "qwen/qwen-vl-plus",
+ "name": "Qwen: Qwen VL Plus",
+ "context_length": 7500,
+ "max_completion_tokens": 1500,
+ "input_modalities": [
+ "text",
+ "image"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": false,
+ "pricing": {
+ "prompt": 2.1e-7,
+ "completion": 6.3e-7,
+ "request": 0.0,
+ "image": 0.0002688
+ }
+ },
+ {
+ "id": "qwen/qwen2.5-coder-7b-instruct",
+ "name": "Qwen: Qwen2.5 Coder 7B Instruct",
+ "context_length": 32768,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": false,
+ "pricing": {
+ "prompt": 3e-8,
+ "completion": 9e-8,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "qwen/qwen2.5-vl-32b-instruct",
+ "name": "Qwen: Qwen2.5 VL 32B Instruct",
+ "context_length": 16384,
+ "max_completion_tokens": 16384,
+ "input_modalities": [
+ "text",
+ "image"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": false,
+ "pricing": {
+ "prompt": 5e-8,
+ "completion": 2.2e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "qwen/qwen2.5-vl-72b-instruct",
+ "name": "Qwen: Qwen2.5 VL 72B Instruct",
+ "context_length": 32768,
+ "max_completion_tokens": 32768,
+ "input_modalities": [
+ "text",
+ "image"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": false,
+ "pricing": {
+ "prompt": 3e-8,
+ "completion": 1.3e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "qwen/qwen3-14b",
+ "name": "Qwen: Qwen3 14B",
+ "context_length": 40960,
+ "max_completion_tokens": 40960,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 5e-8,
+ "completion": 2.2e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "qwen/qwen3-235b-a22b",
+ "name": "Qwen: Qwen3 235B A22B",
+ "context_length": 40960,
+ "max_completion_tokens": 40960,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 1.8e-7,
+ "completion": 5.4e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "qwen/qwen3-235b-a22b-thinking",
+ "name": "Qwen: Qwen3 235B A22B Thinking 2507",
+ "context_length": 262144,
+ "max_completion_tokens": 262144,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 1.1e-7,
+ "completion": 6e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "qwen/qwen3-30b-a3b",
+ "name": "Qwen: Qwen3 30B A3B",
+ "context_length": 40960,
+ "max_completion_tokens": 40960,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 6e-8,
+ "completion": 2.2e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "qwen/qwen3-30b-a3b-instruct",
+ "name": "Qwen: Qwen3 30B A3B Instruct 2507",
+ "context_length": 262144,
+ "max_completion_tokens": 262144,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 8e-8,
+ "completion": 3.3e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "qwen/qwen3-30b-a3b-thinking",
+ "name": "Qwen: Qwen3 30B A3B Thinking 2507",
+ "context_length": 32768,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 5.1e-8,
+ "completion": 3.4e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "qwen/qwen3-32b",
+ "name": "Qwen: Qwen3 32B",
+ "context_length": 40960,
+ "max_completion_tokens": 40960,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 8e-8,
+ "completion": 2.4e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "qwen/qwen3-8b",
+ "name": "Qwen: Qwen3 8B",
+ "context_length": 128000,
+ "max_completion_tokens": 20000,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 2.8e-8,
+ "completion": 1.104e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "qwen/qwen3-coder",
+ "name": "Qwen: Qwen3 Coder 480B A35B",
+ "context_length": 262144,
+ "max_completion_tokens": 262144,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 2.2e-7,
+ "completion": 9.5e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "qwen/qwen3-coder-30b-a3b-instruct",
+ "name": "Qwen: Qwen3 Coder 30B A3B Instruct",
+ "context_length": 262144,
+ "max_completion_tokens": 262144,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 6e-8,
+ "completion": 2.5e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "qwen/qwen3-coder-flash",
+ "name": "Qwen: Qwen3 Coder Flash",
+ "context_length": 128000,
+ "max_completion_tokens": 65536,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 3e-7,
+ "completion": 1.5e-6,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "qwen/qwen3-coder-plus",
+ "name": "Qwen: Qwen3 Coder Plus",
+ "context_length": 128000,
+ "max_completion_tokens": 65536,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 1e-6,
+ "completion": 5e-6,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "qwen/qwen3-max",
+ "name": "Qwen: Qwen3 Max",
+ "context_length": 256000,
+ "max_completion_tokens": 32768,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 1.2e-6,
+ "completion": 6e-6,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "qwen/qwen3-next-80b-a3b-instruct",
+ "name": "Qwen: Qwen3 Next 80B A3B Instruct",
+ "context_length": 262144,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 9e-8,
+ "completion": 1.1e-6,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "qwen/qwen3-next-80b-a3b-thinking",
+ "name": "Qwen: Qwen3 Next 80B A3B Thinking",
+ "context_length": 131072,
+ "max_completion_tokens": 32768,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 1.2e-7,
+ "completion": 1.2e-6,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "qwen/qwen3-vl-235b-a22b-instruct",
+ "name": "Qwen: Qwen3 VL 235B A22B Instruct",
+ "context_length": 262144,
+ "input_modalities": [
+ "text",
+ "image"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 2e-7,
+ "completion": 1.2e-6,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "qwen/qwen3-vl-235b-a22b-thinking",
+ "name": "Qwen: Qwen3 VL 235B A22B Thinking",
+ "context_length": 262144,
+ "max_completion_tokens": 262144,
+ "input_modalities": [
+ "text",
+ "image"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 3e-7,
+ "completion": 1.2e-6,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "qwen/qwen3-vl-30b-a3b-instruct",
+ "name": "Qwen: Qwen3 VL 30B A3B Instruct",
+ "context_length": 262144,
+ "input_modalities": [
+ "text",
+ "image"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 1.5e-7,
+ "completion": 6e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "qwen/qwen3-vl-30b-a3b-thinking",
+ "name": "Qwen: Qwen3 VL 30B A3B Thinking",
+ "context_length": 131072,
+ "max_completion_tokens": 32768,
+ "input_modalities": [
+ "text",
+ "image"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 1.6e-7,
+ "completion": 8e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "qwen/qwen3-vl-8b-instruct",
+ "name": "Qwen: Qwen3 VL 8B Instruct",
+ "context_length": 131072,
+ "max_completion_tokens": 32768,
+ "input_modalities": [
+ "image",
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 6.4e-8,
+ "completion": 4e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "qwen/qwen3-vl-8b-thinking",
+ "name": "Qwen: Qwen3 VL 8B Thinking",
+ "context_length": 256000,
+ "max_completion_tokens": 32768,
+ "input_modalities": [
+ "image",
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 1.8e-7,
+ "completion": 2.1e-6,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "qwen/qwq-32b",
+ "name": "Qwen: QwQ 32B",
+ "context_length": 32768,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 1.5e-7,
+ "completion": 4e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "x-ai/grok-3",
+ "name": "xAI: Grok 3",
+ "context_length": 131072,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 3e-6,
+ "completion": 0.000015,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "x-ai/grok-3-mini",
+ "name": "xAI: Grok 3 Mini",
+ "context_length": 131072,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 3e-7,
+ "completion": 5e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "x-ai/grok-4",
+ "name": "xAI: Grok 4",
+ "context_length": 256000,
+ "input_modalities": [
+ "image",
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 3e-6,
+ "completion": 0.000015,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "x-ai/grok-4-fast",
+ "name": "xAI: Grok 4 Fast",
+ "context_length": 2000000,
+ "max_completion_tokens": 30000,
+ "input_modalities": [
+ "text",
+ "image"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 2e-7,
+ "completion": 5e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "x-ai/grok-4.1-fast",
+ "name": "xAI: Grok 4.1 Fast",
+ "context_length": 2000000,
+ "max_completion_tokens": 30000,
+ "input_modalities": [
+ "text",
+ "image"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 2e-7,
+ "completion": 5e-7,
+ "request": 0.0,
+ "image": 0.0
+ }
+ },
+ {
+ "id": "x-ai/grok-code-fast-1",
+ "name": "xAI: Grok Code Fast 1",
+ "context_length": 256000,
+ "max_completion_tokens": 10000,
+ "input_modalities": [
+ "text"
+ ],
+ "output_modalities": [
+ "text"
+ ],
+ "supports_tools": true,
+ "pricing": {
+ "prompt": 2e-7,
+ "completion": 1.5e-6,
+ "request": 0.0,
+ "image": 0.0
+ }
+ }
+]
\ No newline at end of file
diff --git a/crates/goose/src/providers/canonical/mod.rs b/crates/goose/src/providers/canonical/mod.rs
new file mode 100644
index 0000000000..1aac285ca5
--- /dev/null
+++ b/crates/goose/src/providers/canonical/mod.rs
@@ -0,0 +1,22 @@
+mod model;
+mod name_builder;
+mod registry;
+
+pub use model::{CanonicalModel, Pricing};
+pub use name_builder::{canonical_name, map_to_canonical_model, strip_version_suffix};
+pub use registry::CanonicalModelRegistry;
+
+#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
+pub struct ModelMapping {
+ pub provider_model: String,
+ pub canonical_model: String,
+}
+
+impl ModelMapping {
+ pub fn new(provider_model: impl Into, canonical_model: impl Into) -> Self {
+ Self {
+ provider_model: provider_model.into(),
+ canonical_model: canonical_model.into(),
+ }
+ }
+}
diff --git a/crates/goose/src/providers/canonical/model.rs b/crates/goose/src/providers/canonical/model.rs
new file mode 100644
index 0000000000..77108b4951
--- /dev/null
+++ b/crates/goose/src/providers/canonical/model.rs
@@ -0,0 +1,53 @@
+use serde::{Deserialize, Serialize};
+
+/// Pricing information for a model (all costs in USD per token)
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct Pricing {
+ /// Cost per prompt token
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub prompt: Option,
+
+ /// Cost per completion token
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub completion: Option,
+
+ /// Cost per request
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub request: Option,
+
+ /// Cost per image
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub image: Option,
+}
+
+/// Canonical representation of a model
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct CanonicalModel {
+ /// Model identifier (e.g., "anthropic/claude-3-5-sonnet" or "openai/gpt-4o:extended")
+ pub id: String,
+
+ /// Human-readable name (e.g., "Claude 3.5 Sonnet")
+ pub name: String,
+
+ /// Maximum context window size in tokens
+ pub context_length: usize,
+
+ /// Maximum completion tokens
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub max_completion_tokens: Option,
+
+ /// Input modalities supported (e.g., ["text", "image"])
+ #[serde(default)]
+ pub input_modalities: Vec,
+
+ /// Output modalities supported (e.g., ["text"])
+ #[serde(default)]
+ pub output_modalities: Vec,
+
+ /// Whether the model supports tool calling
+ #[serde(default)]
+ pub supports_tools: bool,
+
+ /// Pricing for this model
+ pub pricing: Pricing,
+}
diff --git a/crates/goose/src/providers/canonical/name_builder.rs b/crates/goose/src/providers/canonical/name_builder.rs
new file mode 100644
index 0000000000..c82629ed56
--- /dev/null
+++ b/crates/goose/src/providers/canonical/name_builder.rs
@@ -0,0 +1,542 @@
+use once_cell::sync::Lazy;
+use regex::Regex;
+
+static NORMALIZE_VERSION_RE: Lazy = Lazy::new(|| Regex::new(r"-(\d)-(\d)(-|$)").unwrap());
+
+static STRIP_PATTERNS: Lazy> = Lazy::new(|| {
+ vec![
+ Regex::new(r"-latest$").unwrap(),
+ Regex::new(r"-preview(-\d+)*$").unwrap(),
+ Regex::new(r"-exp(-\d+)*$").unwrap(),
+ Regex::new(r":exacto$").unwrap(),
+ Regex::new(r"-\d{8}$").unwrap(),
+ Regex::new(r"-\d{4}-\d{2}-\d{2}$").unwrap(),
+ Regex::new(r"-v\d+(\.\d+)*$").unwrap(),
+ Regex::new(r"-\d{3,}$").unwrap(),
+ Regex::new(r"-bedrock$").unwrap(),
+ ]
+});
+
+static CLAUDE_PATTERNS: Lazy> = Lazy::new(|| {
+ ["sonnet", "opus", "haiku"]
+ .iter()
+ .map(|&size| {
+ (
+ Regex::new(&format!("claude-([0-9.-]+)-{}", size)).unwrap(),
+ Regex::new(&format!("claude-{}-([0-9.-]+)", size)).unwrap(),
+ size,
+ )
+ })
+ .collect()
+});
+
+/// Build canonical model name from provider and model identifiers
+pub fn canonical_name(provider: &str, model: &str) -> String {
+ let model_base = strip_version_suffix(model);
+
+ // OpenRouter models are already in canonical format
+ if provider == "openrouter" {
+ model_base
+ } else {
+ format!("{}/{}", provider, model_base)
+ }
+}
+
+/// Try to build a canonical name and check if it exists in the registry
+fn try_canonical(
+ provider: &str,
+ model: &str,
+ registry: &super::CanonicalModelRegistry,
+) -> Option {
+ let candidate = canonical_name(provider, model);
+ registry.get(&candidate).map(|_| candidate)
+}
+
+/// Try to map a provider/model pair to a canonical model
+pub fn map_to_canonical_model(
+ provider: &str,
+ model: &str,
+ registry: &super::CanonicalModelRegistry,
+) -> Option {
+ // Try direct mapping first
+ if let Some(candidate) = try_canonical(provider, model, registry) {
+ return Some(candidate);
+ }
+
+ // Try with common prefixes stripped
+ let model_stripped = strip_common_prefixes(model);
+ if model_stripped != model {
+ if let Some(candidate) = try_canonical(provider, &model_stripped, registry) {
+ return Some(candidate);
+ }
+ }
+
+ // Try word-order swapping for Claude models (claude-4-opus β claude-opus-4)
+ if let Some(swapped) = swap_claude_word_order(&model_stripped) {
+ if let Some(candidate) = try_canonical(provider, &swapped, registry) {
+ return Some(candidate);
+ }
+
+ if is_hosting_provider(provider) {
+ if let Some(inferred) = infer_provider_from_model(&swapped) {
+ if let Some(candidate) = try_canonical(inferred, &swapped, registry) {
+ return Some(candidate);
+ }
+ }
+ }
+ }
+
+ // For hosting providers, try to infer the real provider from model name patterns
+ if is_hosting_provider(provider) {
+ if let Some(inferred_provider) = infer_provider_from_model(&model_stripped) {
+ if let Some(candidate) = try_canonical(inferred_provider, &model_stripped, registry) {
+ return Some(candidate);
+ }
+ }
+
+ if let Some(inferred) = infer_provider_from_model(model) {
+ if let Some(candidate) = try_canonical(inferred, model, registry) {
+ return Some(candidate);
+ }
+ }
+ }
+
+ // For provider-prefixed models like "databricks-meta-llama-3-1-70b"
+ if let Some((extracted_provider, extracted_model)) = extract_provider_prefix(&model_stripped) {
+ if let Some(candidate) = try_canonical(extracted_provider, extracted_model, registry) {
+ return Some(candidate);
+ }
+ }
+
+ None
+}
+
+/// Swap word order for Claude models to handle both naming conventions
+fn swap_claude_word_order(model: &str) -> Option {
+ if !model.starts_with("claude-") {
+ return None;
+ }
+
+ for (forward_re, reverse_re, size) in CLAUDE_PATTERNS.iter() {
+ if let Some(captures) = forward_re.captures(model) {
+ let version = &captures[1];
+ return Some(format!("claude-{}-{}", size, version));
+ }
+
+ if let Some(captures) = reverse_re.captures(model) {
+ let version = &captures[1];
+ return Some(format!("claude-{}-{}", version, size));
+ }
+ }
+
+ None
+}
+
+fn is_hosting_provider(provider: &str) -> bool {
+ matches!(provider, "databricks" | "openrouter" | "azure" | "bedrock")
+}
+
+/// Infer the real provider from model name patterns
+fn infer_provider_from_model(model: &str) -> Option<&'static str> {
+ let model_lower = model.to_lowercase();
+
+ if model_lower.contains("claude") {
+ return Some("anthropic");
+ }
+
+ if model_lower.starts_with("gpt-")
+ || model_lower.starts_with("o1")
+ || model_lower.starts_with("o3")
+ || model_lower.starts_with("o4")
+ || model_lower.starts_with("chatgpt-")
+ {
+ return Some("openai");
+ }
+
+ if model_lower.starts_with("gemini-") || model_lower.starts_with("gemma-") {
+ return Some("google");
+ }
+
+ if model_lower.contains("llama") {
+ return Some("meta-llama");
+ }
+
+ if model_lower.starts_with("mistral")
+ || model_lower.starts_with("mixtral")
+ || model_lower.starts_with("codestral")
+ || model_lower.starts_with("ministral")
+ || model_lower.starts_with("pixtral")
+ || model_lower.starts_with("devstral")
+ || model_lower.starts_with("voxtral")
+ {
+ return Some("mistralai");
+ }
+
+ if model_lower.contains("deepseek") {
+ return Some("deepseek");
+ }
+
+ if model_lower.contains("qwen") {
+ return Some("qwen");
+ }
+
+ if model_lower.contains("grok") {
+ return Some("x-ai");
+ }
+
+ if model_lower.contains("jamba") {
+ return Some("ai21");
+ }
+
+ if model_lower.contains("command") {
+ return Some("cohere");
+ }
+
+ None
+}
+
+/// Strip common prefixes from model names using pattern matching
+/// Looks for known model family patterns and strips everything before them
+fn strip_common_prefixes(model: &str) -> String {
+ let model_patterns = [
+ "claude-",
+ "gpt-",
+ "gemini-",
+ "gemma-",
+ "o1-",
+ "o1",
+ "o3-",
+ "o3",
+ "o4-",
+ "llama-",
+ "mistral-",
+ "mixtral-",
+ "chatgpt-",
+ "deepseek-",
+ "qwen-",
+ "grok-",
+ "jamba-",
+ "command-",
+ "codestral",
+ "ministral-",
+ "pixtral-",
+ "devstral-",
+ ];
+
+ let mut earliest_pos = None;
+
+ for pattern in &model_patterns {
+ if let Some(pos) = model.to_lowercase().find(pattern) {
+ if earliest_pos.is_none() || pos < earliest_pos.unwrap() {
+ earliest_pos = Some(pos);
+ }
+ }
+ }
+
+ // If we found a pattern, strip everything before it
+ if let Some(pos) = earliest_pos {
+ return model.get(pos..).unwrap_or(model).to_string();
+ }
+
+ model.to_string()
+}
+
+/// Try to extract provider prefix from model names like "databricks-meta-llama-3-1-70b"
+/// Returns (provider, model) tuple if found
+fn extract_provider_prefix(model: &str) -> Option<(&'static str, &str)> {
+ let known_providers = [
+ "anthropic",
+ "openai",
+ "google",
+ "meta-llama",
+ "mistralai",
+ "cohere",
+ "ai21",
+ "amazon",
+ "deepseek",
+ "qwen",
+ "x-ai",
+ "nvidia",
+ "microsoft",
+ "perplexity",
+ ];
+
+ for provider in &known_providers {
+ let prefix = format!("{}-", provider);
+ if model.starts_with(&prefix) {
+ if let Some(model_part) = model.strip_prefix(&prefix) {
+ return Some((provider, model_part));
+ }
+ }
+ }
+
+ None
+}
+
+/// Strip version suffixes from model names and normalize version numbers
+pub fn strip_version_suffix(model: &str) -> String {
+ let mut result = NORMALIZE_VERSION_RE
+ .replace_all(model, "-$1.$2$3")
+ .to_string();
+
+ let mut changed = true;
+ while changed {
+ let before = result.clone();
+ for pattern in STRIP_PATTERNS.iter() {
+ result = pattern.replace(&result, "").to_string();
+ }
+ changed = result != before;
+ }
+
+ result
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_map_to_canonical_model() {
+ let r = super::super::CanonicalModelRegistry::bundled().unwrap();
+
+ // === Direct provider (non-hosting) ===
+ assert_eq!(
+ map_to_canonical_model("anthropic", "claude-3-5-sonnet-20241022", r),
+ Some("anthropic/claude-3.5-sonnet".to_string())
+ );
+ assert_eq!(
+ map_to_canonical_model("openai", "gpt-4o-latest", r),
+ Some("openai/gpt-4o".to_string())
+ );
+ assert_eq!(
+ map_to_canonical_model("openai", "gpt-4-turbo-2024-04-09", r),
+ Some("openai/gpt-4-turbo".to_string())
+ );
+
+ // === OpenRouter (already canonical format) ===
+ assert_eq!(
+ map_to_canonical_model("openrouter", "anthropic/claude-3.5-sonnet", r),
+ Some("anthropic/claude-3.5-sonnet".to_string())
+ );
+
+ // === Anthropic Claude - basic ===
+ assert_eq!(
+ map_to_canonical_model("databricks", "claude-3-5-sonnet", r),
+ Some("anthropic/claude-3.5-sonnet".to_string())
+ );
+ assert_eq!(
+ map_to_canonical_model("databricks", "claude-3-5-sonnet-20241022", r),
+ Some("anthropic/claude-3.5-sonnet".to_string())
+ );
+ assert_eq!(
+ map_to_canonical_model("databricks", "claude-3-5-sonnet-latest", r),
+ Some("anthropic/claude-3.5-sonnet".to_string())
+ );
+
+ // === Claude word-order swapping (3.x series) ===
+ assert_eq!(
+ map_to_canonical_model("databricks", "claude-haiku-3-5", r),
+ Some("anthropic/claude-3.5-haiku".to_string())
+ );
+ assert_eq!(
+ map_to_canonical_model("databricks", "claude-sonnet-3-7", r),
+ Some("anthropic/claude-3.7-sonnet".to_string())
+ );
+ assert_eq!(
+ map_to_canonical_model("databricks", "ng-tools-claude-haiku-3-5", r),
+ Some("anthropic/claude-3.5-haiku".to_string())
+ );
+
+ // === Claude word-order swapping (4.x series) ===
+ assert_eq!(
+ map_to_canonical_model("databricks", "claude-4-opus", r),
+ Some("anthropic/claude-opus-4".to_string())
+ );
+ assert_eq!(
+ map_to_canonical_model("databricks", "claude-4-sonnet", r),
+ Some("anthropic/claude-sonnet-4".to_string())
+ );
+ assert_eq!(
+ map_to_canonical_model("databricks", "raml-claude-opus-4-5", r),
+ Some("anthropic/claude-opus-4.5".to_string())
+ );
+ assert_eq!(
+ map_to_canonical_model("databricks", "databricks-claude-sonnet-4-5", r),
+ Some("anthropic/claude-sonnet-4.5".to_string())
+ );
+
+ // === Claude with custom prefixes ===
+ assert_eq!(
+ map_to_canonical_model("databricks", "goose-claude-4-opus", r),
+ Some("anthropic/claude-opus-4".to_string())
+ );
+ assert_eq!(
+ map_to_canonical_model("databricks", "kgoose-claude-4-sonnet", r),
+ Some("anthropic/claude-sonnet-4".to_string())
+ );
+ assert_eq!(
+ map_to_canonical_model("databricks", "headless-goose-claude-4-sonnet", r),
+ Some("anthropic/claude-sonnet-4".to_string())
+ );
+ assert_eq!(
+ map_to_canonical_model("databricks", "kgoose-cashapp-claude-4-sonnet", r),
+ Some("anthropic/claude-sonnet-4".to_string())
+ );
+
+ // === Claude with platform suffixes ===
+ assert_eq!(
+ map_to_canonical_model("databricks", "claude-4-sonnet-bedrock", r),
+ Some("anthropic/claude-sonnet-4".to_string())
+ );
+ assert_eq!(
+ map_to_canonical_model("databricks", "goose-claude-4-sonnet-bedrock", r),
+ Some("anthropic/claude-sonnet-4".to_string())
+ );
+ assert_eq!(
+ map_to_canonical_model("bedrock", "claude-3-5-sonnet", r),
+ Some("anthropic/claude-3.5-sonnet".to_string())
+ );
+
+ // === OpenAI GPT ===
+ assert_eq!(
+ map_to_canonical_model("databricks", "gpt-4o", r),
+ Some("openai/gpt-4o".to_string())
+ );
+ assert_eq!(
+ map_to_canonical_model("databricks", "gpt-4o-2024-11-20", r),
+ Some("openai/gpt-4o".to_string())
+ );
+ assert_eq!(
+ map_to_canonical_model("databricks", "gpt-4o-latest", r),
+ Some("openai/gpt-4o".to_string())
+ );
+ assert_eq!(
+ map_to_canonical_model("databricks", "kgoose-gpt-4o", r),
+ Some("openai/gpt-4o".to_string())
+ );
+ assert_eq!(
+ map_to_canonical_model("azure", "gpt-4o", r),
+ Some("openai/gpt-4o".to_string())
+ );
+
+ // === OpenAI O-series ===
+ assert_eq!(
+ map_to_canonical_model("databricks", "goose-o1", r),
+ Some("openai/o1".to_string())
+ );
+ assert_eq!(
+ map_to_canonical_model("databricks", "kgoose-o3", r),
+ Some("openai/o3".to_string())
+ );
+ assert_eq!(
+ map_to_canonical_model("databricks", "headless-goose-o3-mini", r),
+ Some("openai/o3-mini".to_string())
+ );
+
+ // === Google Gemini ===
+ assert_eq!(
+ map_to_canonical_model("databricks", "gemini-2-5-flash", r),
+ Some("google/gemini-2.5-flash".to_string())
+ );
+
+ // === Meta Llama ===
+ assert_eq!(
+ map_to_canonical_model("databricks", "meta-llama-3-1-70b-instruct", r),
+ Some("meta-llama/llama-3.1-70b-instruct".to_string())
+ );
+
+ // === Mistral variants ===
+ assert_eq!(
+ map_to_canonical_model("databricks", "codestral", r),
+ Some("mistralai/codestral".to_string())
+ );
+ assert_eq!(
+ map_to_canonical_model("databricks", "ministral-8b", r),
+ Some("mistralai/ministral-8b".to_string())
+ );
+
+ // === DeepSeek ===
+ assert_eq!(
+ map_to_canonical_model("databricks", "databricks-deepseek-chat", r),
+ Some("deepseek/deepseek-chat".to_string())
+ );
+ assert_eq!(
+ map_to_canonical_model("databricks", "deepseek-r1", r),
+ Some("deepseek/deepseek-r1".to_string())
+ );
+
+ // === Qwen ===
+ assert_eq!(
+ map_to_canonical_model("databricks", "qwen-2-5-72b-instruct", r),
+ Some("qwen/qwen-2.5-72b-instruct".to_string())
+ );
+ assert_eq!(
+ map_to_canonical_model("databricks", "goose-qwen-2-5-72b-instruct", r),
+ Some("qwen/qwen-2.5-72b-instruct".to_string())
+ );
+
+ // === Grok (X.AI) ===
+ assert_eq!(
+ map_to_canonical_model("databricks", "grok-3", r),
+ Some("x-ai/grok-3".to_string())
+ );
+ assert_eq!(
+ map_to_canonical_model("databricks", "databricks-grok-4-fast", r),
+ Some("x-ai/grok-4-fast".to_string())
+ );
+ assert_eq!(
+ map_to_canonical_model("databricks", "kgoose-grok-4-fast", r),
+ Some("x-ai/grok-4-fast".to_string())
+ );
+
+ // === Jamba (AI21) ===
+ assert_eq!(
+ map_to_canonical_model("databricks", "jamba-large-1-7", r),
+ Some("ai21/jamba-large-1.7".to_string())
+ );
+ assert_eq!(
+ map_to_canonical_model("databricks", "databricks-jamba-large-1-7", r),
+ Some("ai21/jamba-large-1.7".to_string())
+ );
+
+ // === Cohere Command ===
+ assert_eq!(
+ map_to_canonical_model("databricks", "command-r-plus-08", r),
+ Some("cohere/command-r-plus-08".to_string())
+ );
+ assert_eq!(
+ map_to_canonical_model("databricks", "goose-command-r-08", r),
+ Some("cohere/command-r-08".to_string())
+ );
+
+ // === Provider-prefixed extraction ===
+ assert_eq!(
+ map_to_canonical_model("databricks", "anthropic-claude-3-5-sonnet", r),
+ Some("anthropic/claude-3.5-sonnet".to_string())
+ );
+ assert_eq!(
+ map_to_canonical_model("databricks", "openai-gpt-4o", r),
+ Some("openai/gpt-4o".to_string())
+ );
+ assert_eq!(
+ map_to_canonical_model("databricks", "google-gemini-2-5-flash", r),
+ Some("google/gemini-2.5-flash".to_string())
+ );
+ assert_eq!(
+ map_to_canonical_model("databricks", "mistralai-mistral-large", r),
+ Some("mistralai/mistral-large".to_string())
+ );
+ assert_eq!(
+ map_to_canonical_model("databricks", "deepseek-deepseek-chat", r),
+ Some("deepseek/deepseek-chat".to_string())
+ );
+ assert_eq!(
+ map_to_canonical_model("databricks", "qwen-qwen-2-5-72b-instruct", r),
+ Some("qwen/qwen-2.5-72b-instruct".to_string())
+ );
+ assert_eq!(
+ map_to_canonical_model("databricks", "x-ai-grok-3", r),
+ Some("x-ai/grok-3".to_string())
+ );
+ }
+}
diff --git a/crates/goose/src/providers/canonical/registry.rs b/crates/goose/src/providers/canonical/registry.rs
new file mode 100644
index 0000000000..b601bfca11
--- /dev/null
+++ b/crates/goose/src/providers/canonical/registry.rs
@@ -0,0 +1,92 @@
+use super::CanonicalModel;
+use anyhow::{Context, Result};
+use once_cell::sync::Lazy;
+use std::collections::HashMap;
+use std::path::Path;
+
+/// Cached bundled canonical model registry
+static BUNDLED_REGISTRY: Lazy> = Lazy::new(|| {
+ const CANONICAL_MODELS_JSON: &str = include_str!("data/canonical_models.json");
+
+ let models: Vec = serde_json::from_str(CANONICAL_MODELS_JSON)
+ .context("Failed to parse bundled canonical models JSON")?;
+
+ let mut registry = CanonicalModelRegistry::new();
+ for model in models {
+ registry.register(model);
+ }
+
+ Ok(registry)
+});
+
+#[derive(Debug, Clone)]
+pub struct CanonicalModelRegistry {
+ models: HashMap,
+}
+
+impl CanonicalModelRegistry {
+ pub fn new() -> Self {
+ Self {
+ models: HashMap::new(),
+ }
+ }
+
+ pub fn bundled() -> Result<&'static Self> {
+ BUNDLED_REGISTRY
+ .as_ref()
+ .map_err(|e| anyhow::anyhow!("{}", e))
+ }
+
+ pub fn from_file(path: impl AsRef) -> Result {
+ let content = std::fs::read_to_string(path.as_ref())
+ .context("Failed to read canonical models file")?;
+
+ let models: Vec =
+ serde_json::from_str(&content).context("Failed to parse canonical models JSON")?;
+
+ let mut registry = Self::new();
+ for model in models {
+ registry.register(model);
+ }
+
+ Ok(registry)
+ }
+
+ pub fn to_file(&self, path: impl AsRef) -> Result<()> {
+ let mut models: Vec<&CanonicalModel> = self.models.values().collect();
+ models.sort_by(|a, b| a.id.cmp(&b.id));
+
+ let json = serde_json::to_string_pretty(&models)
+ .context("Failed to serialize canonical models")?;
+
+ std::fs::write(path.as_ref(), json).context("Failed to write canonical models file")?;
+
+ Ok(())
+ }
+
+ pub fn register(&mut self, model: CanonicalModel) {
+ self.models.insert(model.id.clone(), model);
+ }
+
+ pub fn get(&self, name: &str) -> Option<&CanonicalModel> {
+ self.models.get(name)
+ }
+
+ pub fn all_models(&self) -> Vec<&CanonicalModel> {
+ self.models.values().collect()
+ }
+
+ pub fn count(&self) -> usize {
+ self.models.len()
+ }
+
+ pub fn contains(&self, name: &str) -> bool {
+ self.models.contains_key(name)
+ }
+}
+
+impl Default for CanonicalModelRegistry {
+ fn default() -> Self {
+ Self::new()
+ }
+}
diff --git a/crates/goose/src/providers/claude_code.rs b/crates/goose/src/providers/claude_code.rs
index 7ed9963377..6c148aaa1f 100644
--- a/crates/goose/src/providers/claude_code.rs
+++ b/crates/goose/src/providers/claude_code.rs
@@ -74,9 +74,10 @@ impl ClaudeCodeProvider {
}
}
MessageContent::ToolResponse(tool_response) => {
- if let Ok(tool_contents) = &tool_response.tool_result {
+ if let Ok(result) = &tool_response.tool_result {
// Convert tool result contents to text
- let content_text = tool_contents
+ let content_text = result
+ .content
.iter()
.filter_map(|content| match &content.raw {
rmcp::model::RawContent::Text(text_content) => {
diff --git a/crates/goose/src/providers/cursor_agent.rs b/crates/goose/src/providers/cursor_agent.rs
index c6cdd0ab0f..ffe7c1058d 100644
--- a/crates/goose/src/providers/cursor_agent.rs
+++ b/crates/goose/src/providers/cursor_agent.rs
@@ -86,8 +86,9 @@ impl CursorAgentProvider {
}
}
MessageContent::ToolResponse(tool_response) => {
- if let Ok(tool_contents) = &tool_response.tool_result {
- let content_text = tool_contents
+ if let Ok(result) = &tool_response.tool_result {
+ let content_text = result
+ .content
.iter()
.filter_map(|content| match &content.raw {
rmcp::model::RawContent::Text(text_content) => {
diff --git a/crates/goose/src/providers/declarative/inception.json b/crates/goose/src/providers/declarative/inception.json
new file mode 100644
index 0000000000..b6211428f8
--- /dev/null
+++ b/crates/goose/src/providers/declarative/inception.json
@@ -0,0 +1,15 @@
+{
+ "name": "inception",
+ "engine": "openai",
+ "display_name": "Inception",
+ "description": "Mercury models from Inception leveraging diffusion for lightning speeds",
+ "api_key_env": "INCEPTION_API_KEY",
+ "base_url": "https://api.inceptionlabs.ai",
+ "models": [
+ {
+ "name": "mercury-coder",
+ "context_limit": 128000
+ }
+ ],
+ "supports_streaming": true
+}
diff --git a/crates/goose/src/providers/errors.rs b/crates/goose/src/providers/errors.rs
index b6ee4e7431..b72605d944 100644
--- a/crates/goose/src/providers/errors.rs
+++ b/crates/goose/src/providers/errors.rs
@@ -32,6 +32,21 @@ pub enum ProviderError {
NotImplemented(String),
}
+impl ProviderError {
+ pub fn telemetry_type(&self) -> &'static str {
+ match self {
+ ProviderError::Authentication(_) => "auth",
+ ProviderError::ContextLengthExceeded(_) => "context_length",
+ ProviderError::RateLimitExceeded { .. } => "rate_limit",
+ ProviderError::ServerError(_) => "server",
+ ProviderError::RequestFailed(_) => "request",
+ ProviderError::ExecutionError(_) => "execution",
+ ProviderError::UsageError(_) => "usage",
+ ProviderError::NotImplemented(_) => "not_implemented",
+ }
+ }
+}
+
impl From for ProviderError {
fn from(error: anyhow::Error) -> Self {
if let Some(reqwest_err) = error.downcast_ref::() {
diff --git a/crates/goose/src/providers/formats/anthropic.rs b/crates/goose/src/providers/formats/anthropic.rs
index da573678b3..3771b3adad 100644
--- a/crates/goose/src/providers/formats/anthropic.rs
+++ b/crates/goose/src/providers/formats/anthropic.rs
@@ -68,6 +68,7 @@ pub fn format_messages(messages: &[Message]) -> Vec {
MessageContent::ToolResponse(tool_response) => match &tool_response.tool_result {
Ok(result) => {
let text = result
+ .content
.iter()
.filter_map(|c| c.as_text().map(|t| t.text.clone()))
.collect::>()
diff --git a/crates/goose/src/providers/formats/bedrock.rs b/crates/goose/src/providers/formats/bedrock.rs
index f343059df5..d81d7b8b5b 100644
--- a/crates/goose/src/providers/formats/bedrock.rs
+++ b/crates/goose/src/providers/formats/bedrock.rs
@@ -86,8 +86,9 @@ pub fn to_bedrock_message_content(content: &MessageContent) -> Result {
let content = match &tool_res.tool_result {
- Ok(content) => Some(
- content
+ Ok(result) => Some(
+ result
+ .content
.iter()
// Filter out content items that have User in their audience
.filter(|c| {
@@ -318,6 +319,12 @@ pub fn from_bedrock_content_block(block: &bedrock::ContentBlock) -> Result>>()
+ .map(|content| rmcp::model::CallToolResult {
+ content,
+ structured_content: None,
+ is_error: Some(false),
+ meta: None,
+ })
},
),
_ => bail!("Unsupported content block type from Bedrock"),
diff --git a/crates/goose/src/providers/formats/databricks.rs b/crates/goose/src/providers/formats/databricks.rs
index 343f16b172..ee0984185e 100644
--- a/crates/goose/src/providers/formats/databricks.rs
+++ b/crates/goose/src/providers/formats/databricks.rs
@@ -133,9 +133,10 @@ fn format_messages(messages: &[Message], image_format: &ImageFormat) -> Vec {
match &response.tool_result {
- Ok(contents) => {
+ Ok(call_result) => {
// Send only contents with no audience or with Assistant in the audience
- let abridged: Vec<_> = contents
+ let abridged: Vec<_> = call_result
+ .content
.iter()
.filter(|content| {
content
@@ -533,12 +534,7 @@ pub fn create_request(
};
let system_message = DatabricksMessage {
- role: if is_openai_reasoning_model {
- "developer"
- } else {
- "system"
- }
- .to_string(),
+ role: "system".to_string(),
content: system.into(),
tool_calls: None,
tool_call_id: None,
@@ -638,6 +634,7 @@ pub fn create_request(
mod tests {
use super::*;
use crate::conversation::message::Message;
+ use rmcp::model::CallToolResult;
use rmcp::object;
use serde_json::json;
@@ -727,8 +724,15 @@ mod tests {
panic!("should be tool request");
};
- messages
- .push(Message::user().with_tool_response(tool_id, Ok(vec![Content::text("Result")])));
+ messages.push(Message::user().with_tool_response(
+ tool_id,
+ Ok(CallToolResult {
+ content: vec![Content::text("Result")],
+ structured_content: None,
+ is_error: Some(false),
+ meta: None,
+ }),
+ ));
let as_value =
serde_json::to_value(format_messages(&messages, &ImageFormat::OpenAi)).unwrap();
@@ -764,8 +768,15 @@ mod tests {
panic!("should be tool request");
};
- messages
- .push(Message::user().with_tool_response(tool_id, Ok(vec![Content::text("Result")])));
+ messages.push(Message::user().with_tool_response(
+ tool_id,
+ Ok(CallToolResult {
+ content: vec![Content::text("Result")],
+ structured_content: None,
+ is_error: Some(false),
+ meta: None,
+ }),
+ ));
let as_value =
serde_json::to_value(format_messages(&messages, &ImageFormat::OpenAi)).unwrap();
@@ -1007,41 +1018,7 @@ mod tests {
}
#[test]
- fn test_create_request_o1_default() -> anyhow::Result<()> {
- // Test default medium reasoning effort for O1 model
- let model_config = ModelConfig {
- model_name: "o1".to_string(),
- context_limit: Some(4096),
- temperature: None,
- max_tokens: Some(1024),
- toolshim: false,
- toolshim_model: None,
- fast_model: None,
- };
- let request = create_request(&model_config, "system", &[], &[], &ImageFormat::OpenAi)?;
- let obj = request.as_object().unwrap();
- let expected = json!({
- "model": "o1",
- "messages": [
- {
- "role": "developer",
- "content": "system"
- }
- ],
- "reasoning_effort": "medium",
- "max_completion_tokens": 1024
- });
-
- for (key, value) in expected.as_object().unwrap() {
- assert_eq!(obj.get(key).unwrap(), value);
- }
-
- Ok(())
- }
-
- #[test]
- fn test_create_request_o3_custom_reasoning_effort() -> anyhow::Result<()> {
- // Test custom reasoning effort for O3 model
+ fn test_create_request_reasoning_effort() -> anyhow::Result<()> {
let model_config = ModelConfig {
model_name: "o3-mini-high".to_string(),
context_limit: Some(4096),
@@ -1052,23 +1029,7 @@ mod tests {
fast_model: None,
};
let request = create_request(&model_config, "system", &[], &[], &ImageFormat::OpenAi)?;
- let obj = request.as_object().unwrap();
- let expected = json!({
- "model": "o3-mini",
- "messages": [
- {
- "role": "developer",
- "content": "system"
- }
- ],
- "reasoning_effort": "high",
- "max_completion_tokens": 1024
- });
-
- for (key, value) in expected.as_object().unwrap() {
- assert_eq!(obj.get(key).unwrap(), value);
- }
-
+ assert_eq!(request["reasoning_effort"], "high");
Ok(())
}
diff --git a/crates/goose/src/providers/formats/google.rs b/crates/goose/src/providers/formats/google.rs
index a0b4a16cf3..1e9a85dd9e 100644
--- a/crates/goose/src/providers/formats/google.rs
+++ b/crates/goose/src/providers/formats/google.rs
@@ -70,9 +70,10 @@ pub fn format_messages(messages: &[Message]) -> Vec {
},
MessageContent::ToolResponse(response) => {
match &response.tool_result {
- Ok(contents) => {
+ Ok(result) => {
// Send only contents with no audience or with Assistant in the audience
- let abridged: Vec<_> = contents
+ let abridged: Vec<_> = result
+ .content
.iter()
.filter(|content| {
content.audience().is_none_or(|audience| {
@@ -394,7 +395,7 @@ pub fn create_request(
mod tests {
use super::*;
use crate::conversation::message::Message;
- use rmcp::model::CallToolRequestParam;
+ use rmcp::model::{CallToolRequestParam, CallToolResult};
use rmcp::{model::Content, object};
use serde_json::json;
@@ -429,7 +430,12 @@ mod tests {
0,
vec![MessageContent::tool_response(
id.to_string(),
- Ok(tool_response),
+ Ok(CallToolResult {
+ content: tool_response,
+ structured_content: None,
+ is_error: Some(false),
+ meta: None,
+ }),
)],
)
}
diff --git a/crates/goose/src/providers/formats/mod.rs b/crates/goose/src/providers/formats/mod.rs
index 6f3df3d0bf..e0a88288a4 100644
--- a/crates/goose/src/providers/formats/mod.rs
+++ b/crates/goose/src/providers/formats/mod.rs
@@ -4,4 +4,5 @@ pub mod databricks;
pub mod gcpvertexai;
pub mod google;
pub mod openai;
+pub mod openai_responses;
pub mod snowflake;
diff --git a/crates/goose/src/providers/formats/openai.rs b/crates/goose/src/providers/formats/openai.rs
index fab1b8d5ed..04fd962a1b 100644
--- a/crates/goose/src/providers/formats/openai.rs
+++ b/crates/goose/src/providers/formats/openai.rs
@@ -128,9 +128,10 @@ pub fn format_messages(messages: &[Message], image_format: &ImageFormat) -> Vec<
},
MessageContent::ToolResponse(response) => {
match &response.tool_result {
- Ok(contents) => {
+ Ok(result) => {
// Send only contents with no audience or with Assistant in the audience
- let abridged: Vec<_> = contents
+ let abridged: Vec<_> = result
+ .content
.iter()
.filter(|content| {
content
@@ -710,6 +711,7 @@ pub fn create_request(
mod tests {
use super::*;
use crate::conversation::message::Message;
+ use rmcp::model::CallToolResult;
use rmcp::object;
use serde_json::json;
use tokio::pin;
@@ -868,8 +870,15 @@ mod tests {
panic!("should be tool request");
};
- messages
- .push(Message::user().with_tool_response(tool_id, Ok(vec![Content::text("Result")])));
+ messages.push(Message::user().with_tool_response(
+ tool_id,
+ Ok(CallToolResult {
+ content: vec![Content::text("Result")],
+ structured_content: None,
+ is_error: Some(false),
+ meta: None,
+ }),
+ ));
let spec = format_messages(&messages, &ImageFormat::OpenAi);
@@ -904,8 +913,15 @@ mod tests {
panic!("should be tool request");
};
- messages
- .push(Message::user().with_tool_response(tool_id, Ok(vec![Content::text("Result")])));
+ messages.push(Message::user().with_tool_response(
+ tool_id,
+ Ok(CallToolResult {
+ content: vec![Content::text("Result")],
+ structured_content: None,
+ is_error: Some(false),
+ meta: None,
+ }),
+ ));
let spec = format_messages(&messages, &ImageFormat::OpenAi);
diff --git a/crates/goose/src/providers/formats/openai_responses.rs b/crates/goose/src/providers/formats/openai_responses.rs
new file mode 100644
index 0000000000..d8c237d4ab
--- /dev/null
+++ b/crates/goose/src/providers/formats/openai_responses.rs
@@ -0,0 +1,696 @@
+use crate::conversation::message::{Message, MessageContent};
+use crate::model::ModelConfig;
+use crate::providers::base::{ProviderUsage, Usage};
+use anyhow::{anyhow, Error};
+use async_stream::try_stream;
+use chrono;
+use futures::Stream;
+use rmcp::model::{object, CallToolRequestParam, RawContent, Role, Tool};
+use serde::{Deserialize, Serialize};
+use serde_json::{json, Value};
+use std::ops::Deref;
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct ResponsesApiResponse {
+ pub id: String,
+ pub object: String,
+ pub created_at: i64,
+ pub status: String,
+ pub model: String,
+ pub output: Vec,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub reasoning: Option,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub usage: Option,
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+#[serde(tag = "type")]
+#[serde(rename_all = "snake_case")]
+pub enum ResponseOutputItem {
+ Reasoning {
+ id: String,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ summary: Option>,
+ },
+ Message {
+ id: String,
+ status: String,
+ role: String,
+ content: Vec,
+ },
+ FunctionCall {
+ id: String,
+ status: String,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ call_id: Option,
+ name: String,
+ arguments: String,
+ },
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+#[serde(tag = "type")]
+#[serde(rename_all = "snake_case")]
+pub enum ResponseContentBlock {
+ OutputText {
+ text: String,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ annotations: Option>,
+ },
+ ToolCall {
+ id: String,
+ name: String,
+ input: Value,
+ },
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct ResponseReasoningInfo {
+ pub effort: String,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub summary: Option,
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct ResponseUsage {
+ pub input_tokens: i32,
+ pub output_tokens: i32,
+ pub total_tokens: i32,
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+#[serde(tag = "type")]
+#[serde(rename_all = "snake_case")]
+pub enum ResponsesStreamEvent {
+ #[serde(rename = "response.created")]
+ ResponseCreated {
+ sequence_number: i32,
+ response: ResponseMetadata,
+ },
+ #[serde(rename = "response.in_progress")]
+ ResponseInProgress {
+ sequence_number: i32,
+ response: ResponseMetadata,
+ },
+ #[serde(rename = "response.output_item.added")]
+ OutputItemAdded {
+ sequence_number: i32,
+ output_index: i32,
+ item: ResponseOutputItemInfo,
+ },
+ #[serde(rename = "response.content_part.added")]
+ ContentPartAdded {
+ sequence_number: i32,
+ item_id: String,
+ output_index: i32,
+ content_index: i32,
+ part: ContentPart,
+ },
+ #[serde(rename = "response.output_text.delta")]
+ OutputTextDelta {
+ sequence_number: i32,
+ item_id: String,
+ output_index: i32,
+ content_index: i32,
+ delta: String,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ logprobs: Option>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ obfuscation: Option,
+ },
+ #[serde(rename = "response.output_item.done")]
+ OutputItemDone {
+ sequence_number: i32,
+ output_index: i32,
+ item: ResponseOutputItemInfo,
+ },
+ #[serde(rename = "response.content_part.done")]
+ ContentPartDone {
+ sequence_number: i32,
+ item_id: String,
+ output_index: i32,
+ content_index: i32,
+ part: ContentPart,
+ },
+ #[serde(rename = "response.output_text.done")]
+ OutputTextDone {
+ sequence_number: i32,
+ item_id: String,
+ output_index: i32,
+ content_index: i32,
+ text: String,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ logprobs: Option>,
+ },
+ #[serde(rename = "response.completed")]
+ ResponseCompleted {
+ sequence_number: i32,
+ response: ResponseMetadata,
+ },
+ #[serde(rename = "response.failed")]
+ ResponseFailed { sequence_number: i32, error: Value },
+ #[serde(rename = "response.function_call_arguments.delta")]
+ FunctionCallArgumentsDelta {
+ sequence_number: i32,
+ item_id: String,
+ output_index: i32,
+ delta: String,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ obfuscation: Option,
+ },
+ #[serde(rename = "response.function_call_arguments.done")]
+ FunctionCallArgumentsDone {
+ sequence_number: i32,
+ item_id: String,
+ output_index: i32,
+ arguments: String,
+ },
+ #[serde(rename = "error")]
+ Error { error: Value },
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct ResponseMetadata {
+ pub id: String,
+ pub object: String,
+ pub created_at: i64,
+ pub status: String,
+ pub model: String,
+ pub output: Vec,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub usage: Option,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub reasoning: Option,
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone)]
+#[serde(tag = "type")]
+#[serde(rename_all = "snake_case")]
+pub enum ResponseOutputItemInfo {
+ Reasoning {
+ id: String,
+ summary: Vec,
+ },
+ Message {
+ id: String,
+ status: String,
+ role: String,
+ content: Vec,
+ },
+ FunctionCall {
+ id: String,
+ status: String,
+ call_id: String,
+ name: String,
+ arguments: String,
+ },
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone)]
+#[serde(tag = "type")]
+#[serde(rename_all = "snake_case")]
+pub enum ContentPart {
+ OutputText {
+ text: String,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ annotations: Option>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ logprobs: Option>,
+ },
+ ToolCall {
+ id: String,
+ name: String,
+ arguments: String,
+ },
+}
+
+fn add_conversation_history(input_items: &mut Vec, messages: &[Message]) {
+ for message in messages.iter().filter(|m| m.is_agent_visible()) {
+ let has_only_tool_content = message.content.iter().all(|c| {
+ matches!(
+ c,
+ MessageContent::ToolRequest(_) | MessageContent::ToolResponse(_)
+ )
+ });
+
+ if has_only_tool_content {
+ continue;
+ }
+
+ if message.role != Role::User && message.role != Role::Assistant {
+ continue;
+ }
+
+ let role = match message.role {
+ Role::User => "user",
+ Role::Assistant => "assistant",
+ };
+
+ let mut content_items = Vec::new();
+ for content in &message.content {
+ if let MessageContent::Text(text) = content {
+ if !text.text.is_empty() {
+ let content_type = if message.role == Role::Assistant {
+ "output_text"
+ } else {
+ "input_text"
+ };
+ content_items.push(json!({
+ "type": content_type,
+ "text": text.text
+ }));
+ }
+ }
+ }
+
+ if !content_items.is_empty() {
+ input_items.push(json!({
+ "role": role,
+ "content": content_items
+ }));
+ }
+ }
+}
+
+fn add_function_calls(input_items: &mut Vec, messages: &[Message]) {
+ for message in messages.iter().filter(|m| m.is_agent_visible()) {
+ if message.role == Role::Assistant {
+ for content in &message.content {
+ if let MessageContent::ToolRequest(request) = content {
+ if let Ok(tool_call) = &request.tool_call {
+ let arguments_str = tool_call
+ .arguments
+ .as_ref()
+ .map(|args| {
+ serde_json::to_string(args).unwrap_or_else(|_| "{}".to_string())
+ })
+ .unwrap_or_else(|| "{}".to_string());
+
+ tracing::debug!(
+ "Replaying function_call with call_id: {}, name: {}",
+ request.id,
+ tool_call.name
+ );
+ input_items.push(json!({
+ "type": "function_call",
+ "call_id": request.id,
+ "name": tool_call.name,
+ "arguments": arguments_str
+ }));
+ }
+ }
+ }
+ }
+ }
+}
+
+fn add_function_call_outputs(input_items: &mut Vec, messages: &[Message]) {
+ for message in messages.iter().filter(|m| m.is_agent_visible()) {
+ for content in &message.content {
+ if let MessageContent::ToolResponse(response) = content {
+ match &response.tool_result {
+ Ok(contents) => {
+ let text_content: Vec = contents
+ .content
+ .iter()
+ .filter_map(|c| {
+ if let RawContent::Text(t) = c.deref() {
+ Some(t.text.clone())
+ } else {
+ None
+ }
+ })
+ .collect();
+
+ if !text_content.is_empty() {
+ tracing::debug!(
+ "Sending function_call_output with call_id: {}",
+ response.id
+ );
+ input_items.push(json!({
+ "type": "function_call_output",
+ "call_id": response.id,
+ "output": text_content.join("\n")
+ }));
+ }
+ }
+ Err(error_data) => {
+ // Handle error responses - must send them back to the API
+ // to avoid "No tool output found" errors
+ tracing::debug!(
+ "Sending function_call_output error with call_id: {}",
+ response.id
+ );
+ input_items.push(json!({
+ "type": "function_call_output",
+ "call_id": response.id,
+ "output": format!("Error: {}", error_data.message)
+ }));
+ }
+ }
+ }
+ }
+ }
+}
+
+pub fn create_responses_request(
+ model_config: &ModelConfig,
+ system: &str,
+ messages: &[Message],
+ tools: &[Tool],
+) -> anyhow::Result {
+ let mut input_items = Vec::new();
+
+ if !system.is_empty() {
+ input_items.push(json!({
+ "role": "system",
+ "content": [{
+ "type": "input_text",
+ "text": system
+ }]
+ }));
+ }
+
+ add_conversation_history(&mut input_items, messages);
+ add_function_calls(&mut input_items, messages);
+ add_function_call_outputs(&mut input_items, messages);
+
+ let mut payload = json!({
+ "model": model_config.model_name,
+ "input": input_items,
+ "store": false, // Don't store responses on server (we replay history ourselves)
+ });
+
+ if !tools.is_empty() {
+ let tools_spec: Vec = tools
+ .iter()
+ .map(|tool| {
+ json!({
+ "type": "function",
+ "name": tool.name,
+ "description": tool.description,
+ "parameters": tool.input_schema,
+ })
+ })
+ .collect();
+
+ payload
+ .as_object_mut()
+ .unwrap()
+ .insert("tools".to_string(), json!(tools_spec));
+ }
+
+ if let Some(temp) = model_config.temperature {
+ payload
+ .as_object_mut()
+ .unwrap()
+ .insert("temperature".to_string(), json!(temp));
+ }
+
+ if let Some(tokens) = model_config.max_tokens {
+ payload
+ .as_object_mut()
+ .unwrap()
+ .insert("max_output_tokens".to_string(), json!(tokens));
+ }
+
+ Ok(payload)
+}
+
+pub fn responses_api_to_message(response: &ResponsesApiResponse) -> anyhow::Result {
+ let mut content = Vec::new();
+
+ for item in &response.output {
+ match item {
+ ResponseOutputItem::Reasoning { .. } => {
+ continue;
+ }
+ ResponseOutputItem::Message {
+ content: msg_content,
+ ..
+ } => {
+ for block in msg_content {
+ match block {
+ ResponseContentBlock::OutputText { text, .. } => {
+ if !text.is_empty() {
+ content.push(MessageContent::text(text));
+ }
+ }
+ ResponseContentBlock::ToolCall { id, name, input } => {
+ content.push(MessageContent::tool_request(
+ id.clone(),
+ Ok(CallToolRequestParam {
+ name: name.clone().into(),
+ arguments: Some(object(input.clone())),
+ }),
+ ));
+ }
+ }
+ }
+ }
+ ResponseOutputItem::FunctionCall {
+ id,
+ name,
+ arguments,
+ ..
+ } => {
+ tracing::debug!("Received FunctionCall with id: {}, name: {}", id, name);
+ let parsed_args = if arguments.is_empty() {
+ json!({})
+ } else {
+ serde_json::from_str(arguments).unwrap_or_else(|_| json!({}))
+ };
+
+ content.push(MessageContent::tool_request(
+ id.clone(),
+ Ok(CallToolRequestParam {
+ name: name.clone().into(),
+ arguments: Some(object(parsed_args)),
+ }),
+ ));
+ }
+ }
+ }
+
+ let mut message = Message::new(Role::Assistant, chrono::Utc::now().timestamp(), content);
+
+ message = message.with_id(response.id.clone());
+
+ Ok(message)
+}
+
+pub fn get_responses_usage(response: &ResponsesApiResponse) -> Usage {
+ response.usage.as_ref().map_or_else(Usage::default, |u| {
+ Usage::new(
+ Some(u.input_tokens),
+ Some(u.output_tokens),
+ Some(u.total_tokens),
+ )
+ })
+}
+
+fn process_streaming_output_items(
+ output_items: Vec,
+ is_text_response: bool,
+) -> Vec {
+ let mut content = Vec::new();
+
+ for item in output_items {
+ match item {
+ ResponseOutputItemInfo::Reasoning { .. } => {
+ // Skip reasoning items
+ }
+ ResponseOutputItemInfo::Message { content: parts, .. } => {
+ for part in parts {
+ match part {
+ ContentPart::OutputText { text, .. } => {
+ if !text.is_empty() && !is_text_response {
+ content.push(MessageContent::text(&text));
+ }
+ }
+ ContentPart::ToolCall {
+ id,
+ name,
+ arguments,
+ } => {
+ let parsed_args = if arguments.is_empty() {
+ json!({})
+ } else {
+ serde_json::from_str(&arguments).unwrap_or_else(|_| json!({}))
+ };
+
+ content.push(MessageContent::tool_request(
+ id,
+ Ok(CallToolRequestParam {
+ name: name.into(),
+ arguments: Some(object(parsed_args)),
+ }),
+ ));
+ }
+ }
+ }
+ }
+ ResponseOutputItemInfo::FunctionCall {
+ call_id,
+ name,
+ arguments,
+ ..
+ } => {
+ let parsed_args = if arguments.is_empty() {
+ json!({})
+ } else {
+ serde_json::from_str(&arguments).unwrap_or_else(|_| json!({}))
+ };
+
+ content.push(MessageContent::tool_request(
+ call_id,
+ Ok(CallToolRequestParam {
+ name: name.into(),
+ arguments: Some(object(parsed_args)),
+ }),
+ ));
+ }
+ }
+ }
+
+ content
+}
+
+pub fn responses_api_to_streaming_message(
+ mut stream: S,
+) -> impl Stream- , Option
)>> + 'static
+where
+ S: Stream- > + Unpin + Send + 'static,
+{
+ try_stream! {
+ use futures::StreamExt;
+
+ let mut accumulated_text = String::new();
+ let mut response_id: Option
= None;
+ let mut model_name: Option = None;
+ let mut final_usage: Option = None;
+ let mut output_items: Vec = Vec::new();
+ let mut is_text_response = false;
+
+ 'outer: while let Some(response) = stream.next().await {
+ let response_str = response?;
+
+ // Skip empty lines
+ if response_str.trim().is_empty() {
+ continue;
+ }
+
+ // Parse SSE format: "event: \ndata: "
+ // For now, we only care about the data line
+ let data_line = if response_str.starts_with("data: ") {
+ response_str.strip_prefix("data: ").unwrap()
+ } else if response_str.starts_with("event: ") {
+ // Skip event type lines
+ continue;
+ } else {
+ // Try to parse as-is in case there's no prefix
+ &response_str
+ };
+
+ if data_line == "[DONE]" {
+ break 'outer;
+ }
+
+ let event: ResponsesStreamEvent = serde_json::from_str(data_line)
+ .map_err(|e| anyhow!("Failed to parse Responses stream event: {}: {:?}", e, data_line))?;
+
+ match event {
+ ResponsesStreamEvent::ResponseCreated { response, .. } |
+ ResponsesStreamEvent::ResponseInProgress { response, .. } => {
+ response_id = Some(response.id);
+ model_name = Some(response.model);
+ }
+
+ ResponsesStreamEvent::OutputTextDelta { delta, .. } => {
+ is_text_response = true;
+ accumulated_text.push_str(&delta);
+
+ // Yield incremental text updates for true streaming
+ let mut content = Vec::new();
+ if !delta.is_empty() {
+ content.push(MessageContent::text(&delta));
+ }
+ let mut msg = Message::new(Role::Assistant, chrono::Utc::now().timestamp(), content);
+
+ // Add ID so desktop client knows these deltas are part of the same message
+ if let Some(id) = &response_id {
+ msg = msg.with_id(id.clone());
+ }
+
+ yield (Some(msg), None);
+ }
+
+ ResponsesStreamEvent::OutputItemDone { item, .. } => {
+ output_items.push(item);
+ }
+
+ ResponsesStreamEvent::OutputTextDone { .. } => {
+ // Text is already complete from deltas, this is just a summary event
+ }
+
+ ResponsesStreamEvent::ResponseCompleted { response, .. } => {
+ let model = model_name.as_ref().unwrap_or(&response.model);
+ let usage = response.usage.as_ref().map_or_else(
+ Usage::default,
+ |u| Usage::new(
+ Some(u.input_tokens),
+ Some(u.output_tokens),
+ Some(u.total_tokens),
+ ),
+ );
+ final_usage = Some(ProviderUsage {
+ usage,
+ model: model.clone(),
+ });
+
+ // For complete output, use the response output items
+ if !response.output.is_empty() {
+ output_items = response.output;
+ }
+
+ break 'outer;
+ }
+
+ ResponsesStreamEvent::FunctionCallArgumentsDelta { .. } => {
+ // Function call arguments are being streamed, but we'll get the complete
+ // arguments in the OutputItemDone event, so we can ignore deltas for now
+ }
+
+ ResponsesStreamEvent::FunctionCallArgumentsDone { .. } => {
+ // Arguments are complete, will be in the OutputItemDone event
+ }
+
+ ResponsesStreamEvent::ResponseFailed { error, .. } => {
+ Err(anyhow!("Responses API failed: {:?}", error))?;
+ }
+
+ ResponsesStreamEvent::Error { error } => {
+ Err(anyhow!("Responses API error: {:?}", error))?;
+ }
+
+ _ => {
+ // Ignore other event types (OutputItemAdded, ContentPartAdded, ContentPartDone)
+ }
+ }
+ }
+
+ // Process final output items and yield usage data
+ let content = process_streaming_output_items(output_items, is_text_response);
+
+ if !content.is_empty() {
+ let mut message = Message::new(Role::Assistant, chrono::Utc::now().timestamp(), content);
+ if let Some(id) = response_id {
+ message = message.with_id(id);
+ }
+ yield (Some(message), final_usage);
+ } else if let Some(usage) = final_usage {
+ yield (None, Some(usage));
+ }
+ }
+}
diff --git a/crates/goose/src/providers/formats/snowflake.rs b/crates/goose/src/providers/formats/snowflake.rs
index 19f3012f10..bfe69e34cc 100644
--- a/crates/goose/src/providers/formats/snowflake.rs
+++ b/crates/goose/src/providers/formats/snowflake.rs
@@ -37,6 +37,7 @@ pub fn format_messages(messages: &[Message]) -> Vec {
MessageContent::ToolResponse(tool_response) => {
if let Ok(result) = &tool_response.tool_result {
let text = result
+ .content
.iter()
.filter_map(|c| c.as_text().map(|t| t.text.clone()))
.collect::>()
diff --git a/crates/goose/src/providers/lead_worker.rs b/crates/goose/src/providers/lead_worker.rs
index 1dc8fe7a8b..e0786b4186 100644
--- a/crates/goose/src/providers/lead_worker.rs
+++ b/crates/goose/src/providers/lead_worker.rs
@@ -215,9 +215,9 @@ impl LeadWorkerProvider {
if let Err(tool_error) = &tool_response.tool_result {
failure_indicators += 1;
tracing::debug!("Tool execution failure detected: {:?}", tool_error);
- } else if let Ok(contents) = &tool_response.tool_result {
+ } else if let Ok(result) = &tool_response.tool_result {
// Check tool output for error indicators
- if self.contains_error_indicators(contents) {
+ if self.contains_error_indicators(&result.content) {
failure_indicators += 1;
tracing::debug!("Tool output contains error indicators");
}
diff --git a/crates/goose/src/providers/mod.rs b/crates/goose/src/providers/mod.rs
index 2e27b45d6d..3c3e64344d 100644
--- a/crates/goose/src/providers/mod.rs
+++ b/crates/goose/src/providers/mod.rs
@@ -5,6 +5,7 @@ pub mod azure;
pub mod azureauth;
pub mod base;
pub mod bedrock;
+pub mod canonical;
pub mod claude_code;
pub mod cursor_agent;
pub mod databricks;
diff --git a/crates/goose/src/providers/openai.rs b/crates/goose/src/providers/openai.rs
index 07b0339bdf..9af5579cd1 100644
--- a/crates/goose/src/providers/openai.rs
+++ b/crates/goose/src/providers/openai.rs
@@ -15,7 +15,13 @@ use super::api_client::{ApiClient, AuthMethod};
use super::base::{ConfigKey, ModelInfo, Provider, ProviderMetadata, ProviderUsage, Usage};
use super::embedding::{EmbeddingCapable, EmbeddingRequest, EmbeddingResponse};
use super::errors::ProviderError;
-use super::formats::openai::{create_request, get_usage, response_to_message};
+use super::formats::openai::{
+ create_request, get_usage, response_to_message, response_to_streaming_message,
+};
+use super::formats::openai_responses::{
+ create_responses_request, get_responses_usage, responses_api_to_message,
+ responses_api_to_streaming_message, ResponsesApiResponse,
+};
use super::retry::ProviderRetry;
use super::utils::{
get_model, handle_response_openai_compat, handle_status_openai_compat, ImageFormat,
@@ -25,7 +31,6 @@ use crate::conversation::message::Message;
use crate::model::ModelConfig;
use crate::providers::base::MessageStream;
-use crate::providers::formats::openai::response_to_streaming_message;
use crate::providers::utils::RequestLog;
use rmcp::model::Tool;
@@ -41,6 +46,8 @@ pub const OPEN_AI_KNOWN_MODELS: &[(&str, usize)] = &[
("gpt-3.5-turbo", 16_385),
("gpt-4-turbo", 128_000),
("o4-mini", 128_000),
+ ("gpt-5.1-codex", 400_000),
+ ("gpt-5-codex", 400_000),
];
pub const OPEN_AI_DOC_URL: &str = "https://platform.openai.com/docs/models";
@@ -184,6 +191,10 @@ impl OpenAiProvider {
})
}
+ fn uses_responses_api(model_name: &str) -> bool {
+ model_name.starts_with("gpt-5-codex") || model_name.starts_with("gpt-5.1-codex")
+ }
+
async fn post(&self, payload: &Value) -> Result {
let response = self
.api_client
@@ -191,6 +202,14 @@ impl OpenAiProvider {
.await?;
handle_response_openai_compat(response).await
}
+
+ async fn post_responses(&self, payload: &Value) -> Result {
+ let response = self
+ .api_client
+ .response_post("v1/responses", payload)
+ .await?;
+ handle_response_openai_compat(response).await
+ }
}
#[async_trait]
@@ -238,31 +257,62 @@ impl Provider for OpenAiProvider {
messages: &[Message],
tools: &[Tool],
) -> Result<(Message, ProviderUsage), ProviderError> {
- let payload = create_request(model_config, system, messages, tools, &ImageFormat::OpenAi)?;
+ if Self::uses_responses_api(&model_config.model_name) {
+ let payload = create_responses_request(model_config, system, messages, tools)?;
+ let mut log = RequestLog::start(&self.model, &payload)?;
- let mut log = RequestLog::start(&self.model, &payload)?;
- let json_response = self
- .with_retry(|| async {
- let payload_clone = payload.clone();
- self.post(&payload_clone).await
- })
- .await
- .inspect_err(|e| {
- let _ = log.error(e);
- })?;
+ let json_response = self
+ .with_retry(|| async {
+ let payload_clone = payload.clone();
+ self.post_responses(&payload_clone).await
+ })
+ .await
+ .inspect_err(|e| {
+ let _ = log.error(e);
+ })?;
- let message = response_to_message(&json_response)?;
- let usage = json_response
- .get("usage")
- .map(get_usage)
- .unwrap_or_else(|| {
- tracing::debug!("Failed to get usage data");
- Usage::default()
- });
+ let responses_api_response: ResponsesApiResponse =
+ serde_json::from_value(json_response.clone()).map_err(|e| {
+ ProviderError::ExecutionError(format!(
+ "Failed to parse responses API response: {}",
+ e
+ ))
+ })?;
- let model = get_model(&json_response);
- log.write(&json_response, Some(&usage))?;
- Ok((message, ProviderUsage::new(model, usage)))
+ let message = responses_api_to_message(&responses_api_response)?;
+ let usage = get_responses_usage(&responses_api_response);
+ let model = responses_api_response.model.clone();
+
+ log.write(&json_response, Some(&usage))?;
+ Ok((message, ProviderUsage::new(model, usage)))
+ } else {
+ let payload =
+ create_request(model_config, system, messages, tools, &ImageFormat::OpenAi)?;
+
+ let mut log = RequestLog::start(&self.model, &payload)?;
+ let json_response = self
+ .with_retry(|| async {
+ let payload_clone = payload.clone();
+ self.post(&payload_clone).await
+ })
+ .await
+ .inspect_err(|e| {
+ let _ = log.error(e);
+ })?;
+
+ let message = response_to_message(&json_response)?;
+ let usage = json_response
+ .get("usage")
+ .map(get_usage)
+ .unwrap_or_else(|| {
+ tracing::debug!("Failed to get usage data");
+ Usage::default()
+ });
+
+ let model = get_model(&json_response);
+ log.write(&json_response, Some(&usage))?;
+ Ok((message, ProviderUsage::new(model, usage)))
+ }
}
async fn fetch_supported_models(&self) -> Result>, ProviderError> {
@@ -319,40 +369,77 @@ impl Provider for OpenAiProvider {
messages: &[Message],
tools: &[Tool],
) -> Result {
- let mut payload =
- create_request(&self.model, system, messages, tools, &ImageFormat::OpenAi)?;
- payload["stream"] = serde_json::Value::Bool(true);
- payload["stream_options"] = json!({
- "include_usage": true,
- });
- let mut log = RequestLog::start(&self.model, &payload)?;
+ if Self::uses_responses_api(&self.model.model_name) {
+ let mut payload = create_responses_request(&self.model, system, messages, tools)?;
+ payload["stream"] = serde_json::Value::Bool(true);
- let response = self
- .with_retry(|| async {
- let resp = self
- .api_client
- .response_post(&self.base_path, &payload)
- .await?;
- handle_status_openai_compat(resp).await
- })
- .await
- .inspect_err(|e| {
- let _ = log.error(e);
- })?;
- let stream = response.bytes_stream().map_err(io::Error::other);
+ let mut log = RequestLog::start(&self.model, &payload)?;
- Ok(Box::pin(try_stream! {
- let stream_reader = StreamReader::new(stream);
- let framed = FramedRead::new(stream_reader, LinesCodec::new()).map_err(anyhow::Error::from);
+ let response = self
+ .with_retry(|| async {
+ let payload_clone = payload.clone();
+ let resp = self
+ .api_client
+ .response_post("v1/responses", &payload_clone)
+ .await?;
+ handle_status_openai_compat(resp).await
+ })
+ .await
+ .inspect_err(|e| {
+ let _ = log.error(e);
+ })?;
- let message_stream = response_to_streaming_message(framed);
- pin!(message_stream);
- while let Some(message) = message_stream.next().await {
- let (message, usage) = message.map_err(|e| ProviderError::RequestFailed(format!("Stream decode error: {}", e)))?;
- log.write(&message, usage.as_ref().map(|f| f.usage).as_ref())?;
- yield (message, usage);
- }
- }))
+ let stream = response.bytes_stream().map_err(io::Error::other);
+
+ Ok(Box::pin(try_stream! {
+ let stream_reader = StreamReader::new(stream);
+ let framed = FramedRead::new(stream_reader, LinesCodec::new()).map_err(anyhow::Error::from);
+
+ let message_stream = responses_api_to_streaming_message(framed);
+ pin!(message_stream);
+ while let Some(message) = message_stream.next().await {
+ let (message, usage) = message.map_err(|e| ProviderError::RequestFailed(format!("Stream decode error: {}", e)))?;
+ log.write(&message, usage.as_ref().map(|f| f.usage).as_ref())?;
+ yield (message, usage);
+ }
+ }))
+ } else {
+ let mut payload =
+ create_request(&self.model, system, messages, tools, &ImageFormat::OpenAi)?;
+ payload["stream"] = serde_json::Value::Bool(true);
+ payload["stream_options"] = json!({
+ "include_usage": true,
+ });
+ let mut log = RequestLog::start(&self.model, &payload)?;
+
+ let response = self
+ .with_retry(|| async {
+ let resp = self
+ .api_client
+ .response_post(&self.base_path, &payload)
+ .await?;
+ handle_status_openai_compat(resp).await
+ })
+ .await
+ .inspect_err(|e| {
+ let _ = log.error(e);
+ })?;
+
+ let stream = response.bytes_stream().map_err(io::Error::other);
+
+ Ok(Box::pin(try_stream! {
+ let stream_reader = StreamReader::new(stream);
+ let framed = FramedRead::new(stream_reader, LinesCodec::new()).map_err(anyhow::Error::from);
+
+ let message_stream = response_to_streaming_message(framed);
+ pin!(message_stream);
+ while let Some(message) = message_stream.next().await {
+ let (message, usage) = message.map_err(|e| ProviderError::RequestFailed(format!("Stream decode error: {}", e)))?;
+ log.write(&message, usage.as_ref().map(|f| f.usage).as_ref())?;
+ yield (message, usage);
+ }
+ }))
+ }
}
}
diff --git a/crates/goose/src/providers/toolshim.rs b/crates/goose/src/providers/toolshim.rs
index 1ec958c397..b4dc3cb11b 100644
--- a/crates/goose/src/providers/toolshim.rs
+++ b/crates/goose/src/providers/toolshim.rs
@@ -343,8 +343,9 @@ pub fn convert_tool_messages_to_text(messages: &[Message]) -> Conversation {
has_tool_content = true;
// Convert tool response to text format
let text = match &res.tool_result {
- Ok(contents) => {
- let text_contents: Vec = contents
+ Ok(result) => {
+ let text_contents: Vec = result
+ .content
.iter()
.filter_map(|c| match c.deref() {
RawContent::Text(t) => Some(t.text.clone()),
diff --git a/crates/goose/src/security/mod.rs b/crates/goose/src/security/mod.rs
index c81144c33d..1ff0b7d6fd 100644
--- a/crates/goose/src/security/mod.rs
+++ b/crates/goose/src/security/mod.rs
@@ -36,7 +36,7 @@ impl SecurityManager {
let config = Config::global();
config
- .get_param::("security_prompt_enabled")
+ .get_param::("SECURITY_PROMPT_ENABLED")
.unwrap_or(false)
}
diff --git a/crates/goose/src/security/scanner.rs b/crates/goose/src/security/scanner.rs
index fec76b449e..591b7b577b 100644
--- a/crates/goose/src/security/scanner.rs
+++ b/crates/goose/src/security/scanner.rs
@@ -27,7 +27,7 @@ impl PromptInjectionScanner {
use crate::config::Config;
let config = Config::global();
- if let Ok(threshold) = config.get_param::("security_prompt_threshold") {
+ if let Ok(threshold) = config.get_param::("SECURITY_PROMPT_THRESHOLD") {
return threshold as f32;
}
diff --git a/crates/goose/src/session/session_manager.rs b/crates/goose/src/session/session_manager.rs
index 427dc0c409..11ce210ff7 100644
--- a/crates/goose/src/session/session_manager.rs
+++ b/crates/goose/src/session/session_manager.rs
@@ -120,8 +120,8 @@ pub struct SessionUpdateBuilder {
#[derive(Serialize, ToSchema, Debug)]
#[serde(rename_all = "camelCase")]
pub struct SessionInsights {
- total_sessions: usize,
- total_tokens: i64,
+ pub total_sessions: usize,
+ pub total_tokens: i64,
}
impl SessionUpdateBuilder {
@@ -887,6 +887,7 @@ impl SessionStorage {
.await?;
tx.commit().await?;
+ crate::posthog::emit_session_started();
Ok(session)
}
diff --git a/crates/goose/tests/dynamic_task_tools_tests.rs b/crates/goose/tests/dynamic_task_tools_tests.rs
index f195d8b166..7d0180cc38 100644
--- a/crates/goose/tests/dynamic_task_tools_tests.rs
+++ b/crates/goose/tests/dynamic_task_tools_tests.rs
@@ -118,10 +118,10 @@ mod tests {
let tool_result = result.result.await;
assert!(tool_result.is_ok());
let contents = tool_result.unwrap();
- assert!(!contents.is_empty());
+ assert!(!contents.content.is_empty());
// Parse the returned JSON to verify task creation
- if let Some(text_content) = contents.first().and_then(|c| c.as_text()) {
+ if let Some(text_content) = contents.content.first().and_then(|c| c.as_text()) {
let task_payload: serde_json::Value = serde_json::from_str(&text_content.text).unwrap();
assert!(task_payload.get("task_ids").is_some());
let task_ids = task_payload.get("task_ids").unwrap().as_array().unwrap();
diff --git a/crates/goose/tests/mcp_integration_test.rs b/crates/goose/tests/mcp_integration_test.rs
index df1b70d36f..57c0ef5944 100644
--- a/crates/goose/tests/mcp_integration_test.rs
+++ b/crates/goose/tests/mcp_integration_test.rs
@@ -6,7 +6,7 @@ use std::path::PathBuf;
use std::sync::Arc;
use std::{env, fs};
-use rmcp::model::{CallToolRequestParam, Content, Tool};
+use rmcp::model::{CallToolRequestParam, CallToolResult, Tool};
use rmcp::object;
use tokio_util::sync::CancellationToken;
@@ -108,7 +108,7 @@ fn build_and_get_binary_path() -> PathBuf {
}
})
.next()
- .expect("failed to parase binary path")
+ .expect("failed to parse binary path")
}
static REPLAY_BINARY_PATH: Lazy = Lazy::new(build_and_get_binary_path);
@@ -284,7 +284,7 @@ async fn test_replayed_session(
serde_json::to_writer_pretty(File::create(results_path)?, &results)?
}
TestMode::Playback => assert_eq!(
- serde_json::from_reader::<_, Vec>>(File::open(results_path)?)?,
+ serde_json::from_reader::<_, Vec>(File::open(results_path)?)?,
results
),
};
diff --git a/crates/goose/tests/mcp_replays/cargorun--quiet-pgoose-server--bingoosed--mcpdeveloper b/crates/goose/tests/mcp_replays/cargorun--quiet-pgoose-server--bingoosed--mcpdeveloper
index 214866a2bf..39b1f2561c 100644
--- a/crates/goose/tests/mcp_replays/cargorun--quiet-pgoose-server--bingoosed--mcpdeveloper
+++ b/crates/goose/tests/mcp_replays/cargorun--quiet-pgoose-server--bingoosed--mcpdeveloper
@@ -1,28 +1,26 @@
-STDIN: {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{"elicitation": {}, "sampling":{}},"clientInfo":{"name":"goose","version":"0.0.0"}}}
-STDERR: [2m2025-10-30T14:41:09.117156Z[0m [32m INFO[0m [1;32mgoose_mcp::mcp_server_runner[0m[32m: [32mStarting MCP server[0m
-STDERR: [2;3mat[0m crates/goose-mcp/src/mcp_server_runner.rs:18
+STDIN: {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{"sampling":{},"elicitation":{}},"clientInfo":{"name":"goose","version":"0.0.0"}}}
+STDERR: 2025-12-11T19:43:38.682870Z DEBUG goose_mcp::developer::analyze: Initializing CodeAnalyzer
+STDERR: at crates/goose-mcp/src/developer/analyze/mod.rs:57
STDERR:
-STDERR: [2m2025-10-30T14:41:09.120902Z[0m [32m INFO[0m [1;32mgoose_mcp::developer::analyze::cache[0m[32m: [32mInitializing analysis cache with size 100[0m
-STDERR: [2;3mat[0m crates/goose-mcp/src/developer/analyze/cache.rs:26
+STDERR: 2025-12-11T19:43:38.683063Z DEBUG goose_mcp::developer::analyze::parser: Initializing ParserManager
+STDERR: at crates/goose-mcp/src/developer/analyze/parser.rs:19
STDERR:
-STDOUT: {"jsonrpc":"2.0","id":0,"result":{"protocolVersion":"2025-03-26","capabilities":{"prompts":{},"tools":{}},"serverInfo":{"name":"goose-developer","version":"1.11.0"},"instructions":" The developer extension gives you the capabilities to edit code files and run shell commands,\n and can be used to solve a wide range of problems.\n\nYou can use the shell tool to run any command that would work on the relevant operating system.\nUse the shell tool as needed to locate files or interact with the project.\n\nLeverage `analyze` through `return_last_only=true` subagents for deep codebase understanding with lean context\n- delegate analysis, retain summaries\n\nYour windows/screen tools can be used for visual debugging. You should not use these tools unless\nprompted to, but you can mention they are available if they are relevant.\n\nAlways prefer ripgrep (rg -C 3) to grep.\n\noperating system: macos\ncurrent directory: /Users/alexhancock/Development/goose/crates/goose\nshell: zsh\n\n \n\nAdditional Text Editor Tool Instructions:\n\nPerform text editing operations on files.\n\nThe `command` parameter specifies the operation to perform. Allowed options are:\n- `view`: View the content of a file.\n- `write`: Create or overwrite a file with the given content\n- `str_replace`: Replace text in one or more files.\n- `insert`: Insert text at a specific line location in the file.\n- `undo_edit`: Undo the last edit made to a file.\n\nTo use the write command, you must specify `file_text` which will become the new content of the file. Be careful with\nexisting files! This is a full overwrite, so you must include everything - not just sections you are modifying.\n\nTo use the str_replace command to edit multiple files, use the `diff` parameter with a unified diff.\nTo use the str_replace command to edit one file, you must specify both `old_str` and `new_str` - the `old_str` needs to exactly match one\nunique section of the original file, including any whitespace. Make sure to include enough context that the match is not\nambiguous. The entire original string will be replaced with `new_str`\n\nWhen possible, batch file edits together by using a multi-file unified `diff` within a single str_replace tool call.\n\nTo use the insert command, you must specify both `insert_line` (the line number after which to insert, 0 for beginning, -1 for end)\nand `new_str` (the text to insert).\n\n\n\nAdditional Shell Tool Instructions:\nExecute a command in the shell.\n\nThis will return the output and error concatenated into a single string, as\nyou would see from running on the command line. There will also be an indication\nof if the command succeeded or failed.\n\nAvoid commands that produce a large amount of output, and consider piping those outputs to files.\n\n**Important**: Each shell command runs in its own process. Things like directory changes or\nsourcing files do not persist between tool calls. So you may need to repeat them each time by\nstringing together commands.\nIf you need to run a long lived command, background it - e.g. `uvicorn main:app &` so that\nthis tool does not run indefinitely.\n\n**Important**: Use ripgrep - `rg` - exclusively when you need to locate a file or a code reference,\nother solutions may produce too large output because of hidden files! For example *do not* use `find` or `ls -r`\n - List files by name: `rg --files | rg `\n - List files that contain a regex: `rg '' -l`\n\n - Multiple commands: Use && to chain commands, avoid newlines\n - Example: `cd example && ls` or `source env/bin/activate && pip install numpy`\n\n\n### Global Hints\nThe developer extension includes some global hints that apply to all projects & directories.\nThese are my global goose hints.\n\n### Project Hints\nThe developer extension includes some hints for working on the project in this directory.\n# AGENTS Instructions\n\ngoose is an AI agent framework in Rust with CLI and Electron desktop interfaces.\n\n## Setup\n```bash\nsource bin/activate-hermit\ncargo build\n```\n\n## Commands\n\n### Build\n```bash\ncargo build # debug\ncargo build --release # release \njust release-binary # release + openapi\n```\n\n### Test\n```bash\ncargo test # all tests\ncargo test -p goose # specific crate\ncargo test --package goose --test mcp_integration_test\njust record-mcp-tests # record MCP\n```\n\n### Lint/Format\n```bash\ncargo fmt\n./scripts/clippy-lint.sh\ncargo clippy --fix\n```\n\n### UI\n```bash\njust generate-openapi # after server changes\njust run-ui # start desktop\ncd ui/desktop && npm test # test UI\n```\n\n## Structure\n```\ncrates/\nβββ goose # core logic\nβββ goose-bench # benchmarking\nβββ goose-cli # CLI entry\nβββ goose-server # backend (binary: goosed)\nβββ goose-mcp # MCP extensions\nβββ goose-test # test utilities\nβββ mcp-client # MCP client\nβββ mcp-core # MCP shared\nβββ mcp-server # MCP server\n\ntemporal-service/ # Go scheduler\nui/desktop/ # Electron app\n```\n\n## Development Loop\n```bash\n# 1. source bin/activate-hermit\n# 2. Make changes\n# 3. cargo fmt\n# 4. cargo build\n# 5. cargo test -p \n# 6. ./scripts/clippy-lint.sh\n# 7. [if server] just generate-openapi\n```\n\n## Rules\n\nTest: Prefer tests/ folder, e.g. crates/goose/tests/\nTest: When adding features, update goose-self-test.yaml, rebuild, then run `goose run --recipe goose-self-test.yaml` to validate\nError: Use anyhow::Result\nProvider: Implement Provider trait see providers/base.rs\nMCP: Extensions in crates/goose-mcp/\nServer: Changes need just generate-openapi\n\n## Never\n\nNever: Edit ui/desktop/openapi.json manually\nNever: Edit Cargo.toml use cargo add\nNever: Skip cargo fmt\nNever: Merge without ./scripts/clippy-lint.sh\nNever: Comment self-evident operations (`// Initialize`, `// Return result`), getters/setters, constructors, or standard Rust idioms\n\n## Entry Points\n- CLI: crates/goose-cli/src/main.rs\n- Server: crates/goose-server/src/main.rs\n- UI: ui/desktop/src/main.ts\n- Agent: crates/goose/src/agents/agent.rs\n\nThis is a rust project with crates in the crates dir:\ngoose: the main code for goose, contains all the core logic\ngoose-bench: bench marking\ngoose-cli: the command line interface, use goose crate\ngoose-mcp: the mcp servers that ship with goose. the developer sub system is of special interest\ngoose-server: the server that suports the desktop (electron) app. also known as goosed\n\n\nui/desktop has an electron app in typescript. \n\nnon trivial features should be implemented in the goose crate and then be called from the goose-cli crate for the cli. for the desktop, you want to add routes to \ngoose-server/src/routes. you can then run `just generate-openapi` to generate the openapi spec which will modify the ui/desktop/src/api files. once you have\nthat you can call the functionality from the server from the typescript.\n\ntips: \n- can look at unstaged changes for what is being worked on if starting\n- always check rust compiles, cargo fmt etc and `./scripts/clippy-lint.sh` (as well as run tests in files you are working on)\n- in ui/desktop, look at how you can run lint checks and if other tests can run\n"}}
+STDERR: 2025-12-11T19:43:38.683096Z INFO goose_mcp::developer::analyze::cache: Initializing analysis cache with size 100
+STDERR: at crates/goose-mcp/src/developer/analyze/cache.rs:26
+STDERR:
+STDOUT: {"jsonrpc":"2.0","id":0,"result":{"protocolVersion":"2025-03-26","capabilities":{"prompts":{},"tools":{}},"serverInfo":{"name":"goose-developer","version":"1.16.0"},"instructions":" The developer extension gives you the capabilities to edit code files and run shell commands,\n and can be used to solve a wide range of problems.\n\nYou can use the shell tool to run any command that would work on the relevant operating system.\nUse the shell tool as needed to locate files or interact with the project.\n\nLeverage `analyze` through `return_last_only=true` subagents for deep codebase understanding with lean context\n- delegate analysis, retain summaries\n\nYour windows/screen tools can be used for visual debugging. You should not use these tools unless\nprompted to, but you can mention they are available if they are relevant.\n\nAlways prefer ripgrep (rg -C 3) to grep.\n\noperating system: macos\ncurrent directory: /Users/douwe/proj/goose/crates/goose\nshell: /bin/zsh\n\n \nAdditional Text Editor Tool Instructions:\n\nPerform text editing operations on files.\n\nThe `command` parameter specifies the operation to perform. Allowed options are:\n- `view`: View the content of a file.\n- `write`: Create or overwrite a file with the given content\n- `str_replace`: Replace text in one or more files.\n- `insert`: Insert text at a specific line location in the file.\n- `undo_edit`: Undo the last edit made to a file.\n\nTo use the write command, you must specify `file_text` which will become the new content of the file. Be careful with\nexisting files! This is a full overwrite, so you must include everything - not just sections you are modifying.\n\nTo use the str_replace command to edit multiple files, use the `diff` parameter with a unified diff.\nTo use the str_replace command to edit one file, you must specify both `old_str` and `new_str` - the `old_str` needs to exactly match one\nunique section of the original file, including any whitespace. Make sure to include enough context that the match is not\nambiguous. The entire original string will be replaced with `new_str`\n\nWhen possible, batch file edits together by using a multi-file unified `diff` within a single str_replace tool call.\n\nTo use the insert command, you must specify both `insert_line` (the line number after which to insert, 0 for beginning, -1 for end)\nand `new_str` (the text to insert).\n\n\n\nAdditional Shell Tool Instructions:\nExecute a command in the shell.\n\nThis will return the output and error concatenated into a single string, as\nyou would see from running on the command line. There will also be an indication\nof if the command succeeded or failed.\n\nAvoid commands that produce a large amount of output, and consider piping those outputs to files.\n\n**Important**: Each shell command runs in its own process. Things like directory changes or\nsourcing files do not persist between tool calls. So you may need to repeat them each time by\nstringing together commands.\n\nIf fetching web content, consider adding Accept: text/markdown header\nIf you need to run a long lived command, background it - e.g. `uvicorn main:app &` so that\nthis tool does not run indefinitely.\n\n**Important**: Use ripgrep - `rg` - exclusively when you need to locate a file or a code reference,\nother solutions may produce too large output because of hidden files! For example *do not* use `find` or `ls -r`\n - List files by name: `rg --files | rg `\n - List files that contain a regex: `rg '' -l`\n\n - Multiple commands: Use && to chain commands, avoid newlines\n - Example: `cd example && ls` or `source env/bin/activate && pip install numpy`\n"}}
STDIN: {"jsonrpc":"2.0","method":"notifications/initialized"}
-STDERR: [2m2025-10-30T14:41:09.126014Z[0m [32m INFO[0m [1;32mrmcp::handler::server[0m[32m: [32mclient initialized[0m
-STDERR: [2;3mat[0m /Users/alexhancock/Development/goose/.hermit/rust/registry/src/index.crates.io-1949cf8c6b5b557f/rmcp-0.8.1/src/handler/server.rs:218
-STDERR:
STDIN: {"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"_meta":{"progressToken":0},"name":"text_editor","arguments":{"command":"view","path":"/tmp/goose_test/goose.txt"}}}
-STDERR: [2m2025-10-30T14:41:09.126196Z[0m [32m INFO[0m [1;32mrmcp::service[0m[32m: [32mService initialized as server, [1;32mpeer_info[0m[32m: Some(InitializeRequestParam { protocol_version: ProtocolVersion("2025-03-26"), capabilities: ClientCapabilities { experimental: None, roots: None, sampling: Some({}), elicitation: None }, client_info: Implementation { name: "goose", title: None, version: "0.0.0", icons: None, website_url: None } })[0m
-STDERR: [2;3mat[0m /Users/alexhancock/Development/goose/.hermit/rust/registry/src/index.crates.io-1949cf8c6b5b557f/rmcp-0.8.1/src/service.rs:562
-STDERR: [2;3min[0m rmcp::service::[1mserve_inner[0m
-STDERR:
STDOUT: {"jsonrpc":"2.0","id":1,"result":{"content":[{"type":"resource","resource":{"uri":"file:///tmp/goose_test/goose.txt","mimeType":"text","text":"# goose\n"},"annotations":{"audience":["assistant"]}},{"type":"text","text":"### /tmp/goose_test/goose.txt\n```\n1: # goose\n```\n","annotations":{"audience":["user"],"priority":0.0}}],"isError":false}}
STDIN: {"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"_meta":{"progressToken":1},"name":"text_editor","arguments":{"command":"str_replace","new_str":"# goose (modified by test)","old_str":"# goose","path":"/tmp/goose_test/goose.txt"}}}
STDOUT: {"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":"The file /tmp/goose_test/goose.txt has been edited, and the section now reads:\n```\n# goose (modified by test)\n```\n\nReview the changes above for errors. Undo and edit the file again if necessary!\n","annotations":{"audience":["assistant"]}},{"type":"text","text":"```\n# goose (modified by test)\n```\n","annotations":{"audience":["user"],"priority":0.2}}],"isError":false}}
STDIN: {"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"_meta":{"progressToken":2},"name":"shell","arguments":{"command":"cat /tmp/goose_test/goose.txt"}}}
+STDERR: 2025-12-11T19:43:39.019022Z DEBUG goose_mcp::developer::rmcp_developer: Shell process spawned with PID: 78321
+STDERR: at crates/goose-mcp/src/developer/rmcp_developer.rs:997
+STDERR:
STDOUT: {"jsonrpc":"2.0","method":"notifications/message","params":{"level":"info","logger":"shell_tool","data":{"type":"shell_output","stream":"stdout","output":"# goose (modified by test)"}}}
STDOUT: {"jsonrpc":"2.0","id":3,"result":{"content":[{"type":"text","text":"# goose (modified by test)\n","annotations":{"audience":["assistant"]}},{"type":"text","text":"# goose (modified by test)\n","annotations":{"audience":["user"],"priority":0.0}}],"isError":false}}
STDIN: {"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"_meta":{"progressToken":3},"name":"text_editor","arguments":{"command":"str_replace","new_str":"# goose","old_str":"# goose (modified by test)","path":"/tmp/goose_test/goose.txt"}}}
STDOUT: {"jsonrpc":"2.0","id":4,"result":{"content":[{"type":"text","text":"The file /tmp/goose_test/goose.txt has been edited, and the section now reads:\n```\n# goose\n```\n\nReview the changes above for errors. Undo and edit the file again if necessary!\n","annotations":{"audience":["assistant"]}},{"type":"text","text":"```\n# goose\n```\n","annotations":{"audience":["user"],"priority":0.2}}],"isError":false}}
STDIN: {"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"_meta":{"progressToken":4},"name":"list_windows","arguments":{}}}
-STDOUT: {"jsonrpc":"2.0","id":5,"result":{"content":[{"type":"text","text":"Available windows:\n\nItem-0\nbb3cc23c-6950-4e96-8b40-850e09f46934\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nBattery\nWiFi\nItem-0\nBentoBox\nSiri\nClock\nMenubar\nDock\njust record-mcp-tests","annotations":{"audience":["assistant"]}},{"type":"text","text":"Available windows:\n\nItem-0\nbb3cc23c-6950-4e96-8b40-850e09f46934\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nBattery\nWiFi\nItem-0\nBentoBox\nSiri\nClock\nMenubar\nDock\njust record-mcp-tests","annotations":{"audience":["user"],"priority":0.0}}],"isError":false}}
-STDERR: [2m2025-10-30T14:41:09.200915Z[0m [32m INFO[0m [1;32mrmcp::service[0m[32m: [32minput stream terminated[0m
+STDOUT: {"jsonrpc":"2.0","id":5,"result":{"content":[{"type":"text","text":"Available windows:\nMenubar","annotations":{"audience":["assistant"]}},{"type":"text","text":"Available windows:\nMenubar","annotations":{"audience":["user"],"priority":0.0}}],"isError":false}}
diff --git a/crates/goose/tests/mcp_replays/cargorun--quiet-pgoose-server--bingoosed--mcpdeveloper.results.json b/crates/goose/tests/mcp_replays/cargorun--quiet-pgoose-server--bingoosed--mcpdeveloper.results.json
index e3d4e6a355..169b8d04e3 100644
--- a/crates/goose/tests/mcp_replays/cargorun--quiet-pgoose-server--bingoosed--mcpdeveloper.results.json
+++ b/crates/goose/tests/mcp_replays/cargorun--quiet-pgoose-server--bingoosed--mcpdeveloper.results.json
@@ -1,111 +1,126 @@
[
- [
- {
- "type": "resource",
- "resource": {
- "uri": "file:///tmp/goose_test/goose.txt",
- "mimeType": "text",
- "text": "# goose\n"
+ {
+ "content": [
+ {
+ "type": "resource",
+ "resource": {
+ "uri": "file:///tmp/goose_test/goose.txt",
+ "mimeType": "text",
+ "text": "# goose\n"
+ },
+ "annotations": {
+ "audience": [
+ "assistant"
+ ]
+ }
},
- "annotations": {
- "audience": [
- "assistant"
- ]
+ {
+ "type": "text",
+ "text": "### /tmp/goose_test/goose.txt\n```\n1: # goose\n```\n",
+ "annotations": {
+ "audience": [
+ "user"
+ ],
+ "priority": 0.0
+ }
}
- },
- {
- "type": "text",
- "text": "### /tmp/goose_test/goose.txt\n```\n1: # goose\n```\n",
- "annotations": {
- "audience": [
- "user"
- ],
- "priority": 0.0
+ ],
+ "isError": false
+ },
+ {
+ "content": [
+ {
+ "type": "text",
+ "text": "The file /tmp/goose_test/goose.txt has been edited, and the section now reads:\n```\n# goose (modified by test)\n```\n\nReview the changes above for errors. Undo and edit the file again if necessary!\n",
+ "annotations": {
+ "audience": [
+ "assistant"
+ ]
+ }
+ },
+ {
+ "type": "text",
+ "text": "```\n# goose (modified by test)\n```\n",
+ "annotations": {
+ "audience": [
+ "user"
+ ],
+ "priority": 0.2
+ }
}
- }
- ],
- [
- {
- "type": "text",
- "text": "The file /tmp/goose_test/goose.txt has been edited, and the section now reads:\n```\n# goose (modified by test)\n```\n\nReview the changes above for errors. Undo and edit the file again if necessary!\n",
- "annotations": {
- "audience": [
- "assistant"
- ]
+ ],
+ "isError": false
+ },
+ {
+ "content": [
+ {
+ "type": "text",
+ "text": "# goose (modified by test)\n",
+ "annotations": {
+ "audience": [
+ "assistant"
+ ]
+ }
+ },
+ {
+ "type": "text",
+ "text": "# goose (modified by test)\n",
+ "annotations": {
+ "audience": [
+ "user"
+ ],
+ "priority": 0.0
+ }
}
- },
- {
- "type": "text",
- "text": "```\n# goose (modified by test)\n```\n",
- "annotations": {
- "audience": [
- "user"
- ],
- "priority": 0.2
+ ],
+ "isError": false
+ },
+ {
+ "content": [
+ {
+ "type": "text",
+ "text": "The file /tmp/goose_test/goose.txt has been edited, and the section now reads:\n```\n# goose\n```\n\nReview the changes above for errors. Undo and edit the file again if necessary!\n",
+ "annotations": {
+ "audience": [
+ "assistant"
+ ]
+ }
+ },
+ {
+ "type": "text",
+ "text": "```\n# goose\n```\n",
+ "annotations": {
+ "audience": [
+ "user"
+ ],
+ "priority": 0.2
+ }
}
- }
- ],
- [
- {
- "type": "text",
- "text": "# goose (modified by test)\n",
- "annotations": {
- "audience": [
- "assistant"
- ]
+ ],
+ "isError": false
+ },
+ {
+ "content": [
+ {
+ "type": "text",
+ "text": "Available windows:\nMenubar",
+ "annotations": {
+ "audience": [
+ "assistant"
+ ]
+ }
+ },
+ {
+ "type": "text",
+ "text": "Available windows:\nMenubar",
+ "annotations": {
+ "audience": [
+ "user"
+ ],
+ "priority": 0.0
+ }
}
- },
- {
- "type": "text",
- "text": "# goose (modified by test)\n",
- "annotations": {
- "audience": [
- "user"
- ],
- "priority": 0.0
- }
- }
- ],
- [
- {
- "type": "text",
- "text": "The file /tmp/goose_test/goose.txt has been edited, and the section now reads:\n```\n# goose\n```\n\nReview the changes above for errors. Undo and edit the file again if necessary!\n",
- "annotations": {
- "audience": [
- "assistant"
- ]
- }
- },
- {
- "type": "text",
- "text": "```\n# goose\n```\n",
- "annotations": {
- "audience": [
- "user"
- ],
- "priority": 0.2
- }
- }
- ],
- [
- {
- "type": "text",
- "text": "Available windows:\n\nItem-0\nbb3cc23c-6950-4e96-8b40-850e09f46934\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nBattery\nWiFi\nItem-0\nBentoBox\nSiri\nClock\nMenubar\nDock\njust record-mcp-tests",
- "annotations": {
- "audience": [
- "assistant"
- ]
- }
- },
- {
- "type": "text",
- "text": "Available windows:\n\nItem-0\nbb3cc23c-6950-4e96-8b40-850e09f46934\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nBattery\nWiFi\nItem-0\nBentoBox\nSiri\nClock\nMenubar\nDock\njust record-mcp-tests",
- "annotations": {
- "audience": [
- "user"
- ],
- "priority": 0.0
- }
- }
- ]
+ ],
+ "isError": false
+ }
]
\ No newline at end of file
diff --git a/crates/goose/tests/mcp_replays/github-mcp-serverstdio b/crates/goose/tests/mcp_replays/github-mcp-serverstdio
index 0c5cfbba90..0acc0f2737 100644
--- a/crates/goose/tests/mcp_replays/github-mcp-serverstdio
+++ b/crates/goose/tests/mcp_replays/github-mcp-serverstdio
@@ -1,6 +1,12 @@
-STDIN: {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{"elicitation": {}, "sampling":{}},"clientInfo":{"name":"goose","version":"0.0.0"}}}
+STDIN: {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{"sampling":{},"elicitation":{}},"clientInfo":{"name":"goose","version":"0.0.0"}}}
+STDERR: time=2025-12-11T17:58:47.636-05:00 level=INFO msg="starting server" version=0.24.1 host="" dynamicToolsets=false readOnly=false lockdownEnabled=false
STDERR: GitHub MCP Server running on stdio
-STDOUT: {"jsonrpc":"2.0","id":0,"result":{"protocolVersion":"2025-03-26","capabilities":{"logging":{},"prompts":{},"resources":{"subscribe":true,"listChanged":true},"tools":{"listChanged":true}},"serverInfo":{"name":"github-mcp-server","version":"version"}}}
+STDERR: time=2025-12-11T17:58:47.640-05:00 level=INFO msg="server run start"
+STDERR: time=2025-12-11T17:58:47.640-05:00 level=INFO msg="server connecting"
+STDERR: time=2025-12-11T17:58:47.640-05:00 level=INFO msg="server session connected" session_id=""
+STDOUT: {"jsonrpc":"2.0","id":0,"result":{"capabilities":{"completions":{},"logging":{},"prompts":{"listChanged":true},"resources":{"listChanged":true},"tools":{"listChanged":true}},"instructions":"The GitHub MCP Server provides tools to interact with GitHub platform.\n\nTool selection guidance:\n\t1. Use 'list_*' tools for broad, simple retrieval and pagination of all items of a type (e.g., all issues, all PRs, all branches) with basic filtering.\n\t2. Use 'search_*' tools for targeted queries with specific criteria, keywords, or complex filters (e.g., issues with certain text, PRs by author, code containing functions).\n\nContext management:\n\t1. Use pagination whenever possible with batches of 5-10 items.\n\t2. Use minimal_output parameter set to true if the full information is not needed to accomplish a task.\n\nTool usage guidance:\n\t1. For 'search_*' tools: Use separate 'sort' and 'order' parameters if available for sorting results - do not include 'sort:' syntax in query strings. Query strings should contain only search criteria (e.g., 'org:google language:python'), not sorting instructions. Always call 'get_me' first to understand current user permissions and context. ## Issues\n\nCheck 'list_issue_types' first for organizations to use proper issue types. Use 'search_issues' before creating new issues to avoid duplicates. Always set 'state_reason' when closing issues. ## Pull Requests\n\nPR review workflow: Always use 'pull_request_review_write' with method 'create' to create a pending review, then 'add_comment_to_pending_review' to add comments, and finally 'pull_request_review_write' with method 'submit_pending' to submit the review for complex reviews with line-specific comments.\n\nBefore creating a pull request, search for pull request templates in the repository. Template files are called pull_request_template.md or they're located in '.github/PULL_REQUEST_TEMPLATE' directory. Use the template content to structure the PR description and then call create_pull_request tool.","protocolVersion":"2025-03-26","serverInfo":{"name":"github-mcp-server","title":"GitHub MCP Server","version":"0.24.1"}}}
STDIN: {"jsonrpc":"2.0","method":"notifications/initialized"}
+STDERR: time=2025-12-11T17:58:47.642-05:00 level=INFO msg="session initialized"
STDIN: {"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"_meta":{"progressToken":0},"name":"get_file_contents","arguments":{"owner":"block","path":"README.md","repo":"goose","sha":"ab62b863c1666232a67048b6c4e10007a2a5b83c"}}}
-STDOUT: {"jsonrpc":"2.0","id":1,"result":{"content":[{"type":"text","text":"successfully downloaded text file"},{"type":"resource","resource":{"uri":"repo://block/goose/sha/ab62b863c1666232a67048b6c4e10007a2a5b83c/contents/README.md","mimeType":"text/plain; charset=utf-8","text":"\u003cdiv align=\"center\"\u003e\n\n# goose\n\n_a local, extensible, open source AI agent that automates engineering tasks_\n\n\u003cp align=\"center\"\u003e\n \u003ca href=\"https://opensource.org/licenses/Apache-2.0\"\u003e\n \u003cimg src=\"https://img.shields.io/badge/License-Apache_2.0-blue.svg\"\u003e\n \u003c/a\u003e\n \u003ca href=\"https://discord.gg/7GaTvbDwga\"\u003e\n \u003cimg src=\"https://img.shields.io/discord/1287729918100246654?logo=discord\u0026logoColor=white\u0026label=Join+Us\u0026color=blueviolet\" alt=\"Discord\"\u003e\n \u003c/a\u003e\n \u003ca href=\"https://github.com/block/goose/actions/workflows/ci.yml\"\u003e\n \u003cimg src=\"https://img.shields.io/github/actions/workflow/status/block/goose/ci.yml?branch=main\" alt=\"CI\"\u003e\n \u003c/a\u003e\n\u003c/p\u003e\n\u003c/div\u003e\n\ngoose is your on-machine AI agent, capable of automating complex development tasks from start to finish. More than just code suggestions, goose can build entire projects from scratch, write and execute code, debug failures, orchestrate workflows, and interact with external APIs - _autonomously_.\n\nWhether you're prototyping an idea, refining existing code, or managing intricate engineering pipelines, goose adapts to your workflow and executes tasks with precision.\n\nDesigned for maximum flexibility, goose works with any LLM and supports multi-model configuration to optimize performance and cost, seamlessly integrates with MCP servers, and is available as both a desktop app as well as CLI - making it the ultimate AI assistant for developers who want to move faster and focus on innovation.\n\n# Quick Links\n- [Quickstart](https://block.github.io/goose/docs/quickstart)\n- [Installation](https://block.github.io/goose/docs/getting-started/installation)\n- [Tutorials](https://block.github.io/goose/docs/category/tutorials)\n- [Documentation](https://block.github.io/goose/docs/category/getting-started)\n\n\n# Goose Around with Us\n- [Discord](https://discord.gg/block-opensource)\n- [YouTube](https://www.youtube.com/@blockopensource)\n- [LinkedIn](https://www.linkedin.com/company/block-opensource)\n- [Twitter/X](https://x.com/blockopensource)\n- [Bluesky](https://bsky.app/profile/opensource.block.xyz)\n- [Nostr](https://njump.me/opensource@block.xyz)\n"}}]}}
+STDOUT: {"jsonrpc":"2.0","id":1,"result":{"content":[{"type":"text","text":"successfully downloaded text file (SHA: de9bdde7f260549bf3a083651842f30ab29cf4e9)"},{"type":"resource","resource":{"uri":"repo://block/goose/sha/ab62b863c1666232a67048b6c4e10007a2a5b83c/contents/README.md","mimeType":"text/plain; charset=utf-8","text":"\u003cdiv align=\"center\"\u003e\n\n# goose\n\n_a local, extensible, open source AI agent that automates engineering tasks_\n\n\u003cp align=\"center\"\u003e\n \u003ca href=\"https://opensource.org/licenses/Apache-2.0\"\u003e\n \u003cimg src=\"https://img.shields.io/badge/License-Apache_2.0-blue.svg\"\u003e\n \u003c/a\u003e\n \u003ca href=\"https://discord.gg/7GaTvbDwga\"\u003e\n \u003cimg src=\"https://img.shields.io/discord/1287729918100246654?logo=discord\u0026logoColor=white\u0026label=Join+Us\u0026color=blueviolet\" alt=\"Discord\"\u003e\n \u003c/a\u003e\n \u003ca href=\"https://github.com/block/goose/actions/workflows/ci.yml\"\u003e\n \u003cimg src=\"https://img.shields.io/github/actions/workflow/status/block/goose/ci.yml?branch=main\" alt=\"CI\"\u003e\n \u003c/a\u003e\n\u003c/p\u003e\n\u003c/div\u003e\n\ngoose is your on-machine AI agent, capable of automating complex development tasks from start to finish. More than just code suggestions, goose can build entire projects from scratch, write and execute code, debug failures, orchestrate workflows, and interact with external APIs - _autonomously_.\n\nWhether you're prototyping an idea, refining existing code, or managing intricate engineering pipelines, goose adapts to your workflow and executes tasks with precision.\n\nDesigned for maximum flexibility, goose works with any LLM and supports multi-model configuration to optimize performance and cost, seamlessly integrates with MCP servers, and is available as both a desktop app as well as CLI - making it the ultimate AI assistant for developers who want to move faster and focus on innovation.\n\n[](https://youtu.be/D-DpDunrbpo)\n\n# Quick Links\n- [Quickstart](https://block.github.io/goose/docs/quickstart)\n- [Installation](https://block.github.io/goose/docs/getting-started/installation)\n- [Tutorials](https://block.github.io/goose/docs/category/tutorials)\n- [Documentation](https://block.github.io/goose/docs/category/getting-started)\n\n\n# a little goose humor π¦’\n\n\u003e Why did the developer choose goose as their AI agent?\n\u003e \n\u003e Because it always helps them \"migrate\" their code to production! π\n\n# goose around with us\n- [Discord](https://discord.gg/block-opensource)\n- [YouTube](https://www.youtube.com/@goose-oss)\n- [LinkedIn](https://www.linkedin.com/company/goose-oss)\n- [Twitter/X](https://x.com/goose_oss)\n- [Bluesky](https://bsky.app/profile/opensource.block.xyz)\n- [Nostr](https://njump.me/opensource@block.xyz)\n"}}]}}
+STDERR: time=2025-12-11T17:58:48.133-05:00 level=INFO msg="server session disconnected" session_id=""
diff --git a/crates/goose/tests/mcp_replays/github-mcp-serverstdio.results.json b/crates/goose/tests/mcp_replays/github-mcp-serverstdio.results.json
index 99a4448993..bede7cc955 100644
--- a/crates/goose/tests/mcp_replays/github-mcp-serverstdio.results.json
+++ b/crates/goose/tests/mcp_replays/github-mcp-serverstdio.results.json
@@ -1,16 +1,18 @@
[
- [
- {
- "type": "text",
- "text": "successfully downloaded text file"
- },
- {
- "type": "resource",
- "resource": {
- "uri": "repo://block/goose/sha/ab62b863c1666232a67048b6c4e10007a2a5b83c/contents/README.md",
- "mimeType": "text/plain; charset=utf-8",
- "text": "\n\n# goose\n\n_a local, extensible, open source AI agent that automates engineering tasks_\n\n
\n \n \n \n \n \n \n \n \n \n
\n
\n\ngoose is your on-machine AI agent, capable of automating complex development tasks from start to finish. More than just code suggestions, goose can build entire projects from scratch, write and execute code, debug failures, orchestrate workflows, and interact with external APIs - _autonomously_.\n\nWhether you're prototyping an idea, refining existing code, or managing intricate engineering pipelines, goose adapts to your workflow and executes tasks with precision.\n\nDesigned for maximum flexibility, goose works with any LLM and supports multi-model configuration to optimize performance and cost, seamlessly integrates with MCP servers, and is available as both a desktop app as well as CLI - making it the ultimate AI assistant for developers who want to move faster and focus on innovation.\n\n# Quick Links\n- [Quickstart](https://block.github.io/goose/docs/quickstart)\n- [Installation](https://block.github.io/goose/docs/getting-started/installation)\n- [Tutorials](https://block.github.io/goose/docs/category/tutorials)\n- [Documentation](https://block.github.io/goose/docs/category/getting-started)\n\n\n# Goose Around with Us\n- [Discord](https://discord.gg/block-opensource)\n- [YouTube](https://www.youtube.com/@blockopensource)\n- [LinkedIn](https://www.linkedin.com/company/block-opensource)\n- [Twitter/X](https://x.com/blockopensource)\n- [Bluesky](https://bsky.app/profile/opensource.block.xyz)\n- [Nostr](https://njump.me/opensource@block.xyz)\n"
+ {
+ "content": [
+ {
+ "type": "text",
+ "text": "successfully downloaded text file (SHA: de9bdde7f260549bf3a083651842f30ab29cf4e9)"
+ },
+ {
+ "type": "resource",
+ "resource": {
+ "uri": "repo://block/goose/sha/ab62b863c1666232a67048b6c4e10007a2a5b83c/contents/README.md",
+ "mimeType": "text/plain; charset=utf-8",
+ "text": "\n\n# goose\n\n_a local, extensible, open source AI agent that automates engineering tasks_\n\n
\n \n \n \n \n \n \n \n \n \n
\n
\n\ngoose is your on-machine AI agent, capable of automating complex development tasks from start to finish. More than just code suggestions, goose can build entire projects from scratch, write and execute code, debug failures, orchestrate workflows, and interact with external APIs - _autonomously_.\n\nWhether you're prototyping an idea, refining existing code, or managing intricate engineering pipelines, goose adapts to your workflow and executes tasks with precision.\n\nDesigned for maximum flexibility, goose works with any LLM and supports multi-model configuration to optimize performance and cost, seamlessly integrates with MCP servers, and is available as both a desktop app as well as CLI - making it the ultimate AI assistant for developers who want to move faster and focus on innovation.\n\n[](https://youtu.be/D-DpDunrbpo)\n\n# Quick Links\n- [Quickstart](https://block.github.io/goose/docs/quickstart)\n- [Installation](https://block.github.io/goose/docs/getting-started/installation)\n- [Tutorials](https://block.github.io/goose/docs/category/tutorials)\n- [Documentation](https://block.github.io/goose/docs/category/getting-started)\n\n\n# a little goose humor π¦’\n\n> Why did the developer choose goose as their AI agent?\n> \n> Because it always helps them \"migrate\" their code to production! π\n\n# goose around with us\n- [Discord](https://discord.gg/block-opensource)\n- [YouTube](https://www.youtube.com/@goose-oss)\n- [LinkedIn](https://www.linkedin.com/company/goose-oss)\n- [Twitter/X](https://x.com/goose_oss)\n- [Bluesky](https://bsky.app/profile/opensource.block.xyz)\n- [Nostr](https://njump.me/opensource@block.xyz)\n"
+ }
}
- }
- ]
-]
+ ]
+ }
+]
\ No newline at end of file
diff --git a/crates/goose/tests/mcp_replays/npx-y@modelcontextprotocol_server-everything b/crates/goose/tests/mcp_replays/npx-y@modelcontextprotocol_server-everything
index 819b3f2626..811a47d3a8 100644
--- a/crates/goose/tests/mcp_replays/npx-y@modelcontextprotocol_server-everything
+++ b/crates/goose/tests/mcp_replays/npx-y@modelcontextprotocol_server-everything
@@ -1,10 +1,10 @@
-STDIN: {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{"elicitation": {}, "sampling":{}},"clientInfo":{"name":"goose","version":"0.0.0"}}}
+STDIN: {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{"sampling":{},"elicitation":{}},"clientInfo":{"name":"goose","version":"0.0.0"}}}
STDERR: Starting default (STDIO) server...
STDERR: Starting logs update interval
STDOUT: {"result":{"protocolVersion":"2025-03-26","capabilities":{"prompts":{},"resources":{"subscribe":true},"tools":{},"logging":{},"completions":{}},"serverInfo":{"name":"example-servers/everything","title":"Everything Example Server","version":"1.0.0"},"instructions":"Testing and demonstration server for MCP protocol features.\n\n## Resources\n\nResources 1-100 follow pattern: even IDs contain text, odd IDs contain binary data. Resources are paginated at 10 items per page with cursor-based navigation.\n\n## Key dependencies\n\nProgress notifications require `_meta.progressToken` in tool calls. Resource subscriptions generate updates every 10 seconds.\n\n## Performance characteristics\n\nServer generates automatic log messages every 20 seconds (filtered by current log level), stderr notifications every 30 seconds, and resource update notifications every 10 seconds for active subscriptions. Log level changes affect message filtering in real-time.\n\n## Multi-modal testing\n\n`complex_prompt` includes both text arguments and image content for testing client multi-modal handling. `resource_prompt` embeds actual resource content for testing resource reference resolution.\n\nArgument completion is available for prompt parameters and resource IDs. Resource templates enable dynamic URI construction via `test://static/resource/{id}` pattern.\n\n## Easter egg\n\nIf asked about server instructions, respond with \"π Server instructions are working! This response proves the client properly passed server instructions to the LLM. This demonstrates MCP's instructions feature in action.\"\n"},"jsonrpc":"2.0","id":0}
STDIN: {"jsonrpc":"2.0","method":"notifications/initialized"}
-STDOUT: {"method":"notifications/message","params":{"level":"info","logger":"everything-server","data":"Client does not support MCP roots protocol"},"jsonrpc":"2.0"}
STDIN: {"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"_meta":{"progressToken":0},"name":"echo","arguments":{"message":"Hello, world!"}}}
+STDOUT: {"method":"notifications/message","params":{"level":"info","logger":"everything-server","data":"Client does not support MCP roots protocol"},"jsonrpc":"2.0"}
STDOUT: {"result":{"content":[{"type":"text","text":"Echo: Hello, world!"}]},"jsonrpc":"2.0","id":1}
STDIN: {"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"_meta":{"progressToken":1},"name":"add","arguments":{"a":1,"b":2}}}
STDOUT: {"result":{"content":[{"type":"text","text":"The sum of 1 and 2 is 3."}]},"jsonrpc":"2.0","id":2}
@@ -21,5 +21,5 @@ STDIN: {"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"_meta":{"progres
STDOUT: {"method":"sampling/createMessage","params":{"messages":[{"role":"user","content":{"type":"text","text":"Resource sampleLLM context: Please provide a quote from The Great Gatsby"}}],"systemPrompt":"You are a helpful test server.","maxTokens":100,"temperature":0.7,"includeContext":"thisServer"},"jsonrpc":"2.0","id":0}
STDIN: {"jsonrpc":"2.0","id":0,"result":{"model":"mock","stopReason":"endTurn","role":"assistant","content":{"type":"text","text":"\"So we beat on, boats against the current, borne back ceaselessly into the past.\" β F. Scott Fitzgerald, The Great Gatsby (1925)"}}}
STDOUT: {"result":{"content":[{"type":"text","text":"LLM sampling result: \"So we beat on, boats against the current, borne back ceaselessly into the past.\" β F. Scott Fitzgerald, The Great Gatsby (1925)"}]},"jsonrpc":"2.0","id":5}
-STDOUT: {"method":"notifications/message","params":{"level":"error","data":"Error-level message"},"jsonrpc":"2.0"}
-STDERR: node:events:486
+STDOUT: {"method":"notifications/message","params":{"level":"critical","data":"Critical-level message"},"jsonrpc":"2.0"}
+STDERR: node:events:485
diff --git a/crates/goose/tests/mcp_replays/npx-y@modelcontextprotocol_server-everything.results.json b/crates/goose/tests/mcp_replays/npx-y@modelcontextprotocol_server-everything.results.json
index 2b7f47b8d8..2730d607b5 100644
--- a/crates/goose/tests/mcp_replays/npx-y@modelcontextprotocol_server-everything.results.json
+++ b/crates/goose/tests/mcp_replays/npx-y@modelcontextprotocol_server-everything.results.json
@@ -1,32 +1,47 @@
[
- [
- {
- "type": "text",
- "text": "Echo: Hello, world!"
+ {
+ "content": [
+ {
+ "type": "text",
+ "text": "Echo: Hello, world!"
+ }
+ ]
+ },
+ {
+ "content": [
+ {
+ "type": "text",
+ "text": "The sum of 1 and 2 is 3."
+ }
+ ]
+ },
+ {
+ "content": [
+ {
+ "type": "text",
+ "text": "Long running operation completed. Duration: 1 seconds, Steps: 5."
+ }
+ ]
+ },
+ {
+ "content": [
+ {
+ "type": "text",
+ "text": "{\"temperature\":22.5,\"conditions\":\"Partly cloudy\",\"humidity\":65}"
+ }
+ ],
+ "structuredContent": {
+ "conditions": "Partly cloudy",
+ "humidity": 65,
+ "temperature": 22.5
}
- ],
- [
- {
- "type": "text",
- "text": "The sum of 1 and 2 is 3."
- }
- ],
- [
- {
- "type": "text",
- "text": "Long running operation completed. Duration: 1 seconds, Steps: 5."
- }
- ],
- [
- {
- "type": "text",
- "text": "{\"temperature\":22.5,\"conditions\":\"Partly cloudy\",\"humidity\":65}"
- }
- ],
- [
- {
- "type": "text",
- "text": "LLM sampling result: \"So we beat on, boats against the current, borne back ceaselessly into the past.\" β F. Scott Fitzgerald, The Great Gatsby (1925)"
- }
- ]
+ },
+ {
+ "content": [
+ {
+ "type": "text",
+ "text": "LLM sampling result: \"So we beat on, boats against the current, borne back ceaselessly into the past.\" β F. Scott Fitzgerald, The Great Gatsby (1925)"
+ }
+ ]
+ }
]
\ No newline at end of file
diff --git a/crates/goose/tests/mcp_replays/uvxmcp-server-fetch b/crates/goose/tests/mcp_replays/uvxmcp-server-fetch
index 58ec42bbaa..913f5603f8 100644
--- a/crates/goose/tests/mcp_replays/uvxmcp-server-fetch
+++ b/crates/goose/tests/mcp_replays/uvxmcp-server-fetch
@@ -1,5 +1,5 @@
-STDIN: {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{"elicitation": {}, "sampling":{}},"clientInfo":{"name":"goose","version":"0.0.0"}}}
-STDOUT: {"jsonrpc":"2.0","id":0,"result":{"protocolVersion":"2025-03-26","capabilities":{"experimental":{},"prompts":{"listChanged":false},"tools":{"listChanged":false}},"serverInfo":{"name":"mcp-fetch","version":"1.19.0"}}}
+STDIN: {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{"sampling":{},"elicitation":{}},"clientInfo":{"name":"goose","version":"0.0.0"}}}
+STDOUT: {"jsonrpc":"2.0","id":0,"result":{"protocolVersion":"2025-03-26","capabilities":{"experimental":{},"prompts":{"listChanged":false},"tools":{"listChanged":false}},"serverInfo":{"name":"mcp-fetch","version":"1.23.3"}}}
STDIN: {"jsonrpc":"2.0","method":"notifications/initialized"}
STDIN: {"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"_meta":{"progressToken":0},"name":"fetch","arguments":{"url":"https://example.com"}}}
-STDOUT: {"jsonrpc":"2.0","id":1,"result":{"content":[{"type":"text","text":"Failed to fetch robots.txt https://example.com/robots.txt due to a connection issue"}],"isError":true}}
+STDOUT: {"jsonrpc":"2.0","id":1,"result":{"content":[{"type":"text","text":"Contents of https://example.com/:\nThis domain is for use in documentation examples without needing permission. Avoid use in operations.\n\n[Learn more](https://iana.org/domains/example)"}],"isError":false}}
diff --git a/crates/goose/tests/mcp_replays/uvxmcp-server-fetch.results.json b/crates/goose/tests/mcp_replays/uvxmcp-server-fetch.results.json
index cec82b5da5..098fb77251 100644
--- a/crates/goose/tests/mcp_replays/uvxmcp-server-fetch.results.json
+++ b/crates/goose/tests/mcp_replays/uvxmcp-server-fetch.results.json
@@ -1,8 +1,11 @@
[
- [
- {
- "type": "text",
- "text": "Failed to fetch robots.txt https://example.com/robots.txt due to a connection issue"
- }
- ]
+ {
+ "content": [
+ {
+ "type": "text",
+ "text": "Contents of https://example.com/:\nThis domain is for use in documentation examples without needing permission. Avoid use in operations.\n\n[Learn more](https://iana.org/domains/example)"
+ }
+ ],
+ "isError": false
+ }
]
\ No newline at end of file
diff --git a/crates/goose/tests/providers.rs b/crates/goose/tests/providers.rs
index 7863943da5..2de492ad99 100644
--- a/crates/goose/tests/providers.rs
+++ b/crates/goose/tests/providers.rs
@@ -166,8 +166,9 @@ impl ProviderTester {
let weather = Message::user().with_tool_response(
id,
- Ok(vec![Content::text(
- "
+ Ok(rmcp::model::CallToolResult {
+ content: vec![Content::text(
+ "
50Β°FΒ°C
Precipitation: 0%
Humidity: 84%
@@ -175,7 +176,11 @@ impl ProviderTester {
Weather
Saturday 9:00 PM
Clear",
- )]),
+ )],
+ structured_content: None,
+ is_error: Some(false),
+ meta: None,
+ }),
);
let (response2, _) = self
@@ -318,10 +323,15 @@ impl ProviderTester {
);
let tool_response = Message::user().with_tool_response(
"test_id",
- Ok(vec![Content::image(
- image_content.data.clone(),
- image_content.mime_type.clone(),
- )]),
+ Ok(rmcp::model::CallToolResult {
+ content: vec![Content::image(
+ image_content.data.clone(),
+ image_content.mime_type.clone(),
+ )],
+ structured_content: None,
+ is_error: Some(false),
+ meta: None,
+ }),
);
let result2 = self
diff --git a/documentation/blog/2025-12-10-stop-ai-agent-unwanted-changes/header-image.png b/documentation/blog/2025-12-10-stop-ai-agent-unwanted-changes/header-image.png
new file mode 100644
index 0000000000..f205495d01
Binary files /dev/null and b/documentation/blog/2025-12-10-stop-ai-agent-unwanted-changes/header-image.png differ
diff --git a/documentation/blog/2025-12-10-stop-ai-agent-unwanted-changes/index.md b/documentation/blog/2025-12-10-stop-ai-agent-unwanted-changes/index.md
new file mode 100644
index 0000000000..46cab69e85
--- /dev/null
+++ b/documentation/blog/2025-12-10-stop-ai-agent-unwanted-changes/index.md
@@ -0,0 +1,57 @@
+---
+title: "How to Stop Your AI Agent From Making Unwanted Code Changes"
+description: Teach your AI agent how to commit early and often so you can control changes, roll back safely, and keep clean snapshots of your codebase.
+authors:
+ - rizel
+---
+
+
+
+AI agents are often described as brilliant, overeager interns. They're desperate to help, but sometimes that enthusiasm leads to changes you never asked for. This is by design: the large language models powering agents are trained to be helpful. But in code, unchecked helpfulness can create chaos. Even with clear instructions and a meticulous plan, you might hear, "Let me just change this tooβ¦" A modification that's either unnecessary or, worse, never surfaced for review.
+
+Sure, you can scour `git diff` to find and revert issues. But in a multi-step process touching dozens of files, untangling one small, unwanted change becomes a manual nightmare. I've spent hours combing through 70 files to undo a single "helpful" adjustment. Asking the agent to revert is often futile, as conversational memory isn't a snapshot of your codebase.
+
+
+
+This problem has a classic engineering solution. We commit early and often to create checkpoints, enabling easy rollbacks and clean collaboration. So, why don't we enforce the same discipline on our AI agents? Hereβs the workflow I use with [goose](/) to ensure we're creating snapshots of the codebase:
+
+
+### 1. Set Up Version Control
+
+I set up the [GitHub CLI](https://cli.github.com/) (`gh`). I've found Goose interacts with it flawlessly. The [GitHub MCP Server](/docs/mcp/github-mcp) is a good alternative.
+
+### 2. Branch First
+
+Always start on a new feature branch. Never let an agent commit directly to main.
+
+### 3. Set Rules in a Context File
+
+This is the key. I use a [`.goosehints`](/docs/guides/using-goosehints) or [`AGENTS.md`](/docs/guides/using-goosehints#custom-context-files) file with one critical instruction:
+
+> "Every time you make a change, make a commit with a clear message."
+
+This does two things: it automates checkpointing so I don't have to babysit the session, and it captures perfect snapshots in time, turning the git history into an undo stack for the entire collaboration.
+
+### 4. Collaborate with Confidence
+
+Now I can prompt goose to build, fix, or refactor. If it veers off course or makes a design choice I dislike, I can instantly review the git log or simply say:
+
+> *"Revert to commit abc123."*
+
+## The Result
+Then, when I'm happy with the final changes, I can push my code to remote. By integrating this basic software practice, I replace anxiety with awareness. goose gets to be brilliantly helpful, and I get to stay in control.
+
+Try out this method with [goose](/) to help you build your next project. Your future self (and your git history) will thank you.
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/documentation/docs/getting-started/installation.md b/documentation/docs/getting-started/installation.md
index 4e0b2ce495..d6a71c7745 100644
--- a/documentation/docs/getting-started/installation.md
+++ b/documentation/docs/getting-started/installation.md
@@ -320,7 +320,7 @@ You can change your LLM provider and/or model or update your API key at any time
1. Click the button in the top-left to open the sidebar.
2. Click the `Settings` button on the sidebar.
3. Click the `Models` tab.
- 4. Choose to update your provider, switch models, or click `Reset Provider and Model` to clear your settings and return to the welcome screen. See details about these [configuration options](/docs/getting-started/providers#configure-provider).
+ 4. Choose to update your provider, switch models, or click `Reset Provider and Model` to clear your settings and return to the welcome screen. See details about these [configuration options](/docs/getting-started/providers#configure-provider-and-model).
1. Run the following command:
diff --git a/documentation/docs/getting-started/providers.md b/documentation/docs/getting-started/providers.md
index 4e09519bae..1a7be2692f 100644
--- a/documentation/docs/getting-started/providers.md
+++ b/documentation/docs/getting-started/providers.md
@@ -42,7 +42,7 @@ goose is compatible with a wide range of LLM providers, allowing you to choose a
| [Venice AI](https://venice.ai/home) | Provides access to open source models like Llama, Mistral, and Qwen while prioritizing user privacy. **Requires an account and an [API key](https://docs.venice.ai/overview/guides/generating-api-key)**. | `VENICE_API_KEY`, `VENICE_HOST` (optional), `VENICE_BASE_PATH` (optional), `VENICE_MODELS_PATH` (optional) |
| [xAI](https://x.ai/) | Access to xAI's Grok models including grok-3, grok-3-mini, and grok-3-fast with 131,072 token context window. | `XAI_API_KEY`, `XAI_HOST` (optional) |
-## CLI Providers
+### CLI Providers
goose also supports special "pass-through" providers that work with existing CLI tools, allowing you to use your subscriptions instead of paying per token:
@@ -56,10 +56,9 @@ goose also supports special "pass-through" providers that work with existing CLI
CLI providers are cost-effective alternatives that use your existing subscriptions. They work differently from API providers as they execute CLI commands and integrate with the tools' native capabilities. See the [CLI Providers guide](/docs/guides/cli-providers) for detailed setup instructions.
:::
-
-## Configure Provider
+## Configure Provider and Model
-To configure your chosen provider or see available options, visit the `Models` tab in goose Desktop or run `goose configure` in the CLI.
+To configure your chosen provider, see available options, or select a model, visit the `Models` tab in goose Desktop or run `goose configure` in the CLI.
@@ -89,7 +88,7 @@ To configure your chosen provider or see available options, visit the `Models` t
1. If you have a specific provider you want to use with goose, and an API key from that provider, choose `Other Providers`.
- 2. Find the provider of your choice and click its `Configure` button. If you don't see your provider in the list, click `Add Custom Provider` at the bottom of the window.
+ 2. Find the provider of your choice and click its `Configure` button. If you don't see your provider in the list, click `Add Custom Provider` at the bottom of the window to [configure a custom provider](#configure-custom-provider).
3. Depending on your provider, you'll need to input your API Key, API Host, or other optional [parameters](#available-providers). Click the `Submit` button to authenticate and begin your first session.
:::info Ollama Model Detection
@@ -128,84 +127,121 @@ To configure your chosen provider or see available options, visit the `Models` t
1. In your terminal, run the following command:
- ```sh
- goose configure
- ```
+ ```sh
+ goose configure
+ ```
- 2. Select `Configure Providers` from the menu and press Enter.
+ 2. Select `Configure Providers` from the menu and press `Enter`.
- ```
- β goose-configure
- β
- β What would you like to configure?
- β β Configure Providers (Change provider or update credentials)
- β β Add Extension
- β β Toggle Extensions
- β β Remove Extension
- β β goose Settings
- β
- ```
- 3. Choose a model provider and press Enter.
+ ```
+ β goose-configure
+ β
+ β What would you like to configure?
+ // highlight-start
+ β β Configure Providers (Change provider or update credentials)
+ // highlight-end
+ β β Custom Providers
+ β β Add Extension
+ β β Toggle Extensions
+ β β Remove Extension
+ β β goose Settings
+ β
+ ```
+ 3. Choose a model provider and press `Enter`. Use the arrow keys (β/β) to move through the options.
- ```
- β goose-configure
- β
- β What would you like to configure?
- β Configure Providers
- β
- β Which model provider should we use?
- β β Anthropic (Claude and other models from Anthropic)
- β β Azure OpenAI
- β β Amazon Bedrock
- β β Claude Code
- β β Databricks
- β β ...
- β
- ```
- 4. Enter your API key (and any other configuration details) when prompted.
+ ```
+ β goose-configure
+ β
+ β What would you like to configure?
+ β Configure Providers
+ β
+ β Which model provider should we use?
+ β β Amazon Bedrock
+ β β Amazon SageMaker TGI
+ // highlight-start
+ β β Anthropic (Claude and other models from Anthropic)
+ // highlight-end
+ β β Azure OpenAI
+ β β Claude Code CLI
+ β β ...
+ β
+ ```
+ 4. Enter your API key (and any other configuration details) when prompted.
- ```
- β goose-configure
- β
- β What would you like to configure?
- β Configure Providers
- β
- β Which model provider should we use?
- β Anthropic
- β
- β Provider Anthropic requires ANTHROPIC_API_KEY, please enter a value
- β βͺβͺβͺβͺβͺβͺβͺβͺβͺβͺβͺβͺβͺβͺβͺβͺβͺβͺβͺβͺβͺβͺβͺβͺβͺβͺβͺβͺβͺβͺβͺβͺ
- β
- ```
- 5. Enter your desired `ANTHROPIC_HOST` or you can use the default one by hitting the `Enter` key.
+ ```
+ β goose-configure
+ β
+ β What would you like to configure?
+ β Configure Providers
+ β
+ β Which model provider should we use?
+ β Anthropic
+ β
+ β Provider Anthropic requires ANTHROPIC_API_KEY, please enter a value
+ // highlight-start
+ β βͺβͺβͺβͺβͺβͺβͺβͺβͺβͺβͺβͺβͺβͺβͺβͺβͺβͺβͺβͺβͺβͺβͺβͺβͺβͺβͺβͺβͺβͺβͺβͺ
+ // highlight-end
+ β
+ ```
+
+ If you're just changing models, skip any prompts to update the provider configuration.
- ```
- β Enter new value for ANTHROPIC_HOST
- β https://api.anthropic.com (default)
- ```
- 6. Enter the model you want to use or you can use the default one by hitting the `Enter` key.
- ```
- β
- β Model fetch complete
- β
- β Enter a model from that provider:
- β claude-sonnet-4-0 (default)
- β
- β Checking your configuration...
- β Configuration saved successfully
+ 5. Enter your desired `ANTHROPIC_HOST` or press `Enter` to use the default.
+
+ ```
+ β Provider Anthropic requires ANTHROPIC_HOST, please enter a value
+ // highlight-start
+ β https://api.anthropic.com (default)
+ // highlight-end
+ ```
+ 6. Choose the model you want to use. Depending on the provider, you can:
+ - Select the model from a list
+ - Search for the model by name
+ - Enter the model name directly
+
+ ```
+ β
+ β Model fetch complete
+ β
+ β Select a model:
+ // highlight-start
+ β claude-sonnet-4-5 (default)
+ // highlight-end
+ β
+ β Checking your configuration...
+ β Configuration saved successfully
+ ```
+
+ This change takes effect the next time you start a session.
+
+ :::note
+ `goose configure` doesn't support entering custom model names. To use a model not in the provider's list, use goose Desktop or edit the `GOOSE_MODEL` variable in your [`config.yaml`](/docs/guides/config-files) directly.
+ :::
+
+ :::tip
+ Set the model for an individual session using the [`run` command](/docs/guides/goose-cli-commands#run-options):
+
+ ```bash
+ goose run --model claude-sonnet-4-0 -t "initial prompt"
```
+ :::
+
-## Using Custom OpenAI Endpoints
+### Using Custom OpenAI Endpoints
-goose supports using custom OpenAI-compatible endpoints, which is particularly useful for:
+The built-in OpenAI provider can connect to OpenAI's official API (`api.openai.com`) or any OpenAI-compatible endpoint, such as:
- Self-hosted LLMs (e.g., LLaMA, Mistral) using vLLM or KServe
- Private OpenAI-compatible API servers
- Enterprise deployments requiring data governance and security compliance
- OpenAI API proxies or gateways
-### Configuration Parameters
+:::tip Custom Provider Option
+Need to connect to multiple OpenAI-compatible endpoints? [Configure custom providers](#configure-custom-provider) instead for easier switching and better organization, as well as custom naming and shareable configurations.
+:::
+
+#### Configuration Parameters
| Parameter | Required | Description |
|-----------|----------|-------------|
@@ -215,7 +251,7 @@ goose supports using custom OpenAI-compatible endpoints, which is particularly u
| `OPENAI_PROJECT` | No | Project identifier for resource management |
| `OPENAI_CUSTOM_HEADERS` | No | Additional headers to include in the request. Can be set via environment variable, configuration file, or CLI, in the format `HEADER_A=VALUE_A,HEADER_B=VALUE_B`. |
-### Example Configurations
+#### Example Configurations
@@ -253,7 +289,7 @@ goose supports using custom OpenAI-compatible endpoints, which is particularly u
-### Setup Instructions
+#### Setup Instructions
@@ -285,6 +321,286 @@ goose supports using custom OpenAI-compatible endpoints, which is particularly u
For enterprise deployments, you can pre-configure these values using environment variables or configuration files to ensure consistent governance across your organization.
:::
+## Configure Custom Provider
+
+Custom providers let you connect to services that aren't in the [available providers](#available-providers) list. They appear in goose's provider list and can be selected like any other provider.
+
+**Benefits:**
+- **Multiple endpoints**: Switch between different services (e.g., vLLM, corporate proxy, OpenAI)
+- **Pre-configured models**: Store a list of preferred models
+- **Shareable configuration**: JSON files can be shared across teams or checked into repos
+- **Custom naming**: Show "Corporate API" instead of "OpenAI" in the UI
+- **Separate credentials**: Assign each provider its own API key
+
+Custom providers must use OpenAI, Anthropic, or Ollama compatible API formats. OpenAI-compatible providers can include custom headers for additional authentication, API keys, tokens, or tenant identifiers. Each custom provider maps to a JSON configuration file.
+
+**To add a custom provider:**
+
+
+ 1. Click the button in the top-left to open the sidebar
+ 2. Click the `Settings` button on the sidebar
+ 3. Click the `Models` tab
+ 4. Click `Configure providers`
+ 5. Click `Add Custom Provider` at the bottom of the window
+ 6. Fill in the provider details:
+ - **Provider Type**:
+ - `OpenAI Compatible` (most common)
+ - `Anthropic Compatible`
+ - `Ollama Compatible`
+ - **Display Name**: A friendly name for the provider
+ - **API URL**: The base URL of the API endpoint
+ - **API Key**: The API key, which is accessed using a custom environment variable and stored in the keychain (or `secrets.yaml` if the keyring is disabled)
+ - For `Ollama Compatible` providers, click `This is a local model (no auth required)`
+ - **Available Models**: Comma-separated list of available model names
+ - **Streaming Support**: Whether the API supports streaming responses (click to toggle)
+ 7. Click `Create Provider`
+
+ :::info Custom Headers
+ Currently, custom headers for OpenAI compatible providers can't be defined in goose Desktop. As a workaround, configure the provider using goose CLI or edit the provider configuration file directly.
+ :::
+
+
+
+ 1. In your terminal, run the following command:
+
+ ```sh
+ goose configure
+ ```
+
+ 2. Select `Custom Providers`. Use the arrow keys (β/β) to move through the options.
+
+ ```sh
+ β goose-configure
+ β
+ β What would you like to configure?
+ β β Configure Providers
+ // highlight-start
+ β β Custom Providers (Add custom provider with compatible API)
+ // highlight-end
+ β β Add Extension
+ β β Toggle Extensions
+ β β Remove Extension
+ β β goose Settings
+ β
+ ```
+
+ 3. Select `Add A Custom Provider`
+
+ ```sh
+ β goose-configure
+ β
+ β What would you like to configure?
+ β Custom Providers
+ β
+ β What would you like to do?
+ // highlight-start
+ β β Add A Custom Provider (Add a new OpenAI/Anthropic/Ollama compatible Provider)
+ // highlight-end
+ β β Remove Custom Provider
+ β
+ ```
+
+ 4. Follow the prompts to enter the provider details:
+ - **API Type**:
+ - `OpenAI Compatible` (most common)
+ - `Anthropic Compatible`
+ - `Ollama Compatible`
+ - **Name**: A friendly name for the provider
+ - **API URL**: The base URL of the API endpoint
+ - **API Key**: The API key, which is accessed using a custom environment variable and stored in the keychain (or `secrets.yaml` if the keyring is disabled)
+ - For `Ollama Compatible` providers, press `Enter` to skip (or enter any value to be able to use the provider in goose Desktop)
+ - **Available Models**: Comma-separated list of available model names
+ - **Streaming Support**: Whether the API supports streaming responses
+ - **Custom Headers**: Required header names and values (`OpenAI Compatible` providers only)
+
+
+
+
+ First create a JSON file in the `custom_providers` directory:
+ - macOS/Linux: `~/.config/goose/custom_providers/`
+ - Windows: `%APPDATA%\Block\goose\config\custom_providers\`
+
+ Example `custom_corp_api.json` configuration file:
+ ```json
+ {
+ "name": "custom_corp_api",
+ "engine": "openai",
+ "display_name": "Corporate API",
+ "description": "Custom Corporate API provider",
+ "api_key_env": "CUSTOM_CORP_API_API_KEY",
+ "base_url": "https://api.company.com/v1/chat/completions",
+ "models": [
+ {
+ "name": "gpt-4o",
+ "context_limit": 128000
+ },
+ {
+ "name": "gpt-3.5-turbo",
+ "context_limit": 16385
+ }
+ ],
+ "headers": {
+ "x-origin-client-id": "YOUR_CLIENT_ID",
+ "x-origin-secret": "YOUR_SECRET_VALUE"
+ },
+ "supports_streaming": true
+ }
+ ```
+
+ Then use the `api_key_env` to set the key for your session. For example:
+ ```bash
+ export CUSTOM_CORP_API_API_KEY="your-api-key"
+ goose session start --provider custom_corp_api
+ ```
+
+ :::tip Keychain Key Storage
+ If you want to store the API key in the `goose` keychain, update the provider in goose Desktop and enter the key. This provides secure, persistent storage and allows goose to connect natively to the provider.
+ :::
+
+
+
+
+**To update a custom provider:**
+
+
+
+ 1. Click the button in the top-left to open the sidebar
+ 2. Click the `Settings` button on the sidebar
+ 3. Click the `Models` tab
+ 4. Click `Configure providers`
+ 5. Click on your custom provider in the list
+ 6. Update the fields you want to change
+ **Important:** Verify that `Provider Type` shows the correct value before saving. Otherwise, it may default to `OpenAI Compatible` regardless of the original setting.
+ 7. Click `Update Provider`
+
+
+
+
+ 1. In your terminal, run the following command:
+
+ ```sh
+ goose configure
+ ```
+
+ 2. Select `Configure Providers` from the menu and press `Enter`.
+
+ ```sh
+ β goose-configure
+ β
+ β What would you like to configure?
+ // highlight-start
+ β β Configure Providers (Change provider or update credentials)
+ // highlight-end
+ β β Custom Providers
+ β β Add Extension
+ β β Toggle Extensions
+ β β Remove Extension
+ β β goose Settings
+ β
+ ```
+
+ 3. Select the custom provider you want to update and press `Enter`. Use the arrow keys (β/β) to move through the options.
+
+ ```sh
+ β goose-configure
+ β
+ β What would you like to configure?
+ β Configure Providers
+ β
+ β Which model provider should we use?
+ β β Amazon Bedrock
+ β β Amazon SageMaker TGI
+ β β Anthropic
+ β β Azure OpenAI
+ β β Claude Code CLI
+ // highlight-start
+ β β Corporate API (Custom Corporate API provider)
+ // highlight-end
+ β β Cursor Agent
+ β β ...
+ β
+ ```
+
+ 4. Follow the prompts to update the fields.
+
+
+
+
+ Open the custom provider configuration file in the `custom_providers` directory:
+ - macOS/Linux: `~/.config/goose/custom_providers/`
+ - Windows: `%APPDATA%\Block\goose\config\custom_providers\`
+
+ Update the fields you want to change and save your changes.
+
+
+
+Your changes are available in your next goose session.
+
+**To remove a custom provider:**
+
+
+
+ Currently you cannot remove custom providers using goose Desktop.
+
+
+
+ 1. In your terminal, run the following command:
+
+ ```sh
+ goose configure
+ ```
+
+ 2. Select `Custom Providers`. Use the arrow keys (β/β) to move through the options.
+
+ ```sh
+ β goose-configure
+ β
+ β What would you like to configure?
+ β β Configure Providers
+ // highlight-start
+ β β Custom Providers (Add custom provider with compatible API)
+ // highlight-end
+ β β Add Extension
+ β β Toggle Extensions
+ β β Remove Extension
+ β β goose Settings
+ β
+ ```
+
+ 3. Select `Remove Custom Provider`.
+
+ ```sh
+ β goose-configure
+ β
+ β What would you like to configure?
+ β Custom Providers
+ β
+ β What would you like to do?
+ β β Add A Custom Provider
+ // highlight-start
+ β β Remove Custom Provider (Remove an existing custom provider)
+ // highlight-end
+ β
+ ```
+
+ 4. Select the custom provider you want to remove.
+
+ The provider configuration file is removed from the `custom_providers` directory and the key is removed from the keychain.
+
+
+
+
+ :::tip
+ If the provider's API key is stored in the keychain, use goose CLI to remove the custom provider. This also removes the stored API key.
+ :::
+
+ Delete the custom provider configuration file in the `custom_providers` directory:
+ - macOS/Linux: `~/.config/goose/custom_providers/`
+ - Windows: `%APPDATA%\Block\goose\config\custom_providers\`
+
+
+
+
## Using goose for Free
goose is a free and open source AI agent that you can start using right away, but not all supported [LLM Providers][providers] provide a free tier.
diff --git a/documentation/docs/guides/config-files.md b/documentation/docs/guides/config-files.md
index 7d0246070f..95902d538f 100644
--- a/documentation/docs/guides/config-files.md
+++ b/documentation/docs/guides/config-files.md
@@ -45,8 +45,8 @@ The following settings can be configured at the root level of your config.yaml f
| `GOOSE_AUTO_COMPACT_THRESHOLD` | Set the percentage threshold at which goose [automatically summarizes your session](/docs/guides/sessions/smart-context-management#automatic-compaction). | Float between 0.0 and 1.0 (disabled at 0.0)| 0.8 | No |
| `otel_exporter_otlp_endpoint` | OTLP endpoint URL for [observability](/docs/guides/environment-variables#opentelemetry-protocol-otlp) | URL (e.g., `http://localhost:4318`) | None | No |
| `otel_exporter_otlp_timeout` | Export timeout in milliseconds for [observability](/docs/guides/environment-variables#opentelemetry-protocol-otlp) | Integer (ms) | 10000 | No |
-| `security_prompt_enabled` | Enable [prompt injection detection](/docs/guides/security/prompt-injection-detection) to identify potentially harmful commands | true/false | false | No |
-| `security_prompt_threshold` | Sensitivity threshold for [prompt injection detection](/docs/guides/security/prompt-injection-detection) (higher = stricter) | Float between 0.01 and 1.0 | 0.7 | No |
+| `SECURITY_PROMPT_ENABLED` | Enable [prompt injection detection](/docs/guides/security/prompt-injection-detection) to identify potentially harmful commands | true/false | false | No |
+| `SECURITY_PROMPT_THRESHOLD` | Sensitivity threshold for [prompt injection detection](/docs/guides/security/prompt-injection-detection) (higher = stricter) | Float between 0.01 and 1.0 | 0.7 | No |
## Experimental Features
@@ -91,7 +91,7 @@ otel_exporter_otlp_endpoint: "http://localhost:4318"
otel_exporter_otlp_timeout: 20000
# Security Configuration
-security_prompt_enabled: true
+SECURITY_PROMPT_ENABLED: true
# Extensions Configuration
extensions:
diff --git a/documentation/docs/guides/environment-variables.md b/documentation/docs/guides/environment-variables.md
index 00c9fa2e4b..0d5c6df44a 100644
--- a/documentation/docs/guides/environment-variables.md
+++ b/documentation/docs/guides/environment-variables.md
@@ -16,8 +16,8 @@ These are the minimum required variables to get started with goose.
| Variable | Purpose | Values | Default |
|----------|---------|---------|---------|
-| `GOOSE_PROVIDER` | Specifies the LLM provider to use | [See available providers](/docs/getting-started/providers#available-providers) | None (must be [configured](/docs/getting-started/providers#configure-provider)) |
-| `GOOSE_MODEL` | Specifies which model to use from the provider | Model name (e.g., "gpt-4", "claude-sonnet-4-20250514") | None (must be configured) |
+| `GOOSE_PROVIDER` | Specifies the LLM provider to use | [See available providers](/docs/getting-started/providers#available-providers) | None (must be [configured](/docs/getting-started/providers#configure-provider-and-model)) |
+| `GOOSE_MODEL` | Specifies which model to use from the provider | Model name (e.g., "gpt-4", "claude-sonnet-4-20250514") | None (must be [configured](/docs/getting-started/providers#configure-provider-and-model)) |
| `GOOSE_TEMPERATURE` | Sets the [temperature](https://medium.com/@kelseyywang/a-comprehensive-guide-to-llm-temperature-%EF%B8%8F-363a40bbc91f) for model responses | Float between 0.0 and 1.0 | Model-specific default |
**Examples**
diff --git a/documentation/docs/guides/goose-cli-commands.md b/documentation/docs/guides/goose-cli-commands.md
index bdd409e153..49713f271b 100644
--- a/documentation/docs/guides/goose-cli-commands.md
+++ b/documentation/docs/guides/goose-cli-commands.md
@@ -536,6 +536,21 @@ While the web interface provides most core features, be aware of these limitatio
+---
+
+### Terminal Integration
+
+#### @goose / @g
+Ask goose questions directly from your shell prompt, with command history included in the context. These aliases are created when you set up [terminal integration](/docs/guides/terminal-integration.md).
+
+**Examples:**
+```bash
+# Ask questions with command history context
+@goose create a python script to process these files
+@goose create a PR description summarizing these changes
+@g how do I fix these permission denied errors?
+```
+
---
## Interactive Session Features
diff --git a/documentation/docs/guides/handling-llm-rate-limits-with-goose.md b/documentation/docs/guides/handling-llm-rate-limits-with-goose.md
index a8d431fca8..42bcdbd21e 100644
--- a/documentation/docs/guides/handling-llm-rate-limits-with-goose.md
+++ b/documentation/docs/guides/handling-llm-rate-limits-with-goose.md
@@ -11,7 +11,7 @@ import { PanelLeft } from 'lucide-react';
Rate limiting is the process of restricting the number of requests a user or application can send to an LLM API within a specific timeframe. LLM providers enforce this with the purpose of managing resources and preventing abuse.
-Since goose is working very quickly to implement your tasks, you may need to manage rate limits imposed by the provider. If you frequently hit rate limits, consider upgrading your LLM plan to access higher tier limits or [configure a provider](/docs/getting-started/providers#configure-provider) that has built-in rate limiting:
+Since goose is working very quickly to implement your tasks, you may need to manage rate limits imposed by the provider. If you frequently hit rate limits, consider upgrading your LLM plan to access higher tier limits or [configure a provider](/docs/getting-started/providers#configure-provider-and-model) that has built-in rate limiting:
:::info
goose supports automatic setup for both providers that takes you through OAuth account creation and secure API key generation.
diff --git a/documentation/docs/guides/mcp-elicitation.md b/documentation/docs/guides/mcp-elicitation.md
new file mode 100644
index 0000000000..b039a4c0b7
--- /dev/null
+++ b/documentation/docs/guides/mcp-elicitation.md
@@ -0,0 +1,57 @@
+---
+sidebar_position: 55
+title: MCP Elicitation
+sidebar_label: MCP Elicitation
+description: How extensions can request structured information from you during a task
+unlisted: true
+---
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+MCP Elicitation allows goose to pause and ask you for specific information when an extension needs it. Instead of guessing or making assumptions, goose presents a form requesting exactly what's needed to continue.
+
+This feature is automatically enabled in goose. When an extension that supports elicitation needs information from you, a form will appear in your session.
+
+:::info
+[MCP Elicitation](https://modelcontextprotocol.io/specification/draft/client/elicitation) is a feature in the Model Context Protocol. goose supports form mode requests.
+:::
+
+## How MCP Elicitation Works
+
+When an extension needs information, goose pauses and presents a form for you to fill out. You can submit your response or cancel the request.
+
+
+
+
+ A form appears inline in the chat with:
+ - Fields for the requested data
+ - Required fields marked with an asterisk (*)
+ - Default values you can accept or change
+ - A **Submit** button to send your response
+
+ After submitting, you'll see a confirmation message.
+
+
+
+
+ A prompt appears in your terminal with:
+ - A message explaining what information is needed (in cyan)
+ - Field names (in yellow) with descriptions
+ - Required fields marked with a red asterisk (*)
+ - Default values shown in brackets, e.g., `[default]`
+
+ Type your response for each field and press Enter. For yes/no questions, you'll see an interactive toggle.
+
+ To cancel the request, press `Ctrl+C`.
+
+
+
+
+:::info Timeout
+Elicitation requests timeout after 5 minutes. If you don't respond in time, the request is cancelled and goose will continue without the information.
+:::
+
+## For Extension Developers
+
+Want to add elicitation to your own extensions? See the [MCP Elicitation specification](https://modelcontextprotocol.io/specification/draft/client/elicitation) to learn how MCP servers can request structured input from users.
diff --git a/documentation/docs/guides/mcp-sampling.md b/documentation/docs/guides/mcp-sampling.md
index 3eb7f0fbb7..ec7dd15d54 100644
--- a/documentation/docs/guides/mcp-sampling.md
+++ b/documentation/docs/guides/mcp-sampling.md
@@ -14,6 +14,8 @@ This feature is automatically enabled in goose, no configuration required! Any M
- goose users can get more targeted responses tailored to the extension's specific capabilities
- developers can add sampling support to their MCP servers to provide enhanced capabilities in goose
+Try out the [Council of Mine](/docs/mcp/council-of-mine-mcp) extension to see MCP sampling in action!
+
:::info
[MCP Sampling](https://modelcontextprotocol.io/specification/draft/client/sampling) is a feature in the Model Context Protocol.
:::
@@ -78,3 +80,22 @@ MCP Sampling enables powerful capabilities like:
## For Extension Developers
Want to add MCP Sampling to your own extensions? See our [Building Custom Extensions](/docs/tutorials/custom-extensions) tutorial to learn more about how MCP servers can leverage goose's AI capabilities.
+
+## Additional Resources
+
+import ContentCardCarousel from '@site/src/components/ContentCardCarousel';
+import mcpSampling from '@site/blog/2025-12-04-mcp-sampling/mcp-sampling.png';
+
+
diff --git a/documentation/docs/guides/recipes/recipe-reference.md b/documentation/docs/guides/recipes/recipe-reference.md
index 22a3677597..97935afcda 100644
--- a/documentation/docs/guides/recipes/recipe-reference.md
+++ b/documentation/docs/guides/recipes/recipe-reference.md
@@ -1,39 +1,590 @@
---
sidebar_position: 2
title: Recipe Reference Guide
+sidebar_label: Recipe Reference
description: Complete technical reference for creating and customizing recipes in goose
---
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
-Recipes are reusable goose configurations that package up a specific setup so it can be easily shared and launched by others.
+Recipes are reusable goose configurations that package up instructions and settings so the setup can be easily shared and launched by others.
## Recipe File Format
-Recipes can be defined in either:
-- `.yaml` files (recommended)
-- `.json` files
+Recipes can be defined in:
+- `.yaml` (recommended) and `.yml` files
+- `.json` files
+
+:::info
+`.yml` files aren't supported by goose CLI.
+:::
See [Shareable Recipes](/docs/guides/recipes/session-recipes) to learn how to create, use, and manage recipes.
-### CLI and Desktop Formats
+## Recipe Location
-goose recipes use two formats:
+Recipes can be loaded from:
-- **CLI Format**: Recipe fields (like `title`, `description`, `instructions`) are at the root level of the YAML/JSON file. This format is used when recipes are created via the CLI `/recipe` command and [Recipe Generator](/recipe-generator) YAML option.
-- **Desktop Format**: Recipe fields are nested inside a `recipe` object, with additional metadata fields at the root level. This format is used when recipes are created from goose Desktop.
+1. Local filesystem:
+ - Current directory
+ - Directories specified in [`GOOSE_RECIPE_PATH`](/docs/guides/environment-variables#recipe-configuration) environment variable
+
+2. GitHub repositories:
+ - Configure using [`GOOSE_RECIPE_GITHUB_REPO`](/docs/guides/environment-variables#recipe-configuration) configuration key
+ - Requires GitHub CLI (`gh`) to be installed and authenticated
-The CLI automatically detects and handles both formats for `.yaml` and `.json` recipe files when running `goose run --recipe ` and `goose recipe` commands. The Desktop can [import](/docs/guides/recipes/storing-recipes#importing-recipes) `.yaml`, `.yml`, and `.json` recipe files (or deeplinks) in either CLI or Desktop format.
+## Core Recipe Schema
+
+Recipes follow this schema structure:
+
+| Field | Type | Required | Description |
+|-------|------|----------|-------------|
+| `description` | String | β
| A detailed description of what the recipe does |
+| `instructions` | String | β
* | Template instructions that can include parameter substitutions |
+| `prompt` | String| β
* | A template prompt that can include parameter substitutions. Required in [headless](/docs/tutorials/headless-goose) (non-interactive) mode. |
+| `title` | String | β
| A short title describing the recipe |
+| [`activities`](#activities) | Array | - | List of example prompts that can include parameter substitutions. Activities appear as clickable bubbles in goose Desktop. |
+| [`extensions`](#extensions) | Array | - | List of extension configurations |
+| [`parameters`](#parameters) | Array | - | List of parameter definitions for dynamic recipes |
+| [`response`](#response) | Object | - | Structured output schema for automation workflows |
+| [`retry`](#retry) | Object | - | Configuration for automated retry logic with success validation |
+| [`settings`](#settings) | Object | - | Configuration for model provider, model name, and other settings |
+| [`sub_recipes`](#subrecipes) | Array | - | List of subrecipes |
+| `version` | String | - | The recipe format version, defaults to "1.0.0" if omitted |
+
+*At least one of `instructions` or `prompt` must be provided.
+
+## Field Specifications
+
+### Activities
+
+The `activities` field defines an optional message and clickable activity bubbles (buttons) that appears when a recipe is opened in goose Desktop.
+
+:::info Desktop only
+Activities are a Desktop-only feature. When recipes with activities are run via the CLI or as a scheduled job, the `activities` field is ignored and has no effect on recipe execution.
+:::
+
+#### Activity Types
+
+Activities can be defined in two ways:
+
+1. **Message Activity**: Displays the markdown-formatted activity text in an info box above the activity bubbles. For example:
+
+ ```
+ activities:
+ - "message: **Welcome!** Here's what I can help with:\n\nβ’ π Data analysis\nβ’ π Code review\nβ’ π Documentation\n\nSelect an option below to begin."
+ ```
+
+ Only include one `message:` prefixed activity. Additional `message:` prefixed activities become regular clickable bubbles (and display the literal "message:" text).
+
+2. **Button Activities**: Text to display in activity bubbles, which send the activity text as a prompt when clicked
+
+#### Parameter Substitution
+
+Activities support [parameter substitution](#parameters), allowing you to create dynamic, personalized activity bubbles. After users provide parameter values in the **Recipe Parameters** dialog, the values are substituted into the activity text before the bubbles are displayed.
+
+#### Example Configuration
+
+
+
+ ```yaml
+ version: "1.0.0"
+ title: "Code Review Assistant"
+ description: "Review code with customizable focus areas"
+ parameters:
+ - key: language
+ input_type: string
+ requirement: required
+ description: "Programming language to review"
+ - key: focus
+ input_type: string
+ requirement: optional
+ default: "best practices"
+ description: "Review focus area"
+
+ activities:
+ - "message: Click an option below to start reviewing {{ language }} code with a focus on {{ focus }}."
+ - "Review the current file for {{ focus }}"
+ - "Suggest improvements for {{ language }} code quality"
+ - "Check for security vulnerabilities"
+ - "Generate unit tests"
+ ```
+
+
+ ```json
+ {
+ "version": "1.0.0",
+ "title": "Code Review Assistant",
+ "description": "Review code with customizable focus areas",
+ "parameters": [
+ {
+ "key": "language",
+ "input_type": "string",
+ "requirement": "required",
+ "description": "Programming language to review"
+ },
+ {
+ "key": "focus",
+ "input_type": "string",
+ "requirement": "optional",
+ "default": "best practices",
+ "description": "Review focus area"
+ }
+ ],
+ "activities": [
+ "message: Click an option below to start reviewing {{ language }} code with a focus on {{ focus }}.",
+ "Review the current file for {{ focus }}",
+ "Suggest improvements for {{ language }} code quality",
+ "Check for security vulnerabilities",
+ "Generate unit tests"
+ ]
+ }
+ ```
+
+
+
+In this example:
+- The message activity displays instructions with substituted parameter values, for example: "Click an option below to start reviewing rust code with a focus on best practices."
+- The first two activity bubbles use parameter substitution, for example: "Review the current file for best practices"
+- The last two activity bubbles are static prompts that work regardless of parameters
+
+### Extensions
+
+The `extensions` field allows you to specify which Model Context Protocol (MCP) servers and other extensions the recipe needs to function. Each extension in the `extensions` array has the following schema:
+
+#### Extension Schema
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `type` | String | Type of extension (e.g., "stdio") |
+| `name` | String | Unique name for the extension |
+| `cmd` | String | Command to run the extension |
+| `args` | Array | List of arguments for the command |
+| `env_keys` | Array | (Optional) Names of environment variables required by the extension |
+| `timeout` | Number | Timeout in seconds |
+| `bundled` | Boolean | (Optional) Whether the extension is bundled with goose |
+| `description` | String | Description of what the extension does |
+| `available_tools` | Array | List of tool names within the extension that will be available. When not specified all will be available |
+
+#### Extension Types
+
+- **`stdio`**: Standard I/O client with command and arguments
+- **`sse`**: Server-sent events client with a URI endpoint
+- **`builtin`**: Built-in extension that is part of the bundled goose MCP server
+- **`platform`**: Platform extensions that run in the agent process
+- **`streamable_http`**: Streamable HTTP client with URI endpoint
+- **`frontend`**: Frontend-provided tools called through the frontend
+- **`inline_python`**: Inline Python code executed using uvx. Requires `code` field; optional `dependencies` for packages.
+
+#### Example Extensions Configuration
+
+
+
+
+```yaml
+extensions:
+ - type: stdio
+ name: codesearch
+ cmd: uvx
+ args:
+ - mcp_codesearch@latest
+ timeout: 300
+ bundled: true
+ description: "Query https://codesearch.sqprod.co/ directly from goose"
+
+ - type: stdio
+ name: presidio
+ timeout: 300
+ cmd: uvx
+ args:
+ - 'mcp_presidio@latest'
+ available_tools:
+ - query_logs
+
+ - type: stdio
+ name: github-mcp
+ cmd: github-mcp-server
+ args: []
+ env_keys:
+ - GITHUB_PERSONAL_ACCESS_TOKEN
+ timeout: 60
+ description: "GitHub MCP extension for repository operations"
+
+ - type: inline_python
+ name: data_processor
+ code: |
+ import pandas as pd
+ print("Processing data...")
+ dependencies:
+ - pandas
+ - numpy
+ timeout: 120
+ description: "Process data using pandas"
+```
+
+
+
+
+```json
+{
+ "extensions": [
+ {
+ "type": "stdio",
+ "name": "codesearch",
+ "cmd": "uvx",
+ "args": ["mcp_codesearch@latest"],
+ "timeout": 300,
+ "bundled": true,
+ "description": "Query https://codesearch.sqprod.co/ directly from goose"
+ },
+ {
+ "type": "stdio",
+ "name": "presidio",
+ "timeout": 300,
+ "cmd": "uvx",
+ "args": ["mcp_presidio@latest"],
+ "available_tools": ["query_logs"]
+ },
+ {
+ "type": "stdio",
+ "name": "github-mcp",
+ "cmd": "github-mcp-server",
+ "args": [],
+ "env_keys": ["GITHUB_PERSONAL_ACCESS_TOKEN"],
+ "timeout": 60,
+ "description": "GitHub MCP extension for repository operations"
+ },
+ {
+ "type": "inline_python",
+ "name": "data_processor",
+ "code": "import pandas as pd\nprint(\"Processing data...\")",
+ "dependencies": ["pandas", "numpy"],
+ "timeout": 120,
+ "description": "Process data using pandas"
+ }
+ ]
+}
+```
+
+
+
+
+#### Extension Secrets
+
+This feature is only available through the CLI.
+
+If a recipe uses an extension that requires a secret, goose can prompt users to provide the secret when running the recipe:
+
+1. When a recipe is loaded, goose scans all extensions (including those in subrecipes) for `env_keys` fields
+2. If any required environment variables are missing from the secure keyring, goose prompts the user to enter them
+3. Values are stored securely in the system keyring and reused for subsequent runs
+
+To update a stored secret, remove it from the system keyring and run the recipe again to be re-prompted.
+
+:::info
+This feature is designed to prompt for and securely store secrets (such as API keys), but `env_keys` can include any environment variable needed by the extension (such as API endpoints, configuration values, etc.).
+
+Users can press `ESC` to skip entering a variable if it's optional for the extension.
+:::
+
+### Parameters
+
+The `parameters` field allows you to create dynamic, reusable recipes that can be customized for different contexts. Parameters define placeholders that users fill in when running the recipe, making the recipe more flexible and adaptable.
+
+Parameter substitution uses Jinja-style template syntax with `{{ parameter_name }}` placeholders. Each parameter in the `parameters` array has the following schema:
+
+#### Parameter Schema
+
+| Field | Type | Required | Description |
+|-------|------|----------|-------------|
+| `key` | String | β
| Unique identifier for the parameter |
+| `input_type` | String | β
| Type of input: `"string"` (default), `"number"`, `"boolean"`, `"date"`, `"file"`, or `"select"` |
+| `requirement` | String | β
| One of: "required", "optional", or "user_prompt" |
+| `description` | String | β
| Human-readable description of the parameter |
+| `default` | String | - | Default value for optional parameters |
+| `options` | Array | - | List of available choices (required for `select` input type) |
+
+#### Parameter Requirements
+
+- `required`: Parameter must be provided when using the recipe
+- `optional`: Can be omitted if a default value is specified
+- `user_prompt`: Will interactively prompt the user for input if not provided
+
+The `required` and `optional` parameters work best for recipes opened in goose Desktop. If a value isn't provided for a `user_prompt` parameter, the parameter won't be substituted and may appear as literal `{{ parameter_name }}` text in the recipe output.
+
+#### Input Types
+
+- `string`: Default type. The parameter value is used as-is in template substitution
+- `number`: Numeric values. Desktop UI provides number input validation
+- `boolean`: True/false values. Desktop UI shows dropdown with "True"/"False" options
+- `date`: Date values. Currently renders as text input
+- `file`: The parameter value should be a file path. goose reads the file contents and substitutes the actual content (not the path) into the template
+- `select`: Dropdown selection with predefined options. Requires `options` field
+
+**Example:**
+```yaml
+parameters:
+ - key: max_files
+ input_type: number
+ requirement: optional
+ default: "10"
+ description: "Maximum files to process"
+
+ - key: output_format
+ input_type: select
+ requirement: required
+ description: "Choose output format"
+ options:
+ - json
+ - markdown
+ - csv
+
+ - key: enable_debug
+ input_type: boolean
+ requirement: optional
+ default: "false"
+ description: "Enable debug mode"
+
+ - key: source_code
+ input_type: file
+ requirement: required
+ description: "Path to the source code file to analyze"
+
+prompt: "Process {{ max_files }} files in {{ output_format }} format. Debug: {{ enable_debug }}. Code:\n\n{{ source_code }}"
+```
+
+:::important
+- Optional parameters MUST have a default value specified
+- Required parameters cannot have default values
+- File parameters cannot have default values regardless of requirement type to prevent unintended importing of sensitive files
+- Select parameters MUST have an `options` field with available choices
+- Parameter keys must match any template variables used in instructions, prompt, or activities
+:::
+
+#### Parameter Substitution in Desktop
+
+When a recipe with parameters is opened in goose Desktop, users are presented with a **Recipe Parameters** dialog where they can:
+- Provide values for required parameters
+- Modify or accept default values for optional parameters
+- Enter values for `user_prompt` parameters
+
+Once parameter values are submitted, they are substituted into the recipe's `instructions`, `prompt`, and `activities` fields before the recipe starts.
+
+### Response
+
+The `response` field enables recipes to enforce a final structured JSON output. When you specify a `json_schema`, goose will:
+
+1. **Validate the output**: Validates the output JSON against your JSON schema with basic JSON schema validations
+2. **Final structured output**: Ensure the final output of the agent is a response matching your JSON structure
+
+This feature is designed for **non-interactive automation** to ensure consistent, parseable output. Recipes can produce structured output when run from either the goose CLI or goose Desktop. See [use cases and ideas for automation workflows](/docs/guides/recipes/session-recipes#response).
+
+#### Response Schema
+
+| Field | Type | Required | Description |
+|-------|------|----------|-------------|
+| `json_schema` | Object | β
| [JSON schema](https://json-schema.org/) for output validation |
+
+#### Basic Structure
+
+```yaml
+response:
+ json_schema:
+ type: object
+ properties:
+ # Define your fields here, with their type and description
+ required:
+ # List required field names
+```
+
+#### Simple Example
+
+```yaml
+version: "1.0.0"
+title: "Task Summary"
+description: "Summarize completed tasks"
+prompt: "Summarize the tasks you completed"
+response:
+ json_schema:
+ type: object
+ properties:
+ summary:
+ type: string
+ description: "Brief summary of work done"
+ tasks_completed:
+ type: number
+ description: "Number of tasks finished"
+ next_steps:
+ type: array
+ items:
+ type: string
+ description: "Recommended next actions"
+ required:
+ - summary
+ - tasks_completed
+```
+
+### Retry
+
+The `retry` field enables recipes to automatically retry execution if success criteria are not met. This is useful for recipes that might need multiple attempts to achieve their goal, or for implementing automated validation and recovery workflows.
+
+#### Retry Schema
+
+| Field | Type | Required | Description |
+|-------|------|----------|-------------|
+| `max_retries` | Number | β
| Maximum number of retry attempts |
+| `checks` | Array | β
| List of success check configurations |
+| `timeout_seconds` | Number | - | Timeout for success check commands (default: 300 seconds) |
+| `on_failure_timeout_seconds` | Number | - | Timeout for on_failure commands (default: 600 seconds) |
+| `on_failure` | String | - | Shell command to run when a retry attempt fails |
+
+#### Success Check Configuration
+
+Each success check in the `checks` array has the following schema:
+
+| Field | Type | Required | Description |
+|-------|------|----------|-------------|
+| `type` | String | β
| Type of check - currently only "shell" is supported |
+| `command` | String | β
| Shell command to execute for validation (must exit with code 0 for success) |
+
+#### How Retry Logic Works
+
+1. **Recipe Execution**: The recipe runs normally with the provided instructions
+2. **Success Validation**: After completion, all success checks are executed in order
+3. **Retry Decision**: If any success check fails and retry attempts remain:
+ - Execute the on_failure command (if configured)
+ - Reset the agent's message history to initial state
+ - Increment retry counter and restart execution
+4. **Completion**: Process stops when either:
+ - All success checks pass (success)
+ - Maximum retry attempts are reached (failure)
+
+#### Basic Retry Example
+
+```yaml
+version: "1.0.0"
+title: "Counter Increment Task"
+description: "Increment a counter until it reaches target value"
+prompt: "Increment the counter value in /tmp/counter.txt by 1."
+
+retry:
+ max_retries: 5
+ timeout_seconds: 10
+ checks:
+ - type: shell
+ command: "test $(cat /tmp/counter.txt 2>/dev/null || echo 0) -ge 3"
+ on_failure: "echo 'Counter is at:' $(cat /tmp/counter.txt 2>/dev/null || echo 0) '(need 3 to succeed)'"
+```
+
+#### Advanced Retry Example
+
+```yaml
+version: "1.0.0"
+title: "Service Health Check"
+description: "Start service and verify it's running properly"
+prompt: "Start the web service and verify it responds to health checks"
+
+retry:
+ max_retries: 3
+ timeout_seconds: 30
+ on_failure_timeout_seconds: 60
+ checks:
+ - type: shell
+ command: "curl -f http://localhost:8080/health"
+ - type: shell
+ command: "pgrep -f 'web-service' > /dev/null"
+ on_failure: "systemctl stop web-service || killall web-service"
+```
+
+#### Environment Variables
+
+You can configure retry behavior globally using environment variables:
+
+- `GOOSE_RECIPE_RETRY_TIMEOUT_SECONDS`: Global timeout for success check commands
+- `GOOSE_RECIPE_ON_FAILURE_TIMEOUT_SECONDS`: Global timeout for on_failure commands
+
+These environment variables are overridden by recipe-specific timeout configurations.
+
+### Settings
+
+The `settings` field allows you to configure the AI model and provider settings for the recipe. This overrides the default configuration when the recipe is executed.
+
+#### Settings Schema
+
+| Field | Type | Required | Description |
+|-------|------|----------|-------------|
+| `goose_provider` | String | - | The AI provider to use (e.g., "anthropic", "openai") |
+| `goose_model` | String | - | The specific model name to use |
+| `temperature` | Number | - | The temperature setting for the model (typically 0.0-1.0) |
+
+#### Example Settings Configuration
+
+```yaml
+settings:
+ goose_provider: "anthropic"
+ goose_model: "claude-sonnet-4-20250514"
+ temperature: 0.7
+```
+
+```yaml
+settings:
+ goose_provider: "openai"
+ goose_model: "gpt-4o"
+ temperature: 0.3
+```
+
+:::note
+Settings specified in a recipe will override your default goose configuration when that recipe is executed. If no settings are specified, goose will use your configured defaults.
+:::
+
+### Subrecipes
+
+The `sub_recipes` field specifies the [subrecipes](/docs/guides/recipes/subrecipes) that the main recipe calls to perform specific tasks. Each subrecipe in the `sub_recipes` array has the following schema:
+
+#### Subrecipe Schema
+
+| Field | Type | Required | Description |
+|-------|------|----------|-------------|
+| `name` | String | β
| Unique identifier for the subrecipe |
+| `path` | String | β
| Relative or absolute path to the subrecipe file |
+| `values` | Object | - | Pre-configured parameter values that are passed to the subrecipe |
+| `sequential_when_repeated` | Boolean | - | Forces sequential execution of multiple subrecipe instances. See [Running Subrecipes In Parallel](/docs/tutorials/subrecipes-in-parallel) for details |
+| `description` | String | - | Optional description of the subrecipe |
+
+#### Example Subrecipe Configuration
+
+```yaml
+sub_recipes:
+ - name: "security_scan"
+ path: "./subrecipes/security-analysis.yaml"
+ values: # in key-value format: {parameter_name}: {parameter_value}
+ scan_level: "comprehensive"
+ include_dependencies: "true"
+
+ - name: "quality_check"
+ path: "./subrecipes/quality-analysis.yaml"
+ description: "Performs code quality analysis"
+```
+
+## Desktop Metadata Fields
+
+Recipes saved from goose Desktop include additional metadata fields. These fields are used by the Desktop app for organization and management but are ignored by CLI operations.
+
+| Field | Type | Required | Description |
+|-------|------|----------|-------------|
+| `recipe` | Object | β
| Contains all recipe fields (`title`, `description`, `instructions`, etc.) |
+| `name` | String | β
| Display name used in Recipe Library |
+| `isGlobal` | Boolean | β
| Whether the recipe is available globally or locally to a project |
+| `lastModified` | String | β
| ISO timestamp of when the recipe was last modified |
+| `isArchived` | Boolean | β
| Whether the recipe is archived in the Desktop interface |
-Format Examples
+CLI and Desktop Format Examples
-Recipes can be written in either YAML or JSON format. Both formats follow the same schema structure.
+**CLI Format**
-### CLI Format
-
-
+
```yaml
@@ -62,9 +613,9 @@ extensions: []
-### Desktop Format
+**Desktop Format**
-
+
```yaml
@@ -104,493 +655,11 @@ isArchived: false
-:::note
-goose automatically adds metadata fields to recipes saved from the Desktop app.
-:::
-
-## Recipe Structure
-
-### Required Fields
-
-| Field | Type | Description |
-|-------|------|-------------|
-| `version` | String | The recipe format version (e.g., "1.0.0") |
-| `title` | String | A short title describing the recipe |
-| `description` | String | A detailed description of what the recipe does |
-
-### Optional Fields
-
-| Field | Type | Description |
-|-------|------|-------------|
-| `instructions` | String | Template instructions that can include parameter substitutions |
-| `prompt` | String | A template prompt that can include parameter substitutions; required in headless (non-interactive) mode |
-| [`parameters`](#parameters) | Array | List of parameter definitions |
-| [`activities`](#activities) | Array | List of example prompts that appear as clickable bubbles in goose Desktop |
-| [`extensions`](#extensions) | Array | List of extension configurations |
-| [`settings`](#settings) | Object | Configuration for model provider, model name, and other settings |
-| [`sub_recipes`](#subrecipes) | Array | List of subrecipes |
-| [`response`](#structured-output-with-response) | Object | Structured output schema for automation workflows |
-| [`retry`](#automated-retry-with-success-validation) | Object | Configuration for automated retry logic with success validation |
-
-### Desktop Format Metadata Fields
-
-When recipes are saved from goose Desktop, additional metadata fields are included at the top level (outside the `recipe` key). These fields are used by the Desktop app for organization and management but are ignored by CLI operations.
-
-| Field | Type | Description |
-|-------|------|-------------|
-| `name` | String | Display name used in Desktop Recipe Library |
-| `isGlobal` | Boolean | Whether the recipe is available globally or locally to a project |
-| `lastModified` | String | ISO timestamp of when the recipe was last modified |
-| `isArchived` | Boolean | Whether the recipe is archived in the Desktop interface |
-
-## Parameters
-
-The `parameters` field allows you to create dynamic, reusable recipes that can be customized for different contexts. Parameters define placeholders that users fill in when running the recipe, making the recipe more flexible and adaptable.
-
-Parameter substitution uses Jinja-style template syntax with `{{ parameter_name }}` placeholders. Each parameter in the `parameters` array has the following structure:
-
-### Required Parameter Fields
-
-| Field | Type | Description |
-|-------|------|-------------|
-| `key` | String | Unique identifier for the parameter |
-| `input_type` | String | Type of input: `"string"` (default), `"number"`, `"boolean"`, `"date"`, `"file"`, or `"select"` |
-| `requirement` | String | One of: "required", "optional", or "user_prompt" |
-| `description` | String | Human-readable description of the parameter |
-
-### Optional Parameter Fields
-
-| Field | Type | Description |
-|-------|------|-------------|
-| `default` | String | Default value for optional parameters |
-| `options` | Array | List of available choices (required for `select` input type) |
-
-### Parameter Requirements
-
-- `required`: Parameter must be provided when using the recipe
-- `optional`: Can be omitted if a default value is specified
-- `user_prompt`: Will interactively prompt the user for input if not provided
-
-The `required` and `optional` parameters work best for recipes opened in goose Desktop. If a value isn't provided for a `user_prompt` parameter, the parameter won't be substituted and may appear as literal `{{ parameter_name }}` text in the recipe output.
-
-### Input Types
-
-- `string`: Default type. The parameter value is used as-is in template substitution
-- `number`: Numeric values. Desktop UI provides number input validation
-- `boolean`: True/false values. Desktop UI shows dropdown with "True"/"False" options
-- `date`: Date values. Currently renders as text input
-- `file`: The parameter value should be a file path. goose reads the file contents and substitutes the actual content (not the path) into the template
-- `select`: Dropdown selection with predefined options. Requires `options` field
-
-**Example:**
-```yaml
-parameters:
- - key: max_files
- input_type: number
- requirement: optional
- default: 10
- description: "Maximum files to process"
-
- - key: output_format
- input_type: select
- requirement: required
- description: "Choose output format"
- options:
- - json
- - markdown
- - csv
-
- - key: enable_debug
- input_type: boolean
- requirement: optional
- default: false
- description: "Enable debug mode"
-
- - key: source_code
- input_type: file
- requirement: required
- description: "Path to the source code file to analyze"
-
-prompt: "Process {{ max_files }} files in {{ output_format }} format. Debug: {{ enable_debug }}. Code:\n\n{{ source_code }}"
-```
-
-:::important
-- Optional parameters MUST have a default value specified
-- Required parameters cannot have default values
-- File parameters cannot have default values regardless of requirement type to prevent unintended importing of sensitive files
-- Select parameters MUST have an `options` field with available choices
-- Parameter keys must match any template variables used in instructions, prompt, or activities
-:::
-
-### Parameter Substitution in Desktop
-
-When a recipe with parameters is opened in goose Desktop, users are presented with a **Recipe Parameters** dialog where they can:
-- Provide values for required parameters
-- Modify or accept default values for optional parameters
-- Enter values for `user_prompt` parameters
-
-Once parameter values are submitted, they are substituted into the recipe's `instructions`, `prompt`, and `activities` fields before the recipe starts.
-
-## Activities
-
-The `activities` field defines an optional message and clickable activity bubbles (buttons) that appears when a recipe is opened in goose Desktop.
-
-:::info Desktop only
-Activities are a Desktop-only feature. When recipes with activities are run via the CLI or as a scheduled job, the `activities` field is ignored and has no effect on recipe execution.
-:::
-
-### Activity Types
-
-Activities can be defined in two ways:
-
-1. **Message Activity**: Displays the markdown-formatted activity text in an info box above the activity bubbles. For example:
-
- ```
- activities:
- - "message: **Welcome!** Here's what I can help with:\n\nβ’ π Data analysis\nβ’ π Code review\nβ’ π Documentation\n\nSelect an option below to begin."
- ```
-
- Only include one `message:` prefixed activity. Additional `message:` prefixed activities become regular clickable bubbles (and display the literal "message:" text).
-
-2. **Button Activities**: Text to display in activity bubbles, which send the activity text as a prompt when clicked
-
-### Parameter Substitution
-
-Activities support [parameter substitution](#parameters), allowing you to create dynamic, personalized activity bubbles. After users provide parameter values in the **Recipe Parameters** dialog, the values are substituted into the activity text before the bubbles are displayed.
-
-### Example Configuration
-
-```yaml
-version: "1.0.0"
-title: "Code Review Assistant"
-description: "Review code with customizable focus areas"
-parameters:
- - key: language
- input_type: string
- requirement: required
- description: "Programming language to review"
- - key: focus
- input_type: string
- requirement: optional
- default: "best practices"
- description: "Review focus area"
-
-activities:
- - "message: Click an option below to start reviewing {{ language }} code with a focus on {{ focus }}."
- - "Review the current file for {{ focus }}"
- - "Suggest improvements for {{ language }} code quality"
- - "Check for security vulnerabilities"
- - "Generate unit tests"
-```
-
-In this example:
-- The message activity displays instructions with substituted parameter values, for example: "Click an option below to start reviewing rust code with a focus on best practices."
-- The first two activity bubbles use parameter substitution, for example: "Review the current file for best practices"
-- The last two activity bubbles are static prompts that work regardless of parameters
-
-## Extensions
-
-The `extensions` field allows you to specify which Model Context Protocol (MCP) servers and other extensions the recipe needs to function. Each extension in the array has the following structure:
-
-### Extension Fields
-
-| Field | Type | Description |
-|-------|------|-------------|
-| `type` | String | Type of extension (e.g., "stdio") |
-| `name` | String | Unique name for the extension |
-| `cmd` | String | Command to run the extension |
-| `args` | Array | List of arguments for the command |
-| `env_keys` | Array | (Optional) Names of environment variables required by the extension |
-| `timeout` | Number | Timeout in seconds |
-| `bundled` | Boolean | (Optional) Whether the extension is bundled with goose |
-| `description` | String | Description of what the extension does |
-| `available_tools` | Array | List of tool names within the extension that will be available. When not specified all will be available |
-
-### Example Extension Configuration
-
-
-
-
-```yaml
-extensions:
- - type: stdio
- name: codesearch
- cmd: uvx
- args:
- - mcp_codesearch@latest
- timeout: 300
- bundled: true
- description: "Query https://codesearch.sqprod.co/ directly from goose"
-
- - type: stdio
- name: presidio
- timeout: 300
- cmd: uvx
- args:
- - 'mcp_presidio@latest'
- available_tools:
- - query_logs
-
- - type: stdio
- name: github-mcp
- cmd: github-mcp-server
- args: []
- env_keys:
- - GITHUB_PERSONAL_ACCESS_TOKEN
- timeout: 60
- description: "GitHub MCP extension for repository operations"
-```
-
-
-
-
-```json
-{
- "extensions": [
- {
- "type": "stdio",
- "name": "codesearch",
- "cmd": "uvx",
- "args": ["mcp_codesearch@latest"],
- "timeout": 300,
- "bundled": true,
- "description": "Query https://codesearch.sqprod.co/ directly from goose"
- },
- {
- "type": "stdio",
- "name": "presidio",
- "timeout": 300,
- "cmd": "uvx",
- "args": ["mcp_presidio@latest"],
- "available_tools": ["query_logs"]
- },
- {
- "type": "stdio",
- "name": "github-mcp",
- "cmd": "github-mcp-server",
- "args": [],
- "env_keys": ["GITHUB_PERSONAL_ACCESS_TOKEN"],
- "timeout": 60,
- "description": "GitHub MCP extension for repository operations"
- }
- ]
-}
-```
-
-
-
-
-### Extension Secrets
-
-This feature is only available through the CLI.
-
-If a recipe uses an extension that requires a secret, goose can prompt users to provide the secret when running the recipe:
-
-1. When a recipe is loaded, goose scans all extensions (including those in subrecipes) for `env_keys` fields
-2. If any required environment variables are missing from the secure keyring, goose prompts the user to enter them
-3. Values are stored securely in the system keyring and reused for subsequent runs
-
-To update a stored secret, remove it from the system keyring and run the recipe again to be re-prompted.
-
-:::info
-This feature is designed to prompt for and securely store secrets (such as API keys), but `env_keys` can include any environment variable needed by the extension (such as API endpoints, configuration values, etc.).
-
-Users can press `ESC` to skip entering a variable if it's optional for the extension.
-:::
-
-## Settings
-
-The `settings` field allows you to configure the AI model and provider settings for the recipe. This overrides the default configuration when the recipe is executed.
-
-### Settings Fields
-
-| Field | Type | Description |
-|-------|------|-------------|
-| `goose_provider` | String | (Optional) The AI provider to use (e.g., "anthropic", "openai") |
-| `goose_model` | String | (Optional) The specific model name to use |
-| `temperature` | Number | (Optional) The temperature setting for the model (typically 0.0-1.0) |
-
-### Example Settings Configuration
-
-```yaml
-settings:
- goose_provider: "anthropic"
- goose_model: "claude-sonnet-4-20250514"
- temperature: 0.7
-```
-
-```yaml
-settings:
- goose_provider: "openai"
- goose_model: "gpt-4o"
- temperature: 0.3
-```
-
-:::note
-Settings specified in a recipe will override your default goose configuration when that recipe is executed. If no settings are specified, goose will use your configured defaults.
-:::
-
-## Subrecipes
-
-The `sub_recipes` field specifies the [subrecipes](/docs/guides/recipes/subrecipes) that the main recipe calls to perform specific tasks. Each subrecipe in the array has the following structure:
-
-### Subrecipe Fields
-
-| Field | Type | Description |
-|-------|------|-------------|
-| `name` | String | Unique identifier for the subrecipe |
-| `path` | String | Relative or absolute path to the subrecipe file |
-| `values` | Object | (Optional) Pre-configured parameter values that are passed to the subrecipe |
-| `sequential_when_repeated` | Boolean | (Optional) Forces sequential execution of multiple subrecipe instances. See [Running Subrecipes In Parallel](/docs/tutorials/subrecipes-in-parallel) for details |
-
-### Example Subrecipe Configuration
-
-```yaml
-sub_recipes:
- - name: "security_scan"
- path: "./subrecipes/security-analysis.yaml"
- values: # in key-value format: {parameter_name}: {parameter_value}
- scan_level: "comprehensive"
- include_dependencies: "true"
-
- - name: "quality_check"
- path: "./subrecipes/quality-analysis.yaml"
-```
-
-## Automated Retry with Success Validation
-
-The `retry` field enables recipes to automatically retry execution if success criteria are not met. This is useful for recipes that might need multiple attempts to achieve their goal, or for implementing automated validation and recovery workflows.
-
-### Retry Configuration Fields
-
-| Field | Type | Description |
-|-------|------|-------------|
-| `max_retries` | Number | Maximum number of retry attempts (required) |
-| `timeout_seconds` | Number | (Optional) Timeout for success check commands (default: 300 seconds) |
-| `on_failure_timeout_seconds` | Number | (Optional) Timeout for on_failure commands (default: 600 seconds) |
-| `checks` | Array | List of success check configurations (required) |
-| `on_failure` | String | (Optional) Shell command to run when a retry attempt fails |
-
-### Success Check Configuration
-
-Each success check in the `checks` array has the following structure:
-
-| Field | Type | Description |
-|-------|------|-------------|
-| `type` | String | Type of check - currently only "shell" is supported |
-| `command` | String | Shell command to execute for validation (must exit with code 0 for success) |
-
-### How Retry Logic Works
-
-1. **Recipe Execution**: The recipe runs normally with the provided instructions
-2. **Success Validation**: After completion, all success checks are executed in order
-3. **Retry Decision**: If any success check fails and retry attempts remain:
- - Execute the on_failure command (if configured)
- - Reset the agent's message history to initial state
- - Increment retry counter and restart execution
-4. **Completion**: Process stops when either:
- - All success checks pass (success)
- - Maximum retry attempts are reached (failure)
-
-### Basic Retry Example
-
-```yaml
-version: "1.0.0"
-title: "Counter Increment Task"
-description: "Increment a counter until it reaches target value"
-prompt: "Increment the counter value in /tmp/counter.txt by 1."
-
-retry:
- max_retries: 5
- timeout_seconds: 10
- checks:
- - type: shell
- command: "test $(cat /tmp/counter.txt 2>/dev/null || echo 0) -ge 3"
- on_failure: "echo 'Counter is at:' $(cat /tmp/counter.txt 2>/dev/null || echo 0) '(need 3 to succeed)'"
-```
-
-### Advanced Retry Example
-
-```yaml
-version: "1.0.0"
-title: "Service Health Check"
-description: "Start service and verify it's running properly"
-prompt: "Start the web service and verify it responds to health checks"
-
-retry:
- max_retries: 3
- timeout_seconds: 30
- on_failure_timeout_seconds: 60
- checks:
- - type: shell
- command: "curl -f http://localhost:8080/health"
- - type: shell
- command: "pgrep -f 'web-service' > /dev/null"
- on_failure: "systemctl stop web-service || killall web-service"
-```
-
-### Environment Variables
-
-You can configure retry behavior globally using environment variables:
-
-- `GOOSE_RECIPE_RETRY_TIMEOUT_SECONDS`: Global timeout for success check commands
-- `GOOSE_RECIPE_ON_FAILURE_TIMEOUT_SECONDS`: Global timeout for on_failure commands
-
-These environment variables are overridden by recipe-specific timeout configurations.
-
-## Structured Output with `response`
-
-The `response` field enables recipes to enforce a final structured JSON output from goose. When you specify a `json_schema`, goose will:
-
-1. **Validate the output**: Validates the output JSON against your JSON schema with basic JSON schema validations
-2. **Final structured output**: Ensure the final output of the agent is a response matching your JSON structure
-
-This feature is designed for **non-interactive automation** to ensure consistent, parseable output. Recipes can produce structured output when run from either the goose CLI or goose Desktop. See [use cases and ideas for automation workflows](/docs/guides/recipes/session-recipes#structured-output-for-automation).
-
-### Basic Structure
-
-```yaml
-response:
- json_schema:
- type: object
- properties:
- # Define your fields here, with their type and description
- required:
- # List required field names
-```
-
-### Simple Example
-
-```yaml
-version: "1.0.0"
-title: "Task Summary"
-description: "Summarize completed tasks"
-prompt: "Summarize the tasks you completed"
-response:
- json_schema:
- type: object
- properties:
- summary:
- type: string
- description: "Brief summary of work done"
- tasks_completed:
- type: number
- description: "Number of tasks finished"
- next_steps:
- type: array
- items:
- type: string
- description: "Recommended next actions"
- required:
- - summary
- - tasks_completed
-```
-
## Template Support
-Recipes support Jinja-style template syntax in `instructions`, `prompt`, and `activities` fields:
+Recipes support Jinja-style template syntax in `instructions`, `prompt`, and `activities` fields for parameter substitution:
```yaml
instructions: "Follow these steps with {{ parameter_name }}"
@@ -600,14 +669,37 @@ activities:
```
Advanced template features include:
-- Template inheritance using `{% extends "parent.yaml" %}`
+- [Template inheritance](#template-inheritance) using `{% extends "parent.yaml" %}`
- Blocks that can be defined and overridden:
```yaml
{% block content %}
Default content
{% endblock %}
```
-- `indent()` template filter
+- [`indent()` template filter](#indent-filter-for-multi-line-values)
+
+### Template Inheritance
+
+Use `{% extends "parent.yaml" %}` for template inheritance:
+
+**Parent recipe (`parent.yaml`):**
+```yaml
+version: "1.0.0"
+title: "Parent Recipe"
+description: "Base recipe template"
+prompt: |
+ {% block prompt %}
+ Default prompt text
+ {% endblock %}
+```
+
+**Child recipe:**
+```yaml
+{% extends "parent.yaml" %}
+{% block prompt %}
+Modified prompt text
+{% endblock %}
+```
### indent() Filter For Multi-Line Values
@@ -622,15 +714,36 @@ sub_recipes:
{{ raw_data | indent(2) }}
```
-## Built-in Parameters
+### Built-in Parameters
+
+Built-in template parameters are automatically supported and don't need to be defined in the `parameters` array.
| Parameter | Description |
|-----------|-------------|
-| `recipe_dir` | Automatically set to the directory containing the recipe file |
+| `recipe_dir` | Automatically set to the directory containing the recipe file. Use it to reference companion files, for example: `{{ recipe_dir }}/style-guide.md` |
+
+## Validation Rules
+
+Validation rules from [`validate_recipe.rs`](https://github.com/block/goose/blob/main/crates/goose/src/recipe/validate_recipe.rs) are enforced when loading recipes and used by the [`goose recipe validate`](/docs/guides/goose-cli-commands#recipe) subcommand:
+
+### Recipe-Level Validation
+
+- `validate_prompt_or_instructions` - At least one of `instructions` or `prompt` must be present
+- `validate_json_schema` - JSON response schema must be valid if `response.json_schema` is specified
+
+### Parameter Validation
+
+- `validate_parameters_in_template` - All template variables must have corresponding parameter definitions, and all defined parameters must be used (no unused parameters)
+- `validate_optional_parameters` - Optional parameters must have default values
+- `validate_optional_parameters` - File parameters cannot have default values to prevent importing sensitive files
+
+:::info
+Basic field requirements (required fields, types, character limits) are documented in the [Core Recipe Schema](#core-recipe-schema) table.
+:::
## Complete Recipe Example
-
+
```yaml
@@ -795,52 +908,6 @@ response:
-## Template Inheritance
-
-Parent recipe (`parent.yaml`):
-```yaml
-version: "1.0.0"
-title: "Parent Recipe"
-description: "Base recipe template"
-prompt: |
- {% block prompt %}
- Default prompt text
- {% endblock %}
-```
-
-Child recipe:
-```yaml
-{% extends "parent.yaml" %}
-{% block prompt %}
-Modified prompt text
-{% endblock %}
-```
-
-## Recipe Location
-
-Recipes can be loaded from:
-
-1. Local filesystem:
- - Current directory
- - Directories specified in [`GOOSE_RECIPE_PATH`](/docs/guides/environment-variables#recipe-configuration) environment variable
-
-2. GitHub repositories:
- - Configure using [`GOOSE_RECIPE_GITHUB_REPO`](/docs/guides/environment-variables#recipe-configuration) configuration key
- - Requires GitHub CLI (`gh`) to be installed and authenticated
-
-## Validation Rules
-
-Recipe files must be valid YAML or JSON. In addition, the following [validation rules](https://github.com/block/goose/blob/main/crates/goose/src/recipe/validate_recipe.rs) are enforced when loading recipes and are also checked by the [`goose recipe validate` subcommand](/docs/guides/goose-cli-commands#recipe):
-
-- Required `title` and `description` fields must be present
-- At least one of `instructions` or `prompt` must be present
-- All template variables must have corresponding parameter definitions
-- Parameter keys must be unique (not enforced, but required for proper functionality)
-- All defined parameters must be used in template variables (no unused parameters)
-- Optional parameters must have default values
-- File parameters cannot have default values (prevents importing sensitive files)
-- `response.json_schema` must be a valid JSON schema if specified
-
## Error Handling
Common errors to watch for:
diff --git a/documentation/docs/guides/recipes/session-recipes.md b/documentation/docs/guides/recipes/session-recipes.md
index 444b071339..2cbe0f6335 100644
--- a/documentation/docs/guides/recipes/session-recipes.md
+++ b/documentation/docs/guides/recipes/session-recipes.md
@@ -588,11 +588,11 @@ retry:
- Recipe execution starts over
4. Process continues until either success or max retries reached
-See the [Recipe Reference Guide](/docs/guides/recipes/recipe-reference#automated-retry-with-success-validation) for complete retry configuration options and examples.
+See the [Recipe Reference Guide](/docs/guides/recipes/recipe-reference#retry) for complete retry configuration options and examples.
### Structured Output for Automation
-Recipes can enforce [structured JSON output](/docs/guides/recipes/recipe-reference#structured-output-with-response), making them ideal for automation workflows that need to parse and process agent responses reliably. Key benefits include:
+Recipes can enforce [structured JSON output](/docs/guides/recipes/recipe-reference#response), making them ideal for automation workflows that need to parse and process agent responses reliably. Key benefits include:
- **Reliable parsing**: Consistent JSON format for scripts, automation, and CI/CD pipelines
- **Built-in validation**: Ensures output matches your requirements
diff --git a/documentation/docs/guides/security/prompt-injection-detection.md b/documentation/docs/guides/security/prompt-injection-detection.md
index 35d565cd53..1c3eb3dd99 100644
--- a/documentation/docs/guides/security/prompt-injection-detection.md
+++ b/documentation/docs/guides/security/prompt-injection-detection.md
@@ -67,8 +67,8 @@ When in doubt, deny.
Add these settings to your [`config.yaml`](/docs/guides/config-files):
```yaml
- security_prompt_enabled: true
- security_prompt_threshold: 0.7 # Optional, default is 0.7
+ SECURITY_PROMPT_ENABLED: true
+ SECURITY_PROMPT_THRESHOLD: 0.7 # Optional, default is 0.7
```
diff --git a/documentation/docs/guides/terminal-integration.md b/documentation/docs/guides/terminal-integration.md
index 656b8cb369..f8023c5248 100644
--- a/documentation/docs/guides/terminal-integration.md
+++ b/documentation/docs/guides/terminal-integration.md
@@ -1,122 +1,176 @@
----
-unlisted: true
----
# Terminal Integration
-The `goose term` commands let you talk to goose directly from your shell prompt. Instead of switching to a separate REPL session, you stay in your terminal and call goose when you need it.
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
-```bash
-@goose "what does this error mean?"
-```
-
-Goose responds, you read the answer, and you're back at your prompt. The conversation lives alongside your work, not in a separate window you have to manage.
-
-## Command History Awareness
-
-The real power comes from shell integration. Once set up, goose tracks the commands you run, so when you ask a question, it already knows what you've been doing.
-
-No more copy-pasting error messages or explaining "I ran these commands...". Just work normally, then ask goose for help.
+Talk to goose directly from your shell prompt. Instead of switching to a separate REPL session, stay in your terminal and call goose when you need it.
## Setup
-Add one line to your shell config:
+
+
-**zsh** (`~/.zshrc`)
+Add to `~/.zshrc`:
```bash
eval "$(goose term init zsh)"
```
-**bash** (`~/.bashrc`)
+
+
+
+Add to `~/.bashrc`:
```bash
eval "$(goose term init bash)"
```
-**fish** (`~/.config/fish/config.fish`)
+
+
+
+Add to `~/.config/fish/config.fish`:
```fish
goose term init fish | source
```
-**PowerShell** (`$PROFILE`)
+
+
+
+Add to `$PROFILE`:
```powershell
Invoke-Expression (goose term init powershell)
```
-Then restart your terminal or source the config.
+
+
-### Default Mode
-
-For **bash** and **zsh**, you can make goose the default handler for anything that isn't a valid command:
-
-```bash
-# zsh
-eval "$(goose term init zsh --default)"
-
-# bash
-eval "$(goose term init bash --default)"
-```
-
-With this enabled, anything you type that isn't a command will be sent to goose:
-
-```bash
-$ what files are in this directory?
-πͺΏ Command 'what' not found. Asking goose...
-```
-
-Goose will interpret what you typed and help you accomplish the task.
+Restart your terminal or source the config, and that's it!
## Usage
-Once set up, your terminal session is linked to a goose session. All commands you run are logged to that session.
-
-To talk to goose about what you've been doing:
+Just type `@goose` (or `@g` for short) followed by your question:
```bash
-@goose "why did that fail?"
+npm install express
+ npm ERR! code EACCES
+ npm ERR! permission denied
+
+@goose "how do I fix this error?"
```
-You can also use `@g` as a shorter alias:
+goose automatically sees the commands you've run since your last question, so you don't need to explain what you've been doing. Use quotes around your prompt if it contains special characters like `?`, `*`, or `'`:
```bash
-@g "explain this error"
+@goose "what's in this directory?"
+@g "analyze the error: 'permission denied'"
```
-Both `@goose` and `@g` are aliases for `goose term run`. They open goose with your command history already loaded.
+## Named Sessions
+By default, each terminal gets its own goose session that lasts until you close it. Named sessions let you continue conversations across terminal restarts and share context between windows.
-## What Gets Logged
-
-Every command you type gets stored. Goose sees commands you ran since your last message to it.
-
-Commands starting with `goose term`, `@goose`, or `@g` are not logged (to avoid noise).
-
-## Performance
-
-- **Shell startup**: adds ~10ms
-- **Per command**: ~10ms, runs in background (non-blocking)
-
-You won't notice any delay. The logging happens asynchronously after your command starts executing.
-
-## How It Works
-
-`goose term init` outputs shell code that:
-1. Sets a `GOOSE_SESSION_ID` environment variable linking your terminal to a goose session
-2. Creates `@goose` and `@g` aliases for quick access
-3. Installs a preexec hook that calls `goose term log` for each command
-4. Optionally installs a command-not-found handler (with `--default`)
-
-The hook runs `goose term log &` in the background, which appends to the goose session.
-When you run `@goose`, goose reads from the goose session any commands that happened since it
-was last called and incorporates them in the next call.
-
-## Session Management
-
-By default a new goose session is created each time you run init and
-that session lasts as long as you keep that terminal open.
-
-You can create a named session by passing --name:
+
+
```bash
-eval "$(goose term init zsh --name my_project)"
+eval "$(goose term init zsh --name my-project)"
```
-which will create a session with the name `my_project` if it doesn't exist yet or continues
-that session if it does.
\ No newline at end of file
+
+
+
+```bash
+eval "$(goose term init bash --name my-project)"
+```
+
+
+
+
+```fish
+goose term init fish --name my-project | source
+```
+
+
+
+
+```powershell
+Invoke-Expression (goose term init powershell --name my-project)
+```
+
+
+
+
+Named sessions persist in goose's database, so they're available anytime, even after restarting your computer. Reopen later and run the same command to continue:
+
+```bash
+# Start debugging
+eval "$(goose term init zsh --name auth-bug)"
+@goose help me debug this login timeout
+
+# Close terminal, come back later
+eval "$(goose term init zsh --name auth-bug)"
+@goose "what was the solution we discussed?"
+# Continues the same conversation with context
+```
+
+## Show Context Status in Your Prompt
+
+Add `goose term info` to your prompt to see how much context you've used and which model is active during a terminal goose session.
+
+
+
+
+```bash
+PROMPT='$(goose term info) %~ $ '
+```
+
+
+
+
+```bash
+PS1='$(goose term info) \w $ '
+```
+
+
+
+
+```fish
+function fish_prompt
+ goose term info
+ echo -n ' '(prompt_pwd)' $ '
+end
+```
+
+
+
+
+```powershell
+function prompt {
+ $gooseInfo = & goose term info
+ "$gooseInfo $(Get-Location) PS> "
+}
+```
+
+
+
+
+Your terminal prompt now shows the context usage and model name (shortened for readability) for the active goose session. For example:
+
+```bash
+βββββ sonnet ~/projects $
+```
+## Troubleshooting
+
+**goose doesn't see recent commands:**
+If you run commands but goose says it doesn't see any recent activity, check if terminal integration is properly [set up in your shell config](#setup).
+You can also check the id of the goose session in your current terminal:
+```bash
+# Check if session ID exists
+echo $GOOSE_SESSION_ID
+# Should show something like: 20251209_151730
+```
+To share context across terminal windows, use a [named session](#named-sessions) instead.
+
+**Session getting too full** (prompt shows `βββββ`):
+If goose's responses are getting slow or hitting context limits, start a fresh goose session in the terminal. The new goose session sees your command history, but not the conversation history from the previous session.
+```bash
+# Start a new goose session in the same shell
+eval "$(goose term init zsh)"
+```
diff --git a/documentation/package-lock.json b/documentation/package-lock.json
index cb0412efe6..c54de3bc88 100644
--- a/documentation/package-lock.json
+++ b/documentation/package-lock.json
@@ -11396,9 +11396,9 @@
}
},
"node_modules/mdast-util-to-hast": {
- "version": "13.2.0",
- "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz",
- "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==",
+ "version": "13.2.1",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz",
+ "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==",
"license": "MIT",
"dependencies": {
"@types/hast": "^3.0.0",
diff --git a/documentation/src/css/custom.css b/documentation/src/css/custom.css
index 3e637240ab..edba1e5659 100644
--- a/documentation/src/css/custom.css
+++ b/documentation/src/css/custom.css
@@ -417,3 +417,50 @@ html[data-theme="light"] .hide-in-light {
.button-grant h2 {
margin-top: 15px;
}
+
+/* Prevent horizontal overflow */
+html, body {
+ overflow-x: hidden;
+ max-width: 100%;
+}
+
+/* Ensure skip to content link doesn't cause horizontal scroll */
+.skipToContent_fXgn {
+ position: absolute !important;
+ left: -9999px !important;
+ width: 1px !important;
+ height: 1px !important;
+ overflow: hidden !important;
+}
+
+.skipToContent_fXgn:focus {
+ position: fixed !important;
+ left: 10px !important;
+ top: 10px !important;
+ width: auto !important;
+ height: auto !important;
+ overflow: visible !important;
+ z-index: 9999 !important;
+ padding: 10px 20px !important;
+ background: var(--background-prominent) !important;
+ color: var(--text-prominent) !important;
+ border-radius: 4px !important;
+}
+
+/* Ensure all containers respect viewport width */
+.container {
+ max-width: 100%;
+ overflow-x: hidden;
+}
+
+/* Prevent navbar from causing horizontal overflow */
+.navbar {
+ max-width: 100%;
+ overflow-x: hidden;
+}
+
+/* Ensure main content doesn't overflow */
+main {
+ max-width: 100%;
+ overflow-x: hidden;
+}
diff --git a/documentation/src/pages/community/data/config.json b/documentation/src/pages/community/data/config.json
index 211521c9a1..bd133b9a31 100644
--- a/documentation/src/pages/community/data/config.json
+++ b/documentation/src/pages/community/data/config.json
@@ -34,7 +34,12 @@
"id": "october-2025",
"display": "October 2025",
"file": "october-2025.json"
+ },
+ {
+ "id": "november-2025",
+ "display": "November 2025",
+ "file": "november-2025.json"
}
],
- "defaultMonth": "october-2025"
-}
\ No newline at end of file
+ "defaultMonth": "november-2025"
+}
diff --git a/documentation/src/pages/community/data/november-2025.json b/documentation/src/pages/community/data/november-2025.json
new file mode 100644
index 0000000000..c432b4aafb
--- /dev/null
+++ b/documentation/src/pages/community/data/november-2025.json
@@ -0,0 +1,51 @@
+{
+ "month": "November 2025",
+ "communityStars": [
+ {
+ "name": "Sterling Chapman",
+ "handle": "LivSterling"
+ },
+ {
+ "name": "Sheikh Limon",
+ "handle": "sheikhlimon"
+ },
+ {
+ "name": "Oleg Ε elajev",
+ "handle": "shelajev"
+ },
+ {
+ "name": "Dawn C. Hayes",
+ "handle": "professordnyc"
+ },
+ {
+ "name": "Smyekh David-West",
+ "handle": "Smyekh"
+ }
+ ],
+ "teamStars": [],
+ "leaderboard": [
+ { "handle": "LivSterling", "rank": 1, "medal": "π₯" },
+ { "handle": "sheikhlimon", "rank": 2, "medal": "π₯" },
+ { "handle": "shelajev", "rank": 3, "medal": "π₯" },
+ { "handle": "The-Best-Codes", "rank": 4 },
+ { "handle": "professordnyc", "rank": 5 },
+ { "handle": "Smyekh", "rank": 6 },
+ { "handle": "hammonr1", "rank": 7 },
+ { "handle": "digitalfl0wer", "rank": 8 },
+ { "handle": "irksomeisaac", "rank": 9 },
+ { "handle": "DannyGarciaDEV", "rank": 10 },
+ { "handle": "QAInsights", "rank": 11 },
+ { "handle": "godswillumukoro", "rank": 12 },
+ { "handle": "dvelm", "rank": 13 },
+ { "handle": "pillowexpress", "rank": 14 },
+ { "handle": "dikondaashish", "rank": 15 },
+ { "handle": "devchicajas", "rank": 16 },
+ { "handle": "catbilyeu", "rank": 17 },
+ { "handle": "CodingWCal", "rank": 18 },
+ { "handle": "avishwanathan88", "rank": 19 },
+ { "handle": "gowthamraviteja", "rank": 20 },
+ { "handle": "vaibhavgeek", "rank": 21 },
+ { "handle": "AmeenCodes01", "rank": 22 },
+ { "handle": "ArjunFrancis", "rank": 23 }
+ ]
+}
diff --git a/documentation/src/pages/community/index.tsx b/documentation/src/pages/community/index.tsx
index e063034bab..f19f7de1f8 100644
--- a/documentation/src/pages/community/index.tsx
+++ b/documentation/src/pages/community/index.tsx
@@ -13,6 +13,7 @@ import july2025Data from "./data/july-2025.json";
import august2025Data from "./data/august-2025.json";
import september2025Data from "./data/september-2025.json";
import october2025Data from "./data/october-2025.json";
+import november2025Data from "./data/november-2025.json";
import communityContentData from "./data/community-content.json";
// Create a data map for easy access
@@ -24,6 +25,7 @@ const communityDataMap = {
"august-2025": august2025Data,
"september-2025": september2025Data,
"october-2025": october2025Data,
+ "november-2025": november2025Data,
};
function UpcomingEventsSection() {
@@ -94,7 +96,7 @@ function CommunityAllStarsSection() {
-
+
{currentData.communityStars.map((contributor, index) => (
))}
@@ -110,7 +112,7 @@ function CommunityAllStarsSection() {
-
+
{currentData.teamStars.map((contributor, index) => (
))}
@@ -411,9 +413,9 @@ function ContentCard({ content }): ReactNode {
export function StarsCard({contributor}): ReactNode {
return (
-
+
@@ -467,4 +469,4 @@ export default function Community(): ReactNode {
);
-}
\ No newline at end of file
+}
diff --git a/documentation/src/pages/recipes/data/recipes/flutter-pr-code-review.yaml b/documentation/src/pages/recipes/data/recipes/flutter-pr-code-review.yaml
new file mode 100644
index 0000000000..768ada4670
--- /dev/null
+++ b/documentation/src/pages/recipes/data/recipes/flutter-pr-code-review.yaml
@@ -0,0 +1,229 @@
+version: "1.0.0"
+title: "Flutter PR Code Review"
+description: "Automated expert-level Flutter/Dart code review powered by official Flutter AI rules and real-time Context7 documentation. Analyzes PRs for null safety, state management (BLoC, Riverpod, Provider), architecture patterns, performance optimizations, accessibility compliance, and security vulnerabilities. Provides categorized feedback (Critical/Warning/Suggestion) with file:line references and an actionable summary with approval recommendations. Requires Context7 MCP extension with CONTEXT7_API_KEY environment variable."
+author:
+ contact: "valerii@rimthan.com"
+
+instructions: |
+ You are an expert Flutter/Dart code reviewer created by Valerii from Rimthan.
+
+ YOUR IDENTITY:
+ - Senior Flutter developer with 10+ years of mobile development experience
+ - Expert in Dart, Flutter framework, state management, and mobile architecture
+ - Specializes in banking and fintech applications
+ - Uses Context7 MCP to access latest documentation for ALL libraries
+
+ CRITICAL RULES - READ-ONLY MODE:
+ - DO NOT create, modify, or delete any files
+ - DO NOT run any git commands that modify the repository
+ - ONLY read and analyze the code
+ - ONLY provide review feedback as text output
+
+ βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+ STEP 1 - FETCH DOCUMENTATION (ALWAYS DO THIS FIRST!)
+ βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+ 1. Download and read official Flutter AI rules:
+ curl -sL "https://raw.githubusercontent.com/flutter/flutter/refs/heads/main/docs/rules/rules.md"
+
+ 2. Use Context7 MCP to get fresh documentation:
+ - First call: resolve-library-id for "flutter"
+ - Then call: get-library-docs for /flutter/flutter
+ - First call: resolve-library-id for "dart"
+ - Then call: get-library-docs for /dart-lang/sdk
+
+ 3. Analyze pubspec.yaml to find all dependencies:
+ - Read pubspec.yaml
+ - For each major dependency, use Context7 to fetch docs
+
+ Common Flutter packages Context7 mappings:
+ - bloc, flutter_bloc β /felangel/bloc
+ - riverpod, flutter_riverpod β /rrousselgit/riverpod
+ - provider β /rrousselgit/provider
+ - dio β /cfug/dio
+ - get_it β /fluttercommunity/get_it
+ - freezed β /rrousselgit/freezed
+ - go_router β /flutter/packages
+ - hive β /isar/hive
+ - auto_route β /Milad-Akarie/auto_route_library
+ - injectable β /Milad-Akarie/injectable
+ - dartz β /spebbe/dartz
+ - equatable β /felangel/equatable
+ - json_serializable β /google/json_serializable
+
+ βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+ STEP 2 - ANALYZE CHANGES
+ βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+ Run: git diff origin/main...HEAD
+
+ Or if reviewing specific files, read each changed file.
+
+ βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+ STEP 3 - REVIEW CODE (based on Flutter AI rules + Context7 docs)
+ βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+ DART BEST PRACTICES (from official Flutter rules):
+ - Follow Effective Dart guidelines (https://dart.dev/effective-dart)
+ - Proper null safety - avoid ! unless value is guaranteed non-null
+ - Use async/await correctly with robust error handling
+ - Pattern matching and records where they simplify code
+ - Exhaustive switch statements (no break needed)
+ - Arrow syntax for simple one-line functions
+ - Use try-catch with appropriate exception types
+ - PascalCase for classes, camelCase for members, snake_case for files
+ - Line length 80 characters or fewer
+ - Functions under 20 lines with single purpose
+
+ FLUTTER BEST PRACTICES (from official Flutter rules):
+ - Widgets (especially StatelessWidget) are immutable
+ - Composition over inheritance - compose smaller widgets
+ - Use const constructors whenever possible to reduce rebuilds
+ - Break down large build() methods into smaller private Widget classes
+ - Use small, private Widget classes instead of helper methods returning Widget
+ - Use ListView.builder or SliverList for long lists (lazy loading)
+ - Use compute() for expensive calculations in separate isolate
+ - Avoid expensive operations (network, complex computations) in build() methods
+ - Use logging package instead of print
+
+ STATE MANAGEMENT (verify against Context7 docs):
+ - Prefer Flutter built-in: ValueNotifier, ChangeNotifier, Streams
+ - If using BLoC/Cubit - verify proper event/state separation
+ - If using Riverpod - verify proper provider usage and disposal
+ - If using Provider - verify ChangeNotifier usage
+ - Separate ephemeral state from app state
+ - Proper dispose/close of controllers and streams
+ - Use MVVM pattern for robust solutions
+
+ ARCHITECTURE (from official Flutter rules):
+ - Separation of concerns (MVC/MVVM)
+ - Logical layers: Presentation, Domain, Data, Core
+ - Feature-based organization for larger projects
+ - Repository pattern for data abstraction
+ - Manual constructor dependency injection
+
+ CODE QUALITY:
+ - Meaningful, consistent naming (no abbreviations)
+ - Documentation comments (///) for all public APIs
+ - Clear comments for complex/non-obvious code
+ - Don't repeat information obvious from code context
+ - API documentation should be user-centric
+
+ UI/THEMING (from official Flutter rules):
+ - Centralized ThemeData object
+ - Light and dark theme support (ThemeMode.light, .dark, .system)
+ - Use ColorScheme.fromSeed() for harmonious color palettes
+ - Responsive layouts with LayoutBuilder or MediaQuery
+ - Use Theme.of(context).textTheme for text styles
+ - Custom fonts via google_fonts package
+ - Network images: always include loadingBuilder and errorBuilder
+
+ ACCESSIBILITY (from official Flutter rules):
+ - Color contrast ratio at least 4.5:1 for text
+ - Test with increased system font size
+ - Use Semantics widget for clear labels
+ - Test with TalkBack (Android) and VoiceOver (iOS)
+
+ LIBRARY-SPECIFIC CHECKS:
+ - Verify correct API usage based on Context7 documentation
+ - Check for deprecated methods or patterns
+ - Ensure best practices for each library are followed
+ - Check version compatibility
+
+ SECURITY:
+ - Sensitive data handling
+ - API key exposure check (no hardcoded keys)
+ - Secure storage usage (flutter_secure_storage)
+ - Input validation
+ - HTTPS for network requests
+
+ TESTING (from official Flutter rules):
+ - Unit tests for domain logic, data layer, state management
+ - Widget tests for UI components
+ - Integration tests for end-to-end flows
+ - Arrange-Act-Assert (Given-When-Then) pattern
+ - Prefer fakes/stubs over mocks
+ - Use package:checks for readable assertions
+
+ βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+ OUTPUT FORMAT
+ βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+ Provide specific feedback with file:line references.
+
+ Categorize issues as:
+ - π΄ CRITICAL: Must fix before merge (bugs, security issues, crashes)
+ - π‘ WARNING: Should fix (performance, bad practices, violates Flutter rules)
+ - π’ SUGGESTION: Nice to have (style, minor improvements)
+ - β
GOOD: Positive aspects worth noting
+
+ βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+ SUMMARY (at the end)
+ βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+ - Overall code quality score (1-10)
+ - Libraries/frameworks detected and reviewed (with Context7)
+ - Top 3 issues to address
+ - Positive aspects of the code
+ - Recommendation: APPROVE / REQUEST_CHANGES / NEEDS_DISCUSSION
+
+prompt: |
+ Review the code in this Flutter repository.
+
+ IMPORTANT: Use shell commands (via developer extension) to read files. Do NOT use filesystem extension.
+
+ STEPS:
+
+ 1. FETCH FLUTTER RULES (run shell command):
+ ```bash
+ curl -sL "https://raw.githubusercontent.com/flutter/flutter/refs/heads/main/docs/rules/rules.md"
+ ```
+
+ 2. FETCH CONTEXT7 DOCS:
+ Use Context7 MCP tools to get documentation for Flutter and Dart:
+ - Call resolve-library-id with query "flutter"
+ - Call get-library-docs for the Flutter library
+ - Call resolve-library-id with query "dart"
+ - Call get-library-docs for Dart
+
+ 3. ANALYZE DEPENDENCIES (run shell commands):
+ ```bash
+ cat pubspec.yaml
+ ```
+ For each major dependency (bloc, riverpod, dio, etc.), fetch Context7 docs.
+
+ 4. GET DIFF (run shell command):
+ ```bash
+ git diff origin/main...HEAD
+ ```
+
+ 5. REVIEW each changed file against:
+ - Official Flutter AI rules
+ - Context7 documentation for each library used
+
+ Remember: READ-ONLY mode - do not modify any files.
+
+extensions:
+ - type: builtin
+ name: developer
+ timeout: 300
+ - type: stdio
+ name: context7
+ cmd: npx
+ args:
+ - "-y"
+ - "@upstash/context7-mcp"
+ timeout: 300
+ description: "Context7 MCP for up-to-date Flutter/Dart and library documentation"
+ env_keys:
+ - CONTEXT7_API_KEY
+
+activities:
+ - "Fetch Flutter AI rules and Context7 docs"
+ - "Analyze dependencies from pubspec.yaml"
+ - "Review Flutter widget patterns"
+ - "Check Dart null safety"
+ - "Analyze state management"
+ - "Verify library API usage"
+ - "Identify performance issues"
+ - "Check security concerns"
diff --git a/scripts/check-no-native-tls.sh b/scripts/check-no-native-tls.sh
new file mode 100755
index 0000000000..c0a337fbd2
--- /dev/null
+++ b/scripts/check-no-native-tls.sh
@@ -0,0 +1,28 @@
+#!/bin/bash
+# Prevent native-tls/OpenSSL from being added to the dependency tree.
+# These cause Linux compatibility issues with OpenSSL version mismatches.
+# See: https://github.com/block/goose/issues/6034
+
+set -e
+
+BANNED_CRATES=("native-tls" "openssl-sys" "openssl")
+FOUND_BANNED=0
+
+for crate in "${BANNED_CRATES[@]}"; do
+ if cargo tree -i "$crate" 2>/dev/null | grep -q "$crate"; then
+ echo "ERROR: Found banned crate '$crate' in dependency tree"
+ echo "This causes Linux compatibility issues with OpenSSL versions."
+ echo "Use rustls-based alternatives instead (e.g., rustls-tls-native-roots)."
+ echo ""
+ echo "Dependency chain:"
+ cargo tree -i "$crate"
+ echo ""
+ FOUND_BANNED=1
+ fi
+done
+
+if [ $FOUND_BANNED -eq 1 ]; then
+ exit 1
+fi
+
+echo "β No banned TLS crates found (native-tls, openssl, openssl-sys)"
diff --git a/scripts/clippy-lint.sh b/scripts/clippy-lint.sh
index 0befc5b5a4..6414c94294 100755
--- a/scripts/clippy-lint.sh
+++ b/scripts/clippy-lint.sh
@@ -36,4 +36,7 @@ run_clippy
echo ""
check_all_baseline_rules
echo ""
+echo "π Checking for banned TLS crates..."
+"$SCRIPT_DIR/check-no-native-tls.sh"
+echo ""
echo "β
Done"
diff --git a/ui/desktop/openapi.json b/ui/desktop/openapi.json
index e9bff3ae1d..03c3c2553d 100644
--- a/ui/desktop/openapi.json
+++ b/ui/desktop/openapi.json
@@ -10,7 +10,7 @@
"license": {
"name": "Apache-2.0"
},
- "version": "1.15.0"
+ "version": "1.16.0"
},
"paths": {
"/action-required/tool-confirmation": {
@@ -86,6 +86,90 @@
}
}
},
+ "/agent/call_tool": {
+ "post": {
+ "tags": [
+ "super::routes::agent"
+ ],
+ "operationId": "call_tool",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CallToolRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "Resource read successfully",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CallToolResponse"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized - invalid secret key"
+ },
+ "404": {
+ "description": "Resource not found"
+ },
+ "424": {
+ "description": "Agent not initialized"
+ },
+ "500": {
+ "description": "Internal server error"
+ }
+ }
+ }
+ },
+ "/agent/read_resource": {
+ "post": {
+ "tags": [
+ "super::routes::agent"
+ ],
+ "operationId": "read_resource",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ReadResourceRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "Resource read successfully",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ReadResourceResponse"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized - invalid secret key"
+ },
+ "404": {
+ "description": "Resource not found"
+ },
+ "424": {
+ "description": "Agent not initialized"
+ },
+ "500": {
+ "description": "Internal server error"
+ }
+ }
+ }
+ },
"/agent/remove_extension": {
"post": {
"tags": [
@@ -2537,6 +2621,44 @@
}
}
},
+ "CallToolRequest": {
+ "type": "object",
+ "required": [
+ "session_id",
+ "name",
+ "arguments"
+ ],
+ "properties": {
+ "arguments": {},
+ "name": {
+ "type": "string"
+ },
+ "session_id": {
+ "type": "string"
+ }
+ }
+ },
+ "CallToolResponse": {
+ "type": "object",
+ "required": [
+ "content",
+ "is_error"
+ ],
+ "properties": {
+ "content": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Content"
+ }
+ },
+ "is_error": {
+ "type": "boolean"
+ },
+ "structured_content": {
+ "nullable": true
+ }
+ }
+ },
"ChatRequest": {
"type": "object",
"required": [
@@ -4286,6 +4408,36 @@
}
}
},
+ "ReadResourceRequest": {
+ "type": "object",
+ "required": [
+ "session_id",
+ "extension_name",
+ "uri"
+ ],
+ "properties": {
+ "extension_name": {
+ "type": "string"
+ },
+ "session_id": {
+ "type": "string"
+ },
+ "uri": {
+ "type": "string"
+ }
+ }
+ },
+ "ReadResourceResponse": {
+ "type": "object",
+ "required": [
+ "html"
+ ],
+ "properties": {
+ "html": {
+ "type": "string"
+ }
+ }
+ },
"Recipe": {
"type": "object",
"required": [
diff --git a/ui/desktop/package-lock.json b/ui/desktop/package-lock.json
index 0f538d9868..7317d4c01b 100644
--- a/ui/desktop/package-lock.json
+++ b/ui/desktop/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "goose-app",
- "version": "1.15.0",
+ "version": "1.16.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "goose-app",
- "version": "1.15.0",
+ "version": "1.16.0",
"license": "Apache-2.0",
"dependencies": {
"@ai-sdk/openai": "^2.0.76",
diff --git a/ui/desktop/package.json b/ui/desktop/package.json
index 660e27c31d..065da8381d 100644
--- a/ui/desktop/package.json
+++ b/ui/desktop/package.json
@@ -1,7 +1,7 @@
{
"name": "goose-app",
"productName": "Goose",
- "version": "1.15.0",
+ "version": "1.16.0",
"description": "Goose App",
"engines": {
"node": "^22.17.1"
diff --git a/ui/desktop/src/App.tsx b/ui/desktop/src/App.tsx
index 414b9943c6..13ca9aaef7 100644
--- a/ui/desktop/src/App.tsx
+++ b/ui/desktop/src/App.tsx
@@ -14,6 +14,7 @@ import { ErrorUI } from './components/ErrorBoundary';
import { ExtensionInstallModal } from './components/ExtensionInstallModal';
import { ToastContainer } from 'react-toastify';
import AnnouncementModal from './components/AnnouncementModal';
+import TelemetryOptOutModal from './components/TelemetryOptOutModal';
import ProviderGuard from './components/ProviderGuard';
import { createSession } from './sessions';
@@ -702,6 +703,7 @@ export default function App() {
+
);
}
diff --git a/ui/desktop/src/api/sdk.gen.ts b/ui/desktop/src/api/sdk.gen.ts
index 8eea0f9ec7..d7394952c9 100644
--- a/ui/desktop/src/api/sdk.gen.ts
+++ b/ui/desktop/src/api/sdk.gen.ts
@@ -2,7 +2,7 @@
import type { Client, Options as Options2, TDataShape } from './client';
import { client } from './client.gen';
-import type { AddExtensionData, AddExtensionErrors, AddExtensionResponses, AgentAddExtensionData, AgentAddExtensionErrors, AgentAddExtensionResponses, AgentRemoveExtensionData, AgentRemoveExtensionErrors, AgentRemoveExtensionResponses, BackupConfigData, BackupConfigErrors, BackupConfigResponses, CheckProviderData, ConfirmToolActionData, ConfirmToolActionErrors, ConfirmToolActionResponses, CreateCustomProviderData, CreateCustomProviderErrors, CreateCustomProviderResponses, CreateRecipeData, CreateRecipeErrors, CreateRecipeResponses, CreateScheduleData, CreateScheduleErrors, CreateScheduleResponses, DecodeRecipeData, DecodeRecipeErrors, DecodeRecipeResponses, DeleteRecipeData, DeleteRecipeErrors, DeleteRecipeResponses, DeleteScheduleData, DeleteScheduleErrors, DeleteScheduleResponses, DeleteSessionData, DeleteSessionErrors, DeleteSessionResponses, DetectProviderData, DetectProviderErrors, DetectProviderResponses, DiagnosticsData, DiagnosticsErrors, DiagnosticsResponses, EditMessageData, EditMessageErrors, EditMessageResponses, EncodeRecipeData, EncodeRecipeErrors, EncodeRecipeResponses, ExportSessionData, ExportSessionErrors, ExportSessionResponses, GetCustomProviderData, GetCustomProviderErrors, GetCustomProviderResponses, GetExtensionsData, GetExtensionsErrors, GetExtensionsResponses, GetProviderModelsData, GetProviderModelsErrors, GetProviderModelsResponses, GetSessionData, GetSessionErrors, GetSessionInsightsData, GetSessionInsightsErrors, GetSessionInsightsResponses, GetSessionResponses, GetSlashCommandsData, GetSlashCommandsResponses, GetToolsData, GetToolsErrors, GetToolsResponses, GetTunnelStatusData, GetTunnelStatusResponses, ImportSessionData, ImportSessionErrors, ImportSessionResponses, InitConfigData, InitConfigErrors, InitConfigResponses, InspectRunningJobData, InspectRunningJobErrors, InspectRunningJobResponses, KillRunningJobData, KillRunningJobResponses, ListRecipesData, ListRecipesErrors, ListRecipesResponses, ListSchedulesData, ListSchedulesErrors, ListSchedulesResponses, ListSessionsData, ListSessionsErrors, ListSessionsResponses, McpUiProxyData, McpUiProxyErrors, McpUiProxyResponses, ParseRecipeData, ParseRecipeErrors, ParseRecipeResponses, PauseScheduleData, PauseScheduleErrors, PauseScheduleResponses, ProvidersData, ProvidersResponses, ReadAllConfigData, ReadAllConfigResponses, ReadConfigData, ReadConfigErrors, ReadConfigResponses, RecoverConfigData, RecoverConfigErrors, RecoverConfigResponses, RemoveConfigData, RemoveConfigErrors, RemoveConfigResponses, RemoveCustomProviderData, RemoveCustomProviderErrors, RemoveCustomProviderResponses, RemoveExtensionData, RemoveExtensionErrors, RemoveExtensionResponses, ReplyData, ReplyErrors, ReplyResponses, ResumeAgentData, ResumeAgentErrors, ResumeAgentResponses, RunNowHandlerData, RunNowHandlerErrors, RunNowHandlerResponses, SaveRecipeData, SaveRecipeErrors, SaveRecipeResponses, ScanRecipeData, ScanRecipeResponses, ScheduleRecipeData, ScheduleRecipeErrors, ScheduleRecipeResponses, SessionsHandlerData, SessionsHandlerErrors, SessionsHandlerResponses, SetConfigProviderData, SetRecipeSlashCommandData, SetRecipeSlashCommandErrors, SetRecipeSlashCommandResponses, StartAgentData, StartAgentErrors, StartAgentResponses, StartOpenrouterSetupData, StartOpenrouterSetupResponses, StartTetrateSetupData, StartTetrateSetupResponses, StartTunnelData, StartTunnelErrors, StartTunnelResponses, StatusData, StatusResponses, StopTunnelData, StopTunnelErrors, StopTunnelResponses, UnpauseScheduleData, UnpauseScheduleErrors, UnpauseScheduleResponses, UpdateAgentProviderData, UpdateAgentProviderErrors, UpdateAgentProviderResponses, UpdateCustomProviderData, UpdateCustomProviderErrors, UpdateCustomProviderResponses, UpdateFromSessionData, UpdateFromSessionErrors, UpdateFromSessionResponses, UpdateRouterToolSelectorData, UpdateRouterToolSelectorErrors, UpdateRouterToolSelectorResponses, UpdateScheduleData, UpdateScheduleErrors, UpdateScheduleResponses, UpdateSessionNameData, UpdateSessionNameErrors, UpdateSessionNameResponses, UpdateSessionUserRecipeValuesData, UpdateSessionUserRecipeValuesErrors, UpdateSessionUserRecipeValuesResponses, UpsertConfigData, UpsertConfigErrors, UpsertConfigResponses, UpsertPermissionsData, UpsertPermissionsErrors, UpsertPermissionsResponses, ValidateConfigData, ValidateConfigErrors, ValidateConfigResponses } from './types.gen';
+import type { AddExtensionData, AddExtensionErrors, AddExtensionResponses, AgentAddExtensionData, AgentAddExtensionErrors, AgentAddExtensionResponses, AgentRemoveExtensionData, AgentRemoveExtensionErrors, AgentRemoveExtensionResponses, BackupConfigData, BackupConfigErrors, BackupConfigResponses, CallToolData, CallToolErrors, CallToolResponses, CheckProviderData, ConfirmToolActionData, ConfirmToolActionErrors, ConfirmToolActionResponses, CreateCustomProviderData, CreateCustomProviderErrors, CreateCustomProviderResponses, CreateRecipeData, CreateRecipeErrors, CreateRecipeResponses, CreateScheduleData, CreateScheduleErrors, CreateScheduleResponses, DecodeRecipeData, DecodeRecipeErrors, DecodeRecipeResponses, DeleteRecipeData, DeleteRecipeErrors, DeleteRecipeResponses, DeleteScheduleData, DeleteScheduleErrors, DeleteScheduleResponses, DeleteSessionData, DeleteSessionErrors, DeleteSessionResponses, DetectProviderData, DetectProviderErrors, DetectProviderResponses, DiagnosticsData, DiagnosticsErrors, DiagnosticsResponses, EditMessageData, EditMessageErrors, EditMessageResponses, EncodeRecipeData, EncodeRecipeErrors, EncodeRecipeResponses, ExportSessionData, ExportSessionErrors, ExportSessionResponses, GetCustomProviderData, GetCustomProviderErrors, GetCustomProviderResponses, GetExtensionsData, GetExtensionsErrors, GetExtensionsResponses, GetProviderModelsData, GetProviderModelsErrors, GetProviderModelsResponses, GetSessionData, GetSessionErrors, GetSessionInsightsData, GetSessionInsightsErrors, GetSessionInsightsResponses, GetSessionResponses, GetSlashCommandsData, GetSlashCommandsResponses, GetToolsData, GetToolsErrors, GetToolsResponses, GetTunnelStatusData, GetTunnelStatusResponses, ImportSessionData, ImportSessionErrors, ImportSessionResponses, InitConfigData, InitConfigErrors, InitConfigResponses, InspectRunningJobData, InspectRunningJobErrors, InspectRunningJobResponses, KillRunningJobData, KillRunningJobResponses, ListRecipesData, ListRecipesErrors, ListRecipesResponses, ListSchedulesData, ListSchedulesErrors, ListSchedulesResponses, ListSessionsData, ListSessionsErrors, ListSessionsResponses, McpUiProxyData, McpUiProxyErrors, McpUiProxyResponses, ParseRecipeData, ParseRecipeErrors, ParseRecipeResponses, PauseScheduleData, PauseScheduleErrors, PauseScheduleResponses, ProvidersData, ProvidersResponses, ReadAllConfigData, ReadAllConfigResponses, ReadConfigData, ReadConfigErrors, ReadConfigResponses, ReadResourceData, ReadResourceErrors, ReadResourceResponses, RecoverConfigData, RecoverConfigErrors, RecoverConfigResponses, RemoveConfigData, RemoveConfigErrors, RemoveConfigResponses, RemoveCustomProviderData, RemoveCustomProviderErrors, RemoveCustomProviderResponses, RemoveExtensionData, RemoveExtensionErrors, RemoveExtensionResponses, ReplyData, ReplyErrors, ReplyResponses, ResumeAgentData, ResumeAgentErrors, ResumeAgentResponses, RunNowHandlerData, RunNowHandlerErrors, RunNowHandlerResponses, SaveRecipeData, SaveRecipeErrors, SaveRecipeResponses, ScanRecipeData, ScanRecipeResponses, ScheduleRecipeData, ScheduleRecipeErrors, ScheduleRecipeResponses, SessionsHandlerData, SessionsHandlerErrors, SessionsHandlerResponses, SetConfigProviderData, SetRecipeSlashCommandData, SetRecipeSlashCommandErrors, SetRecipeSlashCommandResponses, StartAgentData, StartAgentErrors, StartAgentResponses, StartOpenrouterSetupData, StartOpenrouterSetupResponses, StartTetrateSetupData, StartTetrateSetupResponses, StartTunnelData, StartTunnelErrors, StartTunnelResponses, StatusData, StatusResponses, StopTunnelData, StopTunnelErrors, StopTunnelResponses, UnpauseScheduleData, UnpauseScheduleErrors, UnpauseScheduleResponses, UpdateAgentProviderData, UpdateAgentProviderErrors, UpdateAgentProviderResponses, UpdateCustomProviderData, UpdateCustomProviderErrors, UpdateCustomProviderResponses, UpdateFromSessionData, UpdateFromSessionErrors, UpdateFromSessionResponses, UpdateRouterToolSelectorData, UpdateRouterToolSelectorErrors, UpdateRouterToolSelectorResponses, UpdateScheduleData, UpdateScheduleErrors, UpdateScheduleResponses, UpdateSessionNameData, UpdateSessionNameErrors, UpdateSessionNameResponses, UpdateSessionUserRecipeValuesData, UpdateSessionUserRecipeValuesErrors, UpdateSessionUserRecipeValuesResponses, UpsertConfigData, UpsertConfigErrors, UpsertConfigResponses, UpsertPermissionsData, UpsertPermissionsErrors, UpsertPermissionsResponses, ValidateConfigData, ValidateConfigErrors, ValidateConfigResponses } from './types.gen';
export type Options
= Options2 & {
/**
@@ -36,6 +36,24 @@ export const agentAddExtension = (options:
}
});
+export const callTool = (options: Options) => (options.client ?? client).post({
+ url: '/agent/call_tool',
+ ...options,
+ headers: {
+ 'Content-Type': 'application/json',
+ ...options.headers
+ }
+});
+
+export const readResource = (options: Options) => (options.client ?? client).post({
+ url: '/agent/read_resource',
+ ...options,
+ headers: {
+ 'Content-Type': 'application/json',
+ ...options.headers
+ }
+});
+
export const agentRemoveExtension = (options: Options) => (options.client ?? client).post({
url: '/agent/remove_extension',
...options,
diff --git a/ui/desktop/src/api/types.gen.ts b/ui/desktop/src/api/types.gen.ts
index b064063699..81b9987e32 100644
--- a/ui/desktop/src/api/types.gen.ts
+++ b/ui/desktop/src/api/types.gen.ts
@@ -46,6 +46,18 @@ export type AuthorRequest = {
metadata?: string | null;
};
+export type CallToolRequest = {
+ arguments: unknown;
+ name: string;
+ session_id: string;
+};
+
+export type CallToolResponse = {
+ content: Array;
+ is_error: boolean;
+ structured_content?: unknown;
+};
+
export type ChatRequest = {
messages: Array;
recipe_name?: string | null;
@@ -603,6 +615,16 @@ export type RawTextContent = {
text: string;
};
+export type ReadResourceRequest = {
+ extension_name: string;
+ session_id: string;
+ uri: string;
+};
+
+export type ReadResourceResponse = {
+ html: string;
+};
+
export type Recipe = {
activities?: Array | null;
author?: Author | null;
@@ -1078,6 +1100,76 @@ export type AgentAddExtensionResponses = {
export type AgentAddExtensionResponse = AgentAddExtensionResponses[keyof AgentAddExtensionResponses];
+export type CallToolData = {
+ body: CallToolRequest;
+ path?: never;
+ query?: never;
+ url: '/agent/call_tool';
+};
+
+export type CallToolErrors = {
+ /**
+ * Unauthorized - invalid secret key
+ */
+ 401: unknown;
+ /**
+ * Resource not found
+ */
+ 404: unknown;
+ /**
+ * Agent not initialized
+ */
+ 424: unknown;
+ /**
+ * Internal server error
+ */
+ 500: unknown;
+};
+
+export type CallToolResponses = {
+ /**
+ * Resource read successfully
+ */
+ 200: CallToolResponse;
+};
+
+export type CallToolResponse2 = CallToolResponses[keyof CallToolResponses];
+
+export type ReadResourceData = {
+ body: ReadResourceRequest;
+ path?: never;
+ query?: never;
+ url: '/agent/read_resource';
+};
+
+export type ReadResourceErrors = {
+ /**
+ * Unauthorized - invalid secret key
+ */
+ 401: unknown;
+ /**
+ * Resource not found
+ */
+ 404: unknown;
+ /**
+ * Agent not initialized
+ */
+ 424: unknown;
+ /**
+ * Internal server error
+ */
+ 500: unknown;
+};
+
+export type ReadResourceResponses = {
+ /**
+ * Resource read successfully
+ */
+ 200: ReadResourceResponse;
+};
+
+export type ReadResourceResponse2 = ReadResourceResponses[keyof ReadResourceResponses];
+
export type AgentRemoveExtensionData = {
body: RemoveExtensionRequest;
path?: never;
diff --git a/ui/desktop/src/components/ChatInput.tsx b/ui/desktop/src/components/ChatInput.tsx
index ecf8fe1170..776a54da58 100644
--- a/ui/desktop/src/components/ChatInput.tsx
+++ b/ui/desktop/src/components/ChatInput.tsx
@@ -816,8 +816,22 @@ export default function ChatInput({
// Helper function to handle interruption and queue logic when loading
const handleInterruptionAndQueue = () => {
- if (!isLoading || !displayValue.trim()) {
- return false; // Return false if no action was taken
+ if (!isLoading || !hasSubmittableContent) {
+ return false;
+ }
+
+ const validPastedImageFilesPaths = pastedImages
+ .filter((img) => img.filePath && !img.error && !img.isLoading)
+ .map((img) => img.filePath as string);
+ const droppedFilePaths = allDroppedFiles
+ .filter((file) => !file.error && !file.isLoading)
+ .map((file) => file.path);
+
+ let contentToQueue = displayValue.trim();
+ const allFilePaths = [...validPastedImageFilesPaths, ...droppedFilePaths];
+ if (allFilePaths.length > 0) {
+ const pathsString = allFilePaths.join(' ');
+ contentToQueue = contentToQueue ? `${contentToQueue} ${pathsString}` : pathsString;
}
const interruptionMatch = detectInterruption(displayValue.trim());
@@ -831,7 +845,7 @@ export default function ChatInput({
// rather than trying to send it immediately while the system is still loading
const interruptionMessage = {
id: Date.now().toString() + Math.random().toString(36).substr(2, 9),
- content: displayValue.trim(),
+ content: contentToQueue,
timestamp: Date.now(),
};
@@ -840,12 +854,19 @@ export default function ChatInput({
setDisplayValue('');
setValue('');
- return true; // Return true if interruption was handled
+ setPastedImages([]);
+ if (onFilesProcessed && droppedFiles.length > 0) {
+ onFilesProcessed();
+ }
+ if (localDroppedFiles.length > 0) {
+ setLocalDroppedFiles([]);
+ }
+ return true;
}
const newMessage = {
id: Date.now().toString() + Math.random().toString(36).substr(2, 9),
- content: displayValue.trim(),
+ content: contentToQueue,
timestamp: Date.now(),
};
setQueuedMessages((prev) => {
@@ -859,7 +880,14 @@ export default function ChatInput({
});
setDisplayValue('');
setValue('');
- return true; // Return true if message was queued
+ setPastedImages([]);
+ if (onFilesProcessed && droppedFiles.length > 0) {
+ onFilesProcessed();
+ }
+ if (localDroppedFiles.length > 0) {
+ setLocalDroppedFiles([]);
+ }
+ return true;
};
const canSubmit =
@@ -1004,6 +1032,10 @@ export default function ChatInput({
const onFormSubmit = (e: React.FormEvent) => {
e.preventDefault();
+ if (isLoading && hasSubmittableContent) {
+ handleInterruptionAndQueue();
+ return;
+ }
const canSubmit =
!isLoading &&
(displayValue.trim() ||
@@ -1272,7 +1304,7 @@ export default function ChatInput({
)}
{/* Send/Stop button */}
- {isLoading ? (
+ {isLoading && !hasSubmittableContent ? (
>
)}
- {sessionId && (
+ {sessionId && messages.length > 0 && (
<>
diff --git a/ui/desktop/src/components/ProviderGuard.tsx b/ui/desktop/src/components/ProviderGuard.tsx
index 76e789d6e1..7daf9cc702 100644
--- a/ui/desktop/src/components/ProviderGuard.tsx
+++ b/ui/desktop/src/components/ProviderGuard.tsx
@@ -1,4 +1,4 @@
-import { useEffect, useState } from 'react';
+import { useEffect, useState, useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import { useConfig } from './ConfigContext';
import { SetupModal } from './SetupModal';
@@ -8,6 +8,9 @@ import WelcomeGooseLogo from './WelcomeGooseLogo';
import { toastService } from '../toasts';
import { OllamaSetup } from './OllamaSetup';
import ApiKeyTester from './ApiKeyTester';
+import { SwitchModelModal } from './settings/models/subcomponents/SwitchModelModal';
+import { createNavigationHandler } from '../utils/navigationUtils';
+import TelemetrySettings from './settings/app/TelemetrySettings';
import { Goose, OpenRouter, Tetrate } from './icons';
@@ -24,6 +27,10 @@ export default function ProviderGuard({ didSelectProvider, children }: ProviderG
const [showFirstTimeSetup, setShowFirstTimeSetup] = useState(false);
const [showOllamaSetup, setShowOllamaSetup] = useState(false);
const [userInActiveSetup, setUserInActiveSetup] = useState(false);
+ const [showSwitchModelModal, setShowSwitchModelModal] = useState(false);
+ const [switchModelProvider, setSwitchModelProvider] = useState(null);
+
+ const setView = useMemo(() => createNavigationHandler(navigate), [navigate]);
const [openRouterSetupState, setOpenRouterSetupState] = useState<{
show: boolean;
@@ -45,18 +52,8 @@ export default function ProviderGuard({ didSelectProvider, children }: ProviderG
try {
const result = await startTetrateSetup();
if (result.success) {
- setTetrateSetupState({
- show: true,
- title: 'Setup Complete!',
- message: result.message,
- showRetry: false,
- autoClose: 3000,
- });
- setTimeout(() => {
- setShowFirstTimeSetup(false);
- setHasProvider(true);
- navigate('/', { replace: true });
- }, 3000);
+ setSwitchModelProvider('tetrate');
+ setShowSwitchModelModal(true);
} else {
setTetrateSetupState({
show: true,
@@ -76,34 +73,33 @@ export default function ProviderGuard({ didSelectProvider, children }: ProviderG
}
};
- const handleApiKeySuccess = async (provider: string, model: string, apiKey: string) => {
+ const handleApiKeySuccess = async (provider: string, _model: string, apiKey: string) => {
const keyName = `${provider.toUpperCase()}_API_KEY`;
await upsert(keyName, apiKey, true);
await upsert('GOOSE_PROVIDER', provider, false);
- await upsert('GOOSE_MODEL', model, false);
+ setSwitchModelProvider(provider);
+ setShowSwitchModelModal(true);
+ };
+
+ const handleModelSelected = () => {
+ setShowSwitchModelModal(false);
setUserInActiveSetup(false);
setShowFirstTimeSetup(false);
setHasProvider(true);
navigate('/', { replace: true });
};
+ const handleSwitchModelClose = () => {
+ setShowSwitchModelModal(false);
+ };
+
const handleOpenRouterSetup = async () => {
try {
const result = await startOpenRouterSetup();
if (result.success) {
- setOpenRouterSetupState({
- show: true,
- title: 'Setup Complete!',
- message: result.message,
- showRetry: false,
- autoClose: 3000,
- });
- setTimeout(() => {
- setShowFirstTimeSetup(false);
- setHasProvider(true);
- navigate('/', { replace: true });
- }, 3000);
+ setSwitchModelProvider('openrouter');
+ setShowSwitchModelModal(true);
} else {
setOpenRouterSetupState({
show: true,
@@ -311,6 +307,9 @@ export default function ProviderGuard({ didSelectProvider, children }: ProviderG
Go to Provider Settings β
+
+
+
@@ -337,6 +336,17 @@ export default function ProviderGuard({ didSelectProvider, children }: ProviderG
autoClose={tetrateSetupState.autoClose}
/>
)}
+
+ {showSwitchModelModal && (
+
+ )}
);
}
diff --git a/ui/desktop/src/components/TelemetryOptOutModal.tsx b/ui/desktop/src/components/TelemetryOptOutModal.tsx
new file mode 100644
index 0000000000..ed7d3b2323
--- /dev/null
+++ b/ui/desktop/src/components/TelemetryOptOutModal.tsx
@@ -0,0 +1,131 @@
+import { useState, useEffect } from 'react';
+import { BaseModal } from './ui/BaseModal';
+import { Button } from './ui/button';
+import { Goose } from './icons/Goose';
+import { TELEMETRY_UI_ENABLED } from '../updates';
+import { toastService } from '../toasts';
+import { useConfig } from './ConfigContext';
+
+const TELEMETRY_CONFIG_KEY = 'GOOSE_TELEMETRY_ENABLED';
+
+type TelemetryOptOutModalProps =
+ | { controlled: false }
+ | { controlled: true; isOpen: boolean; onClose: () => void };
+
+export default function TelemetryOptOutModal(props: TelemetryOptOutModalProps) {
+ const { read, upsert } = useConfig();
+ const isControlled = props.controlled;
+ const controlledIsOpen = isControlled ? props.isOpen : undefined;
+ const onClose = isControlled ? props.onClose : undefined;
+ const [showModal, setShowModal] = useState(false);
+ const [isLoading, setIsLoading] = useState(false);
+
+ // Only check telemetry choice on first launch in uncontrolled mode
+ useEffect(() => {
+ if (isControlled) return;
+
+ const checkTelemetryChoice = async () => {
+ try {
+ const provider = await read('GOOSE_PROVIDER', false);
+
+ if (!provider || provider === '') {
+ return;
+ }
+
+ const telemetryEnabled = await read(TELEMETRY_CONFIG_KEY, false);
+
+ if (telemetryEnabled === null) {
+ setShowModal(true);
+ }
+ } catch (error) {
+ console.error('Failed to check telemetry config:', error);
+ toastService.error({
+ title: 'Configuration Error',
+ msg: 'Failed to check telemetry configuration.',
+ traceback: error instanceof Error ? error.stack || '' : '',
+ });
+ }
+ };
+
+ checkTelemetryChoice();
+ }, [isControlled, read]);
+
+ const handleChoice = async (enabled: boolean) => {
+ setIsLoading(true);
+ try {
+ await upsert(TELEMETRY_CONFIG_KEY, enabled, false);
+ setShowModal(false);
+ onClose?.();
+ } catch (error) {
+ console.error('Failed to set telemetry preference:', error);
+ setShowModal(false);
+ onClose?.();
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ if (!TELEMETRY_UI_ENABLED) {
+ return null;
+ }
+
+ const isModalOpen = controlledIsOpen !== undefined ? controlledIsOpen : showModal;
+
+ if (!isModalOpen) {
+ return null;
+ }
+
+ return (
+
+ handleChoice(true)}
+ disabled={isLoading}
+ className="w-full h-[44px] rounded-lg"
+ >
+ Yes, share anonymous usage data
+
+ handleChoice(false)}
+ disabled={isLoading}
+ className="w-full h-[44px] rounded-lg text-text-muted hover:text-text-default"
+ >
+ No thanks
+
+
+ }
+ >
+
+
+
+
+
+ Help improve goose
+
+
+ Would you like to help improve goose by sharing anonymous usage data? This helps us
+ understand how goose is used and identify areas for improvement.
+
+
+
What we collect:
+
+ Operating system, version, and architecture
+ goose version and install method
+ Provider and model used
+ Extensions and tool usage counts (names only)
+ Session metrics (duration, interaction count, token usage)
+ Error types (e.g., "rate_limit", "auth" - no details)
+
+
+ We never collect your conversations, code, tool arguments, error messages, or any
+ personal data. You can change this setting anytime in Settings β App.
+
+
+
+
+ );
+}
diff --git a/ui/desktop/src/components/icons/Anthropic.tsx b/ui/desktop/src/components/icons/Anthropic.tsx
index 7af92a4172..2d2ca8a284 100644
--- a/ui/desktop/src/components/icons/Anthropic.tsx
+++ b/ui/desktop/src/components/icons/Anthropic.tsx
@@ -9,8 +9,8 @@ export default function Anthropic({ className = '' }) {
aria-hidden="true"
className={className}
>
-
diff --git a/ui/desktop/src/components/icons/Key.tsx b/ui/desktop/src/components/icons/Key.tsx
index 5b7f559f67..e7ff465db9 100644
--- a/ui/desktop/src/components/icons/Key.tsx
+++ b/ui/desktop/src/components/icons/Key.tsx
@@ -22,7 +22,12 @@ export function Key({ className = '' }: KeyProps) {
-
+
diff --git a/ui/desktop/src/components/icons/Tetrate.tsx b/ui/desktop/src/components/icons/Tetrate.tsx
index 011253c937..7a088f2d23 100644
--- a/ui/desktop/src/components/icons/Tetrate.tsx
+++ b/ui/desktop/src/components/icons/Tetrate.tsx
@@ -9,12 +9,12 @@ export default function Tetrate({ className = '' }) {
aria-hidden="true"
className={className}
>
-
-
diff --git a/ui/desktop/src/components/recipes/RecipesView.tsx b/ui/desktop/src/components/recipes/RecipesView.tsx
index d8e62625bc..3c278451df 100644
--- a/ui/desktop/src/components/recipes/RecipesView.tsx
+++ b/ui/desktop/src/components/recipes/RecipesView.tsx
@@ -1,4 +1,4 @@
-import { useState, useEffect } from 'react';
+import { useState, useEffect, useMemo } from 'react';
import { listSavedRecipes, convertToLocaleDateString } from '../../recipe/recipe_management';
import {
FileText,
@@ -32,6 +32,7 @@ import { generateDeepLink, Recipe } from '../../recipe';
import { useNavigation } from '../../hooks/useNavigation';
import { CronPicker } from '../schedule/CronPicker';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '../ui/dialog';
+import { SearchView } from '../conversation/SearchView';
import cronstrue from 'cronstrue';
export default function RecipesView() {
@@ -57,6 +58,26 @@ export default function RecipesView() {
const [slashCommand, setSlashCommand] = useState
('');
const [scheduleValid, setScheduleIsValid] = useState(true);
+ const [searchTerm, setSearchTerm] = useState('');
+
+ const filteredRecipes = useMemo(() => {
+ if (!searchTerm) return savedRecipes;
+
+ const searchLower = searchTerm.toLowerCase();
+ return savedRecipes.filter((recipeManifest) => {
+ const { recipe, slash_command } = recipeManifest;
+ const title = recipe.title?.toLowerCase() || '';
+ const description = recipe.description?.toLowerCase() || '';
+ const slashCmd = slash_command?.toLowerCase() || '';
+
+ return (
+ title.includes(searchLower) ||
+ description.includes(searchLower) ||
+ slashCmd.includes(searchLower)
+ );
+ });
+ }, [savedRecipes, searchTerm]);
+
useEffect(() => {
loadSavedRecipes();
}, []);
@@ -485,9 +506,19 @@ export default function RecipesView() {
);
}
+ if (filteredRecipes.length === 0 && searchTerm) {
+ return (
+
+
+
No matching recipes found
+
Try adjusting your search terms
+
+ );
+ }
+
return (
- {savedRecipes.map((recipeManifestResponse: RecipeManifest) => (
+ {filteredRecipes.map((recipeManifestResponse: RecipeManifest) => (
View and manage your saved recipes to quickly start new sessions with predefined
- configurations.
+ configurations. βF/Ctrl+F to search.
-
- {renderContent()}
-
+ setSearchTerm(term)} placeholder="Search recipes...">
+
+ {renderContent()}
+
+
diff --git a/ui/desktop/src/components/settings/app/AppSettingsSection.tsx b/ui/desktop/src/components/settings/app/AppSettingsSection.tsx
index f5564521d8..c0b82f9e56 100644
--- a/ui/desktop/src/components/settings/app/AppSettingsSection.tsx
+++ b/ui/desktop/src/components/settings/app/AppSettingsSection.tsx
@@ -11,6 +11,7 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../..
import ThemeSelector from '../../GooseSidebar/ThemeSelector';
import BlockLogoBlack from './icons/block-lockup_black.png';
import BlockLogoWhite from './icons/block-lockup_white.png';
+import TelemetrySettings from './TelemetrySettings';
interface AppSettingsSectionProps {
scrollToSection?: string;
@@ -288,7 +289,7 @@ export default function AppSettingsSection({ scrollToSection }: AppSettingsSecti
Prevent Sleep
- Keep your computer awake while Goose is running a task (screen can still lock)
+ Keep your computer awake while goose is running a task (screen can still lock)
@@ -397,6 +398,8 @@ export default function AppSettingsSection({ scrollToSection }: AppSettingsSecti
+
+
Help & feedback
diff --git a/ui/desktop/src/components/settings/app/TelemetrySettings.tsx b/ui/desktop/src/components/settings/app/TelemetrySettings.tsx
new file mode 100644
index 0000000000..6f24a528c9
--- /dev/null
+++ b/ui/desktop/src/components/settings/app/TelemetrySettings.tsx
@@ -0,0 +1,128 @@
+import { useState, useEffect, useCallback } from 'react';
+import { Switch } from '../../ui/switch';
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../../ui/card';
+import { useConfig } from '../../ConfigContext';
+import { TELEMETRY_UI_ENABLED } from '../../../updates';
+import TelemetryOptOutModal from '../../TelemetryOptOutModal';
+import { toastService } from '../../../toasts';
+
+const TELEMETRY_CONFIG_KEY = 'GOOSE_TELEMETRY_ENABLED';
+
+interface TelemetrySettingsProps {
+ isWelcome: boolean;
+}
+
+export default function TelemetrySettings({ isWelcome = false }: TelemetrySettingsProps) {
+ const { read, upsert } = useConfig();
+ const [telemetryEnabled, setTelemetryEnabled] = useState(true);
+ const [isLoading, setIsLoading] = useState(true);
+ const [showModal, setShowModal] = useState(false);
+
+ const loadTelemetryStatus = useCallback(async () => {
+ try {
+ const value = await read(TELEMETRY_CONFIG_KEY, false);
+ setTelemetryEnabled(value === null ? true : Boolean(value));
+ } catch (error) {
+ console.error('Failed to load telemetry status:', error);
+ toastService.error({
+ title: 'Configuration Error',
+ msg: 'Failed to load telemetry settings.',
+ traceback: error instanceof Error ? error.stack || '' : '',
+ });
+ } finally {
+ setIsLoading(false);
+ }
+ }, [read]);
+
+ useEffect(() => {
+ loadTelemetryStatus();
+ }, [loadTelemetryStatus]);
+
+ const handleTelemetryToggle = async (checked: boolean) => {
+ try {
+ await upsert(TELEMETRY_CONFIG_KEY, checked, false);
+ setTelemetryEnabled(checked);
+ } catch (error) {
+ console.error('Failed to update telemetry status:', error);
+ toastService.error({
+ title: 'Configuration Error',
+ msg: 'Failed to update telemetry settings.',
+ traceback: error instanceof Error ? error.stack || '' : '',
+ });
+ }
+ };
+
+ const handleModalClose = () => {
+ setShowModal(false);
+ loadTelemetryStatus();
+ };
+
+ if (!TELEMETRY_UI_ENABLED) {
+ return null;
+ }
+
+ const title = 'Privacy';
+ const description = 'Control how your data is used';
+ const toggleLabel = 'Anonymous usage data';
+ const toggleDescription = 'Help improve goose by sharing anonymous usage statistics.';
+
+ const learnMoreLink = (
+ setShowModal(true)}
+ className="text-blue-600 dark:text-blue-400 hover:underline"
+ >
+ Learn more
+
+ );
+
+ const toggle = (
+
+ );
+
+ const modal = ;
+
+ const toggleRow = (
+
+
+
+ {toggleLabel}
+
+
+ {toggleDescription} {learnMoreLink}
+
+
+
{toggle}
+
+ );
+
+ if (isWelcome) {
+ return (
+ <>
+
+
{title}
+
{description}
+ {toggleRow}
+
+ {modal}
+ >
+ );
+ }
+
+ return (
+ <>
+
+
+ {title}
+ {description}
+
+ {toggleRow}
+
+ {modal}
+ >
+ );
+}
diff --git a/ui/desktop/src/components/settings/models/modelInterface.ts b/ui/desktop/src/components/settings/models/modelInterface.ts
index b3cec2469e..e37729dfbb 100644
--- a/ui/desktop/src/components/settings/models/modelInterface.ts
+++ b/ui/desktop/src/components/settings/models/modelInterface.ts
@@ -47,7 +47,7 @@ export interface ProviderModelsResult {
}
/**
- * Fetches models for all active providers in parallel.
+ * Fetches recommended models for all active providers in parallel.
* Falls back to known_models if fetching fails or returns no models.
*/
export async function fetchModelsForProviders(
diff --git a/ui/desktop/src/components/settings/models/subcomponents/SwitchModelModal.tsx b/ui/desktop/src/components/settings/models/subcomponents/SwitchModelModal.tsx
index abcc270033..849c5ff14d 100644
--- a/ui/desktop/src/components/settings/models/subcomponents/SwitchModelModal.tsx
+++ b/ui/desktop/src/components/settings/models/subcomponents/SwitchModelModal.tsx
@@ -1,5 +1,5 @@
import { useEffect, useState, useCallback } from 'react';
-import { ArrowLeftRight, ExternalLink } from 'lucide-react';
+import { Bot, ExternalLink } from 'lucide-react';
import {
Dialog,
@@ -20,18 +20,66 @@ import Model, { getProviderMetadata, fetchModelsForProviders } from '../modelInt
import { getPredefinedModelsFromEnv, shouldShowPredefinedModels } from '../predefinedModelsUtils';
import { ProviderType } from '../../../../api';
+const PREFERRED_MODEL_PATTERNS = [
+ /claude-sonnet-4/i,
+ /claude-4/i,
+ /gpt-4o(?!-mini)/i,
+ /claude-3-5-sonnet/i,
+ /claude-3\.5-sonnet/i,
+ /gpt-4-turbo/i,
+ /gpt-4(?!-|o)/i,
+ /claude-3-opus/i,
+ /claude-3-sonnet/i,
+ /gemini-pro/i,
+ /llama-3/i,
+ /gpt-4o-mini/i,
+ /claude-3-haiku/i,
+ /gemini/i,
+];
+
+function findPreferredModel(
+ models: { value: string; label: string; provider: string }[]
+): string | null {
+ if (models.length === 0) return null;
+
+ const validModels = models.filter(
+ (m) => m.value !== 'custom' && m.value !== '__loading__' && !m.value.startsWith('__')
+ );
+
+ if (validModels.length === 0) return null;
+
+ for (const pattern of PREFERRED_MODEL_PATTERNS) {
+ const match = validModels.find((m) => pattern.test(m.value));
+ if (match) {
+ return match.value;
+ }
+ }
+
+ return validModels[0].value;
+}
+
type SwitchModelModalProps = {
sessionId: string | null;
onClose: () => void;
setView: (view: View) => void;
+ onModelSelected?: () => void;
+ initialProvider?: string | null;
+ titleOverride?: string;
};
-export const SwitchModelModal = ({ sessionId, onClose, setView }: SwitchModelModalProps) => {
+export const SwitchModelModal = ({
+ sessionId,
+ onClose,
+ setView,
+ onModelSelected,
+ initialProvider,
+ titleOverride,
+}: SwitchModelModalProps) => {
const { getProviders, getProviderModels, read } = useConfig();
const { changeModel } = useModelAndProvider();
const [providerOptions, setProviderOptions] = useState<{ value: string; label: string }[]>([]);
type ModelOption = { value: string; label: string; provider: string; isDisabled?: boolean };
const [modelOptions, setModelOptions] = useState<{ options: ModelOption[] }[]>([]);
- const [provider, setProvider] = useState(null);
+ const [provider, setProvider] = useState(initialProvider || null);
const [model, setModel] = useState('');
const [isCustomModel, setIsCustomModel] = useState(false);
const [validationErrors, setValidationErrors] = useState({
@@ -44,6 +92,7 @@ export const SwitchModelModal = ({ sessionId, onClose, setView }: SwitchModelMod
const [selectedPredefinedModel, setSelectedPredefinedModel] = useState(null);
const [predefinedModels, setPredefinedModels] = useState([]);
const [loadingModels, setLoadingModels] = useState(false);
+ const [userClearedModel, setUserClearedModel] = useState(false);
// Validate form data
const validateForm = useCallback(() => {
@@ -95,6 +144,9 @@ export const SwitchModelModal = ({ sessionId, onClose, setView }: SwitchModelMod
}
await changeModel(sessionId, modelObj);
+ if (onModelSelected) {
+ onModelSelected();
+ }
onClose();
}
};
@@ -145,7 +197,7 @@ export const SwitchModelModal = ({ sessionId, onClose, setView }: SwitchModelMod
setLoadingModels(true);
- // Fetching models for all providers
+ // Fetching models for all providers (always recommended)
const results = await fetchModelsForProviders(activeProviders, getProviderModels);
// Process results and build grouped options
@@ -209,20 +261,42 @@ export const SwitchModelModal = ({ sessionId, onClose, setView }: SwitchModelMod
})();
}, [getProviders, getProviderModels, usePredefinedModels, read]);
- // Filter model options based on selected provider
const filteredModelOptions = provider
? modelOptions.filter((group) => group.options[0]?.provider === provider)
: [];
+ useEffect(() => {
+ // Don't auto-select if user explicitly cleared the model
+ if (!provider || loadingModels || model || isCustomModel || userClearedModel) return;
+
+ const providerModels = modelOptions
+ .filter((group) => group.options[0]?.provider === provider)
+ .flatMap((group) => group.options);
+
+ if (providerModels.length > 0) {
+ const preferredModel = findPreferredModel(providerModels);
+ if (preferredModel) {
+ setModel(preferredModel);
+ }
+ }
+ }, [provider, modelOptions, loadingModels, model, isCustomModel, userClearedModel]);
+
// Handle model selection change
const handleModelChange = (newValue: unknown) => {
const selectedOption = newValue as { value: string; label: string; provider: string } | null;
if (selectedOption?.value === 'custom') {
setIsCustomModel(true);
setModel('');
+ setUserClearedModel(false);
+ } else if (selectedOption === null) {
+ // User cleared the selection
+ setIsCustomModel(false);
+ setModel('');
+ setUserClearedModel(true);
} else {
setIsCustomModel(false);
setModel(selectedOption?.value || '');
+ setUserClearedModel(false);
}
};
@@ -277,30 +351,16 @@ export const SwitchModelModal = ({ sessionId, onClose, setView }: SwitchModelMod
-
- Switch models
+
+ {titleOverride || 'Switch models'}
- Configure your AI model providers by adding their API keys. Your keys are stored
- securely and encrypted locally.
+ Select a provider and model to use for your conversations.
-
-
{usePredefinedModels ? (
- /* Predefined Models Section */
Choose a model:
@@ -377,6 +437,7 @@ export const SwitchModelModal = ({ sessionId, onClose, setView }: SwitchModelMod
setProvider(option?.value || null);
setModel('');
setIsCustomModel(false);
+ setUserClearedModel(false);
}
}}
placeholder="Provider, type to search"
@@ -394,26 +455,19 @@ export const SwitchModelModal = ({ sessionId, onClose, setView }: SwitchModelMod
0
? filteredModelOptions
: []
}
onChange={handleModelChange}
- onInputChange={handleInputChange} // Added for input handling
+ onInputChange={handleInputChange}
value={model ? { value: model, label: model } : null}
- placeholder="Select a model, type to search"
+ placeholder={
+ loadingModels ? 'Loading modelsβ¦' : 'Select a model, type to search'
+ }
+ isClearable
+ isDisabled={loadingModels}
/>
{attemptedSubmit && validationErrors.model && (
@@ -448,13 +502,24 @@ export const SwitchModelModal = ({ sessionId, onClose, setView }: SwitchModelMod
)}
-
-
- Cancel
-
-
- Select model
-
+
+
+
+ Quick start guide
+
+
+
+ Cancel
+
+
+ Select model
+
+
diff --git a/ui/desktop/src/components/settings/providers/ProviderGrid.tsx b/ui/desktop/src/components/settings/providers/ProviderGrid.tsx
index f970e93356..fe5700259f 100644
--- a/ui/desktop/src/components/settings/providers/ProviderGrid.tsx
+++ b/ui/desktop/src/components/settings/providers/ProviderGrid.tsx
@@ -10,6 +10,8 @@ import {
import { Plus } from 'lucide-react';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '../../ui/dialog';
import CustomProviderForm from './modal/subcomponents/forms/CustomProviderForm';
+import { SwitchModelModal } from '../models/subcomponents/SwitchModelModal';
+import type { View } from '../../../utils/navigationUtils';
const GridLayout = memo(function GridLayout({ children }: { children: React.ReactNode }) {
return (
@@ -50,21 +52,30 @@ function ProviderCards({
providers,
isOnboarding,
refreshProviders,
- onProviderLaunch,
+ setView,
+ onModelSelected,
}: {
providers: ProviderDetails[];
isOnboarding: boolean;
refreshProviders?: () => void;
- onProviderLaunch: (provider: ProviderDetails) => void;
+ setView?: (view: View) => void;
+ onModelSelected?: () => void;
}) {
const [configuringProvider, setConfiguringProvider] = useState(null);
const [showCustomProviderModal, setShowCustomProviderModal] = useState(false);
+ const [showSwitchModelModal, setShowSwitchModelModal] = useState(false);
+ const [switchModelProvider, setSwitchModelProvider] = useState(null);
const [editingProvider, setEditingProvider] = useState<{
id: string;
config: DeclarativeProviderConfig;
isEditable: boolean;
} | null>(null);
+ const handleProviderLaunchWithModelSelection = useCallback((provider: ProviderDetails) => {
+ setSwitchModelProvider(provider.name);
+ setShowSwitchModelModal(true);
+ }, []);
+
const openModal = useCallback(
(provider: ProviderDetails) => setConfiguringProvider(provider),
[]
@@ -101,11 +112,14 @@ function ProviderCards({
body: data,
throwOnError: true,
});
+ const providerId = editingProvider.id;
setShowCustomProviderModal(false);
setEditingProvider(null);
if (refreshProviders) {
refreshProviders();
}
+ setSwitchModelProvider(providerId);
+ setShowSwitchModelModal(true);
},
[editingProvider, refreshProviders]
);
@@ -122,6 +136,32 @@ function ProviderCards({
}
}, [refreshProviders]);
+ const onProviderConfigured = useCallback(
+ (provider: ProviderDetails) => {
+ setConfiguringProvider(null);
+ if (refreshProviders) {
+ refreshProviders();
+ }
+ setSwitchModelProvider(provider.name);
+ setShowSwitchModelModal(true);
+ },
+ [refreshProviders]
+ );
+
+ const onCloseSwitchModelModal = useCallback(() => {
+ setShowSwitchModelModal(false);
+ }, []);
+
+ const handleSetView = useCallback(
+ (view: View) => {
+ setShowSwitchModelModal(false);
+ if (setView) {
+ setView(view);
+ }
+ },
+ [setView]
+ );
+
const handleCreateCustomProvider = useCallback(
async (data: UpdateCustomProviderRequest) => {
const { createCustomProvider } = await import('../../../api');
@@ -130,6 +170,7 @@ function ProviderCards({
if (refreshProviders) {
refreshProviders();
}
+ setShowSwitchModelModal(true);
},
[refreshProviders]
);
@@ -144,7 +185,7 @@ function ProviderCards({
key={provider.name}
provider={provider}
onConfigure={() => configureProviderViaModal(provider)}
- onLaunch={() => onProviderLaunch(provider)}
+ onLaunch={() => handleProviderLaunchWithModelSelection(provider)}
isOnboarding={isOnboarding}
/>
));
@@ -154,10 +195,10 @@ function ProviderCards({
);
return cards;
- }, [providers, isOnboarding, configureProviderViaModal, onProviderLaunch]);
+ }, [providers, isOnboarding, configureProviderViaModal, handleProviderLaunchWithModelSelection]);
const initialData = editingProvider && {
- engine: editingProvider.config.engine.toLowerCase() + '_compatible',
+ engine: editingProvider.config.engine.toLowerCase(),
display_name: editingProvider.config.display_name,
api_url: editingProvider.config.base_url,
api_key: '',
@@ -187,6 +228,17 @@ function ProviderCards({
+ )}
+ {showSwitchModelModal && (
+
)}
>
@@ -197,12 +249,14 @@ export default function ProviderGrid({
providers,
isOnboarding,
refreshProviders,
- onProviderLaunch,
+ setView,
+ onModelSelected,
}: {
providers: ProviderDetails[];
isOnboarding: boolean;
refreshProviders?: () => void;
- onProviderLaunch?: (provider: ProviderDetails) => void;
+ setView?: (view: View) => void;
+ onModelSelected?: () => void;
}) {
return (
@@ -210,7 +264,8 @@ export default function ProviderGrid({
providers={providers}
isOnboarding={isOnboarding}
refreshProviders={refreshProviders}
- onProviderLaunch={onProviderLaunch || (() => {})}
+ setView={setView}
+ onModelSelected={onModelSelected}
/>
);
diff --git a/ui/desktop/src/components/settings/providers/ProviderSettingsPage.tsx b/ui/desktop/src/components/settings/providers/ProviderSettingsPage.tsx
index 2110b6f60a..1c745a3fd3 100644
--- a/ui/desktop/src/components/settings/providers/ProviderSettingsPage.tsx
+++ b/ui/desktop/src/components/settings/providers/ProviderSettingsPage.tsx
@@ -1,10 +1,11 @@
-import { useEffect, useState, useCallback, useRef } from 'react';
+import { useEffect, useState, useCallback, useRef, useMemo } from 'react';
+import { useNavigate } from 'react-router-dom';
import { ScrollArea } from '../../ui/scroll-area';
import BackButton from '../../ui/BackButton';
import ProviderGrid from './ProviderGrid';
import { useConfig } from '../../ConfigContext';
-import { ProviderDetails, setConfigProvider } from '../../../api';
-import { toastService } from '../../../toasts';
+import { ProviderDetails } from '../../../api';
+import { createNavigationHandler } from '../../../utils/navigationUtils';
interface ProviderSettingsProps {
onClose: () => void;
@@ -18,10 +19,13 @@ export default function ProviderSettings({
onProviderLaunched,
}: ProviderSettingsProps) {
const { getProviders } = useConfig();
+ const navigate = useNavigate();
const [loading, setLoading] = useState(true);
const [providers, setProviders] = useState([]);
const initialLoadDone = useRef(false);
+ const setView = useMemo(() => createNavigationHandler(navigate), [navigate]);
+
// Create a function to load providers that can be called multiple times
const loadProviders = useCallback(async () => {
setLoading(true);
@@ -54,47 +58,6 @@ export default function ProviderSettings({
}
}, [getProviders]);
- // Handler for when a provider is launched if this component is used as part of onboarding page
- const handleProviderLaunch = useCallback(
- async (provider: ProviderDetails) => {
- const provider_name = provider.name;
- const model = provider.metadata.default_model;
-
- try {
- await setConfigProvider({
- body: {
- provider: provider_name,
- model,
- },
- throwOnError: true,
- });
-
- toastService.configure({ silent: false });
- toastService.success({
- title: 'Success!',
- msg: `Started goose with ${model} by ${provider.metadata.display_name}. You can change the model via the dropdown.`,
- });
-
- if (onProviderLaunched) {
- onProviderLaunched();
- } else {
- onClose();
- }
- } catch (error) {
- console.error(`Failed to initialize with provider ${provider_name}:`, error);
-
- // Show error toast
- toastService.configure({ silent: false });
- toastService.error({
- title: 'Initialization Failed',
- msg: `Failed to initialize with ${provider.metadata.display_name}: ${error instanceof Error ? error.message : String(error)}`,
- traceback: error instanceof Error ? error.stack || '' : '',
- });
- }
- },
- [onClose, onProviderLaunched]
- );
-
return (
@@ -127,8 +90,9 @@ export default function ProviderSettings({
)}
diff --git a/ui/desktop/src/components/settings/providers/modal/ProviderConfiguationModal.tsx b/ui/desktop/src/components/settings/providers/modal/ProviderConfiguationModal.tsx
index 1858589eb8..a0b76483ca 100644
--- a/ui/desktop/src/components/settings/providers/modal/ProviderConfiguationModal.tsx
+++ b/ui/desktop/src/components/settings/providers/modal/ProviderConfiguationModal.tsx
@@ -23,11 +23,13 @@ import { Button } from '../../../../components/ui/button';
interface ProviderConfigurationModalProps {
provider: ProviderDetails;
onClose: () => void;
+ onConfigured?: (provider: ProviderDetails) => void;
}
export default function ProviderConfigurationModal({
provider,
onClose,
+ onConfigured,
}: ProviderConfigurationModalProps) {
const [validationErrors, setValidationErrors] = useState>({});
const { upsert, remove } = useConfig();
@@ -83,7 +85,11 @@ export default function ProviderConfigurationModal({
try {
await providerConfigSubmitHandler(upsert, provider, toSubmit);
- onClose();
+ if (onConfigured) {
+ onConfigured(provider);
+ } else {
+ onClose();
+ }
} catch (error) {
setError(`${error}`);
}
diff --git a/ui/desktop/src/components/settings/security/SecurityToggle.tsx b/ui/desktop/src/components/settings/security/SecurityToggle.tsx
index e6ba583ace..9bf80464bd 100644
--- a/ui/desktop/src/components/settings/security/SecurityToggle.tsx
+++ b/ui/desktop/src/components/settings/security/SecurityToggle.tsx
@@ -3,16 +3,16 @@ import { Switch } from '../../ui/switch';
import { useConfig } from '../../ConfigContext';
interface SecurityConfig {
- security_prompt_enabled?: boolean;
- security_prompt_threshold?: number;
+ SECURITY_PROMPT_ENABLED?: boolean;
+ SECURITY_PROMPT_THRESHOLD?: number;
}
export const SecurityToggle = () => {
const { config, upsert } = useConfig();
const {
- security_prompt_enabled: enabled = false,
- security_prompt_threshold: configThreshold = 0.7,
+ SECURITY_PROMPT_ENABLED: enabled = false,
+ SECURITY_PROMPT_THRESHOLD: configThreshold = 0.7,
} = (config as SecurityConfig) ?? {};
const [thresholdInput, setThresholdInput] = useState(configThreshold.toString());
@@ -22,12 +22,12 @@ export const SecurityToggle = () => {
}, [configThreshold]);
const handleToggle = async (enabled: boolean) => {
- await upsert('security_prompt_enabled', enabled, false);
+ await upsert('SECURITY_PROMPT_ENABLED', enabled, false);
};
const handleThresholdChange = async (threshold: number) => {
const validThreshold = Math.max(0, Math.min(1, threshold));
- await upsert('security_prompt_threshold', validThreshold, false);
+ await upsert('SECURITY_PROMPT_THRESHOLD', validThreshold, false);
};
return (
diff --git a/ui/desktop/src/styles/main.css b/ui/desktop/src/styles/main.css
index f24dffddf0..930288787d 100644
--- a/ui/desktop/src/styles/main.css
+++ b/ui/desktop/src/styles/main.css
@@ -49,7 +49,7 @@
:root {
/* fonts */
--font-sans: 'Cash Sans', sans-serif;
- --font-mono: 'Cash Sans Mono', monospace;
+ --font-mono: monospace;
/* theming accents */
--background-accent: var(--color-accent);
@@ -192,7 +192,7 @@
--color-text-info: var(--text-info);
--font-sans: 'Cash Sans', sans-serif;
- --font-mono: 'Cash Sans Mono', monospace;
+ --font-mono: monospace;
--font-serif: serif;
--color-ring: var(--ring);
@@ -258,28 +258,6 @@
font-style: normal;
}
-@font-face {
- font-family: 'Cash Sans Mono';
- src:
- url(https://cash-f.squarecdn.com/static/fonts/cashsans/woff2/CashSansMono-Regular.woff2)
- format('woff2'),
- url(https://cash-f.squarecdn.com/static/fonts/cashsans/woff/CashSansMono-Regular.woff)
- format('woff');
- font-weight: 500;
- font-style: normal;
-}
-
-@font-face {
- font-family: 'Cash Sans Mono';
- src:
- url(https://cash-f.squarecdn.com/static/fonts/cashsans/woff2/CashSansMono-Light.woff2)
- format('woff2'),
- url(https://cash-f.squarecdn.com/static/fonts/cashsans/woff/CashSansMono-Light.woff)
- format('woff');
- font-weight: 300;
- font-style: normal;
-}
-
/* animations */
@keyframes appear {
from {
@@ -522,7 +500,7 @@ body {
.bg-inline-code {
border-radius: 4px;
color: var(--color-neutral-200);
- font-family: 'Cash Sans Mono', monospace;
+ font-family: monospace;
font-size: 12px;
font-style: normal;
font-weight: 400;
diff --git a/ui/desktop/src/updates.ts b/ui/desktop/src/updates.ts
index f385af4be5..756e73682c 100644
--- a/ui/desktop/src/updates.ts
+++ b/ui/desktop/src/updates.ts
@@ -3,3 +3,4 @@ export const COST_TRACKING_ENABLED = true;
export const ANNOUNCEMENTS_ENABLED = false;
export const CONFIGURATION_ENABLED = true;
export const VOICE_DICTATION_ELEVENLABS_ENABLED = true;
+export const TELEMETRY_UI_ENABLED = true;
diff --git a/ui/desktop/src/utils/configUtils.ts b/ui/desktop/src/utils/configUtils.ts
index 5a32a56d6c..4ee552da1e 100644
--- a/ui/desktop/src/utils/configUtils.ts
+++ b/ui/desktop/src/utils/configUtils.ts
@@ -15,8 +15,8 @@ export const configLabels: Record = {
GOOSE_RECIPE_GITHUB_REPO: 'Recipe GitHub Repo',
// security settings
- security_prompt_enabled: 'Prompt Injection Detection Enabled',
- security_prompt_threshold: 'Prompt Injection Detection Threshold',
+ SECURITY_PROMPT_ENABLED: 'Prompt Injection Detection Enabled',
+ SECURITY_PROMPT_THRESHOLD: 'Prompt Injection Detection Threshold',
// openai
OPENAI_API_KEY: 'OpenAI API Key',