diff --git a/.github/workflows/beta.yml b/.github/workflows/beta.yml deleted file mode 100644 index a7106667b11..00000000000 --- a/.github/workflows/beta.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: beta - -on: - workflow_dispatch: - schedule: - - cron: "0 * * * *" - -jobs: - sync: - runs-on: blacksmith-4vcpu-ubuntu-2404 - permissions: - contents: write - pull-requests: write - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup Bun - uses: ./.github/actions/setup-bun - - - name: Setup Git Committer - id: setup-git-committer - uses: ./.github/actions/setup-git-committer - with: - opencode-app-id: ${{ vars.OPENCODE_APP_ID }} - opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }} - - - name: Install OpenCode - run: bun i -g opencode-ai - - - name: Sync beta branch - env: - GH_TOKEN: ${{ steps.setup-git-committer.outputs.token }} - OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} - run: bun script/beta.ts diff --git a/.github/workflows/close-stale-prs.yml b/.github/workflows/close-stale-prs.yml deleted file mode 100644 index e0e571b4691..00000000000 --- a/.github/workflows/close-stale-prs.yml +++ /dev/null @@ -1,235 +0,0 @@ -name: close-stale-prs - -on: - workflow_dispatch: - inputs: - dryRun: - description: "Log actions without closing PRs" - type: boolean - default: false - schedule: - - cron: "0 6 * * *" - -permissions: - contents: read - issues: write - pull-requests: write - -jobs: - close-stale-prs: - runs-on: ubuntu-latest - timeout-minutes: 15 - steps: - - name: Close inactive PRs - uses: actions/github-script@v8 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const DAYS_INACTIVE = 60 - const MAX_RETRIES = 3 - - // Adaptive delay: fast for small batches, slower for large to respect - // GitHub's 80 content-generating requests/minute limit - const SMALL_BATCH_THRESHOLD = 10 - const SMALL_BATCH_DELAY_MS = 1000 // 1s for daily operations (≤10 PRs) - const LARGE_BATCH_DELAY_MS = 2000 // 2s for backlog (>10 PRs) = ~30 ops/min, well under 80 limit - - const startTime = Date.now() - const cutoff = new Date(Date.now() - DAYS_INACTIVE * 24 * 60 * 60 * 1000) - const { owner, repo } = context.repo - const dryRun = context.payload.inputs?.dryRun === "true" - - core.info(`Dry run mode: ${dryRun}`) - core.info(`Cutoff date: ${cutoff.toISOString()}`) - - function sleep(ms) { - return new Promise(resolve => setTimeout(resolve, ms)) - } - - async function withRetry(fn, description = 'API call') { - let lastError - for (let attempt = 0; attempt < MAX_RETRIES; attempt++) { - try { - const result = await fn() - return result - } catch (error) { - lastError = error - const isRateLimited = error.status === 403 && - (error.message?.includes('rate limit') || error.message?.includes('secondary')) - - if (!isRateLimited) { - throw error - } - - // Parse retry-after header, default to 60 seconds - const retryAfter = error.response?.headers?.['retry-after'] - ? parseInt(error.response.headers['retry-after']) - : 60 - - // Exponential backoff: retryAfter * 2^attempt - const backoffMs = retryAfter * 1000 * Math.pow(2, attempt) - - core.warning(`${description}: Rate limited (attempt ${attempt + 1}/${MAX_RETRIES}). Waiting ${backoffMs / 1000}s before retry...`) - - await sleep(backoffMs) - } - } - core.error(`${description}: Max retries (${MAX_RETRIES}) exceeded`) - throw lastError - } - - const query = ` - query($owner: String!, $repo: String!, $cursor: String) { - repository(owner: $owner, name: $repo) { - pullRequests(first: 100, states: OPEN, after: $cursor) { - pageInfo { - hasNextPage - endCursor - } - nodes { - number - title - author { - login - } - createdAt - commits(last: 1) { - nodes { - commit { - committedDate - } - } - } - comments(last: 1) { - nodes { - createdAt - } - } - reviews(last: 1) { - nodes { - createdAt - } - } - } - } - } - } - ` - - const allPrs = [] - let cursor = null - let hasNextPage = true - let pageCount = 0 - - while (hasNextPage) { - pageCount++ - core.info(`Fetching page ${pageCount} of open PRs...`) - - const result = await withRetry( - () => github.graphql(query, { owner, repo, cursor }), - `GraphQL page ${pageCount}` - ) - - allPrs.push(...result.repository.pullRequests.nodes) - hasNextPage = result.repository.pullRequests.pageInfo.hasNextPage - cursor = result.repository.pullRequests.pageInfo.endCursor - - core.info(`Page ${pageCount}: fetched ${result.repository.pullRequests.nodes.length} PRs (total: ${allPrs.length})`) - - // Delay between pagination requests (use small batch delay for reads) - if (hasNextPage) { - await sleep(SMALL_BATCH_DELAY_MS) - } - } - - core.info(`Found ${allPrs.length} open pull requests`) - - const stalePrs = allPrs.filter((pr) => { - const dates = [ - new Date(pr.createdAt), - pr.commits.nodes[0] ? new Date(pr.commits.nodes[0].commit.committedDate) : null, - pr.comments.nodes[0] ? new Date(pr.comments.nodes[0].createdAt) : null, - pr.reviews.nodes[0] ? new Date(pr.reviews.nodes[0].createdAt) : null, - ].filter((d) => d !== null) - - const lastActivity = dates.sort((a, b) => b.getTime() - a.getTime())[0] - - if (!lastActivity || lastActivity > cutoff) { - core.info(`PR #${pr.number} is fresh (last activity: ${lastActivity?.toISOString() || "unknown"})`) - return false - } - - core.info(`PR #${pr.number} is STALE (last activity: ${lastActivity.toISOString()})`) - return true - }) - - if (!stalePrs.length) { - core.info("No stale pull requests found.") - return - } - - core.info(`Found ${stalePrs.length} stale pull requests`) - - // ============================================ - // Close stale PRs - // ============================================ - const requestDelayMs = stalePrs.length > SMALL_BATCH_THRESHOLD - ? LARGE_BATCH_DELAY_MS - : SMALL_BATCH_DELAY_MS - - core.info(`Using ${requestDelayMs}ms delay between operations (${stalePrs.length > SMALL_BATCH_THRESHOLD ? 'large' : 'small'} batch mode)`) - - let closedCount = 0 - let skippedCount = 0 - - for (const pr of stalePrs) { - const issue_number = pr.number - const closeComment = `Closing this pull request because it has had no updates for more than ${DAYS_INACTIVE} days. If you plan to continue working on it, feel free to reopen or open a new PR.` - - if (dryRun) { - core.info(`[dry-run] Would close PR #${issue_number} from ${pr.author?.login || 'unknown'}: ${pr.title}`) - continue - } - - try { - // Add comment - await withRetry( - () => github.rest.issues.createComment({ - owner, - repo, - issue_number, - body: closeComment, - }), - `Comment on PR #${issue_number}` - ) - - // Close PR - await withRetry( - () => github.rest.pulls.update({ - owner, - repo, - pull_number: issue_number, - state: "closed", - }), - `Close PR #${issue_number}` - ) - - closedCount++ - core.info(`Closed PR #${issue_number} from ${pr.author?.login || 'unknown'}: ${pr.title}`) - - // Delay before processing next PR - await sleep(requestDelayMs) - } catch (error) { - skippedCount++ - core.error(`Failed to close PR #${issue_number}: ${error.message}`) - } - } - - const elapsed = Math.round((Date.now() - startTime) / 1000) - core.info(`\n========== Summary ==========`) - core.info(`Total open PRs found: ${allPrs.length}`) - core.info(`Stale PRs identified: ${stalePrs.length}`) - core.info(`PRs closed: ${closedCount}`) - core.info(`PRs skipped (errors): ${skippedCount}`) - core.info(`Elapsed time: ${elapsed}s`) - core.info(`=============================`) diff --git a/.github/workflows/compliance-close.yml b/.github/workflows/compliance-close.yml deleted file mode 100644 index c3bcf9f686f..00000000000 --- a/.github/workflows/compliance-close.yml +++ /dev/null @@ -1,95 +0,0 @@ -name: compliance-close - -on: - schedule: - # Run every 30 minutes to check for expired compliance windows - - cron: "*/30 * * * *" - workflow_dispatch: - -permissions: - contents: read - issues: write - pull-requests: write - -jobs: - close-non-compliant: - runs-on: ubuntu-latest - steps: - - name: Close non-compliant issues and PRs after 2 hours - uses: actions/github-script@v7 - with: - script: | - const { data: items } = await github.rest.issues.listForRepo({ - owner: context.repo.owner, - repo: context.repo.repo, - labels: 'needs:compliance', - state: 'open', - per_page: 100, - }); - - if (items.length === 0) { - core.info('No open issues/PRs with needs:compliance label'); - return; - } - - const now = Date.now(); - const twoHours = 2 * 60 * 60 * 1000; - - for (const item of items) { - const isPR = !!item.pull_request; - const kind = isPR ? 'PR' : 'issue'; - - const { data: comments } = await github.rest.issues.listComments({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: item.number, - }); - - const complianceComment = comments.find(c => c.body.includes('')); - if (!complianceComment) continue; - - const commentAge = now - new Date(complianceComment.created_at).getTime(); - if (commentAge < twoHours) { - core.info(`${kind} #${item.number} still within 2-hour window (${Math.round(commentAge / 60000)}m elapsed)`); - continue; - } - - const closeMessage = isPR - ? 'This pull request has been automatically closed because it was not updated to meet our [contributing guidelines](../blob/dev/CONTRIBUTING.md) within the 2-hour window.\n\nFeel free to open a new pull request that follows our guidelines.' - : 'This issue has been automatically closed because it was not updated to meet our [contributing guidelines](../blob/dev/CONTRIBUTING.md) within the 2-hour window.\n\nFeel free to open a new issue that follows our issue templates.'; - - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: item.number, - body: closeMessage, - }); - - try { - await github.rest.issues.removeLabel({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: item.number, - name: 'needs:compliance', - }); - } catch (e) {} - - if (isPR) { - await github.rest.pulls.update({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: item.number, - state: 'closed', - }); - } else { - await github.rest.issues.update({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: item.number, - state: 'closed', - state_reason: 'not_planned', - }); - } - - core.info(`Closed non-compliant ${kind} #${item.number} after 2-hour window`); - } diff --git a/.github/workflows/containers.yml b/.github/workflows/containers.yml deleted file mode 100644 index c7df066d41c..00000000000 --- a/.github/workflows/containers.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: containers - -on: - push: - branches: - - dev - paths: - - packages/containers/** - - .github/workflows/containers.yml - - package.json - workflow_dispatch: - -permissions: - contents: read - packages: write - -jobs: - build: - runs-on: blacksmith-4vcpu-ubuntu-2404 - env: - REGISTRY: ghcr.io/${{ github.repository_owner }} - TAG: "24.04" - steps: - - uses: actions/checkout@v4 - - - uses: ./.github/actions/setup-bun - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to GHCR - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Build and push containers - run: bun ./packages/containers/script/build.ts --push - env: - REGISTRY: ${{ env.REGISTRY }} - TAG: ${{ env.TAG }} diff --git a/.github/workflows/daily-issues-recap.yml b/.github/workflows/daily-issues-recap.yml deleted file mode 100644 index 31cf08233b9..00000000000 --- a/.github/workflows/daily-issues-recap.yml +++ /dev/null @@ -1,170 +0,0 @@ -name: daily-issues-recap - -on: - schedule: - # Run at 6 PM EST (23:00 UTC, or 22:00 UTC during daylight saving) - - cron: "0 23 * * *" - workflow_dispatch: # Allow manual trigger for testing - -jobs: - daily-recap: - runs-on: blacksmith-4vcpu-ubuntu-2404 - permissions: - contents: read - issues: read - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - - uses: ./.github/actions/setup-bun - - - name: Install opencode - run: curl -fsSL https://opencode.ai/install | bash - - - name: Generate daily issues recap - id: recap - env: - OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - OPENCODE_PERMISSION: | - { - "bash": { - "*": "deny", - "gh issue*": "allow", - "gh search*": "allow" - }, - "webfetch": "deny", - "edit": "deny", - "write": "deny" - } - run: | - # Get today's date range - TODAY=$(date -u +%Y-%m-%d) - - opencode run -m opencode/claude-sonnet-4-5 "Generate a daily issues recap for the OpenCode repository. - - TODAY'S DATE: ${TODAY} - - STEP 1: Gather today's issues - Search for all OPEN issues created today (${TODAY}) using: - gh issue list --repo ${{ github.repository }} --state open --search \"created:${TODAY}\" --json number,title,body,labels,state,comments,createdAt,author --limit 500 - - IMPORTANT: EXCLUDE all issues authored by Anomaly team members. Filter out issues where the author login matches ANY of these: - adamdotdevin, Brendonovich, fwang, Hona, iamdavidhill, jayair, kitlangton, kommander, MrMushrooooom, R44VC0RP, rekram1-node, thdxr - This recap is specifically for COMMUNITY (external) issues only. - - STEP 2: Analyze and categorize - For each issue created today, categorize it: - - **Severity Assessment:** - - CRITICAL: Crashes, data loss, security issues, blocks major functionality - - HIGH: Significant bugs affecting many users, important features broken - - MEDIUM: Bugs with workarounds, minor features broken - - LOW: Minor issues, cosmetic, nice-to-haves - - **Activity Assessment:** - - Note issues with high comment counts or engagement - - Note issues from repeat reporters (check if author has filed before) - - STEP 3: Cross-reference with existing issues - For issues that seem like feature requests or recurring bugs: - - Search for similar older issues to identify patterns - - Note if this is a frequently requested feature - - Identify any issues that are duplicates of long-standing requests - - STEP 4: Generate the recap - Create a structured recap with these sections: - - ===DISCORD_START=== - **Daily Issues Recap - ${TODAY}** - - **Summary Stats** - - Total issues opened today: [count] - - By category: [bugs/features/questions] - - **Critical/High Priority Issues** - [List any CRITICAL or HIGH severity issues with brief descriptions and issue numbers] - - **Most Active/Discussed** - [Issues with significant engagement or from active community members] - - **Trending Topics** - [Patterns noticed - e.g., 'Multiple reports about X', 'Continued interest in Y feature'] - - **Duplicates & Related** - [Issues that relate to existing open issues] - ===DISCORD_END=== - - STEP 5: Format for Discord - Format the recap as a Discord-compatible message: - - Use Discord markdown (**, __, etc.) - - BE EXTREMELY CONCISE - this is an EOD summary, not a detailed report - - Use hyperlinked issue numbers with suppressed embeds: [#1234]() - - Group related issues on single lines where possible - - Add emoji sparingly for critical items only - - HARD LIMIT: Keep under 1800 characters total - - Skip sections that have nothing notable (e.g., if no critical issues, omit that section) - - Prioritize signal over completeness - only surface what matters - - OUTPUT: Output ONLY the content between ===DISCORD_START=== and ===DISCORD_END=== markers. Include the markers so I can extract it." > /tmp/recap_raw.txt - - # Extract only the Discord message between markers - sed -n '/===DISCORD_START===/,/===DISCORD_END===/p' /tmp/recap_raw.txt | grep -v '===DISCORD' > /tmp/recap.txt - - echo "recap_file=/tmp/recap.txt" >> $GITHUB_OUTPUT - - - name: Post to Discord - env: - DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_ISSUES_WEBHOOK_URL }} - run: | - if [ -z "$DISCORD_WEBHOOK_URL" ]; then - echo "Warning: DISCORD_ISSUES_WEBHOOK_URL secret not set, skipping Discord post" - cat /tmp/recap.txt - exit 0 - fi - - # Read the recap - RECAP_RAW=$(cat /tmp/recap.txt) - RECAP_LENGTH=${#RECAP_RAW} - - echo "Recap length: ${RECAP_LENGTH} chars" - - # Function to post a message to Discord - post_to_discord() { - local msg="$1" - local content=$(echo "$msg" | jq -Rs '.') - curl -s -H "Content-Type: application/json" \ - -X POST \ - -d "{\"content\": ${content}}" \ - "$DISCORD_WEBHOOK_URL" - sleep 1 - } - - # If under limit, send as single message - if [ "$RECAP_LENGTH" -le 1950 ]; then - post_to_discord "$RECAP_RAW" - else - echo "Splitting into multiple messages..." - remaining="$RECAP_RAW" - while [ ${#remaining} -gt 0 ]; do - if [ ${#remaining} -le 1950 ]; then - post_to_discord "$remaining" - break - else - chunk="${remaining:0:1900}" - last_newline=$(echo "$chunk" | grep -bo $'\n' | tail -1 | cut -d: -f1) - if [ -n "$last_newline" ] && [ "$last_newline" -gt 500 ]; then - chunk="${remaining:0:$last_newline}" - remaining="${remaining:$((last_newline+1))}" - else - chunk="${remaining:0:1900}" - remaining="${remaining:1900}" - fi - post_to_discord "$chunk" - fi - done - fi - - echo "Posted daily recap to Discord" diff --git a/.github/workflows/daily-pr-recap.yml b/.github/workflows/daily-pr-recap.yml deleted file mode 100644 index 2f0f023cfd0..00000000000 --- a/.github/workflows/daily-pr-recap.yml +++ /dev/null @@ -1,173 +0,0 @@ -name: daily-pr-recap - -on: - schedule: - # Run at 5pm EST (22:00 UTC, or 21:00 UTC during daylight saving) - - cron: "0 22 * * *" - workflow_dispatch: # Allow manual trigger for testing - -jobs: - pr-recap: - runs-on: blacksmith-4vcpu-ubuntu-2404 - permissions: - contents: read - pull-requests: read - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - - uses: ./.github/actions/setup-bun - - - name: Install opencode - run: curl -fsSL https://opencode.ai/install | bash - - - name: Generate daily PR recap - id: recap - env: - OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - OPENCODE_PERMISSION: | - { - "bash": { - "*": "deny", - "gh pr*": "allow", - "gh search*": "allow" - }, - "webfetch": "deny", - "edit": "deny", - "write": "deny" - } - run: | - TODAY=$(date -u +%Y-%m-%d) - - opencode run -m opencode/claude-sonnet-4-5 "Generate a daily PR activity recap for the OpenCode repository. - - TODAY'S DATE: ${TODAY} - - STEP 1: Gather PR data - Run these commands to gather PR information. ONLY include OPEN PRs created or updated TODAY (${TODAY}): - - # Open PRs created today - gh pr list --repo ${{ github.repository }} --state open --search \"created:${TODAY}\" --json number,title,author,labels,createdAt,updatedAt,reviewDecision,isDraft,additions,deletions --limit 100 - - # Open PRs with activity today (updated today) - gh pr list --repo ${{ github.repository }} --state open --search \"updated:${TODAY}\" --json number,title,author,labels,createdAt,updatedAt,reviewDecision,isDraft,additions,deletions --limit 100 - - IMPORTANT: EXCLUDE all PRs authored by Anomaly team members. Filter out PRs where the author login matches ANY of these: - adamdotdevin, Brendonovich, fwang, Hona, iamdavidhill, jayair, kitlangton, kommander, MrMushrooooom, R44VC0RP, rekram1-node, thdxr - This recap is specifically for COMMUNITY (external) contributions only. - - - - STEP 2: For high-activity PRs, check comment counts - For promising PRs, run: - gh pr view [NUMBER] --repo ${{ github.repository }} --json comments --jq '[.comments[] | select(.author.login != \"copilot-pull-request-reviewer\" and .author.login != \"github-actions\")] | length' - - IMPORTANT: When counting comments/activity, EXCLUDE these bot accounts: - - copilot-pull-request-reviewer - - github-actions - - STEP 3: Identify what matters (ONLY from today's PRs) - - **Bug Fixes From Today:** - - PRs with 'fix' or 'bug' in title created/updated today - - Small bug fixes (< 100 lines changed) that are easy to review - - Bug fixes from community contributors - - **High Activity Today:** - - PRs with significant human comments today (excluding bots listed above) - - PRs with back-and-forth discussion today - - **Quick Wins:** - - Small PRs (< 50 lines) that are approved or nearly approved - - PRs that just need a final review - - STEP 4: Generate the recap - Create a structured recap: - - ===DISCORD_START=== - **Daily PR Recap - ${TODAY}** - - **New PRs Today** - [PRs opened today - group by type: bug fixes, features, etc.] - - **Active PRs Today** - [PRs with activity/updates today - significant discussion] - - **Quick Wins** - [Small PRs ready to merge] - ===DISCORD_END=== - - STEP 5: Format for Discord - - Use Discord markdown (**, __, etc.) - - BE EXTREMELY CONCISE - surface what we might miss - - Use hyperlinked PR numbers with suppressed embeds: [#1234]() - - Include PR author: [#1234]() (@author) - - For bug fixes, add brief description of what it fixes - - Show line count for quick wins: \"(+15/-3 lines)\" - - HARD LIMIT: Keep under 1800 characters total - - Skip empty sections - - Focus on PRs that need human eyes - - OUTPUT: Output ONLY the content between ===DISCORD_START=== and ===DISCORD_END=== markers. Include the markers so I can extract it." > /tmp/pr_recap_raw.txt - - # Extract only the Discord message between markers - sed -n '/===DISCORD_START===/,/===DISCORD_END===/p' /tmp/pr_recap_raw.txt | grep -v '===DISCORD' > /tmp/pr_recap.txt - - echo "recap_file=/tmp/pr_recap.txt" >> $GITHUB_OUTPUT - - - name: Post to Discord - env: - DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_ISSUES_WEBHOOK_URL }} - run: | - if [ -z "$DISCORD_WEBHOOK_URL" ]; then - echo "Warning: DISCORD_ISSUES_WEBHOOK_URL secret not set, skipping Discord post" - cat /tmp/pr_recap.txt - exit 0 - fi - - # Read the recap - RECAP_RAW=$(cat /tmp/pr_recap.txt) - RECAP_LENGTH=${#RECAP_RAW} - - echo "Recap length: ${RECAP_LENGTH} chars" - - # Function to post a message to Discord - post_to_discord() { - local msg="$1" - local content=$(echo "$msg" | jq -Rs '.') - curl -s -H "Content-Type: application/json" \ - -X POST \ - -d "{\"content\": ${content}}" \ - "$DISCORD_WEBHOOK_URL" - sleep 1 - } - - # If under limit, send as single message - if [ "$RECAP_LENGTH" -le 1950 ]; then - post_to_discord "$RECAP_RAW" - else - echo "Splitting into multiple messages..." - remaining="$RECAP_RAW" - while [ ${#remaining} -gt 0 ]; do - if [ ${#remaining} -le 1950 ]; then - post_to_discord "$remaining" - break - else - chunk="${remaining:0:1900}" - last_newline=$(echo "$chunk" | grep -bo $'\n' | tail -1 | cut -d: -f1) - if [ -n "$last_newline" ] && [ "$last_newline" -gt 500 ]; then - chunk="${remaining:0:$last_newline}" - remaining="${remaining:$((last_newline+1))}" - else - chunk="${remaining:0:1900}" - remaining="${remaining:1900}" - fi - post_to_discord "$chunk" - fi - done - fi - - echo "Posted daily PR recap to Discord" diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml deleted file mode 100644 index c08d7edf3b1..00000000000 --- a/.github/workflows/deploy.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: deploy - -on: - push: - branches: - - dev - - production - workflow_dispatch: - -concurrency: ${{ github.workflow }}-${{ github.ref }} - -jobs: - deploy: - runs-on: blacksmith-4vcpu-ubuntu-2404 - steps: - - uses: actions/checkout@v3 - - - uses: ./.github/actions/setup-bun - - - uses: actions/setup-node@v4 - with: - node-version: "24" - - # Workaround for Pulumi version conflict: - # GitHub runners have Pulumi 3.212.0+ pre-installed, which removed the -root flag - # from pulumi-language-nodejs (see https://github.com/pulumi/pulumi/pull/21065). - # SST 3.17.x uses Pulumi SDK 3.210.0 which still passes -root, causing a conflict. - # Removing the system language plugin forces SST to use its bundled compatible version. - # TODO: Remove when sst supports Pulumi >3.210.0 - - name: Fix Pulumi version conflict - run: sudo rm -f /usr/local/bin/pulumi-language-nodejs - - - run: bun sst deploy --stage=${{ github.ref_name }} - env: - CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} - PLANETSCALE_SERVICE_TOKEN_NAME: ${{ secrets.PLANETSCALE_SERVICE_TOKEN_NAME }} - PLANETSCALE_SERVICE_TOKEN: ${{ secrets.PLANETSCALE_SERVICE_TOKEN }} - STRIPE_SECRET_KEY: ${{ github.ref_name == 'production' && secrets.STRIPE_SECRET_KEY_PROD || secrets.STRIPE_SECRET_KEY_DEV }} diff --git a/.github/workflows/docs-locale-sync.yml b/.github/workflows/docs-locale-sync.yml deleted file mode 100644 index 2bc19d6141a..00000000000 --- a/.github/workflows/docs-locale-sync.yml +++ /dev/null @@ -1,132 +0,0 @@ -name: docs-locale-sync - -on: - push: - branches: - - dev - paths: - - packages/web/src/content/docs/*.mdx - -jobs: - sync-locales: - if: github.actor != 'opencode-agent[bot]' - runs-on: blacksmith-4vcpu-ubuntu-2404 - permissions: - contents: write - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - persist-credentials: false - fetch-depth: 0 - ref: ${{ github.ref_name }} - - - name: Setup Bun - uses: ./.github/actions/setup-bun - - - name: Setup git committer - id: committer - uses: ./.github/actions/setup-git-committer - with: - opencode-app-id: ${{ vars.OPENCODE_APP_ID }} - opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }} - - - name: Compute changed English docs - id: changes - run: | - FILES=$(git diff --name-only "${{ github.event.before }}" "${{ github.sha }}" -- 'packages/web/src/content/docs/*.mdx' || true) - if [ -z "$FILES" ]; then - echo "has_changes=false" >> "$GITHUB_OUTPUT" - echo "No English docs changed in push range" - exit 0 - fi - echo "has_changes=true" >> "$GITHUB_OUTPUT" - { - echo "files<> "$GITHUB_OUTPUT" - - - name: Install OpenCode - if: steps.changes.outputs.has_changes == 'true' - run: curl -fsSL https://opencode.ai/install | bash - - - name: Sync locale docs with OpenCode - if: steps.changes.outputs.has_changes == 'true' - env: - OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} - OPENCODE_CONFIG_CONTENT: | - { - "permission": { - "*": "deny", - "read": { - "*": "deny", - "packages/web/src/content/docs": "allow", - "packages/web/src/content/docs/*": "allow", - "packages/web/src/content/docs/*.mdx": "allow", - "packages/web/src/content/docs/*/*.mdx": "allow", - ".opencode": "allow", - ".opencode/agent": "allow", - ".opencode/glossary": "allow", - ".opencode/agent/translator.md": "allow", - ".opencode/glossary/*.md": "allow" - }, - "edit": { - "*": "deny", - "packages/web/src/content/docs/*/*.mdx": "allow" - }, - "glob": { - "*": "deny", - "packages/web/src/content/docs*": "allow", - ".opencode/glossary*": "allow" - }, - "task": { - "*": "deny", - "translator": "allow" - } - }, - "agent": { - "translator": { - "permission": { - "*": "deny", - "read": { - "*": "deny", - ".opencode/agent/translator.md": "allow", - ".opencode/glossary/*.md": "allow" - } - } - } - } - } - run: | - opencode run --agent docs --model opencode/gpt-5.3-codex <<'EOF' - Update localized docs to match the latest English docs changes. - - Changed English doc files: - - ${{ steps.changes.outputs.files }} - - - Requirements: - 1. Update all relevant locale docs under packages/web/src/content/docs// so they reflect these English page changes. - 2. You MUST use the Task tool for translation work and launch subagents with subagent_type `translator` (defined in .opencode/agent/translator.md). - 3. Do not translate directly in the primary agent. Use translator subagent output as the source for locale text updates. - 4. Run translator subagent Task calls in parallel whenever file/locale translation work is independent. - 5. Use only the minimum tools needed for this task (read/glob, file edits, and translator Task). Do not use shell, web, search, or GitHub tools for translation work. - 6. Preserve frontmatter keys, internal links, code blocks, and existing locale-specific metadata unless the English change requires an update. - 7. Keep locale docs structure aligned with their corresponding English pages. - 8. Do not modify English source docs in packages/web/src/content/docs/*.mdx. - 9. If no locale updates are needed, make no changes. - EOF - - - name: Commit and push locale docs updates - if: steps.changes.outputs.has_changes == 'true' - run: | - if [ -z "$(git status --porcelain)" ]; then - echo "No locale docs changes to commit" - exit 0 - fi - git add -A - git commit -m "docs(i18n): sync locale docs from english changes" - git pull --rebase --autostash origin "$GITHUB_REF_NAME" - git push origin HEAD:"$GITHUB_REF_NAME" diff --git a/.github/workflows/docs-update.yml b/.github/workflows/docs-update.yml deleted file mode 100644 index 900ad2b0c58..00000000000 --- a/.github/workflows/docs-update.yml +++ /dev/null @@ -1,72 +0,0 @@ -name: docs-update - -on: - schedule: - - cron: "0 */12 * * *" - workflow_dispatch: - -env: - LOOKBACK_HOURS: 4 - -jobs: - update-docs: - if: github.repository == 'sst/opencode' - runs-on: blacksmith-4vcpu-ubuntu-2404 - permissions: - id-token: write - contents: write - pull-requests: write - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 # Fetch full history to access commits - - - name: Setup Bun - uses: ./.github/actions/setup-bun - - - name: Get recent commits - id: commits - run: | - COMMITS=$(git log --since="${{ env.LOOKBACK_HOURS }} hours ago" --pretty=format:"- %h %s" 2>/dev/null || echo "") - if [ -z "$COMMITS" ]; then - echo "No commits in the last ${{ env.LOOKBACK_HOURS }} hours" - echo "has_commits=false" >> $GITHUB_OUTPUT - else - echo "has_commits=true" >> $GITHUB_OUTPUT - { - echo "list<> $GITHUB_OUTPUT - fi - - - name: Run opencode - if: steps.commits.outputs.has_commits == 'true' - uses: sst/opencode/github@latest - env: - OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} - with: - model: opencode/gpt-5.2 - agent: docs - prompt: | - Review the following commits from the last ${{ env.LOOKBACK_HOURS }} hours and identify any new features that may need documentation. - - - ${{ steps.commits.outputs.list }} - - - Steps: - 1. For each commit that looks like a new feature or significant change: - - Read the changed files to understand what was added - - Check if the feature is already documented in packages/web/src/content/docs/* - 2. If you find undocumented features: - - Update the relevant documentation files in packages/web/src/content/docs/* - - Follow the existing documentation style and structure - - Make sure to document the feature clearly with examples where appropriate - 3. If all new features are already documented, report that no updates are needed - 4. If you are creating a new documentation file be sure to update packages/web/astro.config.mjs too. - - Focus on user-facing features and API changes. Skip internal refactors, bug fixes, and test updates unless they affect user-facing behavior. - Don't feel the need to document every little thing. It is perfectly okay to make 0 changes at all. - Try to keep documentation only for large features or changes that already have a good spot to be documented. diff --git a/.github/workflows/duplicate-issues.yml b/.github/workflows/duplicate-issues.yml deleted file mode 100644 index 6c1943fe7b8..00000000000 --- a/.github/workflows/duplicate-issues.yml +++ /dev/null @@ -1,177 +0,0 @@ -name: duplicate-issues - -on: - issues: - types: [opened, edited] - -jobs: - check-duplicates: - if: github.event.action == 'opened' - runs-on: blacksmith-4vcpu-ubuntu-2404 - permissions: - contents: read - issues: write - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - - uses: ./.github/actions/setup-bun - - - name: Install opencode - run: curl -fsSL https://opencode.ai/install | bash - - - name: Check duplicates and compliance - env: - OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - OPENCODE_PERMISSION: | - { - "bash": { - "*": "deny", - "gh issue*": "allow" - }, - "webfetch": "deny" - } - run: | - opencode run -m opencode/claude-sonnet-4-6 "A new issue has been created: - - Issue number: ${{ github.event.issue.number }} - - Lookup this issue with gh issue view ${{ github.event.issue.number }}. - - You have TWO tasks. Perform both, then post a SINGLE comment (if needed). - - --- - - TASK 1: CONTRIBUTING GUIDELINES COMPLIANCE CHECK - - Check whether the issue follows our contributing guidelines and issue templates. - - This project has three issue templates that every issue MUST use one of: - - 1. Bug Report - requires a Description field with real content - 2. Feature Request - requires a verification checkbox and description, title should start with [FEATURE]: - 3. Question - requires the Question field with real content - - Additionally check: - - No AI-generated walls of text (long, AI-generated descriptions are not acceptable) - - The issue has real content, not just template placeholder text left unchanged - - Bug reports should include some context about how to reproduce - - Feature requests should explain the problem or need - - We want to push for having the user provide system description & information - - Do NOT be nitpicky about optional fields. Only flag real problems like: no template used, required fields empty or placeholder text only, obviously AI-generated walls of text, or completely empty/nonsensical content. - - --- - - TASK 2: DUPLICATE CHECK - - Search through existing issues (excluding #${{ github.event.issue.number }}) to find potential duplicates. - Consider: - 1. Similar titles or descriptions - 2. Same error messages or symptoms - 3. Related functionality or components - 4. Similar feature requests - - Additionally, if the issue mentions keybinds, keyboard shortcuts, or key bindings, note the pinned keybinds issue #4997. - - --- - - POSTING YOUR COMMENT: - - Based on your findings, post a SINGLE comment on issue #${{ github.event.issue.number }}. Build the comment as follows: - - If the issue is NOT compliant, start the comment with: - - Then explain what needs to be fixed and that they have 2 hours to edit the issue before it is automatically closed. Also add the label needs:compliance to the issue using: gh issue edit ${{ github.event.issue.number }} --add-label needs:compliance - - If duplicates were found, include a section about potential duplicates with links. - - If the issue mentions keybinds/keyboard shortcuts, include a note about #4997. - - If the issue IS compliant AND no duplicates were found AND no keybind reference, do NOT comment at all. - - Use this format for the comment: - - [If not compliant:] - - This issue doesn't fully meet our [contributing guidelines](../blob/dev/CONTRIBUTING.md). - - **What needs to be fixed:** - - [specific reasons] - - Please edit this issue to address the above within **2 hours**, or it will be automatically closed. - - [If duplicates found, add:] - --- - This issue might be a duplicate of existing issues. Please check: - - #[issue_number]: [brief description of similarity] - - [If keybind-related, add:] - For keybind-related issues, please also check our pinned keybinds documentation: #4997 - - [End with if not compliant:] - If you believe this was flagged incorrectly, please let a maintainer know. - - Remember: post at most ONE comment combining all findings. If everything is fine, post nothing." - - recheck-compliance: - if: github.event.action == 'edited' && contains(github.event.issue.labels.*.name, 'needs:compliance') - runs-on: blacksmith-4vcpu-ubuntu-2404 - permissions: - contents: read - issues: write - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - - uses: ./.github/actions/setup-bun - - - name: Install opencode - run: curl -fsSL https://opencode.ai/install | bash - - - name: Recheck compliance - env: - OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - OPENCODE_PERMISSION: | - { - "bash": { - "*": "deny", - "gh issue*": "allow" - }, - "webfetch": "deny" - } - run: | - opencode run -m opencode/claude-sonnet-4-6 "Issue #${{ github.event.issue.number }} was previously flagged as non-compliant and has been edited. - - Lookup this issue with gh issue view ${{ github.event.issue.number }}. - - Re-check whether the issue now follows our contributing guidelines and issue templates. - - This project has three issue templates that every issue MUST use one of: - - 1. Bug Report - requires a Description field with real content - 2. Feature Request - requires a verification checkbox and description, title should start with [FEATURE]: - 3. Question - requires the Question field with real content - - Additionally check: - - No AI-generated walls of text (long, AI-generated descriptions are not acceptable) - - The issue has real content, not just template placeholder text left unchanged - - Bug reports should include some context about how to reproduce - - Feature requests should explain the problem or need - - We want to push for having the user provide system description & information - - Do NOT be nitpicky about optional fields. Only flag real problems like: no template used, required fields empty or placeholder text only, obviously AI-generated walls of text, or completely empty/nonsensical content. - - If the issue is NOW compliant: - 1. Remove the needs:compliance label: gh issue edit ${{ github.event.issue.number }} --remove-label needs:compliance - 2. Find and delete the previous compliance comment (the one containing ) using: gh api repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/comments --jq '.[] | select(.body | contains(\"\")) | .id' then delete it with: gh api -X DELETE repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/comments/{id} - 3. Post a short comment thanking them for updating the issue. - - If the issue is STILL not compliant: - Post a comment explaining what still needs to be fixed. Keep the needs:compliance label." diff --git a/.github/workflows/generate.yml b/.github/workflows/generate.yml deleted file mode 100644 index 706ab2989e1..00000000000 --- a/.github/workflows/generate.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: generate - -on: - push: - branches: - - dev - -jobs: - generate: - runs-on: blacksmith-4vcpu-ubuntu-2404 - permissions: - contents: write - pull-requests: write - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Setup Bun - uses: ./.github/actions/setup-bun - - - name: Setup git committer - id: committer - uses: ./.github/actions/setup-git-committer - with: - opencode-app-id: ${{ vars.OPENCODE_APP_ID }} - opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }} - - - name: Generate - run: ./script/generate.ts - - - name: Commit and push - run: | - if [ -z "$(git status --porcelain)" ]; then - echo "No changes to commit" - exit 0 - fi - git add -A - git commit -m "chore: generate" --allow-empty - git push origin HEAD:${{ github.ref_name }} --no-verify - # if ! git push origin HEAD:${{ github.event.pull_request.head.ref || github.ref_name }} --no-verify; then - # echo "" - # echo "============================================" - # echo "Failed to push generated code." - # echo "Please run locally and push:" - # echo "" - # echo " ./script/generate.ts" - # echo " git add -A && git commit -m \"chore: generate\" && git push" - # echo "" - # echo "============================================" - # exit 1 - # fi diff --git a/.github/workflows/nix-eval.yml b/.github/workflows/nix-eval.yml deleted file mode 100644 index c76b2c97297..00000000000 --- a/.github/workflows/nix-eval.yml +++ /dev/null @@ -1,95 +0,0 @@ -name: nix-eval - -on: - push: - branches: [dev] - pull_request: - branches: [dev] - workflow_dispatch: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -permissions: - contents: read - -jobs: - nix-eval: - runs-on: blacksmith-4vcpu-ubuntu-2404 - timeout-minutes: 15 - steps: - - name: Checkout repository - uses: actions/checkout@v6 - - - name: Setup Nix - uses: nixbuild/nix-quick-install-action@v34 - - - name: Evaluate flake outputs (all systems) - run: | - set -euo pipefail - nix --version - - echo "=== Flake metadata ===" - nix flake metadata - - echo "" - echo "=== Flake structure ===" - nix flake show --all-systems - - SYSTEMS="x86_64-linux aarch64-linux x86_64-darwin aarch64-darwin" - PACKAGES="opencode" - # TODO: move 'desktop' to PACKAGES when #11755 is fixed - OPTIONAL_PACKAGES="desktop" - - echo "" - echo "=== Evaluating packages for all systems ===" - for system in $SYSTEMS; do - echo "" - echo "--- $system ---" - for pkg in $PACKAGES; do - printf " %s: " "$pkg" - if output=$(nix eval ".#packages.$system.$pkg.drvPath" --raw 2>&1); then - echo "✓" - else - echo "✗" - echo "::error::Evaluation failed for packages.$system.$pkg" - echo "$output" - exit 1 - fi - done - done - - echo "" - echo "=== Evaluating optional packages ===" - for system in $SYSTEMS; do - echo "" - echo "--- $system ---" - for pkg in $OPTIONAL_PACKAGES; do - printf " %s: " "$pkg" - if output=$(nix eval ".#packages.$system.$pkg.drvPath" --raw 2>&1); then - echo "✓" - else - echo "✗" - echo "::warning::Evaluation failed for packages.$system.$pkg" - echo "$output" - fi - done - done - - echo "" - echo "=== Evaluating devShells for all systems ===" - for system in $SYSTEMS; do - printf "%s: " "$system" - if output=$(nix eval ".#devShells.$system.default.drvPath" --raw 2>&1); then - echo "✓" - else - echo "✗" - echo "::error::Evaluation failed for devShells.$system.default" - echo "$output" - exit 1 - fi - done - - echo "" - echo "=== All evaluations passed ===" diff --git a/.github/workflows/nix-hashes.yml b/.github/workflows/nix-hashes.yml deleted file mode 100644 index 2529c14c208..00000000000 --- a/.github/workflows/nix-hashes.yml +++ /dev/null @@ -1,148 +0,0 @@ -name: nix-hashes - -permissions: - contents: write - -on: - workflow_dispatch: - push: - branches: [dev, beta] - paths: - - "bun.lock" - - "package.json" - - "packages/*/package.json" - - "flake.lock" - - "nix/node_modules.nix" - - "nix/scripts/**" - - "patches/**" - - ".github/workflows/nix-hashes.yml" - -jobs: - # Native runners required: bun install cross-compilation flags (--os/--cpu) - # do not produce byte-identical node_modules as native installs. - compute-hash: - strategy: - fail-fast: false - matrix: - include: - - system: x86_64-linux - runner: blacksmith-4vcpu-ubuntu-2404 - - system: aarch64-linux - runner: blacksmith-4vcpu-ubuntu-2404-arm - - system: x86_64-darwin - runner: macos-15-intel - - system: aarch64-darwin - runner: macos-latest - runs-on: ${{ matrix.runner }} - - steps: - - name: Checkout repository - uses: actions/checkout@v6 - - - name: Setup Nix - uses: nixbuild/nix-quick-install-action@v34 - - - name: Compute node_modules hash - id: hash - env: - SYSTEM: ${{ matrix.system }} - run: | - set -euo pipefail - - BUILD_LOG=$(mktemp) - trap 'rm -f "$BUILD_LOG"' EXIT - - # Build with fakeHash to trigger hash mismatch and reveal correct hash - nix build ".#packages.${SYSTEM}.node_modules_updater" --no-link 2>&1 | tee "$BUILD_LOG" || true - - # Extract hash from build log with portability - HASH="$(grep -oE 'sha256-[A-Za-z0-9+/=]+' "$BUILD_LOG" | tail -n1 || true)" - - if [ -z "$HASH" ]; then - echo "::error::Failed to compute hash for ${SYSTEM}" - cat "$BUILD_LOG" - exit 1 - fi - - echo "$HASH" > hash.txt - echo "Computed hash for ${SYSTEM}: $HASH" - - - name: Upload hash - uses: actions/upload-artifact@v4 - with: - name: hash-${{ matrix.system }} - path: hash.txt - retention-days: 1 - - update-hashes: - needs: compute-hash - if: github.event_name != 'pull_request' - runs-on: blacksmith-4vcpu-ubuntu-2404 - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - persist-credentials: false - fetch-depth: 0 - ref: ${{ github.ref_name }} - - - name: Setup git committer - uses: ./.github/actions/setup-git-committer - with: - opencode-app-id: ${{ vars.OPENCODE_APP_ID }} - opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }} - - - name: Pull latest changes - run: | - git pull --rebase --autostash origin "$GITHUB_REF_NAME" - - - name: Download hash artifacts - uses: actions/download-artifact@v4 - with: - path: hashes - pattern: hash-* - - - name: Update hashes.json - run: | - set -euo pipefail - - HASH_FILE="nix/hashes.json" - - [ -f "$HASH_FILE" ] || echo '{"nodeModules":{}}' > "$HASH_FILE" - - for SYSTEM in x86_64-linux aarch64-linux x86_64-darwin aarch64-darwin; do - FILE="hashes/hash-${SYSTEM}/hash.txt" - if [ -f "$FILE" ]; then - HASH="$(tr -d '[:space:]' < "$FILE")" - echo "${SYSTEM}: ${HASH}" - jq --arg sys "$SYSTEM" --arg h "$HASH" '.nodeModules[$sys] = $h' "$HASH_FILE" > tmp.json - mv tmp.json "$HASH_FILE" - else - echo "::warning::Missing hash for ${SYSTEM}" - fi - done - - cat "$HASH_FILE" - - - name: Commit changes - run: | - set -euo pipefail - - HASH_FILE="nix/hashes.json" - - if [ -z "$(git status --short -- "$HASH_FILE")" ]; then - echo "No changes to commit" - echo "### Nix hashes" >> "$GITHUB_STEP_SUMMARY" - echo "Status: no changes" >> "$GITHUB_STEP_SUMMARY" - exit 0 - fi - - git add "$HASH_FILE" - git commit -m "chore: update nix node_modules hashes" - - git pull --rebase --autostash origin "$GITHUB_REF_NAME" - git push origin HEAD:"$GITHUB_REF_NAME" - - echo "### Nix hashes" >> "$GITHUB_STEP_SUMMARY" - echo "Status: committed $(git rev-parse --short HEAD)" >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/notify-discord.yml b/.github/workflows/notify-discord.yml deleted file mode 100644 index b1d8053603a..00000000000 --- a/.github/workflows/notify-discord.yml +++ /dev/null @@ -1,14 +0,0 @@ -name: notify-discord - -on: - release: - types: [released] # fires when a draft release is published - -jobs: - notify: - runs-on: blacksmith-4vcpu-ubuntu-2404 - steps: - - name: Send nicely-formatted embed to Discord - uses: SethCohen/github-releases-to-discord@v1 - with: - webhook_url: ${{ secrets.DISCORD_WEBHOOK }} diff --git a/.github/workflows/opencode.yml b/.github/workflows/opencode.yml deleted file mode 100644 index 76e75fcaefb..00000000000 --- a/.github/workflows/opencode.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: opencode - -on: - issue_comment: - types: [created] - pull_request_review_comment: - types: [created] - -jobs: - opencode: - if: | - contains(github.event.comment.body, ' /oc') || - startsWith(github.event.comment.body, '/oc') || - contains(github.event.comment.body, ' /opencode') || - startsWith(github.event.comment.body, '/opencode') - runs-on: blacksmith-4vcpu-ubuntu-2404 - permissions: - id-token: write - contents: read - pull-requests: read - issues: read - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - uses: ./.github/actions/setup-bun - - - name: Run opencode - uses: anomalyco/opencode/github@latest - env: - OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} - OPENCODE_PERMISSION: '{"bash": "deny"}' - with: - model: opencode/claude-opus-4-5 diff --git a/.github/workflows/pr-management.yml b/.github/workflows/pr-management.yml deleted file mode 100644 index 35bd7ae36f2..00000000000 --- a/.github/workflows/pr-management.yml +++ /dev/null @@ -1,95 +0,0 @@ -name: pr-management - -on: - pull_request_target: - types: [opened] - -jobs: - check-duplicates: - runs-on: blacksmith-4vcpu-ubuntu-2404 - permissions: - contents: read - pull-requests: write - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - - name: Check team membership - id: team-check - run: | - LOGIN="${{ github.event.pull_request.user.login }}" - if [ "$LOGIN" = "opencode-agent[bot]" ] || grep -qxF "$LOGIN" .github/TEAM_MEMBERS; then - echo "is_team=true" >> "$GITHUB_OUTPUT" - echo "Skipping: $LOGIN is a team member or bot" - else - echo "is_team=false" >> "$GITHUB_OUTPUT" - fi - - - name: Setup Bun - if: steps.team-check.outputs.is_team != 'true' - uses: ./.github/actions/setup-bun - - - name: Install dependencies - if: steps.team-check.outputs.is_team != 'true' - run: bun install - - - name: Install opencode - if: steps.team-check.outputs.is_team != 'true' - run: curl -fsSL https://opencode.ai/install | bash - - - name: Build prompt - if: steps.team-check.outputs.is_team != 'true' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - PR_NUMBER: ${{ github.event.pull_request.number }} - run: | - { - echo "Check for duplicate PRs related to this new PR:" - echo "" - echo "CURRENT_PR_NUMBER: $PR_NUMBER" - echo "" - echo "Title: $(gh pr view "$PR_NUMBER" --json title --jq .title)" - echo "" - echo "Description:" - gh pr view "$PR_NUMBER" --json body --jq .body - } > pr_info.txt - - - name: Check for duplicate PRs - if: steps.team-check.outputs.is_team != 'true' - env: - OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - PR_NUMBER: ${{ github.event.pull_request.number }} - run: | - COMMENT=$(bun script/duplicate-pr.ts -f pr_info.txt "Check the attached file for PR details and search for duplicates") - - if [ "$COMMENT" != "No duplicate PRs found" ]; then - gh pr comment "$PR_NUMBER" --body "_The following comment was made by an LLM, it may be inaccurate:_ - - $COMMENT" - fi - - add-contributor-label: - runs-on: ubuntu-latest - permissions: - pull-requests: write - issues: write - steps: - - name: Add Contributor Label - uses: actions/github-script@v8 - with: - script: | - const isPR = !!context.payload.pull_request; - const issueNumber = isPR ? context.payload.pull_request.number : context.payload.issue.number; - const authorAssociation = isPR ? context.payload.pull_request.author_association : context.payload.issue.author_association; - - if (authorAssociation === 'CONTRIBUTOR') { - await github.rest.issues.addLabels({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issueNumber, - labels: ['contributor'] - }); - } diff --git a/.github/workflows/pr-standards.yml b/.github/workflows/pr-standards.yml deleted file mode 100644 index 1edbd5d061d..00000000000 --- a/.github/workflows/pr-standards.yml +++ /dev/null @@ -1,351 +0,0 @@ -name: pr-standards - -on: - pull_request_target: - types: [opened, edited, synchronize] - -jobs: - check-standards: - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: write - steps: - - name: Check PR standards - uses: actions/github-script@v7 - with: - script: | - const pr = context.payload.pull_request; - const login = pr.user.login; - - // Skip PRs older than Feb 18, 2026 at 6PM EST (Feb 19, 2026 00:00 UTC) - const cutoff = new Date('2026-02-19T00:00:00Z'); - const prCreated = new Date(pr.created_at); - if (prCreated < cutoff) { - console.log(`Skipping: PR #${pr.number} was created before cutoff (${prCreated.toISOString()})`); - return; - } - - // Check if author is a team member or bot - if (login === 'opencode-agent[bot]') return; - const { data: file } = await github.rest.repos.getContent({ - owner: context.repo.owner, - repo: context.repo.repo, - path: '.github/TEAM_MEMBERS', - ref: 'dev' - }); - const members = Buffer.from(file.content, 'base64').toString().split('\n').map(l => l.trim()).filter(Boolean); - if (members.includes(login)) { - console.log(`Skipping: ${login} is a team member`); - return; - } - - const title = pr.title; - - async function addLabel(label) { - await github.rest.issues.addLabels({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: pr.number, - labels: [label] - }); - } - - async function removeLabel(label) { - try { - await github.rest.issues.removeLabel({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: pr.number, - name: label - }); - } catch (e) { - // Label wasn't present, ignore - } - } - - async function comment(marker, body) { - const markerText = ``; - const { data: comments } = await github.rest.issues.listComments({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: pr.number - }); - - const existing = comments.find(c => c.body.includes(markerText)); - if (existing) return; - - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: pr.number, - body: markerText + '\n' + body - }); - } - - // Step 1: Check title format - // Matches: feat:, feat(scope):, feat (scope):, etc. - const titlePattern = /^(feat|fix|docs|chore|refactor|test)\s*(\([a-zA-Z0-9-]+\))?\s*:/; - const hasValidTitle = titlePattern.test(title); - - if (!hasValidTitle) { - await addLabel('needs:title'); - await comment('title', `Hey! Your PR title \`${title}\` doesn't follow conventional commit format. - - Please update it to start with one of: - - \`feat:\` or \`feat(scope):\` new feature - - \`fix:\` or \`fix(scope):\` bug fix - - \`docs:\` or \`docs(scope):\` documentation changes - - \`chore:\` or \`chore(scope):\` maintenance tasks - - \`refactor:\` or \`refactor(scope):\` code refactoring - - \`test:\` or \`test(scope):\` adding or updating tests - - Where \`scope\` is the package name (e.g., \`app\`, \`desktop\`, \`opencode\`). - - See [CONTRIBUTING.md](../blob/dev/CONTRIBUTING.md#pr-titles) for details.`); - return; - } - - await removeLabel('needs:title'); - - // Step 2: Check for linked issue (skip for docs/refactor/feat PRs) - const skipIssueCheck = /^(docs|refactor|feat)\s*(\([a-zA-Z0-9-]+\))?\s*:/.test(title); - if (skipIssueCheck) { - await removeLabel('needs:issue'); - console.log('Skipping issue check for docs/refactor/feat PR'); - return; - } - const query = ` - query($owner: String!, $repo: String!, $number: Int!) { - repository(owner: $owner, name: $repo) { - pullRequest(number: $number) { - closingIssuesReferences(first: 1) { - totalCount - } - } - } - } - `; - - const result = await github.graphql(query, { - owner: context.repo.owner, - repo: context.repo.repo, - number: pr.number - }); - - const linkedIssues = result.repository.pullRequest.closingIssuesReferences.totalCount; - - if (linkedIssues === 0) { - await addLabel('needs:issue'); - await comment('issue', `Thanks for your contribution! - - This PR doesn't have a linked issue. All PRs must reference an existing issue. - - Please: - 1. Open an issue describing the bug/feature (if one doesn't exist) - 2. Add \`Fixes #\` or \`Closes #\` to this PR description - - See [CONTRIBUTING.md](../blob/dev/CONTRIBUTING.md#issue-first-policy) for details.`); - return; - } - - await removeLabel('needs:issue'); - console.log('PR meets all standards'); - - check-compliance: - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: write - steps: - - name: Check PR template compliance - uses: actions/github-script@v7 - with: - script: | - const pr = context.payload.pull_request; - const login = pr.user.login; - - // Skip PRs older than Feb 18, 2026 at 6PM EST (Feb 19, 2026 00:00 UTC) - const cutoff = new Date('2026-02-19T00:00:00Z'); - const prCreated = new Date(pr.created_at); - if (prCreated < cutoff) { - console.log(`Skipping: PR #${pr.number} was created before cutoff (${prCreated.toISOString()})`); - return; - } - - // Check if author is a team member or bot - if (login === 'opencode-agent[bot]') return; - const { data: file } = await github.rest.repos.getContent({ - owner: context.repo.owner, - repo: context.repo.repo, - path: '.github/TEAM_MEMBERS', - ref: 'dev' - }); - const members = Buffer.from(file.content, 'base64').toString().split('\n').map(l => l.trim()).filter(Boolean); - if (members.includes(login)) { - console.log(`Skipping: ${login} is a team member`); - return; - } - - const body = pr.body || ''; - const title = pr.title; - const isDocsRefactorOrFeat = /^(docs|refactor|feat)\s*(\([a-zA-Z0-9-]+\))?\s*:/.test(title); - - const issues = []; - - // Check: template sections exist - const hasWhatSection = /### What does this PR do\?/.test(body); - const hasTypeSection = /### Type of change/.test(body); - const hasVerifySection = /### How did you verify your code works\?/.test(body); - const hasChecklistSection = /### Checklist/.test(body); - const hasIssueSection = /### Issue for this PR/.test(body); - - if (!hasWhatSection || !hasTypeSection || !hasVerifySection || !hasChecklistSection || !hasIssueSection) { - issues.push('PR description is missing required template sections. Please use the [PR template](../blob/dev/.github/pull_request_template.md).'); - } - - // Check: "What does this PR do?" has real content (not just placeholder text) - if (hasWhatSection) { - const whatMatch = body.match(/### What does this PR do\?\s*\n([\s\S]*?)(?=###|$)/); - const whatContent = whatMatch ? whatMatch[1].trim() : ''; - const placeholder = 'Please provide a description of the issue'; - const onlyPlaceholder = whatContent.includes(placeholder) && whatContent.replace(placeholder, '').replace(/[*\s]/g, '').length < 20; - if (!whatContent || onlyPlaceholder) { - issues.push('"What does this PR do?" section is empty or only contains placeholder text. Please describe your changes.'); - } - } - - // Check: at least one "Type of change" checkbox is checked - if (hasTypeSection) { - const typeMatch = body.match(/### Type of change\s*\n([\s\S]*?)(?=###|$)/); - const typeContent = typeMatch ? typeMatch[1] : ''; - const hasCheckedBox = /- \[x\]/i.test(typeContent); - if (!hasCheckedBox) { - issues.push('No "Type of change" checkbox is checked. Please select at least one.'); - } - } - - // Check: issue reference (skip for docs/refactor/feat) - if (!isDocsRefactorOrFeat && hasIssueSection) { - const issueMatch = body.match(/### Issue for this PR\s*\n([\s\S]*?)(?=###|$)/); - const issueContent = issueMatch ? issueMatch[1].trim() : ''; - const hasIssueRef = /(closes|fixes|resolves)\s+#\d+/i.test(issueContent) || /#\d+/.test(issueContent); - if (!hasIssueRef) { - issues.push('No issue referenced. Please add `Closes #` linking to the relevant issue.'); - } - } - - // Check: "How did you verify" has content - if (hasVerifySection) { - const verifyMatch = body.match(/### How did you verify your code works\?\s*\n([\s\S]*?)(?=###|$)/); - const verifyContent = verifyMatch ? verifyMatch[1].trim() : ''; - if (!verifyContent) { - issues.push('"How did you verify your code works?" section is empty. Please explain how you tested.'); - } - } - - // Check: checklist boxes are checked - if (hasChecklistSection) { - const checklistMatch = body.match(/### Checklist\s*\n([\s\S]*?)(?=###|$)/); - const checklistContent = checklistMatch ? checklistMatch[1] : ''; - const unchecked = (checklistContent.match(/- \[ \]/g) || []).length; - const checked = (checklistContent.match(/- \[x\]/gi) || []).length; - if (checked < 2) { - issues.push('Not all checklist items are checked. Please confirm you have tested locally and have not included unrelated changes.'); - } - } - - // Helper functions - async function addLabel(label) { - await github.rest.issues.addLabels({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: pr.number, - labels: [label] - }); - } - - async function removeLabel(label) { - try { - await github.rest.issues.removeLabel({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: pr.number, - name: label - }); - } catch (e) {} - } - - const hasComplianceLabel = pr.labels.some(l => l.name === 'needs:compliance'); - - if (issues.length > 0) { - // Non-compliant - if (!hasComplianceLabel) { - await addLabel('needs:compliance'); - } - - const marker = ''; - const { data: comments } = await github.rest.issues.listComments({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: pr.number - }); - const existing = comments.find(c => c.body.includes(marker)); - - const body_text = `${marker} - This PR doesn't fully meet our [contributing guidelines](../blob/dev/CONTRIBUTING.md) and [PR template](../blob/dev/.github/pull_request_template.md). - - **What needs to be fixed:** - ${issues.map(i => `- ${i}`).join('\n')} - - Please edit this PR description to address the above within **2 hours**, or it will be automatically closed. - - If you believe this was flagged incorrectly, please let a maintainer know.`; - - if (existing) { - await github.rest.issues.updateComment({ - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: existing.id, - body: body_text - }); - } else { - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: pr.number, - body: body_text - }); - } - - console.log(`PR #${pr.number} is non-compliant: ${issues.join(', ')}`); - } else if (hasComplianceLabel) { - // Was non-compliant, now fixed - await removeLabel('needs:compliance'); - - const { data: comments } = await github.rest.issues.listComments({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: pr.number - }); - const marker = ''; - const existing = comments.find(c => c.body.includes(marker)); - if (existing) { - await github.rest.issues.deleteComment({ - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: existing.id - }); - } - - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: pr.number, - body: 'Thanks for updating your PR! It now meets our contributing guidelines. :+1:' - }); - - console.log(`PR #${pr.number} is now compliant, label removed`); - } else { - console.log(`PR #${pr.number} is compliant`); - } diff --git a/.github/workflows/publish-github-action.yml b/.github/workflows/publish-github-action.yml deleted file mode 100644 index d2789373a34..00000000000 --- a/.github/workflows/publish-github-action.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: publish-github-action - -on: - workflow_dispatch: - push: - tags: - - "github-v*.*.*" - - "!github-v1" - -concurrency: ${{ github.workflow }}-${{ github.ref }} - -permissions: - contents: write - -jobs: - publish: - runs-on: blacksmith-4vcpu-ubuntu-2404 - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - run: git fetch --force --tags - - - name: Publish - run: | - git config --global user.email "opencode@sst.dev" - git config --global user.name "opencode" - ./script/publish - working-directory: ./github diff --git a/.github/workflows/publish-vscode.yml b/.github/workflows/publish-vscode.yml deleted file mode 100644 index f49a1057807..00000000000 --- a/.github/workflows/publish-vscode.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: publish-vscode - -on: - workflow_dispatch: - push: - tags: - - "vscode-v*.*.*" - -concurrency: ${{ github.workflow }}-${{ github.ref }} - -permissions: - contents: write - -jobs: - publish: - runs-on: blacksmith-4vcpu-ubuntu-2404 - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - uses: ./.github/actions/setup-bun - - - run: git fetch --force --tags - - run: bun install -g @vscode/vsce - - - name: Install extension dependencies - run: bun install - working-directory: ./sdks/vscode - - - name: Publish - run: | - ./script/publish - working-directory: ./sdks/vscode - env: - VSCE_PAT: ${{ secrets.VSCE_PAT }} - OPENVSX_TOKEN: ${{ secrets.OPENVSX_TOKEN }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml deleted file mode 100644 index 8d4c9038a7e..00000000000 --- a/.github/workflows/publish.yml +++ /dev/null @@ -1,310 +0,0 @@ -name: publish -run-name: "${{ format('release {0}', inputs.bump) }}" - -on: - push: - branches: - - ci - - dev - - beta - - snapshot-* - workflow_dispatch: - inputs: - bump: - description: "Bump major, minor, or patch" - required: false - type: choice - options: - - major - - minor - - patch - version: - description: "Override version (optional)" - required: false - type: string - -concurrency: ${{ github.workflow }}-${{ github.ref }}-${{ inputs.version || inputs.bump }} - -permissions: - id-token: write - contents: write - packages: write - -jobs: - version: - runs-on: blacksmith-4vcpu-ubuntu-2404 - if: github.repository == 'anomalyco/opencode' - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - uses: ./.github/actions/setup-bun - - - name: Setup git committer - id: committer - uses: ./.github/actions/setup-git-committer - with: - opencode-app-id: ${{ vars.OPENCODE_APP_ID }} - opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }} - - - name: Install OpenCode - if: inputs.bump || inputs.version - run: bun i -g opencode-ai - - - id: version - run: | - ./script/version.ts - env: - GH_TOKEN: ${{ steps.committer.outputs.token }} - OPENCODE_BUMP: ${{ inputs.bump }} - OPENCODE_VERSION: ${{ inputs.version }} - OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} - GH_REPO: ${{ (github.ref_name == 'beta' && 'anomalyco/opencode-beta') || github.repository }} - outputs: - version: ${{ steps.version.outputs.version }} - release: ${{ steps.version.outputs.release }} - tag: ${{ steps.version.outputs.tag }} - repo: ${{ steps.version.outputs.repo }} - - build-cli: - needs: version - runs-on: blacksmith-4vcpu-ubuntu-2404 - if: github.repository == 'anomalyco/opencode' - steps: - - uses: actions/checkout@v3 - with: - fetch-tags: true - - - uses: ./.github/actions/setup-bun - - - name: Setup git committer - id: committer - uses: ./.github/actions/setup-git-committer - with: - opencode-app-id: ${{ vars.OPENCODE_APP_ID }} - opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }} - - - name: Build - id: build - run: | - ./packages/opencode/script/build.ts - env: - OPENCODE_VERSION: ${{ needs.version.outputs.version }} - OPENCODE_RELEASE: ${{ needs.version.outputs.release }} - GH_REPO: ${{ needs.version.outputs.repo }} - GH_TOKEN: ${{ steps.committer.outputs.token }} - - - uses: actions/upload-artifact@v4 - with: - name: opencode-cli - path: packages/opencode/dist - - outputs: - version: ${{ needs.version.outputs.version }} - - build-tauri: - needs: - - build-cli - - version - continue-on-error: false - strategy: - fail-fast: false - matrix: - settings: - - host: macos-latest - target: x86_64-apple-darwin - - host: macos-latest - target: aarch64-apple-darwin - - host: blacksmith-4vcpu-windows-2025 - target: x86_64-pc-windows-msvc - - host: blacksmith-4vcpu-ubuntu-2404 - target: x86_64-unknown-linux-gnu - - host: blacksmith-8vcpu-ubuntu-2404-arm - target: aarch64-unknown-linux-gnu - runs-on: ${{ matrix.settings.host }} - steps: - - uses: actions/checkout@v3 - with: - fetch-tags: true - - - uses: apple-actions/import-codesign-certs@v2 - if: ${{ runner.os == 'macOS' }} - with: - keychain: build - p12-file-base64: ${{ secrets.APPLE_CERTIFICATE }} - p12-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} - - - name: Verify Certificate - if: ${{ runner.os == 'macOS' }} - run: | - CERT_INFO=$(security find-identity -v -p codesigning build.keychain | grep "Developer ID Application") - CERT_ID=$(echo "$CERT_INFO" | awk -F'"' '{print $2}') - echo "CERT_ID=$CERT_ID" >> $GITHUB_ENV - echo "Certificate imported." - - - name: Setup Apple API Key - if: ${{ runner.os == 'macOS' }} - run: | - echo "${{ secrets.APPLE_API_KEY_PATH }}" > $RUNNER_TEMP/apple-api-key.p8 - - - uses: ./.github/actions/setup-bun - - - name: Cache apt packages - if: contains(matrix.settings.host, 'ubuntu') - uses: actions/cache@v4 - with: - path: ~/apt-cache - key: ${{ runner.os }}-${{ matrix.settings.target }}-apt-${{ hashFiles('.github/workflows/publish.yml') }} - restore-keys: | - ${{ runner.os }}-${{ matrix.settings.target }}-apt- - - - name: install dependencies (ubuntu only) - if: contains(matrix.settings.host, 'ubuntu') - run: | - mkdir -p ~/apt-cache && chmod -R a+rw ~/apt-cache - sudo apt-get update - sudo apt-get install -y --no-install-recommends -o dir::cache::archives="$HOME/apt-cache" libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf - sudo chmod -R a+rw ~/apt-cache - - - name: install Rust stable - uses: dtolnay/rust-toolchain@stable - with: - targets: ${{ matrix.settings.target }} - - - uses: Swatinem/rust-cache@v2 - with: - workspaces: packages/desktop/src-tauri - shared-key: ${{ matrix.settings.target }} - - - name: Prepare - run: | - cd packages/desktop - bun ./scripts/prepare.ts - env: - OPENCODE_VERSION: ${{ needs.version.outputs.version }} - GITHUB_TOKEN: ${{ steps.committer.outputs.token }} - RUST_TARGET: ${{ matrix.settings.target }} - GH_TOKEN: ${{ github.token }} - GITHUB_RUN_ID: ${{ github.run_id }} - - - name: Resolve tauri portable SHA - if: contains(matrix.settings.host, 'ubuntu') - run: echo "TAURI_PORTABLE_SHA=$(git ls-remote https://github.com/tauri-apps/tauri.git refs/heads/feat/truly-portable-appimage | cut -f1)" >> "$GITHUB_ENV" - - # Fixes AppImage build issues, can be removed when https://github.com/tauri-apps/tauri/pull/12491 is released - - name: Install tauri-cli from portable appimage branch - uses: taiki-e/cache-cargo-install-action@v3 - if: contains(matrix.settings.host, 'ubuntu') - with: - tool: tauri-cli - git: https://github.com/tauri-apps/tauri - # branch: feat/truly-portable-appimage - rev: ${{ env.TAURI_PORTABLE_SHA }} - - - name: Show tauri-cli version - if: contains(matrix.settings.host, 'ubuntu') - run: cargo tauri --version - - - name: Setup git committer - id: committer - uses: ./.github/actions/setup-git-committer - with: - opencode-app-id: ${{ vars.OPENCODE_APP_ID }} - opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }} - - - name: Build and upload artifacts - uses: tauri-apps/tauri-action@390cbe447412ced1303d35abe75287949e43437a - timeout-minutes: 60 - with: - projectPath: packages/desktop - uploadWorkflowArtifacts: true - tauriScript: ${{ (contains(matrix.settings.host, 'ubuntu') && 'cargo tauri') || '' }} - args: --target ${{ matrix.settings.target }} --config ${{ (github.ref_name == 'beta' && './src-tauri/tauri.beta.conf.json') || './src-tauri/tauri.prod.conf.json' }} --verbose - updaterJsonPreferNsis: true - releaseId: ${{ needs.version.outputs.release }} - tagName: ${{ needs.version.outputs.tag }} - releaseDraft: true - releaseAssetNamePattern: opencode-desktop-[platform]-[arch][ext] - repo: ${{ (github.ref_name == 'beta' && 'opencode-beta') || '' }} - releaseCommitish: ${{ github.sha }} - env: - GITHUB_TOKEN: ${{ steps.committer.outputs.token }} - TAURI_BUNDLER_NEW_APPIMAGE_FORMAT: true - TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} - TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} - APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} - APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} - APPLE_SIGNING_IDENTITY: ${{ env.CERT_ID }} - APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }} - APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }} - APPLE_API_KEY_PATH: ${{ runner.temp }}/apple-api-key.p8 - - publish: - needs: - - version - - build-cli - - build-tauri - runs-on: blacksmith-4vcpu-ubuntu-2404 - steps: - - uses: actions/checkout@v3 - - - uses: ./.github/actions/setup-bun - - - name: Login to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - uses: actions/setup-node@v4 - with: - node-version: "24" - registry-url: "https://registry.npmjs.org" - - - name: Setup git committer - id: committer - uses: ./.github/actions/setup-git-committer - with: - opencode-app-id: ${{ vars.OPENCODE_APP_ID }} - opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }} - - - uses: actions/download-artifact@v4 - with: - name: opencode-cli - path: packages/opencode/dist - - - name: Cache apt packages (AUR) - uses: actions/cache@v4 - with: - path: /var/cache/apt/archives - key: ${{ runner.os }}-apt-aur-${{ hashFiles('.github/workflows/publish.yml') }} - restore-keys: | - ${{ runner.os }}-apt-aur- - - - name: Setup SSH for AUR - run: | - sudo apt-get update - sudo apt-get install -y pacman-package-manager - mkdir -p ~/.ssh - echo "${{ secrets.AUR_KEY }}" > ~/.ssh/id_rsa - chmod 600 ~/.ssh/id_rsa - git config --global user.email "opencode@sst.dev" - git config --global user.name "opencode" - ssh-keyscan -H aur.archlinux.org >> ~/.ssh/known_hosts || true - - - run: ./script/publish.ts - env: - OPENCODE_VERSION: ${{ needs.version.outputs.version }} - OPENCODE_RELEASE: ${{ needs.version.outputs.release }} - AUR_KEY: ${{ secrets.AUR_KEY }} - GITHUB_TOKEN: ${{ steps.committer.outputs.token }} - GH_REPO: ${{ needs.version.outputs.repo }} - NPM_CONFIG_PROVENANCE: false diff --git a/.github/workflows/release-github-action.yml b/.github/workflows/release-github-action.yml deleted file mode 100644 index 3f5caa55c8d..00000000000 --- a/.github/workflows/release-github-action.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: release-github-action - -on: - push: - branches: - - dev - paths: - - "github/**" - -concurrency: ${{ github.workflow }}-${{ github.ref }} - -permissions: - contents: write - -jobs: - release: - runs-on: blacksmith-4vcpu-ubuntu-2404 - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - run: git fetch --force --tags - - - name: Release - run: | - git config --global user.email "opencode@sst.dev" - git config --global user.name "opencode" - ./github/script/release diff --git a/.github/workflows/review.yml b/.github/workflows/review.yml deleted file mode 100644 index 58e73fac8fb..00000000000 --- a/.github/workflows/review.yml +++ /dev/null @@ -1,83 +0,0 @@ -name: review - -on: - issue_comment: - types: [created] - -jobs: - check-guidelines: - if: | - github.event.issue.pull_request && - startsWith(github.event.comment.body, '/review') && - contains(fromJson('["OWNER","MEMBER"]'), github.event.comment.author_association) - runs-on: blacksmith-4vcpu-ubuntu-2404 - permissions: - contents: read - pull-requests: write - steps: - - name: Get PR number - id: pr-number - run: | - if [ "${{ github.event_name }}" = "pull_request_target" ]; then - echo "number=${{ github.event.pull_request.number }}" >> $GITHUB_OUTPUT - else - echo "number=${{ github.event.issue.number }}" >> $GITHUB_OUTPUT - fi - - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - - uses: ./.github/actions/setup-bun - - - name: Install opencode - run: curl -fsSL https://opencode.ai/install | bash - - - name: Get PR details - id: pr-details - run: | - gh api /repos/${{ github.repository }}/pulls/${{ steps.pr-number.outputs.number }} > pr_data.json - echo "title=$(jq -r .title pr_data.json)" >> $GITHUB_OUTPUT - echo "sha=$(jq -r .head.sha pr_data.json)" >> $GITHUB_OUTPUT - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Check PR guidelines compliance - env: - ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - OPENCODE_PERMISSION: '{ "bash": { "*": "deny", "gh*": "allow", "gh pr review*": "deny" } }' - PR_TITLE: ${{ steps.pr-details.outputs.title }} - run: | - PR_BODY=$(jq -r .body pr_data.json) - opencode run -m anthropic/claude-opus-4-5 "A new pull request has been created: '${PR_TITLE}' - - - ${{ steps.pr-number.outputs.number }} - - - - $PR_BODY - - - Please check all the code changes in this pull request against the style guide, also look for any bugs if they exist. Diffs are important but make sure you read the entire file to get proper context. Make it clear the suggestions are merely suggestions and the human can decide what to do - - When critiquing code against the style guide, be sure that the code is ACTUALLY in violation, don't complain about else statements if they already use early returns there. You may complain about excessive nesting though, regardless of else statement usage. - When critiquing code style don't be a zealot, we don't like "let" statements but sometimes they are the simplest option, if someone does a bunch of nesting with let, they should consider using iife (see packages/opencode/src/util.iife.ts) - - Use the gh cli to create comments on the files for the violations. Try to leave the comment on the exact line number. If you have a suggested fix include it in a suggestion code block. - If you are writing suggested fixes, BE SURE THAT the change you are recommending is actually valid typescript, often I have seen missing closing "}" or other syntax errors. - Generally, write a comment instead of writing suggested change if you can help it. - - Command MUST be like this. - \`\`\` - gh api \ - --method POST \ - -H \"Accept: application/vnd.github+json\" \ - -H \"X-GitHub-Api-Version: 2022-11-28\" \ - /repos/${{ github.repository }}/pulls/${{ steps.pr-number.outputs.number }}/comments \ - -f 'body=[summary of issue]' -f 'commit_id=${{ steps.pr-details.outputs.sha }}' -f 'path=[path-to-file]' -F \"line=[line]\" -f 'side=RIGHT' - \`\`\` - - Only create comments for actual violations. If the code follows all guidelines, comment on the issue using gh cli: 'lgtm' AND NOTHING ELSE!!!!." diff --git a/.github/workflows/sign-cli.yml b/.github/workflows/sign-cli.yml deleted file mode 100644 index d9d61fd800e..00000000000 --- a/.github/workflows/sign-cli.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: sign-cli - -on: - push: - branches: - - brendan/desktop-signpath - workflow_dispatch: - -permissions: - contents: read - actions: read - -jobs: - sign-cli: - runs-on: blacksmith-4vcpu-ubuntu-2404 - if: github.repository == 'anomalyco/opencode' - steps: - - uses: actions/checkout@v3 - with: - fetch-tags: true - - - uses: ./.github/actions/setup-bun - - - name: Build - run: | - ./packages/opencode/script/build.ts - - - name: Upload unsigned Windows CLI - id: upload_unsigned_windows_cli - uses: actions/upload-artifact@v4 - with: - name: unsigned-opencode-windows-cli - path: packages/opencode/dist/opencode-windows-x64/bin/opencode.exe - if-no-files-found: error - - - name: Submit SignPath signing request - id: submit_signpath_signing_request - uses: signpath/github-action-submit-signing-request@v1 - with: - api-token: ${{ secrets.SIGNPATH_API_KEY }} - organization-id: ${{ secrets.SIGNPATH_ORGANIZATION_ID }} - project-slug: ${{ secrets.SIGNPATH_PROJECT_SLUG }} - signing-policy-slug: ${{ secrets.SIGNPATH_SIGNING_POLICY_SLUG }} - artifact-configuration-slug: ${{ secrets.SIGNPATH_ARTIFACT_CONFIGURATION_SLUG }} - github-artifact-id: ${{ steps.upload_unsigned_windows_cli.outputs.artifact-id }} - wait-for-completion: true - output-artifact-directory: signed-opencode-cli - - - name: Upload signed Windows CLI - uses: actions/upload-artifact@v4 - with: - name: signed-opencode-windows-cli - path: signed-opencode-cli/*.exe - if-no-files-found: error diff --git a/.github/workflows/stale-issues.yml b/.github/workflows/stale-issues.yml deleted file mode 100644 index a4b8583f928..00000000000 --- a/.github/workflows/stale-issues.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: stale-issues - -on: - schedule: - - cron: "30 1 * * *" # Daily at 1:30 AM - workflow_dispatch: - -env: - DAYS_BEFORE_STALE: 90 - DAYS_BEFORE_CLOSE: 7 - -jobs: - stale: - runs-on: ubuntu-latest - permissions: - issues: write - steps: - - uses: actions/stale@v10 - with: - days-before-stale: ${{ env.DAYS_BEFORE_STALE }} - days-before-close: ${{ env.DAYS_BEFORE_CLOSE }} - stale-issue-label: "stale" - close-issue-message: | - [automated] Closing due to ${{ env.DAYS_BEFORE_STALE }}+ days of inactivity. - - Feel free to reopen if you still need this! - stale-issue-message: | - [automated] This issue has had no activity for ${{ env.DAYS_BEFORE_STALE }} days. - - It will be closed in ${{ env.DAYS_BEFORE_CLOSE }} days if there's no new activity. - remove-stale-when-updated: true - exempt-issue-labels: "pinned,security,feature-request,on-hold" - start-date: "2025-12-27" diff --git a/.github/workflows/stats.yml b/.github/workflows/stats.yml deleted file mode 100644 index 824733901d6..00000000000 --- a/.github/workflows/stats.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: stats - -on: - schedule: - - cron: "0 12 * * *" # Run daily at 12:00 UTC - workflow_dispatch: # Allow manual trigger - -concurrency: ${{ github.workflow }}-${{ github.ref }} - -jobs: - stats: - if: github.repository == 'anomalyco/opencode' - runs-on: blacksmith-4vcpu-ubuntu-2404 - permissions: - contents: write - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Bun - uses: ./.github/actions/setup-bun - - - name: Run stats script - run: bun script/stats.ts - - - name: Commit stats - run: | - git config --local user.email "action@github.com" - git config --local user.name "GitHub Action" - git add STATS.md - git diff --staged --quiet || git commit -m "ignore: update download stats $(date -I)" - git push - env: - POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }} diff --git a/.github/workflows/sync-zed-extension.yml b/.github/workflows/sync-zed-extension.yml deleted file mode 100644 index f14487cde97..00000000000 --- a/.github/workflows/sync-zed-extension.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: "sync-zed-extension" - -on: - workflow_dispatch: - release: - types: [published] - -jobs: - zed: - name: Release Zed Extension - runs-on: blacksmith-4vcpu-ubuntu-2404 - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - uses: ./.github/actions/setup-bun - - - name: Get version tag - id: get_tag - run: | - if [ "${{ github.event_name }}" = "release" ]; then - TAG="${{ github.event.release.tag_name }}" - else - TAG=$(git tag --list 'v[0-9]*.*' --sort=-version:refname | head -n 1) - fi - echo "tag=${TAG}" >> $GITHUB_OUTPUT - echo "Using tag: ${TAG}" - - - name: Sync Zed extension - run: | - ./script/sync-zed.ts ${{ steps.get_tag.outputs.tag }} - env: - ZED_EXTENSIONS_PAT: ${{ secrets.ZED_EXTENSIONS_PAT }} - ZED_PR_PAT: ${{ secrets.ZED_PR_PAT }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index f7b00516f8d..00000000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,103 +0,0 @@ -name: test - -on: - push: - branches: - - dev - pull_request: - workflow_dispatch: -jobs: - unit: - name: unit (${{ matrix.settings.name }}) - strategy: - fail-fast: false - matrix: - settings: - - name: linux - host: blacksmith-4vcpu-ubuntu-2404 - - name: windows - host: blacksmith-4vcpu-windows-2025 - runs-on: ${{ matrix.settings.host }} - defaults: - run: - shell: bash - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Setup Bun - uses: ./.github/actions/setup-bun - - - name: Configure git identity - run: | - git config --global user.email "bot@opencode.ai" - git config --global user.name "opencode" - - - name: Run unit tests - run: bun turbo test - - e2e: - name: e2e (${{ matrix.settings.name }}) - needs: unit - strategy: - fail-fast: false - matrix: - settings: - - name: linux - host: blacksmith-4vcpu-ubuntu-2404 - playwright: bunx playwright install --with-deps - - name: windows - host: blacksmith-4vcpu-windows-2025 - playwright: bunx playwright install - runs-on: ${{ matrix.settings.host }} - env: - PLAYWRIGHT_BROWSERS_PATH: 0 - defaults: - run: - shell: bash - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Setup Bun - uses: ./.github/actions/setup-bun - - - name: Install Playwright browsers - working-directory: packages/app - run: ${{ matrix.settings.playwright }} - - - name: Run app e2e tests - run: bun --cwd packages/app test:e2e:local - env: - CI: true - timeout-minutes: 30 - - - name: Upload Playwright artifacts - if: failure() - uses: actions/upload-artifact@v4 - with: - name: playwright-${{ matrix.settings.name }}-${{ github.run_attempt }} - if-no-files-found: ignore - retention-days: 7 - path: | - packages/app/e2e/test-results - packages/app/e2e/playwright-report - - required: - name: test (linux) - runs-on: blacksmith-4vcpu-ubuntu-2404 - needs: - - unit - - e2e - if: always() - steps: - - name: Verify upstream test jobs passed - run: | - echo "unit=${{ needs.unit.result }}" - echo "e2e=${{ needs.e2e.result }}" - test "${{ needs.unit.result }}" = "success" - test "${{ needs.e2e.result }}" = "success" diff --git a/.github/workflows/triage.yml b/.github/workflows/triage.yml deleted file mode 100644 index 99e7b5b34fe..00000000000 --- a/.github/workflows/triage.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: triage - -on: - issues: - types: [opened] - -jobs: - triage: - runs-on: blacksmith-4vcpu-ubuntu-2404 - permissions: - contents: read - issues: write - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - - name: Setup Bun - uses: ./.github/actions/setup-bun - - - name: Install opencode - run: curl -fsSL https://opencode.ai/install | bash - - - name: Triage issue - env: - OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - ISSUE_NUMBER: ${{ github.event.issue.number }} - ISSUE_TITLE: ${{ github.event.issue.title }} - ISSUE_BODY: ${{ github.event.issue.body }} - run: | - opencode run --agent triage "The following issue was just opened, triage it: - - Title: $ISSUE_TITLE - - $ISSUE_BODY" diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml deleted file mode 100644 index b247d24b40d..00000000000 --- a/.github/workflows/typecheck.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: typecheck - -on: - push: - branches: [dev] - pull_request: - branches: [dev] - workflow_dispatch: - -jobs: - typecheck: - runs-on: blacksmith-4vcpu-ubuntu-2404 - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Setup Bun - uses: ./.github/actions/setup-bun - - - name: Run typecheck - run: bun typecheck diff --git a/.github/workflows/vouch-check-issue.yml b/.github/workflows/vouch-check-issue.yml deleted file mode 100644 index 4c2aa960b2a..00000000000 --- a/.github/workflows/vouch-check-issue.yml +++ /dev/null @@ -1,116 +0,0 @@ -name: vouch-check-issue - -on: - issues: - types: [opened] - -permissions: - contents: read - issues: write - -jobs: - check: - runs-on: ubuntu-latest - steps: - - name: Check if issue author is denounced - uses: actions/github-script@v7 - with: - script: | - const author = context.payload.issue.user.login; - const issueNumber = context.payload.issue.number; - - // Skip bots - if (author.endsWith('[bot]')) { - core.info(`Skipping bot: ${author}`); - return; - } - - // Read the VOUCHED.td file via API (no checkout needed) - let content; - try { - const response = await github.rest.repos.getContent({ - owner: context.repo.owner, - repo: context.repo.repo, - path: '.github/VOUCHED.td', - }); - content = Buffer.from(response.data.content, 'base64').toString('utf-8'); - } catch (error) { - if (error.status === 404) { - core.info('No .github/VOUCHED.td file found, skipping check.'); - return; - } - throw error; - } - - // Parse the .td file for vouched and denounced users - const vouched = new Set(); - const denounced = new Map(); - for (const line of content.split('\n')) { - const trimmed = line.trim(); - if (!trimmed || trimmed.startsWith('#')) continue; - - const isDenounced = trimmed.startsWith('-'); - const rest = isDenounced ? trimmed.slice(1).trim() : trimmed; - if (!rest) continue; - - const spaceIdx = rest.indexOf(' '); - const handle = spaceIdx === -1 ? rest : rest.slice(0, spaceIdx); - const reason = spaceIdx === -1 ? null : rest.slice(spaceIdx + 1).trim(); - - // Handle platform:username or bare username - // Only match bare usernames or github: prefix (skip other platforms) - const colonIdx = handle.indexOf(':'); - if (colonIdx !== -1) { - const platform = handle.slice(0, colonIdx).toLowerCase(); - if (platform !== 'github') continue; - } - const username = colonIdx === -1 ? handle : handle.slice(colonIdx + 1); - if (!username) continue; - - if (isDenounced) { - denounced.set(username.toLowerCase(), reason); - continue; - } - - vouched.add(username.toLowerCase()); - } - - // Check if the author is denounced - const reason = denounced.get(author.toLowerCase()); - if (reason !== undefined) { - // Author is denounced — close the issue - const body = 'This issue has been automatically closed.'; - - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issueNumber, - body, - }); - - await github.rest.issues.update({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issueNumber, - state: 'closed', - state_reason: 'not_planned', - }); - - core.info(`Closed issue #${issueNumber} from denounced user ${author}`); - return; - } - - // Author is positively vouched — add label - if (!vouched.has(author.toLowerCase())) { - core.info(`User ${author} is not denounced or vouched. Allowing issue.`); - return; - } - - await github.rest.issues.addLabels({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issueNumber, - labels: ['Vouched'], - }); - - core.info(`Added vouched label to issue #${issueNumber} from ${author}`); diff --git a/.github/workflows/vouch-check-pr.yml b/.github/workflows/vouch-check-pr.yml deleted file mode 100644 index 51816dfb759..00000000000 --- a/.github/workflows/vouch-check-pr.yml +++ /dev/null @@ -1,114 +0,0 @@ -name: vouch-check-pr - -on: - pull_request_target: - types: [opened] - -permissions: - contents: read - issues: write - pull-requests: write - -jobs: - check: - runs-on: ubuntu-latest - steps: - - name: Check if PR author is denounced - uses: actions/github-script@v7 - with: - script: | - const author = context.payload.pull_request.user.login; - const prNumber = context.payload.pull_request.number; - - // Skip bots - if (author.endsWith('[bot]')) { - core.info(`Skipping bot: ${author}`); - return; - } - - // Read the VOUCHED.td file via API (no checkout needed) - let content; - try { - const response = await github.rest.repos.getContent({ - owner: context.repo.owner, - repo: context.repo.repo, - path: '.github/VOUCHED.td', - }); - content = Buffer.from(response.data.content, 'base64').toString('utf-8'); - } catch (error) { - if (error.status === 404) { - core.info('No .github/VOUCHED.td file found, skipping check.'); - return; - } - throw error; - } - - // Parse the .td file for vouched and denounced users - const vouched = new Set(); - const denounced = new Map(); - for (const line of content.split('\n')) { - const trimmed = line.trim(); - if (!trimmed || trimmed.startsWith('#')) continue; - - const isDenounced = trimmed.startsWith('-'); - const rest = isDenounced ? trimmed.slice(1).trim() : trimmed; - if (!rest) continue; - - const spaceIdx = rest.indexOf(' '); - const handle = spaceIdx === -1 ? rest : rest.slice(0, spaceIdx); - const reason = spaceIdx === -1 ? null : rest.slice(spaceIdx + 1).trim(); - - // Handle platform:username or bare username - // Only match bare usernames or github: prefix (skip other platforms) - const colonIdx = handle.indexOf(':'); - if (colonIdx !== -1) { - const platform = handle.slice(0, colonIdx).toLowerCase(); - if (platform !== 'github') continue; - } - const username = colonIdx === -1 ? handle : handle.slice(colonIdx + 1); - if (!username) continue; - - if (isDenounced) { - denounced.set(username.toLowerCase(), reason); - continue; - } - - vouched.add(username.toLowerCase()); - } - - // Check if the author is denounced - const reason = denounced.get(author.toLowerCase()); - if (reason !== undefined) { - // Author is denounced — close the PR - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: prNumber, - body: 'This pull request has been automatically closed.', - }); - - await github.rest.pulls.update({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: prNumber, - state: 'closed', - }); - - core.info(`Closed PR #${prNumber} from denounced user ${author}`); - return; - } - - // Author is positively vouched — add label - if (!vouched.has(author.toLowerCase())) { - core.info(`User ${author} is not denounced or vouched. Allowing PR.`); - return; - } - - await github.rest.issues.addLabels({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: prNumber, - labels: ['Vouched'], - }); - - core.info(`Added vouched label to PR #${prNumber} from ${author}`); diff --git a/.github/workflows/vouch-manage-by-issue.yml b/.github/workflows/vouch-manage-by-issue.yml deleted file mode 100644 index 9604bf87f37..00000000000 --- a/.github/workflows/vouch-manage-by-issue.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: vouch-manage-by-issue - -on: - issue_comment: - types: [created] - -concurrency: - group: vouch-manage - cancel-in-progress: false - -permissions: - contents: write - issues: write - pull-requests: read - -jobs: - manage: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - persist-credentials: false - fetch-depth: 0 - - - name: Setup git committer - id: committer - uses: ./.github/actions/setup-git-committer - with: - opencode-app-id: ${{ vars.OPENCODE_APP_ID }} - opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }} - - - uses: mitchellh/vouch/action/manage-by-issue@main - with: - issue-id: ${{ github.event.issue.number }} - comment-id: ${{ github.event.comment.id }} - roles: admin,maintain - env: - GITHUB_TOKEN: ${{ steps.committer.outputs.token }} diff --git a/.opencode/glossary/tr.md b/.opencode/glossary/tr.md new file mode 100644 index 00000000000..72b1cdfb40b --- /dev/null +++ b/.opencode/glossary/tr.md @@ -0,0 +1,38 @@ +# tr Glossary + +## Sources + +- PR #15835: https://github.com/anomalyco/opencode/pull/15835 + +## Do Not Translate (Locale Additions) + +- `OpenCode` (preserve casing in prose, docs, and UI copy) +- Keep lowercase `opencode` in commands, package names, paths, URLs, and other exact identifiers +- `` stays the literal key token in code blocks; use `Tab` for the nearby explanatory label in prose +- Commands, flags, file paths, and code literals (keep exactly as written) + +## Preferred Terms + +These are PR-backed wording preferences and may evolve. + +| English / Context | Preferred | Notes | +| ------------------------- | --------------------------------------- | ------------------------------------------------------------- | +| available in beta | `beta olarak mevcut` | Prefer this over `beta olarak kullanılabilir` | +| privacy-first | `Gizlilik öncelikli tasarlandı` | Prefer this over `Önce gizlilik için tasarlandı` | +| connect your local models | `yerel modellerinizi bağlayabilirsiniz` | Use the fuller, more direct action phrase | +| `` key label | `Tab` | Use `Tab` in prose; keep `` in literal UI or code blocks | +| cross-platform | `cross-platform (tüm platformlarda)` | Keep the English term, add a short clarification when helpful | + +## Guidance + +- Prefer natural Turkish phrasing over literal translation +- Merge broken sentence fragments into one clear sentence when the source is a single thought +- Keep product naming consistent: `OpenCode` in prose, `opencode` only for exact technical identifiers +- When an English technical term is intentionally kept, add a short Turkish clarification only if it improves readability + +## Avoid + +- Avoid `beta olarak kullanılabilir` when `beta olarak mevcut` fits +- Avoid `Önce gizlilik için tasarlandı`; use the more natural reviewed wording instead +- Avoid `Sekme` for the translated key label in prose when referring to `` +- Avoid changing `opencode` to `OpenCode` inside commands, URLs, package names, or code literals diff --git a/AGENTS.md b/AGENTS.md index 758714d10aa..2158d73af1b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -20,6 +20,17 @@ Prefer single word names for variables and functions. Only use multiple words if necessary. +### Naming Enforcement (Read This) + +THIS RULE IS MANDATORY FOR AGENT WRITTEN CODE. + +- Use single word names by default for new locals, params, and helper functions. +- Multi-word names are allowed only when a single word would be unclear or ambiguous. +- Do not introduce new camelCase compounds when a short single-word alternative is clear. +- Before finishing edits, review touched lines and shorten newly introduced identifiers where possible. +- Good short names to prefer: `pid`, `cfg`, `err`, `opts`, `dir`, `root`, `child`, `state`, `timeout`. +- Examples to avoid unless truly required: `inputPID`, `existingClient`, `connectTimeout`, `workerPath`. + ```ts // Good const foo = 1 diff --git a/bun.lock b/bun.lock index 8df1d6456c2..badb0410ab8 100644 --- a/bun.lock +++ b/bun.lock @@ -25,7 +25,7 @@ }, "packages/app": { "name": "@opencode-ai/app", - "version": "1.2.15", + "version": "1.2.16", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -75,7 +75,7 @@ }, "packages/console/app": { "name": "@opencode-ai/console-app", - "version": "1.2.15", + "version": "1.2.16", "dependencies": { "@cloudflare/vite-plugin": "1.15.2", "@ibm/plex": "6.4.1", @@ -109,7 +109,7 @@ }, "packages/console/core": { "name": "@opencode-ai/console-core", - "version": "1.2.15", + "version": "1.2.16", "dependencies": { "@aws-sdk/client-sts": "3.782.0", "@jsx-email/render": "1.1.1", @@ -136,7 +136,7 @@ }, "packages/console/function": { "name": "@opencode-ai/console-function", - "version": "1.2.15", + "version": "1.2.16", "dependencies": { "@ai-sdk/anthropic": "2.0.0", "@ai-sdk/openai": "2.0.2", @@ -160,7 +160,7 @@ }, "packages/console/mail": { "name": "@opencode-ai/console-mail", - "version": "1.2.15", + "version": "1.2.16", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", @@ -184,7 +184,7 @@ }, "packages/desktop": { "name": "@opencode-ai/desktop", - "version": "1.2.15", + "version": "1.2.16", "dependencies": { "@opencode-ai/app": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -215,9 +215,39 @@ "vite": "catalog:", }, }, + "packages/desktop-electron": { + "name": "@opencode-ai/desktop-electron", + "version": "1.2.6", + "dependencies": { + "@opencode-ai/app": "workspace:*", + "@opencode-ai/ui": "workspace:*", + "@solid-primitives/i18n": "2.2.1", + "@solid-primitives/storage": "catalog:", + "@solidjs/meta": "catalog:", + "@solidjs/router": "0.15.4", + "electron-log": "^5", + "electron-store": "^10", + "electron-updater": "^6", + "electron-window-state": "^5.0.3", + "marked": "^15", + "solid-js": "catalog:", + "tree-kill": "^1.2.2", + }, + "devDependencies": { + "@actions/artifact": "4.0.0", + "@types/bun": "catalog:", + "@types/node": "catalog:", + "@typescript/native-preview": "catalog:", + "electron": "40.4.1", + "electron-builder": "^26", + "electron-vite": "^5", + "typescript": "~5.6.2", + "vite": "catalog:", + }, + }, "packages/enterprise": { "name": "@opencode-ai/enterprise", - "version": "1.2.15", + "version": "1.2.16", "dependencies": { "@opencode-ai/ui": "workspace:*", "@opencode-ai/util": "workspace:*", @@ -246,7 +276,7 @@ }, "packages/function": { "name": "@opencode-ai/function", - "version": "1.2.15", + "version": "1.2.16", "dependencies": { "@octokit/auth-app": "8.0.1", "@octokit/rest": "catalog:", @@ -262,7 +292,7 @@ }, "packages/opencode": { "name": "opencode", - "version": "1.2.15", + "version": "1.2.16", "bin": { "opencode": "./bin/opencode", }, @@ -304,8 +334,8 @@ "@opencode-ai/sdk": "workspace:*", "@opencode-ai/util": "workspace:*", "@openrouter/ai-sdk-provider": "1.5.4", - "@opentui/core": "0.1.81", - "@opentui/solid": "0.1.81", + "@opentui/core": "0.1.86", + "@opentui/solid": "0.1.86", "@parcel/watcher": "2.5.1", "@pierre/diffs": "catalog:", "@solid-primitives/event-bus": "1.1.2", @@ -376,7 +406,7 @@ }, "packages/plugin": { "name": "@opencode-ai/plugin", - "version": "1.2.15", + "version": "1.2.16", "dependencies": { "@opencode-ai/sdk": "workspace:*", "zod": "catalog:", @@ -396,7 +426,7 @@ }, "packages/sdk/js": { "name": "@opencode-ai/sdk", - "version": "1.2.15", + "version": "1.2.16", "devDependencies": { "@hey-api/openapi-ts": "0.90.10", "@tsconfig/node22": "catalog:", @@ -407,7 +437,7 @@ }, "packages/slack": { "name": "@opencode-ai/slack", - "version": "1.2.15", + "version": "1.2.16", "dependencies": { "@opencode-ai/sdk": "workspace:*", "@slack/bolt": "^3.17.1", @@ -423,17 +453,18 @@ "devDependencies": { "@opencode-ai/ui": "workspace:*", "@solidjs/meta": "catalog:", - "@storybook/addon-a11y": "^10.2.10", - "@storybook/addon-docs": "^10.2.10", - "@storybook/addon-links": "^10.2.10", - "@storybook/addon-onboarding": "^10.2.10", - "@storybook/addon-vitest": "^10.2.10", + "@storybook/addon-a11y": "^10.2.13", + "@storybook/addon-docs": "^10.2.13", + "@storybook/addon-links": "^10.2.13", + "@storybook/addon-onboarding": "^10.2.13", + "@storybook/addon-vitest": "^10.2.13", + "@tailwindcss/vite": "catalog:", "@tsconfig/node22": "catalog:", "@types/node": "catalog:", "@types/react": "18.0.25", "react": "18.2.0", "solid-js": "catalog:", - "storybook": "^10.2.10", + "storybook": "^10.2.13", "storybook-solidjs-vite": "^10.0.9", "typescript": "catalog:", "vite": "catalog:", @@ -441,7 +472,7 @@ }, "packages/ui": { "name": "@opencode-ai/ui", - "version": "1.2.15", + "version": "1.2.16", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -452,7 +483,6 @@ "@solid-primitives/media": "2.3.3", "@solid-primitives/resize-observer": "2.1.3", "@solidjs/meta": "catalog:", - "@typescript/native-preview": "catalog:", "dompurify": "3.3.1", "fuzzysort": "catalog:", "katex": "0.16.27", @@ -461,6 +491,9 @@ "marked-katex-extension": "5.1.6", "marked-shiki": "catalog:", "morphdom": "2.7.8", + "motion": "12.34.5", + "motion-dom": "12.34.3", + "motion-utils": "12.29.2", "remeda": "catalog:", "shiki": "catalog:", "solid-js": "catalog:", @@ -474,6 +507,7 @@ "@types/bun": "catalog:", "@types/katex": "0.16.7", "@types/luxon": "catalog:", + "@typescript/native-preview": "catalog:", "tailwindcss": "catalog:", "typescript": "catalog:", "vite": "catalog:", @@ -483,7 +517,7 @@ }, "packages/util": { "name": "@opencode-ai/util", - "version": "1.2.15", + "version": "1.2.16", "dependencies": { "zod": "catalog:", }, @@ -494,7 +528,7 @@ }, "packages/web": { "name": "@opencode-ai/web", - "version": "1.2.15", + "version": "1.2.16", "dependencies": { "@astrojs/cloudflare": "12.6.3", "@astrojs/markdown-remark": "6.3.1", @@ -527,6 +561,7 @@ }, }, "trustedDependencies": [ + "electron", "esbuild", "web-tree-sitter", "tree-sitter-bash", @@ -583,6 +618,8 @@ "zod": "4.1.8", }, "packages": { + "7zip-bin": ["7zip-bin@5.2.0", "", {}, "sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A=="], + "@actions/artifact": ["@actions/artifact@5.0.1", "", { "dependencies": { "@actions/core": "^2.0.0", "@actions/github": "^6.0.1", "@actions/http-client": "^3.0.0", "@azure/storage-blob": "^12.29.1", "@octokit/core": "^5.2.1", "@octokit/plugin-request-log": "^1.0.4", "@octokit/plugin-retry": "^3.0.9", "@octokit/request": "^8.4.1", "@octokit/request-error": "^5.1.1", "@protobuf-ts/plugin": "^2.2.3-alpha.1", "archiver": "^7.0.1", "jwt-decode": "^3.1.2", "unzip-stream": "^0.3.1" } }, "sha512-dHJ5rHduhCKUikKTT9eXeWoUvfKia3IjR1sO/VTAV3DVAL4yMTRnl2iO5mcfiBjySHLwPNezwENAVskKYU5ymw=="], "@actions/core": ["@actions/core@1.11.1", "", { "dependencies": { "@actions/exec": "^1.1.1", "@actions/http-client": "^2.0.1" } }, "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A=="], @@ -855,6 +892,8 @@ "@babel/plugin-syntax-typescript": ["@babel/plugin-syntax-typescript@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A=="], + "@babel/plugin-transform-arrow-functions": ["@babel/plugin-transform-arrow-functions@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA=="], + "@babel/plugin-transform-modules-commonjs": ["@babel/plugin-transform-modules-commonjs@7.28.6", "", { "dependencies": { "@babel/helper-module-transforms": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA=="], "@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="], @@ -907,12 +946,30 @@ "@ctrl/tinycolor": ["@ctrl/tinycolor@4.2.0", "", {}, "sha512-kzyuwOAQnXJNLS9PSyrk0CWk35nWJW/zl/6KvnTBMFK65gm7U1/Z5BqjxeapjZCIhQcM/DsrEmcbRwDyXyXK4A=="], + "@develar/schema-utils": ["@develar/schema-utils@2.6.5", "", { "dependencies": { "ajv": "^6.12.0", "ajv-keywords": "^3.4.1" } }, "sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig=="], + "@dimforge/rapier2d-simd-compat": ["@dimforge/rapier2d-simd-compat@0.17.3", "", {}, "sha512-bijvwWz6NHsNj5e5i1vtd3dU2pDhthSaTUZSh14DUGGKJfw8eMnlWZsxwHBxB/a3AXVNDjL9abuHw1k9FGR+jg=="], "@dot/log": ["@dot/log@0.1.5", "", { "dependencies": { "chalk": "^4.1.2", "loglevelnext": "^6.0.0", "p-defer": "^3.0.0" } }, "sha512-ECraEVJWv2f2mWK93lYiefUkphStVlKD6yKDzisuoEmxuLKrxO9iGetHK2DoEAkj7sxjE886n0OUVVCUx0YPNg=="], "@drizzle-team/brocli": ["@drizzle-team/brocli@0.11.0", "", {}, "sha512-hD3pekGiPg0WPCCGAZmusBBJsDqGUR66Y452YgQsZOnkdQ7ViEPKuyP4huUGEZQefp8g34RRodXYmJ2TbCH+tg=="], + "@electron/asar": ["@electron/asar@3.4.1", "", { "dependencies": { "commander": "^5.0.0", "glob": "^7.1.6", "minimatch": "^3.0.4" }, "bin": { "asar": "bin/asar.js" } }, "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA=="], + + "@electron/fuses": ["@electron/fuses@1.8.0", "", { "dependencies": { "chalk": "^4.1.1", "fs-extra": "^9.0.1", "minimist": "^1.2.5" }, "bin": { "electron-fuses": "dist/bin.js" } }, "sha512-zx0EIq78WlY/lBb1uXlziZmDZI4ubcCXIMJ4uGjXzZW0nS19TjSPeXPAjzzTmKQlJUZm0SbmZhPKP7tuQ1SsEw=="], + + "@electron/get": ["@electron/get@2.0.3", "", { "dependencies": { "debug": "^4.1.1", "env-paths": "^2.2.0", "fs-extra": "^8.1.0", "got": "^11.8.5", "progress": "^2.0.3", "semver": "^6.2.0", "sumchecker": "^3.0.1" }, "optionalDependencies": { "global-agent": "^3.0.0" } }, "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ=="], + + "@electron/notarize": ["@electron/notarize@2.5.0", "", { "dependencies": { "debug": "^4.1.1", "fs-extra": "^9.0.1", "promise-retry": "^2.0.1" } }, "sha512-jNT8nwH1f9X5GEITXaQ8IF/KdskvIkOFfB2CvwumsveVidzpSc+mvhhTMdAGSYF3O+Nq49lJ7y+ssODRXu06+A=="], + + "@electron/osx-sign": ["@electron/osx-sign@1.3.3", "", { "dependencies": { "compare-version": "^0.1.2", "debug": "^4.3.4", "fs-extra": "^10.0.0", "isbinaryfile": "^4.0.8", "minimist": "^1.2.6", "plist": "^3.0.5" }, "bin": { "electron-osx-flat": "bin/electron-osx-flat.js", "electron-osx-sign": "bin/electron-osx-sign.js" } }, "sha512-KZ8mhXvWv2rIEgMbWZ4y33bDHyUKMXnx4M0sTyPNK/vcB81ImdeY9Ggdqy0SWbMDgmbqyQ+phgejh6V3R2QuSg=="], + + "@electron/rebuild": ["@electron/rebuild@4.0.3", "", { "dependencies": { "@malept/cross-spawn-promise": "^2.0.0", "debug": "^4.1.1", "detect-libc": "^2.0.1", "got": "^11.7.0", "graceful-fs": "^4.2.11", "node-abi": "^4.2.0", "node-api-version": "^0.2.1", "node-gyp": "^11.2.0", "ora": "^5.1.0", "read-binary-file-arch": "^1.0.6", "semver": "^7.3.5", "tar": "^7.5.6", "yargs": "^17.0.1" }, "bin": { "electron-rebuild": "lib/cli.js" } }, "sha512-u9vpTHRMkOYCs/1FLiSVAFZ7FbjsXK+bQuzviJZa+lG7BHZl1nz52/IcGvwa3sk80/fc3llutBkbCq10Vh8WQA=="], + + "@electron/universal": ["@electron/universal@2.0.3", "", { "dependencies": { "@electron/asar": "^3.3.1", "@malept/cross-spawn-promise": "^2.0.0", "debug": "^4.3.1", "dir-compare": "^4.2.0", "fs-extra": "^11.1.1", "minimatch": "^9.0.3", "plist": "^3.1.0" } }, "sha512-Wn9sPYIVFRFl5HmwMJkARCCf7rqK/EurkfQ/rJZ14mHP3iYTjZSIOSVonEAnhWeAXwtw7zOekGRlc6yTtZ0t+g=="], + + "@electron/windows-sign": ["@electron/windows-sign@1.2.2", "", { "dependencies": { "cross-dirname": "^0.1.0", "debug": "^4.3.4", "fs-extra": "^11.1.1", "minimist": "^1.2.8", "postject": "^1.0.0-alpha.6" }, "bin": { "electron-windows-sign": "bin/electron-windows-sign.js" } }, "sha512-dfZeox66AvdPtb2lD8OsIIQh12Tp0GNCRUDfBHIKGpbmopZto2/A8nSpYYLoedPIHpqkeblZ/k8OV0Gy7PYuyQ=="], + "@emmetio/abbreviation": ["@emmetio/abbreviation@2.3.3", "", { "dependencies": { "@emmetio/scanner": "^1.0.4" } }, "sha512-mgv58UrU3rh4YgbE/TzgLQwJ3pFsHHhCLqY20aJq+9comytTXUDNGG/SMtSeMJdkpxgXSXunBGLD8Boka3JyVA=="], "@emmetio/css-abbreviation": ["@emmetio/css-abbreviation@2.1.8", "", { "dependencies": { "@emmetio/scanner": "^1.0.4" } }, "sha512-s9yjhJ6saOO/uk1V74eifykk2CBYi01STTK3WlXWGOepyKa23ymJ053+DNQjpFcy1ingpaO7AxCcwLvHFY9tuw=="], @@ -1229,6 +1286,10 @@ "@lukeed/ms": ["@lukeed/ms@2.0.2", "", {}, "sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA=="], + "@malept/cross-spawn-promise": ["@malept/cross-spawn-promise@2.0.0", "", { "dependencies": { "cross-spawn": "^7.0.1" } }, "sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg=="], + + "@malept/flatpak-bundler": ["@malept/flatpak-bundler@0.4.0", "", { "dependencies": { "debug": "^4.1.1", "fs-extra": "^9.0.0", "lodash": "^4.17.15", "tmp-promise": "^3.0.2" } }, "sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q=="], + "@mdx-js/mdx": ["@mdx-js/mdx@3.1.1", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdx": "^2.0.0", "acorn": "^8.0.0", "collapse-white-space": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "estree-util-scope": "^1.0.0", "estree-walker": "^3.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "markdown-extensions": "^2.0.0", "recma-build-jsx": "^1.0.0", "recma-jsx": "^1.0.0", "recma-stringify": "^1.0.0", "rehype-recma": "^1.0.0", "remark-mdx": "^3.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "source-map": "^0.7.0", "unified": "^11.0.0", "unist-util-position-from-estree": "^2.0.0", "unist-util-stringify-position": "^4.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ=="], "@mdx-js/react": ["@mdx-js/react@3.1.1", "", { "dependencies": { "@types/mdx": "^2.0.0" }, "peerDependencies": { "@types/react": ">=16", "react": ">=16" } }, "sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw=="], @@ -1257,6 +1318,10 @@ "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], + "@npmcli/agent": ["@npmcli/agent@3.0.0", "", { "dependencies": { "agent-base": "^7.1.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.1", "lru-cache": "^10.0.1", "socks-proxy-agent": "^8.0.3" } }, "sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q=="], + + "@npmcli/fs": ["@npmcli/fs@4.0.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q=="], + "@octokit/auth-app": ["@octokit/auth-app@8.0.1", "", { "dependencies": { "@octokit/auth-oauth-app": "^9.0.1", "@octokit/auth-oauth-user": "^6.0.0", "@octokit/request": "^10.0.2", "@octokit/request-error": "^7.0.0", "@octokit/types": "^14.0.0", "toad-cache": "^3.7.0", "universal-github-app-jwt": "^2.2.0", "universal-user-agent": "^7.0.0" } }, "sha512-P2J5pB3pjiGwtJX4WqJVYCtNkcZ+j5T2Wm14aJAEIC3WJOrv12jvBley3G1U/XI8q9o1A7QMG54LiFED2BiFlg=="], "@octokit/auth-oauth-app": ["@octokit/auth-oauth-app@9.0.3", "", { "dependencies": { "@octokit/auth-oauth-device": "^8.0.3", "@octokit/auth-oauth-user": "^6.0.2", "@octokit/request": "^10.0.6", "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.0" } }, "sha512-+yoFQquaF8OxJSxTb7rnytBIC2ZLbLqA/yb71I4ZXT9+Slw4TziV9j/kyGhUFRRTF2+7WlnIWsePZCWHs+OGjg=="], @@ -1315,6 +1380,8 @@ "@opencode-ai/desktop": ["@opencode-ai/desktop@workspace:packages/desktop"], + "@opencode-ai/desktop-electron": ["@opencode-ai/desktop-electron@workspace:packages/desktop-electron"], + "@opencode-ai/enterprise": ["@opencode-ai/enterprise@workspace:packages/enterprise"], "@opencode-ai/function": ["@opencode-ai/function@workspace:packages/function"], @@ -1341,21 +1408,21 @@ "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], - "@opentui/core": ["@opentui/core@0.1.81", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "marked": "17.0.1", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.81", "@opentui/core-darwin-x64": "0.1.81", "@opentui/core-linux-arm64": "0.1.81", "@opentui/core-linux-x64": "0.1.81", "@opentui/core-win32-arm64": "0.1.81", "@opentui/core-win32-x64": "0.1.81", "bun-webgpu": "0.1.5", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-ooFjkkQ80DDC4X5eLvH8dBcLAtWwGp9RTaWsaeWet3GOv4N0SDcN8mi1XGhYnUlTuxmofby5eQrPegjtWHODlA=="], + "@opentui/core": ["@opentui/core@0.1.86", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "marked": "17.0.1", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.86", "@opentui/core-darwin-x64": "0.1.86", "@opentui/core-linux-arm64": "0.1.86", "@opentui/core-linux-x64": "0.1.86", "@opentui/core-win32-arm64": "0.1.86", "@opentui/core-win32-x64": "0.1.86", "bun-webgpu": "0.1.5", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-3tRLbI9ADrQE1jEEn4x2aJexEOQZkv9Emk2BixMZqxfVhz2zr2SxtpimDAX0vmZK3+GnWAwBWxuaCAsxZpY4+w=="], - "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.81", "", { "os": "darwin", "cpu": "arm64" }, "sha512-I3Ry5JbkSQXs2g1me8yYr0v3CUcIIfLHzbWz9WMFla8kQDSa+HOr8IpZbqZDeIFgOVzolAXBmZhg0VJI3bZ7MA=="], + "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.86", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Zp7q64+d+Dcx6YrH3mRcnHq8EOBnrfc1RvjgSWLhpXr49hY6LzuhqpfZM57aGErPYlR+ff8QM6e5FUkFnDfyjw=="], - "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.81", "", { "os": "darwin", "cpu": "x64" }, "sha512-CrtNKu41D6+bOQdUOmDX4Q3hTL6p+sT55wugPzbDq7cdqFZabCeguBAyOlvRl2g2aJ93kmOWW6MXG0bPPklEFg=="], + "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.86", "", { "os": "darwin", "cpu": "x64" }, "sha512-NcxfjCJm1kLnTMVOpAPdRYNi8W8XdAXNa6N7i9khiVFrl2v5KRQfUjbrSOUYVxFJNc3jKFG6rsn3jEApvn92qA=="], - "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.81", "", { "os": "linux", "cpu": "arm64" }, "sha512-FJw9zmJop9WiMvtT07nSrfBLPLqskxL6xfV3GNft0mSYV+C3hdJ0qkiczGSHUX/6V7fmouM84RWwmY53Rb6hYQ=="], + "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.86", "", { "os": "linux", "cpu": "arm64" }, "sha512-EDHAvqSOr8CXzbDvo1aE5blJ6wu1aSbR2LqoXtoeXHemr2T2W42D2TdIWewG6K+/BuRbzZnqt9wnYFBksLW6lw=="], - "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.81", "", { "os": "linux", "cpu": "x64" }, "sha512-Rj2AFIiuWI0BEMIvh/Jeuxty9Gp5ZhLuQU7ZHJJhojKo/mpBpMs9X+5kwZPZya/tyR8uVDAVyB6AOLkhdRW5lw=="], + "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.86", "", { "os": "linux", "cpu": "x64" }, "sha512-VBaBkVdQDxYV4WcKjb+jgyMS5PiVHepvfaoKWpz1Bq+J01xXW4XPcXyPGkgR1+2R93KzaugEnLscTW4mWtLHlQ=="], - "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.81", "", { "os": "win32", "cpu": "arm64" }, "sha512-AiZB+mZ1cVr8plAPrPT98e3kw6D0OdOSe2CQYLgJRbfRlPqq3jl26lHPzDb3ZO2OR0oVGRPJvXraus939mvoiQ=="], + "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.86", "", { "os": "win32", "cpu": "arm64" }, "sha512-xKbT7sEKYKGwUPkoqmLfHjbJU+vwHPDwf/r/mIunL41JXQBB35CSZ3/QgIwpp2kkteu7oE1tdBdg15ogUU4OMg=="], - "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.81", "", { "os": "win32", "cpu": "x64" }, "sha512-l8R2Ni1CR4eHi3DTmSkEL/EjHAtOZ/sndYs3VVw+Ej2esL3Mf0W7qSO5S0YNBanz2VXZhbkmM6ERm9keH8RD3w=="], + "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.86", "", { "os": "win32", "cpu": "x64" }, "sha512-HRfgAUlcu71/MrtgfX4Gj7PsDtfXZiuC506Pkn1OnRN1Xomcu10BVRDweUa0/g8ldU9i9kLjMGGnpw6/NjaBFg=="], - "@opentui/solid": ["@opentui/solid@0.1.81", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.81", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-QRjS0wPuIhBRdY8tpG3yprCM4ZnOxWWHTuaZ4hhia2wFZygf7Ome6EuZnLXmtuOQjkjCwu0if8Yik6toc6QylA=="], + "@opentui/solid": ["@opentui/solid@0.1.86", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.86", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-pOZC9dlZIH+bpstVVZ2AvYukBnslZTKSl/y5H8FWcMTHGv/BzpGxXBxstL65E/IQASqPFbvFcs7yMRzdLhynmA=="], "@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="], @@ -1631,7 +1698,7 @@ "@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="], - "@sindresorhus/is": ["@sindresorhus/is@7.2.0", "", {}, "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw=="], + "@sindresorhus/is": ["@sindresorhus/is@4.6.0", "", {}, "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw=="], "@slack/bolt": ["@slack/bolt@3.22.0", "", { "dependencies": { "@slack/logger": "^4.0.0", "@slack/oauth": "^2.6.3", "@slack/socket-mode": "^1.3.6", "@slack/types": "^2.13.0", "@slack/web-api": "^6.13.0", "@types/express": "^4.16.1", "@types/promise.allsettled": "^1.0.3", "@types/tsscmp": "^1.0.0", "axios": "^1.7.4", "express": "^4.21.0", "path-to-regexp": "^8.1.0", "promise.allsettled": "^1.0.2", "raw-body": "^2.3.3", "tsscmp": "^1.0.6" } }, "sha512-iKDqGPEJDnrVwxSVlFW6OKTkijd7s4qLBeSufoBsTM0reTyfdp/5izIQVkxNfzjHi3o6qjdYbRXkYad5HBsBog=="], @@ -1803,30 +1870,32 @@ "@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="], - "@storybook/addon-a11y": ["@storybook/addon-a11y@10.2.10", "", { "dependencies": { "@storybook/global": "^5.0.0", "axe-core": "^4.2.0" }, "peerDependencies": { "storybook": "^10.2.10" } }, "sha512-1S9pDXgvbHhBStGarCvfJ3/rfcaiAcQHRhuM3Nk4WGSIYtC1LCSRuzYdDYU0aNRpdCbCrUA7kUCbqvIE3tH+3Q=="], + "@storybook/addon-a11y": ["@storybook/addon-a11y@10.2.13", "", { "dependencies": { "@storybook/global": "^5.0.0", "axe-core": "^4.2.0" }, "peerDependencies": { "storybook": "^10.2.13" } }, "sha512-zuR1n1xgWoieEnr6E5xdTR40BI61IBQahgmsRpTvqRffL3mxAs5aFoORDmA5pZWI2LE9URdMkY85h218ijuLiw=="], - "@storybook/addon-docs": ["@storybook/addon-docs@10.2.10", "", { "dependencies": { "@mdx-js/react": "^3.0.0", "@storybook/csf-plugin": "10.2.10", "@storybook/icons": "^2.0.1", "@storybook/react-dom-shim": "10.2.10", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "ts-dedent": "^2.0.0" }, "peerDependencies": { "storybook": "^10.2.10" } }, "sha512-2wIYtdvZIzPbQ5194M5Igpy8faNbQ135nuO5ZaZ2VuttqGr+IJcGnDP42zYwbAsGs28G8ohpkbSgIzVyJWUhPQ=="], + "@storybook/addon-docs": ["@storybook/addon-docs@10.2.13", "", { "dependencies": { "@mdx-js/react": "^3.0.0", "@storybook/csf-plugin": "10.2.13", "@storybook/icons": "^2.0.1", "@storybook/react-dom-shim": "10.2.13", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "ts-dedent": "^2.0.0" }, "peerDependencies": { "storybook": "^10.2.13" } }, "sha512-puMxpJbt/CuodLIbKDxWrW1ZgADYomfNHWEKp2d2l2eJjp17rADx0h3PABuNbX+YHbJwYcDdqluSnQwMysFEOA=="], - "@storybook/addon-links": ["@storybook/addon-links@10.2.10", "", { "dependencies": { "@storybook/global": "^5.0.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "storybook": "^10.2.10" }, "optionalPeers": ["react"] }, "sha512-oo9Xx4/2OVJtptXKpqH4ySri7ZuBdiSOXlZVGejEfLa0Jeajlh/KIlREpGvzPPOqUVT7dSddWzBjJmJUyQC3ew=="], + "@storybook/addon-links": ["@storybook/addon-links@10.2.13", "", { "dependencies": { "@storybook/global": "^5.0.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "storybook": "^10.2.13" }, "optionalPeers": ["react"] }, "sha512-8wnAomGiHaUpNIc+lOzmazTrebxa64z9rihIbM/Q59vkOImHQNkGp7KP/qNgJA4GPTFtu8+fLjX2qCoAQPM0jQ=="], - "@storybook/addon-onboarding": ["@storybook/addon-onboarding@10.2.10", "", { "peerDependencies": { "storybook": "^10.2.10" } }, "sha512-DkzZQTXHp99SpHMIQ5plbbHcs4EWVzWhLXlW+icA8sBlKo5Bwj540YcOApKbqB0m/OzWprsznwN7Kv4vfvHu4w=="], + "@storybook/addon-onboarding": ["@storybook/addon-onboarding@10.2.13", "", { "peerDependencies": { "storybook": "^10.2.13" } }, "sha512-kw2GgIY67UR8YXKfuVS0k+mfWL1joNQHeSe5DlDL4+7qbgp9zfV6cRJ199BMdfRAQNMzQoxHgRUcAMAqs3Rkpw=="], - "@storybook/addon-vitest": ["@storybook/addon-vitest@10.2.10", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^2.0.1" }, "peerDependencies": { "@vitest/browser": "^3.0.0 || ^4.0.0", "@vitest/browser-playwright": "^4.0.0", "@vitest/runner": "^3.0.0 || ^4.0.0", "storybook": "^10.2.10", "vitest": "^3.0.0 || ^4.0.0" }, "optionalPeers": ["@vitest/browser", "@vitest/browser-playwright", "@vitest/runner", "vitest"] }, "sha512-U2oHw+Ar+Xd06wDTB74VlujhIIW89OHThpJjwgqgM6NWrOC/XLllJ53ILFDyREBkMwpBD7gJQIoQpLEcKBIEhw=="], + "@storybook/addon-vitest": ["@storybook/addon-vitest@10.2.13", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^2.0.1" }, "peerDependencies": { "@vitest/browser": "^3.0.0 || ^4.0.0", "@vitest/browser-playwright": "^4.0.0", "@vitest/runner": "^3.0.0 || ^4.0.0", "storybook": "^10.2.13", "vitest": "^3.0.0 || ^4.0.0" }, "optionalPeers": ["@vitest/browser", "@vitest/browser-playwright", "@vitest/runner", "vitest"] }, "sha512-qQD3xzxc31cQHS0loF9enGWi5sgA6zBTbaJ0HuSUNGO81iwfLSALh8L/1vrD5NfN2vlBeUMTsgv3EkCuLfe9EQ=="], "@storybook/builder-vite": ["@storybook/builder-vite@10.2.10", "", { "dependencies": { "@storybook/csf-plugin": "10.2.10", "ts-dedent": "^2.0.0" }, "peerDependencies": { "storybook": "^10.2.10", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-Wd6CYL7LvRRNiXMz977x9u/qMm7nmMw/7Dow2BybQo+Xbfy1KhVjIoZ/gOiG515zpojSozctNrJUbM0+jH1jwg=="], - "@storybook/csf-plugin": ["@storybook/csf-plugin@10.2.10", "", { "dependencies": { "unplugin": "^2.3.5" }, "peerDependencies": { "esbuild": "*", "rollup": "*", "storybook": "^10.2.10", "vite": "*", "webpack": "*" }, "optionalPeers": ["esbuild", "rollup", "vite", "webpack"] }, "sha512-aFvgaNDAnKMjuyhPK5ialT22pPqMN0XfPBNPeeNVPYztngkdKBa8WFqF/umDd47HxAjebq+vn6uId1xHyOHH3g=="], + "@storybook/csf-plugin": ["@storybook/csf-plugin@10.2.13", "", { "dependencies": { "unplugin": "^2.3.5" }, "peerDependencies": { "esbuild": "*", "rollup": "*", "storybook": "^10.2.13", "vite": "*", "webpack": "*" }, "optionalPeers": ["esbuild", "rollup", "vite", "webpack"] }, "sha512-gUCR7PmyrWYj3dIJJgxOm25dcXFolPIUPmug3z90Aaon7YPXw3pUN+dNDx8KqDJqRK1WDIB4HaefgYZIm5V7iA=="], "@storybook/global": ["@storybook/global@5.0.0", "", {}, "sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ=="], "@storybook/icons": ["@storybook/icons@2.0.1", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-/smVjw88yK3CKsiuR71vNgWQ9+NuY2L+e8X7IMrFjexjm6ZR8ULrV2DRkTA61aV6ryefslzHEGDInGpnNeIocg=="], - "@storybook/react-dom-shim": ["@storybook/react-dom-shim@10.2.10", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "storybook": "^10.2.10" } }, "sha512-TmBrhyLHn8B8rvDHKk5uW5BqzO1M1T+fqFNWg88NIAJOoyX4Uc90FIJjDuN1OJmWKGwB5vLmPwaKBYsTe1yS+w=="], + "@storybook/react-dom-shim": ["@storybook/react-dom-shim@10.2.13", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "storybook": "^10.2.13" } }, "sha512-ZSduoB10qTI0V9z22qeULmQLsvTs8d/rtJi03qbVxpPiMRor86AmyAaBrfhGGmWBxWQZpOGQQm6yIT2YLoPs7w=="], "@stripe/stripe-js": ["@stripe/stripe-js@8.6.1", "", {}, "sha512-UJ05U2062XDgydbUcETH1AoRQLNhigQ2KmDn1BG8sC3xfzu6JKg95Qt6YozdzFpxl1Npii/02m2LEWFt1RYjVA=="], "@swc/helpers": ["@swc/helpers@0.5.18", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ=="], + "@szmarczak/http-timer": ["@szmarczak/http-timer@4.0.6", "", { "dependencies": { "defer-to-connect": "^2.0.0" } }, "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w=="], + "@tailwindcss/node": ["@tailwindcss/node@4.1.11", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "enhanced-resolve": "^5.18.1", "jiti": "^2.4.2", "lightningcss": "1.30.1", "magic-string": "^0.30.17", "source-map-js": "^1.2.1", "tailwindcss": "4.1.11" } }, "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q=="], "@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.11", "", { "dependencies": { "detect-libc": "^2.0.4", "tar": "^7.4.3" }, "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.11", "@tailwindcss/oxide-darwin-arm64": "4.1.11", "@tailwindcss/oxide-darwin-x64": "4.1.11", "@tailwindcss/oxide-freebsd-x64": "4.1.11", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.11", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.11", "@tailwindcss/oxide-linux-arm64-musl": "4.1.11", "@tailwindcss/oxide-linux-x64-gnu": "4.1.11", "@tailwindcss/oxide-linux-x64-musl": "4.1.11", "@tailwindcss/oxide-wasm32-wasi": "4.1.11", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.11", "@tailwindcss/oxide-win32-x64-msvc": "4.1.11" } }, "sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg=="], @@ -1947,6 +2016,8 @@ "@types/bun": ["@types/bun@1.3.9", "", { "dependencies": { "bun-types": "1.3.9" } }, "sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw=="], + "@types/cacheable-request": ["@types/cacheable-request@6.0.3", "", { "dependencies": { "@types/http-cache-semantics": "*", "@types/keyv": "^3.1.4", "@types/node": "*", "@types/responselike": "^1.0.0" } }, "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw=="], + "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="], "@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="], @@ -1965,8 +2036,12 @@ "@types/fontkit": ["@types/fontkit@2.0.8", "", { "dependencies": { "@types/node": "*" } }, "sha512-wN+8bYxIpJf+5oZdrdtaX04qUuWHcKxcDEgRS9Qm9ZClSHjzEn13SxUC+5eRM+4yXIeTYk8mTzLAWGF64847ew=="], + "@types/fs-extra": ["@types/fs-extra@9.0.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA=="], + "@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="], + "@types/http-cache-semantics": ["@types/http-cache-semantics@4.2.0", "", {}, "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q=="], + "@types/http-errors": ["@types/http-errors@2.0.5", "", {}, "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg=="], "@types/is-stream": ["@types/is-stream@1.1.0", "", { "dependencies": { "@types/node": "*" } }, "sha512-jkZatu4QVbR60mpIzjINmtS1ZF4a/FqdTUTBeQDVOQ2PYyidtwFKr0B5G6ERukKwliq+7mIXvxyppwzG5EgRYg=="], @@ -1979,6 +2054,8 @@ "@types/katex": ["@types/katex@0.16.7", "", {}, "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ=="], + "@types/keyv": ["@types/keyv@3.1.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg=="], + "@types/luxon": ["@types/luxon@3.7.1", "", {}, "sha512-H3iskjFIAn5SlJU7OuxUmTEpebK6TKB8rxZShDslBMZJ5u9S//KM1sbdAisiSrqwLQncVjnpi2OK2J51h+4lsg=="], "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="], @@ -2001,6 +2078,8 @@ "@types/node-fetch": ["@types/node-fetch@2.6.13", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.4" } }, "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw=="], + "@types/plist": ["@types/plist@3.0.5", "", { "dependencies": { "@types/node": "*", "xmlbuilder": ">=11.0.1" } }, "sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA=="], + "@types/promise.allsettled": ["@types/promise.allsettled@1.0.6", "", {}, "sha512-wA0UT0HeT2fGHzIFV9kWpYz5mdoyLxKrTgMdZQM++5h6pYAFH73HXcQhefg24nD1yivUFEn5KU+EF4b+CXJ4Wg=="], "@types/prop-types": ["@types/prop-types@15.7.15", "", {}, "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw=="], @@ -2013,6 +2092,8 @@ "@types/readable-stream": ["@types/readable-stream@4.0.23", "", { "dependencies": { "@types/node": "*" } }, "sha512-wwXrtQvbMHxCbBgjHaMGEmImFTQxxpfMOR/ZoQnXxB1woqkUbdLGFDgauo00Py9IudiaqSeiBiulSV9i6XIPig=="], + "@types/responselike": ["@types/responselike@1.0.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw=="], + "@types/retry": ["@types/retry@0.12.0", "", {}, "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA=="], "@types/sax": ["@types/sax@1.2.7", "", { "dependencies": { "@types/node": "*" } }, "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A=="], @@ -2033,6 +2114,8 @@ "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], + "@types/verror": ["@types/verror@1.10.11", "", {}, "sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg=="], + "@types/whatwg-mimetype": ["@types/whatwg-mimetype@3.0.2", "", {}, "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA=="], "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], @@ -2041,6 +2124,8 @@ "@types/yargs-parser": ["@types/yargs-parser@21.0.3", "", {}, "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ=="], + "@types/yauzl": ["@types/yauzl@2.10.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q=="], + "@typescript/native-preview": ["@typescript/native-preview@7.0.0-dev.20251207.1", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20251207.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20251207.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20251207.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20251207.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20251207.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20251207.1", "@typescript/native-preview-win32-x64": "7.0.0-dev.20251207.1" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-4QcRnzB0pi9rS0AOvg8kWbmuwHv5X7B2EXHbgcms9+56hsZ8SZrZjNgBJb2rUIodJ4kU5mrkj/xlTTT4r9VcpQ=="], "@typescript/native-preview-darwin-arm64": ["@typescript/native-preview-darwin-arm64@7.0.0-dev.20251207.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-waWJnuuvkXh4WdpbTjYf7pyahJzx0ycesV2BylyHrE9OxU9FSKcD/cRLQYvbq3YcBSdF7sZwRLDBer7qTeLsYA=="], @@ -2099,6 +2184,8 @@ "@webgpu/types": ["@webgpu/types@0.1.54", "", {}, "sha512-81oaalC8LFrXjhsczomEQ0u3jG+TqE6V9QHLA8GNZq/Rnot0KDugu3LhSYSlie8tSdooAN1Hov05asrUUp9qgg=="], + "@xmldom/xmldom": ["@xmldom/xmldom@0.8.11", "", {}, "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw=="], + "@zip.js/zip.js": ["@zip.js/zip.js@2.7.62", "", {}, "sha512-OaLvZ8j4gCkLn048ypkZu29KX30r8/OfFF2w4Jo5WXFr+J04J+lzJ5TKZBVgFXhlvSkqNFQdfnY1Q8TMTCyBVA=="], "abbrev": ["abbrev@2.0.0", "", {}, "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ=="], @@ -2129,6 +2216,8 @@ "ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="], + "ajv-keywords": ["ajv-keywords@3.5.2", "", { "peerDependencies": { "ajv": "^6.9.1" } }, "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ=="], + "ansi-align": ["ansi-align@3.0.1", "", { "dependencies": { "string-width": "^4.1.0" } }, "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w=="], "ansi-colors": ["ansi-colors@4.1.3", "", {}, "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="], @@ -2145,6 +2234,10 @@ "anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="], + "app-builder-bin": ["app-builder-bin@5.0.0-alpha.12", "", {}, "sha512-j87o0j6LqPL3QRr8yid6c+Tt5gC7xNfYo6uQIQkorAC6MpeayVMZrEDzKmJJ/Hlv7EnOQpaRm53k6ktDYZyB6w=="], + + "app-builder-lib": ["app-builder-lib@26.8.1", "", { "dependencies": { "@develar/schema-utils": "~2.6.5", "@electron/asar": "3.4.1", "@electron/fuses": "^1.8.0", "@electron/get": "^3.0.0", "@electron/notarize": "2.5.0", "@electron/osx-sign": "1.3.3", "@electron/rebuild": "^4.0.3", "@electron/universal": "2.0.3", "@malept/flatpak-bundler": "^0.4.0", "@types/fs-extra": "9.0.13", "async-exit-hook": "^2.0.1", "builder-util": "26.8.1", "builder-util-runtime": "9.5.1", "chromium-pickle-js": "^0.2.0", "ci-info": "4.3.1", "debug": "^4.3.4", "dotenv": "^16.4.5", "dotenv-expand": "^11.0.6", "ejs": "^3.1.8", "electron-publish": "26.8.1", "fs-extra": "^10.1.0", "hosted-git-info": "^4.1.0", "isbinaryfile": "^5.0.0", "jiti": "^2.4.2", "js-yaml": "^4.1.0", "json5": "^2.2.3", "lazy-val": "^1.0.5", "minimatch": "^10.0.3", "plist": "3.1.0", "proper-lockfile": "^4.1.2", "resedit": "^1.7.0", "semver": "~7.7.3", "tar": "^7.5.7", "temp-file": "^3.4.0", "tiny-async-pool": "1.3.0", "which": "^5.0.0" }, "peerDependencies": { "dmg-builder": "26.8.1", "electron-builder-squirrel-windows": "26.8.1" } }, "sha512-p0Im/Dx5C4tmz8QEE1Yn4MkuPC8PrnlRneMhWJj7BBXQfNTJUshM/bp3lusdEsDbvvfJZpXWnYesgSLvwtM2Zw=="], + "archiver": ["archiver@7.0.1", "", { "dependencies": { "archiver-utils": "^5.0.2", "async": "^3.2.4", "buffer-crc32": "^1.0.0", "readable-stream": "^4.0.0", "readdir-glob": "^1.1.2", "tar-stream": "^3.0.0", "zip-stream": "^6.0.1" } }, "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ=="], "archiver-utils": ["archiver-utils@5.0.2", "", { "dependencies": { "glob": "^10.0.0", "graceful-fs": "^4.2.0", "is-stream": "^2.0.1", "lazystream": "^1.0.0", "lodash": "^4.17.15", "normalize-path": "^3.0.0", "readable-stream": "^4.0.0" } }, "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA=="], @@ -2171,10 +2264,14 @@ "arraybuffer.prototype.slice": ["arraybuffer.prototype.slice@1.0.4", "", { "dependencies": { "array-buffer-byte-length": "^1.0.1", "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "is-array-buffer": "^3.0.4" } }, "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ=="], + "assert-plus": ["assert-plus@1.0.0", "", {}, "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw=="], + "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], "ast-types": ["ast-types@0.16.1", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg=="], + "astral-regex": ["astral-regex@2.0.0", "", {}, "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ=="], + "astring": ["astring@1.9.0", "", { "bin": { "astring": "bin/astring" } }, "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg=="], "astro": ["astro@5.7.13", "", { "dependencies": { "@astrojs/compiler": "^2.11.0", "@astrojs/internal-helpers": "0.6.1", "@astrojs/markdown-remark": "6.3.1", "@astrojs/telemetry": "3.2.1", "@capsizecss/unpack": "^2.4.0", "@oslojs/encoding": "^1.1.0", "@rollup/pluginutils": "^5.1.4", "acorn": "^8.14.1", "aria-query": "^5.3.2", "axobject-query": "^4.1.0", "boxen": "8.0.1", "ci-info": "^4.2.0", "clsx": "^2.1.1", "common-ancestor-path": "^1.0.1", "cookie": "^1.0.2", "cssesc": "^3.0.0", "debug": "^4.4.0", "deterministic-object-hash": "^2.0.2", "devalue": "^5.1.1", "diff": "^5.2.0", "dlv": "^1.1.3", "dset": "^3.1.4", "es-module-lexer": "^1.6.0", "esbuild": "^0.25.0", "estree-walker": "^3.0.3", "flattie": "^1.1.1", "fontace": "~0.3.0", "github-slugger": "^2.0.0", "html-escaper": "3.0.3", "http-cache-semantics": "^4.1.1", "js-yaml": "^4.1.0", "kleur": "^4.1.5", "magic-string": "^0.30.17", "magicast": "^0.3.5", "mrmime": "^2.0.1", "neotraverse": "^0.6.18", "p-limit": "^6.2.0", "p-queue": "^8.1.0", "package-manager-detector": "^1.1.0", "picomatch": "^4.0.2", "prompts": "^2.4.2", "rehype": "^13.0.2", "semver": "^7.7.1", "shiki": "^3.2.1", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.12", "tsconfck": "^3.1.5", "ultrahtml": "^1.6.0", "unifont": "~0.5.0", "unist-util-visit": "^5.0.0", "unstorage": "^1.15.0", "vfile": "^6.0.3", "vite": "^6.3.4", "vitefu": "^1.0.6", "xxhash-wasm": "^1.1.0", "yargs-parser": "^21.1.1", "yocto-spinner": "^0.2.1", "zod": "^3.24.2", "zod-to-json-schema": "^3.24.5", "zod-to-ts": "^1.2.0" }, "optionalDependencies": { "sharp": "^0.33.3" }, "bin": { "astro": "astro.js" } }, "sha512-cRGq2llKOhV3XMcYwQpfBIUcssN6HEK5CRbcMxAfd9OcFhvWE7KUy50zLioAZVVl3AqgUTJoNTlmZfD2eG0G1w=="], @@ -2183,12 +2280,18 @@ "async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="], + "async-exit-hook": ["async-exit-hook@2.0.1", "", {}, "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw=="], + "async-function": ["async-function@1.0.0", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="], "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], + "at-least-node": ["at-least-node@1.0.0", "", {}, "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg=="], + "atomic-sleep": ["atomic-sleep@1.0.0", "", {}, "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="], + "atomically": ["atomically@2.1.1", "", { "dependencies": { "stubborn-fs": "^2.0.0", "when-exit": "^2.1.4" } }, "sha512-P4w9o2dqARji6P7MHprklbfiArZAWvo07yW7qs3pdljb3BWr12FIB7W+p0zJiuiVsUpRO0iZn1kFFcpPegg0tQ=="], + "autoprefixer": ["autoprefixer@10.4.24", "", { "dependencies": { "browserslist": "^4.28.1", "caniuse-lite": "^1.0.30001766", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-uHZg7N9ULTVbutaIsDRoUkoS8/h3bdsmVJYZ5l3wv8Cp/6UIIoRDm90hZ+BwxUj/hGBEzLxdHNSKuFpn8WOyZw=="], "available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="], @@ -2257,6 +2360,8 @@ "boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="], + "boolean": ["boolean@3.2.0", "", {}, "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw=="], + "bottleneck": ["bottleneck@2.19.5", "", {}, "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw=="], "bowser": ["bowser@2.14.1", "", {}, "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg=="], @@ -2281,6 +2386,10 @@ "buffers": ["buffers@0.1.1", "", {}, "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ=="], + "builder-util": ["builder-util@26.8.1", "", { "dependencies": { "7zip-bin": "~5.2.0", "@types/debug": "^4.1.6", "app-builder-bin": "5.0.0-alpha.12", "builder-util-runtime": "9.5.1", "chalk": "^4.1.2", "cross-spawn": "^7.0.6", "debug": "^4.3.4", "fs-extra": "^10.1.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "js-yaml": "^4.1.0", "sanitize-filename": "^1.6.3", "source-map-support": "^0.5.19", "stat-mode": "^1.0.0", "temp-file": "^3.4.0", "tiny-async-pool": "1.3.0" } }, "sha512-pm1lTYbGyc90DHgCDO7eo8Rl4EqKLciayNbZqGziqnH9jrlKe8ZANGdityLZU+pJh16dfzjAx2xQq9McuIPEtw=="], + + "builder-util-runtime": ["builder-util-runtime@9.5.1", "", { "dependencies": { "debug": "^4.3.4", "sax": "^1.2.4" } }, "sha512-qt41tMfgHTllhResqM5DcnHyDIWNgzHvuY2jDcYP9iaGpkWxTUzV6GQjDeLnlR1/DtdlcsWQbA7sByMpmJFTLQ=="], + "bun-ffi-structs": ["bun-ffi-structs@0.1.2", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-Lh1oQAYHDcnesJauieA4UNkWGXY9hYck7OA5IaRwE3Bp6K2F2pJSNYqq+hIy7P3uOvo3km3oxS8304g5gDMl/w=="], "bun-pty": ["bun-pty@0.4.8", "", {}, "sha512-rO70Mrbr13+jxHHHu2YBkk2pNqrJE5cJn29WE++PUr+GFA0hq/VgtQPZANJ8dJo6d7XImvBk37Innt8GM7O28w=="], @@ -2303,6 +2412,14 @@ "c12": ["c12@3.3.3", "", { "dependencies": { "chokidar": "^5.0.0", "confbox": "^0.2.2", "defu": "^6.1.4", "dotenv": "^17.2.3", "exsolve": "^1.0.8", "giget": "^2.0.0", "jiti": "^2.6.1", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^2.0.0", "pkg-types": "^2.3.0", "rc9": "^2.1.2" }, "peerDependencies": { "magicast": "*" }, "optionalPeers": ["magicast"] }, "sha512-750hTRvgBy5kcMNPdh95Qo+XUBeGo8C7nsKSmedDmaQI+E0r82DwHeM6vBewDe4rGFbnxoa4V9pw+sPh5+Iz8Q=="], + "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], + + "cacache": ["cacache@19.0.1", "", { "dependencies": { "@npmcli/fs": "^4.0.0", "fs-minipass": "^3.0.0", "glob": "^10.2.2", "lru-cache": "^10.0.1", "minipass": "^7.0.3", "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "p-map": "^7.0.2", "ssri": "^12.0.0", "tar": "^7.4.3", "unique-filename": "^4.0.0" } }, "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ=="], + + "cacheable-lookup": ["cacheable-lookup@5.0.4", "", {}, "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA=="], + + "cacheable-request": ["cacheable-request@7.0.4", "", { "dependencies": { "clone-response": "^1.0.2", "get-stream": "^5.1.0", "http-cache-semantics": "^4.0.0", "keyv": "^4.0.0", "lowercase-keys": "^2.0.0", "normalize-url": "^6.0.1", "responselike": "^2.0.0" } }, "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg=="], + "call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="], "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], @@ -2345,6 +2462,8 @@ "chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], + "chromium-pickle-js": ["chromium-pickle-js@0.2.0", "", {}, "sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw=="], + "ci-info": ["ci-info@4.4.0", "", {}, "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg=="], "citty": ["citty@0.1.6", "", { "dependencies": { "consola": "^3.2.3" } }, "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ=="], @@ -2355,14 +2474,20 @@ "cli-boxes": ["cli-boxes@3.0.0", "", {}, "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g=="], + "cli-cursor": ["cli-cursor@3.1.0", "", { "dependencies": { "restore-cursor": "^3.1.0" } }, "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw=="], + "cli-spinners": ["cli-spinners@3.4.0", "", {}, "sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw=="], + "cli-truncate": ["cli-truncate@2.1.0", "", { "dependencies": { "slice-ansi": "^3.0.0", "string-width": "^4.2.0" } }, "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg=="], + "clipboardy": ["clipboardy@4.0.0", "", { "dependencies": { "execa": "^8.0.1", "is-wsl": "^3.1.0", "is64bit": "^2.0.0" } }, "sha512-5mOlNS0mhX0707P2I0aZ2V/cmHUEO/fL7VFLqszkhUsxt7RwnmrInf/eEQKlf5GzvYeHIjT+Ov1HRfNmymlG0w=="], "cliui": ["cliui@9.0.1", "", { "dependencies": { "string-width": "^7.2.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" } }, "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w=="], "clone": ["clone@2.1.2", "", {}, "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w=="], + "clone-response": ["clone-response@1.0.3", "", { "dependencies": { "mimic-response": "^1.0.0" } }, "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA=="], + "cloudflare": ["cloudflare@5.2.0", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" } }, "sha512-dVzqDpPFYR9ApEC9e+JJshFJZXcw4HzM8W+3DHzO5oy9+8rLC53G7x6fEf9A7/gSuSCxuvndzui5qJKftfIM9A=="], "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], @@ -2387,10 +2512,16 @@ "common-ancestor-path": ["common-ancestor-path@1.0.1", "", {}, "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w=="], + "compare-version": ["compare-version@0.1.2", "", {}, "sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A=="], + "compress-commons": ["compress-commons@6.0.2", "", { "dependencies": { "crc-32": "^1.2.0", "crc32-stream": "^6.0.0", "is-stream": "^2.0.1", "normalize-path": "^3.0.0", "readable-stream": "^4.0.0" } }, "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg=="], + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + "condense-newlines": ["condense-newlines@0.2.1", "", { "dependencies": { "extend-shallow": "^2.0.1", "is-whitespace": "^0.3.0", "kind-of": "^3.0.2" } }, "sha512-P7X+QL9Hb9B/c8HI5BFFKmjgBu2XpQuF98WZ9XkO+dBGgk5XgwiQz7o1SmpglNWId3581UcS0SFAWfoIhMHPfg=="], + "conf": ["conf@14.0.0", "", { "dependencies": { "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "atomically": "^2.0.3", "debounce-fn": "^6.0.0", "dot-prop": "^9.0.0", "env-paths": "^3.0.0", "json-schema-typed": "^8.0.1", "semver": "^7.7.2", "uint8array-extras": "^1.4.0" } }, "sha512-L6BuueHTRuJHQvQVc6YXYZRtN5vJUtOdCTLn0tRYYV5azfbAFcPghB5zEE40mVrV6w7slMTqUfkDomutIK14fw=="], + "confbox": ["confbox@0.2.4", "", {}, "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ=="], "config-chain": ["config-chain@1.1.13", "", { "dependencies": { "ini": "^1.3.4", "proto-list": "~1.2.1" } }, "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ=="], @@ -2409,14 +2540,18 @@ "cookie-signature": ["cookie-signature@1.0.7", "", {}, "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA=="], - "core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="], + "core-util-is": ["core-util-is@1.0.2", "", {}, "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ=="], "cors": ["cors@2.8.6", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw=="], + "crc": ["crc@3.8.0", "", { "dependencies": { "buffer": "^5.1.0" } }, "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ=="], + "crc-32": ["crc-32@1.2.2", "", { "bin": { "crc32": "bin/crc32.njs" } }, "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ=="], "crc32-stream": ["crc32-stream@6.0.0", "", { "dependencies": { "crc-32": "^1.2.0", "readable-stream": "^4.0.0" } }, "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g=="], + "cross-dirname": ["cross-dirname@0.1.0", "", {}, "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q=="], + "cross-fetch": ["cross-fetch@3.2.0", "", { "dependencies": { "node-fetch": "^2.7.0" } }, "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q=="], "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], @@ -2447,12 +2582,16 @@ "db0": ["db0@0.3.4", "", { "peerDependencies": { "@electric-sql/pglite": "*", "@libsql/client": "*", "better-sqlite3": "*", "drizzle-orm": "*", "mysql2": "*", "sqlite3": "*" }, "optionalPeers": ["@electric-sql/pglite", "@libsql/client", "better-sqlite3", "drizzle-orm", "mysql2", "sqlite3"] }, "sha512-RiXXi4WaNzPTHEOu8UPQKMooIbqOEyqA1t7Z6MsdxSCeb8iUC9ko3LcmsLmeUt2SM5bctfArZKkRQggKZz7JNw=="], + "debounce-fn": ["debounce-fn@6.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-rBMW+F2TXryBwB54Q0d8drNEI+TfoS9JpNTAoVpukbWEhjXQq4rySFYLaqXMFXwdv61Zb2OHtj5bviSoimqxRQ=="], + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], "decimal.js": ["decimal.js@10.5.0", "", {}, "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw=="], "decode-named-character-reference": ["decode-named-character-reference@1.3.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q=="], + "decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="], + "deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="], "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="], @@ -2461,6 +2600,10 @@ "default-browser-id": ["default-browser-id@5.0.1", "", {}, "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q=="], + "defaults": ["defaults@1.0.4", "", { "dependencies": { "clone": "^1.0.2" } }, "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A=="], + + "defer-to-connect": ["defer-to-connect@2.0.1", "", {}, "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg=="], + "define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="], "define-lazy-prop": ["define-lazy-prop@3.0.0", "", {}, "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg=="], @@ -2485,6 +2628,8 @@ "detect-libc": ["detect-libc@1.0.3", "", { "bin": { "detect-libc": "./bin/detect-libc.js" } }, "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg=="], + "detect-node": ["detect-node@2.1.0", "", {}, "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g=="], + "detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="], "deterministic-object-hash": ["deterministic-object-hash@2.0.2", "", { "dependencies": { "base-64": "^1.0.0" } }, "sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ=="], @@ -2499,12 +2644,18 @@ "diff": ["diff@8.0.2", "", {}, "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg=="], + "dir-compare": ["dir-compare@4.2.0", "", { "dependencies": { "minimatch": "^3.0.5", "p-limit": "^3.1.0 " } }, "sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ=="], + "dir-glob": ["dir-glob@3.0.1", "", { "dependencies": { "path-type": "^4.0.0" } }, "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA=="], "direction": ["direction@2.0.1", "", { "bin": { "direction": "cli.js" } }, "sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA=="], "dlv": ["dlv@1.1.3", "", {}, "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="], + "dmg-builder": ["dmg-builder@26.8.1", "", { "dependencies": { "app-builder-lib": "26.8.1", "builder-util": "26.8.1", "fs-extra": "^10.1.0", "iconv-lite": "^0.6.2", "js-yaml": "^4.1.0" }, "optionalDependencies": { "dmg-license": "^1.0.11" } }, "sha512-glMJgnTreo8CFINujtAhCgN96QAqApDMZ8Vl1r8f0QT8QprvC1UCltV4CcWj20YoIyLZx6IUskaJZ0NV8fokcg=="], + + "dmg-license": ["dmg-license@1.0.11", "", { "dependencies": { "@types/plist": "^3.0.1", "@types/verror": "^1.10.3", "ajv": "^6.10.0", "crc": "^3.8.0", "iconv-corefoundation": "^1.1.7", "plist": "^3.0.4", "smart-buffer": "^4.0.2", "verror": "^1.10.0" }, "os": "darwin", "bin": { "dmg-license": "bin/dmg-license.js" } }, "sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q=="], + "dns-packet": ["dns-packet@5.6.1", "", { "dependencies": { "@leichtgewicht/ip-codec": "^2.0.1" } }, "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw=="], "dom-accessibility-api": ["dom-accessibility-api@0.6.3", "", {}, "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w=="], @@ -2523,7 +2674,9 @@ "dot-prop": ["dot-prop@8.0.2", "", { "dependencies": { "type-fest": "^3.8.0" } }, "sha512-xaBe6ZT4DHPkg0k4Ytbvn5xoxgpG0jOS1dYxSOwAHPuNLjP3/OzN0gH55SrLqpx8cBfSaVt91lXYkApjb+nYdQ=="], - "dotenv": ["dotenv@17.3.1", "", {}, "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA=="], + "dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="], + + "dotenv-expand": ["dotenv-expand@11.0.7", "", { "dependencies": { "dotenv": "^16.4.5" } }, "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA=="], "drizzle-kit": ["drizzle-kit@1.0.0-beta.12-a5629fb", "", { "dependencies": { "@drizzle-team/brocli": "^0.11.0", "@js-temporal/polyfill": "^0.5.1", "esbuild": "^0.25.10", "tsx": "^4.20.6" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-l+p4QOMvPGYBYEE9NBlU7diu+NSlxuOUwi0I7i01Uj1PpfU0NxhPzaks/9q1MDw4FAPP8vdD0dOhoqosKtRWWQ=="], @@ -2541,8 +2694,30 @@ "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], + "ejs": ["ejs@3.1.10", "", { "dependencies": { "jake": "^10.8.5" }, "bin": { "ejs": "bin/cli.js" } }, "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA=="], + + "electron": ["electron@40.4.1", "", { "dependencies": { "@electron/get": "^2.0.0", "@types/node": "^24.9.0", "extract-zip": "^2.0.1" }, "bin": { "electron": "cli.js" } }, "sha512-N1ZXybQZL8kYemO8vAeh9nrk4mSvqlAO8xs0QCHkXIvRnuB/7VGwEehjvQbsU5/f4bmTKpG+2GQERe/zmKpudQ=="], + + "electron-builder": ["electron-builder@26.8.1", "", { "dependencies": { "app-builder-lib": "26.8.1", "builder-util": "26.8.1", "builder-util-runtime": "9.5.1", "chalk": "^4.1.2", "ci-info": "^4.2.0", "dmg-builder": "26.8.1", "fs-extra": "^10.1.0", "lazy-val": "^1.0.5", "simple-update-notifier": "2.0.0", "yargs": "^17.6.2" }, "bin": { "electron-builder": "cli.js", "install-app-deps": "install-app-deps.js" } }, "sha512-uWhx1r74NGpCagG0ULs/P9Nqv2nsoo+7eo4fLUOB8L8MdWltq9odW/uuLXMFCDGnPafknYLZgjNX0ZIFRzOQAw=="], + + "electron-builder-squirrel-windows": ["electron-builder-squirrel-windows@26.8.1", "", { "dependencies": { "app-builder-lib": "26.8.1", "builder-util": "26.8.1", "electron-winstaller": "5.4.0" } }, "sha512-o288fIdgPLHA76eDrFADHPoo7VyGkDCYbLV1GzndaMSAVBoZrGvM9m2IehdcVMzdAZJ2eV9bgyissQXHv5tGzA=="], + + "electron-log": ["electron-log@5.4.3", "", {}, "sha512-sOUsM3LjZdugatazSQ/XTyNcw8dfvH1SYhXWiJyfYodAAKOZdHs0txPiLDXFzOZbhXgAgshQkshH2ccq0feyLQ=="], + + "electron-publish": ["electron-publish@26.8.1", "", { "dependencies": { "@types/fs-extra": "^9.0.11", "builder-util": "26.8.1", "builder-util-runtime": "9.5.1", "chalk": "^4.1.2", "form-data": "^4.0.5", "fs-extra": "^10.1.0", "lazy-val": "^1.0.5", "mime": "^2.5.2" } }, "sha512-q+jrSTIh/Cv4eGZa7oVR+grEJo/FoLMYBAnSL5GCtqwUpr1T+VgKB/dn1pnzxIxqD8S/jP1yilT9VrwCqINR4w=="], + + "electron-store": ["electron-store@10.1.0", "", { "dependencies": { "conf": "^14.0.0", "type-fest": "^4.41.0" } }, "sha512-oL8bRy7pVCLpwhmXy05Rh/L6O93+k9t6dqSw0+MckIc3OmCTZm6Mp04Q4f/J0rtu84Ky6ywkR8ivtGOmrq+16w=="], + "electron-to-chromium": ["electron-to-chromium@1.5.286", "", {}, "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A=="], + "electron-updater": ["electron-updater@6.8.3", "", { "dependencies": { "builder-util-runtime": "9.5.1", "fs-extra": "^10.1.0", "js-yaml": "^4.1.0", "lazy-val": "^1.0.5", "lodash.escaperegexp": "^4.1.2", "lodash.isequal": "^4.5.0", "semver": "~7.7.3", "tiny-typed-emitter": "^2.1.0" } }, "sha512-Z6sgw3jgbikWKXei1ENdqFOxBP0WlXg3TtKfz0rgw2vIZFJUyI4pD7ZN7jrkm7EoMK+tcm/qTnPUdqfZukBlBQ=="], + + "electron-vite": ["electron-vite@5.0.0", "", { "dependencies": { "@babel/core": "^7.28.4", "@babel/plugin-transform-arrow-functions": "^7.27.1", "cac": "^6.7.14", "esbuild": "^0.25.11", "magic-string": "^0.30.19", "picocolors": "^1.1.1" }, "peerDependencies": { "@swc/core": "^1.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["@swc/core"], "bin": { "electron-vite": "bin/electron-vite.js" } }, "sha512-OHp/vjdlubNlhNkPkL/+3JD34ii5ov7M0GpuXEVdQeqdQ3ulvVR7Dg/rNBLfS5XPIFwgoBLDf9sjjrL+CuDyRQ=="], + + "electron-window-state": ["electron-window-state@5.0.3", "", { "dependencies": { "jsonfile": "^4.0.0", "mkdirp": "^0.5.1" } }, "sha512-1mNTwCfkolXl3kMf50yW3vE2lZj0y92P/HYWFBrb+v2S/pCka5mdwN3cagKm458A7NjndSwijynXgcLWRodsVg=="], + + "electron-winstaller": ["electron-winstaller@5.4.0", "", { "dependencies": { "@electron/asar": "^3.2.1", "debug": "^4.1.1", "fs-extra": "^7.0.1", "lodash": "^4.17.21", "temp": "^0.9.0" }, "optionalDependencies": { "@electron/windows-sign": "^1.1.2" } }, "sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg=="], + "emmet": ["emmet@2.4.11", "", { "dependencies": { "@emmetio/abbreviation": "^2.3.3", "@emmetio/css-abbreviation": "^2.1.8" } }, "sha512-23QPJB3moh/U9sT4rQzGgeyyGIrcM+GH5uVYg2C6wZIxAIJq7Ng3QLT79tl8FUwDXhyq9SusfknOrofAKqvgyQ=="], "emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], @@ -2551,6 +2726,10 @@ "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], + "encoding": ["encoding@0.1.13", "", { "dependencies": { "iconv-lite": "^0.6.2" } }, "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A=="], + + "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="], + "engine.io-client": ["engine.io-client@6.6.4", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1", "engine.io-parser": "~5.2.1", "ws": "~8.18.3", "xmlhttprequest-ssl": "~2.1.1" } }, "sha512-+kjUJnZGwzewFDw951CDWcwj35vMNf2fcj7xQWOctq1F2i1jkDdVvdFG9kM/BEChymCH36KgjnW0NsL58JYRxw=="], "engine.io-parser": ["engine.io-parser@5.2.3", "", {}, "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q=="], @@ -2559,6 +2738,10 @@ "entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], + "env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="], + + "err-code": ["err-code@2.0.3", "", {}, "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA=="], + "error-stack-parser": ["error-stack-parser@2.1.4", "", { "dependencies": { "stackframe": "^1.3.4" } }, "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ=="], "error-stack-parser-es": ["error-stack-parser-es@1.0.5", "", {}, "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA=="], @@ -2581,6 +2764,8 @@ "es-to-primitive": ["es-to-primitive@1.3.0", "", { "dependencies": { "is-callable": "^1.2.7", "is-date-object": "^1.0.5", "is-symbol": "^1.0.4" } }, "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g=="], + "es6-error": ["es6-error@4.1.1", "", {}, "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg=="], + "esast-util-from-estree": ["esast-util-from-estree@2.0.0", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "devlop": "^1.0.0", "estree-util-visit": "^2.0.0", "unist-util-position-from-estree": "^2.0.0" } }, "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ=="], "esast-util-from-js": ["esast-util-from-js@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "acorn": "^8.0.0", "esast-util-from-estree": "^2.0.0", "vfile-message": "^4.0.0" } }, "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw=="], @@ -2633,6 +2818,8 @@ "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], + "exponential-backoff": ["exponential-backoff@3.1.3", "", {}, "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA=="], + "express": ["express@4.22.1", "", { "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "~1.20.3", "content-disposition": "~0.5.4", "content-type": "~1.0.4", "cookie": "~0.7.1", "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "~1.3.1", "fresh": "~0.5.2", "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "~0.19.0", "serve-static": "~1.16.2", "setprototypeof": "1.2.0", "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" } }, "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g=="], "express-rate-limit": ["express-rate-limit@7.5.1", "", { "peerDependencies": { "express": ">= 4.11" } }, "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw=="], @@ -2645,6 +2832,10 @@ "extend-shallow": ["extend-shallow@2.0.1", "", { "dependencies": { "is-extendable": "^0.1.0" } }, "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug=="], + "extract-zip": ["extract-zip@2.0.1", "", { "dependencies": { "debug": "^4.1.1", "get-stream": "^5.1.0", "yauzl": "^2.10.0" }, "optionalDependencies": { "@types/yauzl": "^2.9.1" }, "bin": { "extract-zip": "cli.js" } }, "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg=="], + + "extsprintf": ["extsprintf@1.4.1", "", {}, "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA=="], + "fast-content-type-parse": ["fast-content-type-parse@3.0.0", "", {}, "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg=="], "fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="], @@ -2655,6 +2846,8 @@ "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], + "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], + "fast-json-stringify": ["fast-json-stringify@6.3.0", "", { "dependencies": { "@fastify/merge-json-schemas": "^0.2.0", "ajv": "^8.12.0", "ajv-formats": "^3.0.1", "fast-uri": "^3.0.0", "json-schema-ref-resolver": "^3.0.0", "rfdc": "^1.2.0" } }, "sha512-oRCntNDY/329HJPlmdNLIdogNtt6Vyjb1WuT01Soss3slIdyUp8kAcDU3saQTOquEK8KFVfwIIF7FebxUAu+yA=="], "fast-querystring": ["fast-querystring@1.1.2", "", { "dependencies": { "fast-decode-uri-component": "^1.0.1" } }, "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg=="], @@ -2669,12 +2862,16 @@ "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="], + "fd-slicer": ["fd-slicer@1.1.0", "", { "dependencies": { "pend": "~1.2.0" } }, "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g=="], + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], "fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="], "file-type": ["file-type@16.5.4", "", { "dependencies": { "readable-web-to-node-stream": "^3.0.0", "strtok3": "^6.2.4", "token-types": "^4.1.1" } }, "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw=="], + "filelist": ["filelist@1.0.6", "", { "dependencies": { "minimatch": "^5.0.1" } }, "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA=="], + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], "finalhandler": ["finalhandler@1.3.2", "", { "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "statuses": "~2.0.2", "unpipe": "~1.0.0" } }, "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg=="], @@ -2717,6 +2914,8 @@ "fs-extra": ["fs-extra@10.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ=="], + "fs-minipass": ["fs-minipass@3.0.3", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw=="], + "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="], "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], @@ -2751,7 +2950,7 @@ "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], - "get-stream": ["get-stream@8.0.1", "", {}, "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA=="], + "get-stream": ["get-stream@5.2.0", "", { "dependencies": { "pump": "^3.0.0" } }, "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA=="], "get-symbol-description": ["get-symbol-description@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6" } }, "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg=="], @@ -2771,6 +2970,8 @@ "glob-to-regexp": ["glob-to-regexp@0.4.1", "", {}, "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="], + "global-agent": ["global-agent@3.0.0", "", { "dependencies": { "boolean": "^3.0.1", "es6-error": "^4.1.1", "matcher": "^3.0.0", "roarr": "^2.15.3", "semver": "^7.3.2", "serialize-error": "^7.0.1" } }, "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q=="], + "globalthis": ["globalthis@1.0.4", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="], "globby": ["globby@11.0.4", "", { "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", "fast-glob": "^3.1.1", "ignore": "^5.1.4", "merge2": "^1.3.0", "slash": "^3.0.0" } }, "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg=="], @@ -2781,6 +2982,8 @@ "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + "got": ["got@11.8.6", "", { "dependencies": { "@sindresorhus/is": "^4.0.0", "@szmarczak/http-timer": "^4.0.5", "@types/cacheable-request": "^6.0.1", "@types/responselike": "^1.0.0", "cacheable-lookup": "^5.0.3", "cacheable-request": "^7.0.2", "decompress-response": "^6.0.0", "http2-wrapper": "^1.0.0-beta.5.2", "lowercase-keys": "^2.0.0", "p-cancelable": "^2.0.0", "responselike": "^2.0.0" } }, "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g=="], + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], "graphql": ["graphql@16.12.0", "", {}, "sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ=="], @@ -2859,6 +3062,8 @@ "hono-openapi": ["hono-openapi@1.1.2", "", { "peerDependencies": { "@hono/standard-validator": "^0.2.0", "@standard-community/standard-json": "^0.3.5", "@standard-community/standard-openapi": "^0.2.9", "@types/json-schema": "^7.0.15", "hono": "^4.8.3", "openapi-types": "^12.1.3" }, "optionalPeers": ["@hono/standard-validator", "hono"] }, "sha512-toUcO60MftRBxqcVyxsHNYs2m4vf4xkQaiARAucQx3TiBPDtMNNkoh+C4I1vAretQZiGyaLOZNWn1YxfSyUA5g=="], + "hosted-git-info": ["hosted-git-info@4.1.0", "", { "dependencies": { "lru-cache": "^6.0.0" } }, "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA=="], + "html-entities": ["html-entities@2.3.3", "", {}, "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA=="], "html-escaper": ["html-escaper@3.0.3", "", {}, "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ=="], @@ -2881,6 +3086,8 @@ "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="], + "http2-wrapper": ["http2-wrapper@1.0.3", "", { "dependencies": { "quick-lru": "^5.1.1", "resolve-alpn": "^1.0.0" } }, "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg=="], + "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], "human-signals": ["human-signals@5.0.0", "", {}, "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ=="], @@ -2891,6 +3098,8 @@ "i18next": ["i18next@23.16.8", "", { "dependencies": { "@babel/runtime": "^7.23.2" } }, "sha512-06r/TitrM88Mg5FdUXAKL96dJMzgqLE5dv3ryBAra4KCwD9mJ4ndOTS95ZuymIGoE+2hzfdaMak2X11/es7ZWg=="], + "iconv-corefoundation": ["iconv-corefoundation@1.1.7", "", { "dependencies": { "cli-truncate": "^2.1.0", "node-addon-api": "^1.6.3" }, "os": "darwin" }, "sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ=="], + "iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="], "ieee754": ["ieee754@1.1.13", "", {}, "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="], @@ -2903,8 +3112,12 @@ "import-meta-resolve": ["import-meta-resolve@4.2.0", "", {}, "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg=="], + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], + "indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="], + "inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="], + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], "ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], @@ -2913,6 +3126,8 @@ "internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="], + "ip-address": ["ip-address@10.1.0", "", {}, "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q=="], + "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], "iron-webcrypto": ["iron-webcrypto@1.2.1", "", {}, "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg=="], @@ -2969,6 +3184,8 @@ "is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="], + "is-interactive": ["is-interactive@1.0.0", "", {}, "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w=="], + "is-map": ["is-map@2.0.3", "", {}, "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw=="], "is-negative-zero": ["is-negative-zero@2.0.3", "", {}, "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw=="], @@ -2997,6 +3214,8 @@ "is-typed-array": ["is-typed-array@1.1.15", "", { "dependencies": { "which-typed-array": "^1.1.16" } }, "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ=="], + "is-unicode-supported": ["is-unicode-supported@0.1.0", "", {}, "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw=="], + "is-weakmap": ["is-weakmap@2.0.2", "", {}, "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w=="], "is-weakref": ["is-weakref@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew=="], @@ -3013,7 +3232,9 @@ "isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="], - "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + "isbinaryfile": ["isbinaryfile@5.0.7", "", {}, "sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ=="], + + "isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="], "isomorphic-ws": ["isomorphic-ws@5.0.0", "", { "peerDependencies": { "ws": "*" } }, "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw=="], @@ -3023,6 +3244,8 @@ "jackspeak": ["jackspeak@4.2.3", "", { "dependencies": { "@isaacs/cliui": "^9.0.0" } }, "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg=="], + "jake": ["jake@10.9.4", "", { "dependencies": { "async": "^3.2.6", "filelist": "^1.0.4", "picocolors": "^1.1.1" }, "bin": { "jake": "bin/cli.js" } }, "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA=="], + "jimp": ["jimp@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/diff": "1.6.0", "@jimp/js-bmp": "1.6.0", "@jimp/js-gif": "1.6.0", "@jimp/js-jpeg": "1.6.0", "@jimp/js-png": "1.6.0", "@jimp/js-tiff": "1.6.0", "@jimp/plugin-blit": "1.6.0", "@jimp/plugin-blur": "1.6.0", "@jimp/plugin-circle": "1.6.0", "@jimp/plugin-color": "1.6.0", "@jimp/plugin-contain": "1.6.0", "@jimp/plugin-cover": "1.6.0", "@jimp/plugin-crop": "1.6.0", "@jimp/plugin-displace": "1.6.0", "@jimp/plugin-dither": "1.6.0", "@jimp/plugin-fisheye": "1.6.0", "@jimp/plugin-flip": "1.6.0", "@jimp/plugin-hash": "1.6.0", "@jimp/plugin-mask": "1.6.0", "@jimp/plugin-print": "1.6.0", "@jimp/plugin-quantize": "1.6.0", "@jimp/plugin-resize": "1.6.0", "@jimp/plugin-rotate": "1.6.0", "@jimp/plugin-threshold": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0" } }, "sha512-YcwCHw1kiqEeI5xRpDlPPBGL2EOpBKLwO4yIBJcXWHPj5PnA5urGq0jbyhM5KoNpypQ6VboSoxc9D8HyfvngSg=="], "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], @@ -3051,6 +3274,8 @@ "json-bigint": ["json-bigint@1.0.0", "", { "dependencies": { "bignumber.js": "^9.0.0" } }, "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ=="], + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], + "json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="], "json-schema-ref-resolver": ["json-schema-ref-resolver@3.0.0", "", { "dependencies": { "dequal": "^2.0.3" } }, "sha512-hOrZIVL5jyYFjzk7+y7n5JDzGlU8rfWDuYyHwGa2WA8/pcmMHezp2xsVwxrebD/Q9t8Nc5DboieySDpCp4WG4A=="], @@ -3061,11 +3286,13 @@ "json-schema-typed": ["json-schema-typed@8.0.2", "", {}, "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA=="], + "json-stringify-safe": ["json-stringify-safe@5.0.1", "", {}, "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="], + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], "jsonc-parser": ["jsonc-parser@3.3.1", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="], - "jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="], + "jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="], "jsonwebtoken": ["jsonwebtoken@9.0.3", "", { "dependencies": { "jws": "^4.0.1", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", "lodash.isnumber": "^3.0.3", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^7.5.4" } }, "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g=="], @@ -3077,6 +3304,8 @@ "katex": ["katex@0.16.27", "", { "dependencies": { "commander": "^8.3.0" }, "bin": { "katex": "cli.js" } }, "sha512-aeQoDkuRWSqQN6nSvVCEFvfXdqo1OQiCmmW1kc9xSdjutPv7BGO7pqY9sQRJpMOGrEdfDgF2TfRXe5eUAD2Waw=="], + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], + "kind-of": ["kind-of@6.0.3", "", {}, "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="], "kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="], @@ -3087,6 +3316,8 @@ "language-map": ["language-map@1.5.0", "", {}, "sha512-n7gFZpe+DwEAX9cXVTw43i3wiudWDDtSn28RmdnS/HCPr284dQI/SztsamWanRr75oSlKSaGbV2nmWCTzGCoVg=="], + "lazy-val": ["lazy-val@1.0.5", "", {}, "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q=="], + "lazystream": ["lazystream@1.0.1", "", { "dependencies": { "readable-stream": "^2.0.5" } }, "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw=="], "leac": ["leac@0.6.0", "", {}, "sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg=="], @@ -3123,10 +3354,14 @@ "lodash": ["lodash@4.17.23", "", {}, "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w=="], + "lodash.escaperegexp": ["lodash.escaperegexp@4.1.2", "", {}, "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw=="], + "lodash.includes": ["lodash.includes@4.3.0", "", {}, "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="], "lodash.isboolean": ["lodash.isboolean@3.0.3", "", {}, "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="], + "lodash.isequal": ["lodash.isequal@4.5.0", "", {}, "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ=="], + "lodash.isinteger": ["lodash.isinteger@4.0.4", "", {}, "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="], "lodash.isnumber": ["lodash.isnumber@3.0.3", "", {}, "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="], @@ -3137,6 +3372,8 @@ "lodash.once": ["lodash.once@4.1.1", "", {}, "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="], + "log-symbols": ["log-symbols@4.1.0", "", { "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" } }, "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg=="], + "loglevelnext": ["loglevelnext@6.0.0", "", {}, "sha512-FDl1AI2sJGjHHG3XKJd6sG3/6ncgiGCQ0YkW46nxe7SfqQq6hujd9CvFXIXtkGBUN83KPZ2KSOJK8q5P0bSSRQ=="], "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], @@ -3149,6 +3386,8 @@ "lower-case": ["lower-case@2.0.2", "", { "dependencies": { "tslib": "^2.0.3" } }, "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg=="], + "lowercase-keys": ["lowercase-keys@2.0.0", "", {}, "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA=="], + "lru-cache": ["lru-cache@11.2.6", "", {}, "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ=="], "lru.min": ["lru.min@1.1.4", "", {}, "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA=="], @@ -3163,6 +3402,8 @@ "magicast": ["magicast@0.3.5", "", { "dependencies": { "@babel/parser": "^7.25.4", "@babel/types": "^7.25.4", "source-map-js": "^1.2.0" } }, "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ=="], + "make-fetch-happen": ["make-fetch-happen@14.0.3", "", { "dependencies": { "@npmcli/agent": "^3.0.0", "cacache": "^19.0.1", "http-cache-semantics": "^4.1.1", "minipass": "^7.0.2", "minipass-fetch": "^4.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^1.0.0", "proc-log": "^5.0.0", "promise-retry": "^2.0.1", "ssri": "^12.0.0" } }, "sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ=="], + "markdown-extensions": ["markdown-extensions@2.0.0", "", {}, "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q=="], "markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="], @@ -3173,6 +3414,8 @@ "marked-shiki": ["marked-shiki@1.2.1", "", { "peerDependencies": { "marked": ">=7.0.0", "shiki": ">=1.0.0" } }, "sha512-yHxYQhPY5oYaIRnROn98foKhuClark7M373/VpLxiy5TrDu9Jd/LsMwo8w+U91Up4oDb9IXFrP0N1MFRz8W/DQ=="], + "matcher": ["matcher@3.0.0", "", { "dependencies": { "escape-string-regexp": "^4.0.0" } }, "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng=="], + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], "md-to-react-email": ["md-to-react-email@5.0.0", "", { "dependencies": { "marked": "7.0.4" }, "peerDependencies": { "react": "18.x" } }, "sha512-GdBrBUbAAJHypnuyofYGfVos8oUslxHx69hs3CW9P0L8mS1sT6GnJuMBTlz/Fw+2widiwdavcu9UwyLF/BzZ4w=="], @@ -3309,6 +3552,10 @@ "mimic-fn": ["mimic-fn@4.0.0", "", {}, "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw=="], + "mimic-function": ["mimic-function@5.0.1", "", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="], + + "mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="], + "min-indent": ["min-indent@1.0.1", "", {}, "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg=="], "miniflare": ["miniflare@4.20251118.1", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "acorn": "8.14.0", "acorn-walk": "8.3.2", "exit-hook": "2.2.1", "glob-to-regexp": "0.4.1", "sharp": "^0.33.5", "stoppable": "1.1.0", "undici": "7.14.0", "workerd": "1.20251118.0", "ws": "8.18.0", "youch": "4.1.0-beta.10", "zod": "3.22.3" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-uLSAE/DvOm392fiaig4LOaatxLjM7xzIniFRG5Y3yF9IduOYLLK/pkCPQNCgKQH3ou0YJRHnTN+09LPfqYNTQQ=="], @@ -3319,12 +3566,28 @@ "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], + "minipass-collect": ["minipass-collect@2.0.1", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw=="], + + "minipass-fetch": ["minipass-fetch@4.0.1", "", { "dependencies": { "minipass": "^7.0.3", "minipass-sized": "^1.0.3", "minizlib": "^3.0.1" }, "optionalDependencies": { "encoding": "^0.1.13" } }, "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ=="], + + "minipass-flush": ["minipass-flush@1.0.5", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw=="], + + "minipass-pipeline": ["minipass-pipeline@1.2.4", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A=="], + + "minipass-sized": ["minipass-sized@1.0.3", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g=="], + "minizlib": ["minizlib@3.1.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw=="], "mkdirp": ["mkdirp@0.5.6", "", { "dependencies": { "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw=="], "morphdom": ["morphdom@2.7.8", "", {}, "sha512-D/fR4xgGUyVRbdMGU6Nejea1RFzYxYtyurG4Fbv2Fi/daKlWKuXGLOdXtl+3eIwL110cI2hz1ZojGICjjFLgTg=="], + "motion": ["motion@12.34.5", "", { "dependencies": { "framer-motion": "^12.34.5", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-N06NLJ9IeBHeielRqIvYvjPfXuRdyTxa+9++BgpGa+hY2D7TcMkI6QzV3jaRuv0aZRXgMa7cPy9YcBUBisPzAQ=="], + + "motion-dom": ["motion-dom@12.34.3", "", { "dependencies": { "motion-utils": "^12.29.2" } }, "sha512-sYgFe+pR9aIM7o4fhs2aXtOI+oqlUd33N9Yoxcgo1Fv7M20sRkHtCmzE/VRNIcq7uNJ+qio+Xubt1FXH3pQ+eQ=="], + + "motion-utils": ["motion-utils@12.29.2", "", {}, "sha512-G3kc34H2cX2gI63RqU+cZq+zWRRPSsNIOjpdl9TN4AQwC4sgwYPl/Q/Obf/d53nOm569T0fYK+tcoSV50BWx8A=="], + "mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="], "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], @@ -3361,14 +3624,20 @@ "no-case": ["no-case@3.0.4", "", { "dependencies": { "lower-case": "^2.0.2", "tslib": "^2.0.3" } }, "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg=="], + "node-abi": ["node-abi@4.26.0", "", { "dependencies": { "semver": "^7.6.3" } }, "sha512-8QwIZqikRvDIkXS2S93LjzhsSPJuIbfaMETWH+Bx8oOT9Sa9UsUtBFQlc3gBNd1+QINjaTloitXr1W3dQLi9Iw=="], + "node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="], + "node-api-version": ["node-api-version@0.2.1", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q=="], + "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="], "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], "node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="], + "node-gyp": ["node-gyp@11.5.0", "", { "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", "graceful-fs": "^4.2.6", "make-fetch-happen": "^14.0.3", "nopt": "^8.0.0", "proc-log": "^5.0.0", "semver": "^7.3.5", "tar": "^7.4.3", "tinyglobby": "^0.2.12", "which": "^5.0.0" }, "bin": { "node-gyp": "bin/node-gyp.js" } }, "sha512-ra7Kvlhxn5V9Slyus0ygMa2h+UqExPqUIkfk7Pc8QTLT956JLSy51uWFwHtIYy0vI8cB4BDhc/S03+880My/LQ=="], + "node-gyp-build": ["node-gyp-build@4.8.4", "", { "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", "node-gyp-build-test": "build-test.js" } }, "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ=="], "node-html-parser": ["node-html-parser@7.0.2", "", { "dependencies": { "css-select": "^5.1.0", "he": "1.2.0" } }, "sha512-DxodLVh7a6JMkYzWyc8nBX9MaF4M0lLFYkJHlWOiu7+9/I6mwNK9u5TbAMC7qfqDJEPX9OIoWA2A9t4C2l1mUQ=="], @@ -3381,6 +3650,8 @@ "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="], + "normalize-url": ["normalize-url@6.1.0", "", {}, "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A=="], + "npm-run-path": ["npm-run-path@5.3.0", "", { "dependencies": { "path-key": "^4.0.0" } }, "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ=="], "nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="], @@ -3433,12 +3704,16 @@ "opentui-spinner": ["opentui-spinner@0.0.6", "", { "dependencies": { "cli-spinners": "^3.3.0" }, "peerDependencies": { "@opentui/core": "^0.1.49", "@opentui/react": "^0.1.49", "@opentui/solid": "^0.1.49", "typescript": "^5" }, "optionalPeers": ["@opentui/react", "@opentui/solid"] }, "sha512-xupLOeVQEAXEvVJCvHkfX6fChDWmJIPHe5jyUrVb8+n4XVTX8mBNhitFfB9v2ZbkC1H2UwPab/ElePHoW37NcA=="], + "ora": ["ora@5.4.1", "", { "dependencies": { "bl": "^4.1.0", "chalk": "^4.1.0", "cli-cursor": "^3.1.0", "cli-spinners": "^2.5.0", "is-interactive": "^1.0.0", "is-unicode-supported": "^0.1.0", "log-symbols": "^4.1.0", "strip-ansi": "^6.0.0", "wcwidth": "^1.0.1" } }, "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ=="], + "own-keys": ["own-keys@1.0.1", "", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="], "oxc-minify": ["oxc-minify@0.96.0", "", { "optionalDependencies": { "@oxc-minify/binding-android-arm64": "0.96.0", "@oxc-minify/binding-darwin-arm64": "0.96.0", "@oxc-minify/binding-darwin-x64": "0.96.0", "@oxc-minify/binding-freebsd-x64": "0.96.0", "@oxc-minify/binding-linux-arm-gnueabihf": "0.96.0", "@oxc-minify/binding-linux-arm-musleabihf": "0.96.0", "@oxc-minify/binding-linux-arm64-gnu": "0.96.0", "@oxc-minify/binding-linux-arm64-musl": "0.96.0", "@oxc-minify/binding-linux-riscv64-gnu": "0.96.0", "@oxc-minify/binding-linux-s390x-gnu": "0.96.0", "@oxc-minify/binding-linux-x64-gnu": "0.96.0", "@oxc-minify/binding-linux-x64-musl": "0.96.0", "@oxc-minify/binding-wasm32-wasi": "0.96.0", "@oxc-minify/binding-win32-arm64-msvc": "0.96.0", "@oxc-minify/binding-win32-x64-msvc": "0.96.0" } }, "sha512-dXeeGrfPJJ4rMdw+NrqiCRtbzVX2ogq//R0Xns08zql2HjV3Zi2SBJ65saqfDaJzd2bcHqvGWH+M44EQCHPAcA=="], "oxc-transform": ["oxc-transform@0.96.0", "", { "optionalDependencies": { "@oxc-transform/binding-android-arm64": "0.96.0", "@oxc-transform/binding-darwin-arm64": "0.96.0", "@oxc-transform/binding-darwin-x64": "0.96.0", "@oxc-transform/binding-freebsd-x64": "0.96.0", "@oxc-transform/binding-linux-arm-gnueabihf": "0.96.0", "@oxc-transform/binding-linux-arm-musleabihf": "0.96.0", "@oxc-transform/binding-linux-arm64-gnu": "0.96.0", "@oxc-transform/binding-linux-arm64-musl": "0.96.0", "@oxc-transform/binding-linux-riscv64-gnu": "0.96.0", "@oxc-transform/binding-linux-s390x-gnu": "0.96.0", "@oxc-transform/binding-linux-x64-gnu": "0.96.0", "@oxc-transform/binding-linux-x64-musl": "0.96.0", "@oxc-transform/binding-wasm32-wasi": "0.96.0", "@oxc-transform/binding-win32-arm64-msvc": "0.96.0", "@oxc-transform/binding-win32-x64-msvc": "0.96.0" } }, "sha512-dQPNIF+gHpSkmC0+Vg9IktNyhcn28Y8R3eTLyzn52UNymkasLicl3sFAtz7oEVuFmCpgGjaUTKkwk+jW2cHpDQ=="], + "p-cancelable": ["p-cancelable@2.1.1", "", {}, "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg=="], + "p-defer": ["p-defer@3.0.0", "", {}, "sha512-ugZxsxmtTln604yeYd29EGrNhazN2lywetzpKhfmQjW/VJmhpDmWbiX+h0zL8V91R0UXkhb3KtPmyq9PZw3aYw=="], "p-finally": ["p-finally@1.0.0", "", {}, "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow=="], @@ -3447,6 +3722,8 @@ "p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], + "p-map": ["p-map@7.0.4", "", {}, "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ=="], + "p-queue": ["p-queue@8.1.1", "", { "dependencies": { "eventemitter3": "^5.0.1", "p-timeout": "^6.1.2" } }, "sha512-aNZ+VfjobsWryoiPnEApGGmf5WmNsCo9xu8dfaYamG5qaLP7ClhLN6NgsFe6SwJ2UbLEBK5dv9x8Mn5+RVhMWQ=="], "p-retry": ["p-retry@4.6.2", "", { "dependencies": { "@types/retry": "0.12.0", "retry": "^0.13.1" } }, "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ=="], @@ -3491,6 +3768,8 @@ "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], + "path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="], + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], @@ -3505,10 +3784,14 @@ "pathval": ["pathval@2.0.1", "", {}, "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ=="], + "pe-library": ["pe-library@0.4.1", "", {}, "sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw=="], + "peberminta": ["peberminta@0.9.0", "", {}, "sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ=="], "peek-readable": ["peek-readable@4.1.0", "", {}, "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg=="], + "pend": ["pend@1.2.0", "", {}, "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="], + "perfect-debounce": ["perfect-debounce@2.1.0", "", {}, "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g=="], "piccolore": ["piccolore@0.1.3", "", {}, "sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw=="], @@ -3543,6 +3826,8 @@ "playwright-core": ["playwright-core@1.57.0", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ=="], + "plist": ["plist@3.1.0", "", { "dependencies": { "@xmldom/xmldom": "^0.8.8", "base64-js": "^1.5.1", "xmlbuilder": "^15.1.1" } }, "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ=="], + "pngjs": ["pngjs@7.0.0", "", {}, "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow=="], "possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="], @@ -3565,6 +3850,8 @@ "postgres": ["postgres@3.4.7", "", {}, "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw=="], + "postject": ["postject@1.0.0-alpha.6", "", { "dependencies": { "commander": "^9.4.0" }, "bin": { "postject": "dist/cli.js" } }, "sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A=="], + "powershell-utils": ["powershell-utils@0.1.0", "", {}, "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A=="], "prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="], @@ -3575,16 +3862,24 @@ "prismjs": ["prismjs@1.30.0", "", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="], + "proc-log": ["proc-log@5.0.0", "", {}, "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ=="], + "process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="], "process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="], "process-warning": ["process-warning@5.0.0", "", {}, "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA=="], + "progress": ["progress@2.0.3", "", {}, "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="], + + "promise-retry": ["promise-retry@2.0.1", "", { "dependencies": { "err-code": "^2.0.2", "retry": "^0.12.0" } }, "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g=="], + "promise.allsettled": ["promise.allsettled@1.0.7", "", { "dependencies": { "array.prototype.map": "^1.0.5", "call-bind": "^1.0.2", "define-properties": "^1.2.0", "es-abstract": "^1.22.1", "get-intrinsic": "^1.2.1", "iterate-value": "^1.0.2" } }, "sha512-hezvKvQQmsFkOdrZfYxUxkyxl8mgFQeT259Ajj9PXdbg9VzBCWrItOev72JyWxkCD5VSSqAeHmlN3tWx4DlmsA=="], "prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="], + "proper-lockfile": ["proper-lockfile@4.1.2", "", { "dependencies": { "graceful-fs": "^4.2.4", "retry": "^0.12.0", "signal-exit": "^3.0.2" } }, "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA=="], + "property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="], "proto-list": ["proto-list@1.2.4", "", {}, "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA=="], @@ -3593,6 +3888,8 @@ "proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="], + "pump": ["pump@3.0.4", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA=="], + "punycode": ["punycode@1.3.2", "", {}, "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw=="], "qs": ["qs@6.15.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ=="], @@ -3605,6 +3902,8 @@ "quick-format-unescaped": ["quick-format-unescaped@4.0.4", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="], + "quick-lru": ["quick-lru@5.1.1", "", {}, "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA=="], + "radix3": ["radix3@1.1.2", "", {}, "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA=="], "range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="], @@ -3633,6 +3932,8 @@ "react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="], + "read-binary-file-arch": ["read-binary-file-arch@1.0.6", "", { "dependencies": { "debug": "^4.3.4" }, "bin": { "read-binary-file-arch": "cli.js" } }, "sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg=="], + "read-cache": ["read-cache@1.0.0", "", { "dependencies": { "pify": "^2.3.0" } }, "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA=="], "readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="], @@ -3707,16 +4008,24 @@ "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + "resedit": ["resedit@1.7.2", "", { "dependencies": { "pe-library": "^0.4.1" } }, "sha512-vHjcY2MlAITJhC0eRD/Vv8Vlgmu9Sd3LX9zZvtGzU5ZImdTN3+d6e/4mnTyV8vEbyf1sgNIrWxhWlrys52OkEA=="], + "reselect": ["reselect@4.1.8", "", {}, "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ=="], "resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="], + "resolve-alpn": ["resolve-alpn@1.2.1", "", {}, "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g=="], + "resolve-cwd": ["resolve-cwd@3.0.0", "", { "dependencies": { "resolve-from": "^5.0.0" } }, "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg=="], "resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], + "responselike": ["responselike@2.0.1", "", { "dependencies": { "lowercase-keys": "^2.0.0" } }, "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw=="], + + "restore-cursor": ["restore-cursor@3.1.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA=="], + "restructure": ["restructure@3.0.2", "", {}, "sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw=="], "ret": ["ret@0.5.0", "", {}, "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw=="], @@ -3729,7 +4038,7 @@ "retext-stringify": ["retext-stringify@4.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0", "nlcst-to-string": "^4.0.0", "unified": "^11.0.0" } }, "sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA=="], - "retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="], + "retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="], "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], @@ -3737,6 +4046,8 @@ "rimraf": ["rimraf@5.0.10", "", { "dependencies": { "glob": "^10.3.7" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ=="], + "roarr": ["roarr@2.15.4", "", { "dependencies": { "boolean": "^3.0.1", "detect-node": "^2.0.4", "globalthis": "^1.0.1", "json-stringify-safe": "^5.0.1", "semver-compare": "^1.0.0", "sprintf-js": "^1.1.2" } }, "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A=="], + "rollup": ["rollup@4.57.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.57.1", "@rollup/rollup-android-arm64": "4.57.1", "@rollup/rollup-darwin-arm64": "4.57.1", "@rollup/rollup-darwin-x64": "4.57.1", "@rollup/rollup-freebsd-arm64": "4.57.1", "@rollup/rollup-freebsd-x64": "4.57.1", "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", "@rollup/rollup-linux-arm-musleabihf": "4.57.1", "@rollup/rollup-linux-arm64-gnu": "4.57.1", "@rollup/rollup-linux-arm64-musl": "4.57.1", "@rollup/rollup-linux-loong64-gnu": "4.57.1", "@rollup/rollup-linux-loong64-musl": "4.57.1", "@rollup/rollup-linux-ppc64-gnu": "4.57.1", "@rollup/rollup-linux-ppc64-musl": "4.57.1", "@rollup/rollup-linux-riscv64-gnu": "4.57.1", "@rollup/rollup-linux-riscv64-musl": "4.57.1", "@rollup/rollup-linux-s390x-gnu": "4.57.1", "@rollup/rollup-linux-x64-gnu": "4.57.1", "@rollup/rollup-linux-x64-musl": "4.57.1", "@rollup/rollup-openbsd-x64": "4.57.1", "@rollup/rollup-openharmony-arm64": "4.57.1", "@rollup/rollup-win32-arm64-msvc": "4.57.1", "@rollup/rollup-win32-ia32-msvc": "4.57.1", "@rollup/rollup-win32-x64-gnu": "4.57.1", "@rollup/rollup-win32-x64-msvc": "4.57.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A=="], "rou3": ["rou3@0.7.12", "", {}, "sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg=="], @@ -3763,6 +4074,8 @@ "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + "sanitize-filename": ["sanitize-filename@1.6.3", "", { "dependencies": { "truncate-utf8-bytes": "^1.0.0" } }, "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg=="], + "sax": ["sax@1.2.1", "", {}, "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA=="], "scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="], @@ -3775,10 +4088,14 @@ "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + "semver-compare": ["semver-compare@1.0.0", "", {}, "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow=="], + "send": ["send@0.19.2", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "~0.5.2", "http-errors": "~2.0.1", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "~2.4.1", "range-parser": "~1.2.1", "statuses": "~2.0.2" } }, "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg=="], "seq-queue": ["seq-queue@0.0.5", "", {}, "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q=="], + "serialize-error": ["serialize-error@7.0.1", "", { "dependencies": { "type-fest": "^0.13.1" } }, "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw=="], + "seroval": ["seroval@1.3.2", "", {}, "sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ=="], "seroval-plugins": ["seroval-plugins@1.3.3", "", { "peerDependencies": { "seroval": "^1.0" } }, "sha512-16OL3NnUBw8JG1jBLUoZJsLnQq0n5Ua6aHalhJK4fMQkz1lqR7Osz1sA30trBtd9VUDc2NgkuRCn8+/pBwqZ+w=="], @@ -3819,6 +4136,8 @@ "simple-swizzle": ["simple-swizzle@0.2.4", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw=="], + "simple-update-notifier": ["simple-update-notifier@2.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w=="], + "simple-xml-to-json": ["simple-xml-to-json@1.2.3", "", {}, "sha512-kWJDCr9EWtZ+/EYYM5MareWj2cRnZGF93YDNpH4jQiHB+hBIZnfPFSQiVMzZOdk+zXWqTZ/9fTeQNu2DqeiudA=="], "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="], @@ -3827,12 +4146,20 @@ "slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="], + "slice-ansi": ["slice-ansi@3.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", "is-fullwidth-code-point": "^3.0.0" } }, "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ=="], + + "smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="], + "smol-toml": ["smol-toml@1.6.0", "", {}, "sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw=="], "socket.io-client": ["socket.io-client@4.8.3", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1", "engine.io-client": "~6.6.1", "socket.io-parser": "~4.2.4" } }, "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g=="], "socket.io-parser": ["socket.io-parser@4.2.5", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1" } }, "sha512-bPMmpy/5WWKHea5Y/jYAP6k74A+hvmRCQaJuJB6I/ML5JZq/KfNieUVo/3Mh7SAqn7TyFdIo6wqYHInG1MU1bQ=="], + "socks": ["socks@2.8.7", "", { "dependencies": { "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" } }, "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A=="], + + "socks-proxy-agent": ["socks-proxy-agent@8.0.5", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" } }, "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw=="], + "solid-js": ["solid-js@1.9.10", "", { "dependencies": { "csstype": "^3.1.0", "seroval": "~1.3.0", "seroval-plugins": "~1.3.0" } }, "sha512-Coz956cos/EPDlhs6+jsdTxKuJDPT7B5SVIWgABwROyxjY7Xbr8wkzD68Et+NxnV7DLJ3nJdAC2r9InuV/4Jew=="], "solid-list": ["solid-list@0.3.0", "", { "dependencies": { "@corvu/utils": "~0.4.0" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-t4hx/F/l8Vmq+ib9HtZYl7Z9F1eKxq3eKJTXlvcm7P7yI4Z8O7QSOOEVHb/K6DD7M0RxzVRobK/BS5aSfLRwKg=="], @@ -3865,6 +4192,8 @@ "srvx": ["srvx@0.9.8", "", { "bin": { "srvx": "bin/srvx.mjs" } }, "sha512-RZaxTKJEE/14HYn8COLuUOJAt0U55N9l1Xf6jj+T0GoA01EUH1Xz5JtSUOI+EHn+AEgPCVn7gk6jHJffrr06fQ=="], + "ssri": ["ssri@12.0.0", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ=="], + "sst": ["sst@3.18.10", "", { "dependencies": { "aws-sdk": "2.1692.0", "aws4fetch": "1.0.18", "jose": "5.2.3", "opencontrol": "0.0.6", "openid-client": "5.6.4" }, "optionalDependencies": { "sst-darwin-arm64": "3.18.10", "sst-darwin-x64": "3.18.10", "sst-linux-arm64": "3.18.10", "sst-linux-x64": "3.18.10", "sst-linux-x86": "3.18.10", "sst-win32-arm64": "3.18.10", "sst-win32-x64": "3.18.10", "sst-win32-x86": "3.18.10" }, "bin": { "sst": "bin/sst.mjs" } }, "sha512-SY+ldeJ9K5E9q+DhjXA3e2W3BEOzBwkE3IyLSD71uA3/5nRhUAST31iOWEpW36LbIvSQ9uOVDFcebztoLJ8s7w=="], "sst-darwin-arm64": ["sst-darwin-arm64@3.18.10", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3MwIpMZhhdZKDqLp9ZQNlwkWix5+q+N0PWstuTomYwgZOxCCe6u9IIsoIszSk+GAJJN/jvGZyLiXKeV4iiQvw=="], @@ -3889,6 +4218,8 @@ "stage-js": ["stage-js@1.0.1", "", {}, "sha512-cz14aPp/wY0s3bkb/B93BPP5ZAEhgBbRmAT3CCDqert8eCAqIpQ0RB2zpK8Ksxf+Pisl5oTzvPHtL4CVzzeHcw=="], + "stat-mode": ["stat-mode@1.0.0", "", {}, "sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg=="], + "statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="], "std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="], @@ -3897,7 +4228,7 @@ "stoppable": ["stoppable@1.1.0", "", {}, "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw=="], - "storybook": ["storybook@10.2.10", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^2.0.1", "@testing-library/jest-dom": "^6.6.3", "@testing-library/user-event": "^14.6.1", "@vitest/expect": "3.2.4", "@vitest/spy": "3.2.4", "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0", "open": "^10.2.0", "recast": "^0.23.5", "semver": "^7.7.3", "use-sync-external-store": "^1.5.0", "ws": "^8.18.0" }, "peerDependencies": { "prettier": "^2 || ^3" }, "optionalPeers": ["prettier"], "bin": "./dist/bin/dispatcher.js" }, "sha512-N4U42qKgzMHS7DjqLz5bY4P7rnvJtYkWFCyKspZr3FhPUuy6CWOae3aYC2BjXkHrdug0Jyta6VxFTuB1tYUKhg=="], + "storybook": ["storybook@10.2.13", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^2.0.1", "@testing-library/jest-dom": "^6.6.3", "@testing-library/user-event": "^14.6.1", "@vitest/expect": "3.2.4", "@vitest/spy": "3.2.4", "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0", "open": "^10.2.0", "recast": "^0.23.5", "semver": "^7.7.3", "use-sync-external-store": "^1.5.0", "ws": "^8.18.0" }, "peerDependencies": { "prettier": "^2 || ^3" }, "optionalPeers": ["prettier"], "bin": "./dist/bin/dispatcher.js" }, "sha512-heMfJjOfbHvL+wlCAwFZlSxcakyJ5yQDam6e9k2RRArB1veJhRnsjO6lO1hOXjJYrqxfHA/ldIugbBVlCDqfvQ=="], "storybook-solidjs-vite": ["storybook-solidjs-vite@10.0.9", "", { "dependencies": { "@joshwooding/vite-plugin-react-docgen-typescript": "^0.6.1", "@storybook/builder-vite": "^10.0.0", "@storybook/global": "^5.0.0", "vite-plugin-solid": "^2.11.8" }, "peerDependencies": { "solid-js": "^1.9.0", "storybook": "^0.0.0-0 || ^10.0.0", "typescript": ">= 4.9.x", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["typescript"] }, "sha512-n6MwWCL9mK/qIaUutE9vhGB0X1I1hVnKin2NL+iVC5oXfAiuaABVZlr/1oEeEypsgCdyDOcbEbhJmDWmaqGpPw=="], @@ -3935,12 +4266,18 @@ "strtok3": ["strtok3@6.3.0", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "peek-readable": "^4.1.0" } }, "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw=="], + "stubborn-fs": ["stubborn-fs@2.0.0", "", { "dependencies": { "stubborn-utils": "^1.0.1" } }, "sha512-Y0AvSwDw8y+nlSNFXMm2g6L51rBGdAQT20J3YSOqxC53Lo3bjWRtr2BKcfYoAf352WYpsZSTURrA0tqhfgudPA=="], + + "stubborn-utils": ["stubborn-utils@1.0.2", "", {}, "sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg=="], + "style-to-js": ["style-to-js@1.1.21", "", { "dependencies": { "style-to-object": "1.0.14" } }, "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ=="], "style-to-object": ["style-to-object@1.0.14", "", { "dependencies": { "inline-style-parser": "0.2.7" } }, "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw=="], "sucrase": ["sucrase@3.35.1", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "tinyglobby": "^0.2.11", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw=="], + "sumchecker": ["sumchecker@3.0.1", "", { "dependencies": { "debug": "^4.1.0" } }, "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg=="], + "superstruct": ["superstruct@1.0.4", "", {}, "sha512-7JpaAoX2NGyoFlI9NBh66BQXGONc+uE+MRS5i2iOBKuS4e+ccgMDjATgZldkah+33DakBxDHiss9kvUcGAO8UQ=="], "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], @@ -3961,6 +4298,10 @@ "tedious": ["tedious@18.6.2", "", { "dependencies": { "@azure/core-auth": "^1.7.2", "@azure/identity": "^4.2.1", "@azure/keyvault-keys": "^4.4.0", "@js-joda/core": "^5.6.1", "@types/node": ">=18", "bl": "^6.0.11", "iconv-lite": "^0.6.3", "js-md4": "^0.3.2", "native-duplexpair": "^1.0.0", "sprintf-js": "^1.1.3" } }, "sha512-g7jC56o3MzLkE3lHkaFe2ZdOVFBahq5bsB60/M4NYUbocw/MCrS89IOEQUFr+ba6pb8ZHczZ/VqCyYeYq0xBAg=="], + "temp": ["temp@0.9.4", "", { "dependencies": { "mkdirp": "^0.5.1", "rimraf": "~2.6.2" } }, "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA=="], + + "temp-file": ["temp-file@3.4.0", "", { "dependencies": { "async-exit-hook": "^2.0.1", "fs-extra": "^10.0.0" } }, "sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg=="], + "terracotta": ["terracotta@1.1.0", "", { "dependencies": { "solid-use": "^0.9.1" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-kfQciWUBUBgYkXu7gh3CK3FAJng/iqZslAaY08C+k1Hdx17aVEpcFFb/WPaysxAfcupNH3y53s/pc53xxZauww=="], "terser": ["terser@5.46.0", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg=="], @@ -3977,10 +4318,14 @@ "thunky": ["thunky@1.1.0", "", {}, "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA=="], + "tiny-async-pool": ["tiny-async-pool@1.3.0", "", { "dependencies": { "semver": "^5.5.0" } }, "sha512-01EAw5EDrcVrdgyCLgoSPvqznC0sVxDSVeiOz09FUpjh71G79VCqneOr+xvt7T1r76CF6ZZfPjHorN2+d+3mqA=="], + "tiny-inflate": ["tiny-inflate@1.0.3", "", {}, "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="], "tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="], + "tiny-typed-emitter": ["tiny-typed-emitter@2.1.0", "", {}, "sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA=="], + "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], "tinycolor2": ["tinycolor2@1.6.0", "", {}, "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw=="], @@ -3995,6 +4340,10 @@ "titleize": ["titleize@4.0.0", "", {}, "sha512-ZgUJ1K83rhdu7uh7EHAC2BgY5DzoX8V5rTvoWI4vFysggi6YjLe5gUXABPWAU7VkvGP7P/0YiWq+dcPeYDsf1g=="], + "tmp": ["tmp@0.2.5", "", {}, "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow=="], + + "tmp-promise": ["tmp-promise@3.0.3", "", { "dependencies": { "tmp": "^0.2.0" } }, "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ=="], + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], "toad-cache": ["toad-cache@3.7.0", "", {}, "sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw=="], @@ -4009,12 +4358,16 @@ "traverse": ["traverse@0.3.9", "", {}, "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ=="], + "tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="], + "tree-sitter-bash": ["tree-sitter-bash@0.25.0", "", { "dependencies": { "node-addon-api": "^8.2.1", "node-gyp-build": "^4.8.2" }, "peerDependencies": { "tree-sitter": "^0.25.0" }, "optionalPeers": ["tree-sitter"] }, "sha512-gZtlj9+qFS81qKxpLfD6H0UssQ3QBc/F0nKkPsiFDyfQF2YBqYvglFJUzchrPpVhZe9kLZTrJ9n2J6lmka69Vg=="], "trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="], "trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="], + "truncate-utf8-bytes": ["truncate-utf8-bytes@1.0.2", "", { "dependencies": { "utf8-byte-length": "^1.0.1" } }, "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ=="], + "ts-algebra": ["ts-algebra@2.0.0", "", {}, "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw=="], "ts-dedent": ["ts-dedent@2.2.0", "", {}, "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ=="], @@ -4069,6 +4422,8 @@ "ufo": ["ufo@1.6.3", "", {}, "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q=="], + "uint8array-extras": ["uint8array-extras@1.5.0", "", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="], + "ulid": ["ulid@3.0.1", "", { "bin": { "ulid": "dist/cli.js" } }, "sha512-dPJyqPzx8preQhqq24bBG1YNkvigm87K8kVEHCD+ruZg24t6IFEFv00xMWfxcC4djmFtiTLdFuADn4+DOz6R7Q=="], "ultrahtml": ["ultrahtml@1.6.0", "", {}, "sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw=="], @@ -4091,6 +4446,10 @@ "unifont": ["unifont@0.5.2", "", { "dependencies": { "css-tree": "^3.0.0", "ofetch": "^1.4.1", "ohash": "^2.0.0" } }, "sha512-LzR4WUqzH9ILFvjLAUU7dK3Lnou/qd5kD+IakBtBK4S15/+x2y9VX+DcWQv6s551R6W+vzwgVS6tFg3XggGBgg=="], + "unique-filename": ["unique-filename@4.0.0", "", { "dependencies": { "unique-slug": "^5.0.0" } }, "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ=="], + + "unique-slug": ["unique-slug@5.0.0", "", { "dependencies": { "imurmurhash": "^0.1.4" } }, "sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg=="], + "unist-util-find-after": ["unist-util-find-after@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ=="], "unist-util-is": ["unist-util-is@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g=="], @@ -4127,6 +4486,8 @@ "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], + "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], + "url": ["url@0.10.3", "", { "dependencies": { "punycode": "1.3.2", "querystring": "0.2.0" } }, "sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ=="], "use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="], @@ -4135,6 +4496,8 @@ "use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="], + "utf8-byte-length": ["utf8-byte-length@1.0.5", "", {}, "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA=="], + "utif2": ["utif2@4.1.0", "", { "dependencies": { "pako": "^1.0.11" } }, "sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w=="], "util": ["util@0.12.5", "", { "dependencies": { "inherits": "^2.0.3", "is-arguments": "^1.0.4", "is-generator-function": "^1.0.7", "is-typed-array": "^1.1.3", "which-typed-array": "^1.1.2" } }, "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA=="], @@ -4147,6 +4510,8 @@ "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="], + "verror": ["verror@1.10.1", "", { "dependencies": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", "extsprintf": "^1.2.0" } }, "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg=="], + "vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="], "vfile-location": ["vfile-location@5.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile": "^6.0.0" } }, "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg=="], @@ -4201,6 +4566,8 @@ "vscode-uri": ["vscode-uri@3.1.0", "", {}, "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ=="], + "wcwidth": ["wcwidth@1.0.1", "", { "dependencies": { "defaults": "^1.0.3" } }, "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg=="], + "web-namespaces": ["web-namespaces@2.0.1", "", {}, "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="], "web-streams-polyfill": ["web-streams-polyfill@4.0.0-beta.3", "", {}, "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug=="], @@ -4215,7 +4582,9 @@ "whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], - "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + "when-exit": ["when-exit@2.1.5", "", {}, "sha512-VGkKJ564kzt6Ms1dbgPP/yuIoQCrsFAnRbptpC5wOEsDaNsbCB2bnfnaA8i/vRs5tjUSEOtIuvl9/MyVsvQZCg=="], + + "which": ["which@5.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ=="], "which-boxed-primitive": ["which-boxed-primitive@1.1.1", "", { "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" } }, "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA=="], @@ -4269,6 +4638,8 @@ "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + "yauzl": ["yauzl@2.10.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } }, "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g=="], + "yocto-queue": ["yocto-queue@1.2.2", "", {}, "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ=="], "yocto-spinner": ["yocto-spinner@0.2.3", "", { "dependencies": { "yoctocolors": "^2.1.1" } }, "sha512-sqBChb33loEnkoXte1bLg45bEBsOP9N1kzQh5JZNKj/0rik4zAPTNSAVPj3uQAdc6slYJ0Ksc403G2XgxsJQFQ=="], @@ -4539,8 +4910,38 @@ "@cspotcode/source-map-support/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="], + "@develar/schema-utils/ajv": ["ajv@6.14.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw=="], + "@dot/log/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + "@electron/asar/commander": ["commander@5.1.0", "", {}, "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg=="], + + "@electron/asar/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], + + "@electron/asar/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], + + "@electron/fuses/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + + "@electron/fuses/fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="], + + "@electron/get/fs-extra": ["fs-extra@8.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="], + + "@electron/get/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "@electron/notarize/fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="], + + "@electron/osx-sign/isbinaryfile": ["isbinaryfile@4.0.10", "", {}, "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw=="], + + "@electron/rebuild/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "@electron/rebuild/yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], + + "@electron/universal/fs-extra": ["fs-extra@11.3.3", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg=="], + + "@electron/universal/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + + "@electron/windows-sign/fs-extra": ["fs-extra@11.3.3", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg=="], + "@fastify/proxy-addr/ipaddr.js": ["ipaddr.js@2.3.0", "", {}, "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg=="], "@gitlab/gitlab-ai-provider/openai": ["openai@6.22.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-7Yvy17F33Bi9RutWbsaYt5hJEEJ/krRPOrwan+f9aCPuMat1WVsb2VNSII5W1EksKT6fF69TG/xj4XzodK3JZw=="], @@ -4595,6 +4996,8 @@ "@jsx-email/doiuse-email/htmlparser2": ["htmlparser2@9.1.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.1.0", "entities": "^4.5.0" } }, "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ=="], + "@malept/flatpak-bundler/fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="], + "@mdx-js/mdx/source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], "@modelcontextprotocol/sdk/express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="], @@ -4605,6 +5008,8 @@ "@modelcontextprotocol/sdk/zod-to-json-schema": ["zod-to-json-schema@3.25.1", "", { "peerDependencies": { "zod": "^3.25 || ^4" } }, "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA=="], + "@npmcli/agent/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + "@octokit/auth-app/@octokit/request": ["@octokit/request@10.0.7", "", { "dependencies": { "@octokit/endpoint": "^11.0.2", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "fast-content-type-parse": "^3.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA=="], "@octokit/auth-app/@octokit/request-error": ["@octokit/request-error@7.1.0", "", { "dependencies": { "@octokit/types": "^16.0.0" } }, "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw=="], @@ -4669,6 +5074,12 @@ "@opencode-ai/desktop/typescript": ["typescript@5.6.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw=="], + "@opencode-ai/desktop-electron/@actions/artifact": ["@actions/artifact@4.0.0", "", { "dependencies": { "@actions/core": "^1.10.0", "@actions/github": "^6.0.1", "@actions/http-client": "^2.1.0", "@azure/core-http": "^3.0.5", "@azure/storage-blob": "^12.15.0", "@octokit/core": "^5.2.1", "@octokit/plugin-request-log": "^1.0.4", "@octokit/plugin-retry": "^3.0.9", "@octokit/request": "^8.4.1", "@octokit/request-error": "^5.1.1", "@protobuf-ts/plugin": "^2.2.3-alpha.1", "archiver": "^7.0.1", "jwt-decode": "^3.1.2", "unzip-stream": "^0.3.1" } }, "sha512-HCc2jMJRAfviGFAh0FsOR/jNfWhirxl7W6z8zDtttt0GltwxBLdEIjLiweOPFl9WbyJRW1VWnPUSAixJqcWUMQ=="], + + "@opencode-ai/desktop-electron/marked": ["marked@15.0.12", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA=="], + + "@opencode-ai/desktop-electron/typescript": ["typescript@5.6.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw=="], + "@opencode-ai/web/@shikijs/transformers": ["@shikijs/transformers@3.20.0", "", { "dependencies": { "@shikijs/core": "3.20.0", "@shikijs/types": "3.20.0" } }, "sha512-PrHHMRr3Q5W1qB/42kJW6laqFyWdhrPF2hNR9qjOm1xcSiAO3hAHo7HaVyHE6pMyevmy3i51O8kuGGXC78uK3g=="], "@opentui/solid/@babel/core": ["@babel/core@7.28.0", "", { "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.0", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.27.3", "@babel/helpers": "^7.27.6", "@babel/parser": "^7.28.0", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.0", "@babel/types": "^7.28.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ=="], @@ -4681,6 +5092,8 @@ "@pierre/diffs/diff": ["diff@8.0.3", "", {}, "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ=="], + "@poppinss/dumper/@sindresorhus/is": ["@sindresorhus/is@7.2.0", "", {}, "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw=="], + "@poppinss/dumper/supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="], "@protobuf-ts/plugin/typescript": ["typescript@3.9.10", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q=="], @@ -4721,6 +5134,8 @@ "@solidjs/start/vite": ["vite@7.1.10", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-CmuvUBzVJ/e3HGxhg6cYk88NGgTnBoOo7ogtfJJ0fefUWAxN/WDSUa50o+oVBxuIhO8FoEZW0j2eW7sfjs5EtA=="], + "@storybook/builder-vite/@storybook/csf-plugin": ["@storybook/csf-plugin@10.2.10", "", { "dependencies": { "unplugin": "^2.3.5" }, "peerDependencies": { "esbuild": "*", "rollup": "*", "storybook": "^10.2.10", "vite": "*", "webpack": "*" }, "optionalPeers": ["esbuild", "rollup", "vite", "webpack"] }, "sha512-aFvgaNDAnKMjuyhPK5ialT22pPqMN0XfPBNPeeNVPYztngkdKBa8WFqF/umDd47HxAjebq+vn6uId1xHyOHH3g=="], + "@tailwindcss/oxide/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="], @@ -4745,6 +5160,8 @@ "@testing-library/dom/dom-accessibility-api": ["dom-accessibility-api@0.5.16", "", {}, "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="], + "@types/plist/xmlbuilder": ["xmlbuilder@15.1.1", "", {}, "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="], + "@types/serve-static/@types/send": ["@types/send@0.17.6", "", { "dependencies": { "@types/mime": "^1", "@types/node": "*" } }, "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og=="], "@vitest/expect/@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="], @@ -4771,10 +5188,18 @@ "ai-gateway-provider/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.33", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-2KMcR2xAul3u5dGZD7gONgbIki3Hg7Ey+sFu7gsiJ4U2iRU0GDV3ccNq79dTuAEXPDFcOWCUpW8A8jXc0kxJxQ=="], + "ajv-keywords/ajv": ["ajv@6.14.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw=="], + "ansi-align/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], "anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "app-builder-lib/@electron/get": ["@electron/get@3.1.0", "", { "dependencies": { "debug": "^4.1.1", "env-paths": "^2.2.0", "fs-extra": "^8.1.0", "got": "^11.8.5", "progress": "^2.0.3", "semver": "^6.2.0", "sumchecker": "^3.0.1" }, "optionalDependencies": { "global-agent": "^3.0.0" } }, "sha512-F+nKc0xW+kVbBRhFzaMgPy3KwmuNTYX1fx6+FxxoSnNgwYX6LD7AKBTWkU0MQ6IBoe7dz069CNkR673sPAgkCQ=="], + + "app-builder-lib/ci-info": ["ci-info@4.3.1", "", {}, "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA=="], + + "app-builder-lib/minimatch": ["minimatch@10.2.1", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-MClCe8IL5nRRmawL6ib/eT4oLyeKMGCghibcDWK+J0hh0Q8kqSdia6BvbRMVk6mPa6WqUa5uR2oxt6C5jd533A=="], + "archiver-utils/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], "archiver-utils/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], @@ -4807,14 +5232,46 @@ "buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + "builder-util/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + + "builder-util-runtime/sax": ["sax@1.4.4", "", {}, "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw=="], + "bun-webgpu/@webgpu/types": ["@webgpu/types@0.1.69", "", {}, "sha512-RPmm6kgRbI8e98zSD3RVACvnuktIja5+yLgDAkTmxLr90BEwdTXRQWNLF3ETTTyH/8mKhznZuN5AveXYFEsMGQ=="], "c12/chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="], + "c12/dotenv": ["dotenv@17.3.1", "", {}, "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA=="], + + "cacache/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], + + "cacache/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + + "cli-truncate/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "clone-response/mimic-response": ["mimic-response@1.0.1", "", {}, "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ=="], + "compress-commons/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], "condense-newlines/kind-of": ["kind-of@3.2.2", "", { "dependencies": { "is-buffer": "^1.1.5" } }, "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ=="], + "conf/dot-prop": ["dot-prop@9.0.0", "", { "dependencies": { "type-fest": "^4.18.2" } }, "sha512-1gxPBJpI/pcjQhKgIU91II6Wkay+dLcN3M6rf2uwP8hRur3HtQXjVrdAK3sjC0piaEuxzMwjXChcETiJl47lAQ=="], + + "conf/env-paths": ["env-paths@3.0.0", "", {}, "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A=="], + + "crc/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], + + "cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "defaults/clone": ["clone@1.0.4", "", {}, "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg=="], + + "dir-compare/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], + + "dir-compare/p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], + + "dmg-builder/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + + "dmg-license/ajv": ["ajv@6.14.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw=="], + "dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], "dot-prop/type-fest": ["type-fest@3.13.1", "", {}, "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g=="], @@ -4823,6 +5280,18 @@ "editorconfig/minimatch": ["minimatch@9.0.1", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w=="], + "electron-builder/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + + "electron-builder/yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], + + "electron-publish/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + + "electron-publish/mime": ["mime@2.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg=="], + + "electron-winstaller/fs-extra": ["fs-extra@7.0.1", "", { "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw=="], + + "encoding/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + "engine.io-client/ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="], "es-get-iterator/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], @@ -4833,6 +5302,8 @@ "estree-util-to-js/source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], + "execa/get-stream": ["get-stream@8.0.1", "", {}, "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA=="], + "execa/is-stream": ["is-stream@3.0.0", "", {}, "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA=="], "express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], @@ -4845,10 +5316,14 @@ "fetch-blob/web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="], + "filelist/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="], + "finalhandler/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], "form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + "fs-extra/jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="], + "gaxios/node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], "glob/minimatch": ["minimatch@10.2.1", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-MClCe8IL5nRRmawL6ib/eT4oLyeKMGCghibcDWK+J0hh0Q8kqSdia6BvbRMVk6mPa6WqUa5uR2oxt6C5jd533A=="], @@ -4859,12 +5334,16 @@ "happy-dom/ws": ["ws@8.19.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg=="], + "hosted-git-info/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + "html-minifier-terser/commander": ["commander@10.0.1", "", {}, "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug=="], "html-minifier-terser/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], "htmlparser2/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], + "iconv-corefoundation/node-addon-api": ["node-addon-api@1.7.2", "", {}, "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg=="], + "js-beautify/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], "katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], @@ -4875,6 +5354,12 @@ "lightningcss/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + "log-symbols/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + + "make-fetch-happen/negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], + + "matcher/escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], + "md-to-react-email/marked": ["marked@7.0.4", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-t8eP0dXRJMtMvBojtkcsA7n48BkauktUKzfkPSCq85ZMTJ0v76Rke4DYz01omYpPTUh4p/f7HePgRo3ebG8+QQ=="], "mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], @@ -4887,10 +5372,20 @@ "miniflare/zod": ["zod@3.22.3", "", {}, "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug=="], + "minipass-flush/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "minipass-pipeline/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "minipass-sized/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "motion/framer-motion": ["framer-motion@12.34.5", "", { "dependencies": { "motion-dom": "^12.34.5", "motion-utils": "^12.29.2", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-Z2dQ+o7BsfpJI3+u0SQUNCrN+ajCKJen1blC4rCHx1Ta2EOHs+xKJegLT2aaD9iSMbU3OoX+WabQXkloUbZmJQ=="], + "mssql/commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="], "nitro/h3": ["h3@2.0.1-rc.5", "", { "dependencies": { "rou3": "^0.7.9", "srvx": "^0.9.1" }, "peerDependencies": { "crossws": "^0.4.1" }, "optionalPeers": ["crossws"] }, "sha512-qkohAzCab0nLzXNm78tBjZDvtKMTmtygS8BJLT3VPczAQofdqlFXDPkXdLMJN4r05+xqneG8snZJ0HgkERCZTg=="], + "node-gyp/nopt": ["nopt@8.1.0", "", { "dependencies": { "abbrev": "^3.0.0" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A=="], + "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="], "nypm/citty": ["citty@0.2.1", "", {}, "sha512-kEV95lFBhQgtogAPlQfJJ0WGVSokvLr/UEoFPiKKOXF7pl98HfUVUD0ejsuTCld/9xH9vogSywZ5KqHzXrZpqg=="], @@ -4917,8 +5412,18 @@ "openid-client/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + "ora/bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], + + "ora/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + + "ora/cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="], + + "ora/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], + "p-retry/retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="], + "parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], "parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], @@ -4929,22 +5434,32 @@ "playwright/fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="], + "plist/xmlbuilder": ["xmlbuilder@15.1.1", "", {}, "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="], + "postcss-css-variables/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], "postcss-load-config/lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="], + "postject/commander": ["commander@9.5.0", "", {}, "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ=="], + "pretty-format/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], "prompts/kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="], + "proper-lockfile/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + "raw-body/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="], "readable-stream/buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], "readdir-glob/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="], + "restore-cursor/onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], + + "restore-cursor/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + "rimraf/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], "router/path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="], @@ -4957,6 +5472,8 @@ "send/mime": ["mime@1.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="], + "serialize-error/type-fest": ["type-fest@0.13.1", "", {}, "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="], + "sharp/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], "shiki/@shikijs/core": ["@shikijs/core@3.20.0", "", { "dependencies": { "@shikijs/types": "3.20.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-f2ED7HYV4JEk827mtMDwe/yQ25pRiXZmtHjWF8uzZKuKiEsJR7Ce1nuQ+HhV9FzDcbIo4ObBCD9GPTzNuy9S1g=="], @@ -4987,8 +5504,12 @@ "tedious/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + "temp/rimraf": ["rimraf@2.6.3", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "./bin.js" } }, "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA=="], + "terser/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], + "tiny-async-pool/semver": ["semver@5.7.2", "", { "bin": { "semver": "bin/semver" } }, "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="], + "token-types/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], "tree-sitter-bash/node-addon-api": ["node-addon-api@8.5.0", "", {}, "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A=="], @@ -5003,6 +5524,8 @@ "unifont/ofetch": ["ofetch@1.5.1", "", { "dependencies": { "destr": "^2.0.5", "node-fetch-native": "^1.6.7", "ufo": "^1.6.1" } }, "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA=="], + "uri-js/punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + "utif2/pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="], "vite-plugin-icons-spritesheet/glob": ["glob@11.1.0", "", { "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", "minimatch": "^10.1.1", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw=="], @@ -5039,6 +5562,8 @@ "yargs/yargs-parser": ["yargs-parser@22.0.0", "", {}, "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw=="], + "yauzl/buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="], + "zod-to-json-schema/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], "zod-to-ts/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], @@ -5141,6 +5666,26 @@ "@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + "@develar/schema-utils/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], + + "@electron/asar/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], + + "@electron/fuses/fs-extra/jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="], + + "@electron/get/fs-extra/universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="], + + "@electron/notarize/fs-extra/jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="], + + "@electron/rebuild/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], + + "@electron/rebuild/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "@electron/universal/fs-extra/jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="], + + "@electron/universal/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + + "@electron/windows-sign/fs-extra/jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="], + "@jsx-email/cli/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.19.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA=="], "@jsx-email/cli/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.19.12", "", { "os": "android", "cpu": "arm" }, "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w=="], @@ -5201,6 +5746,8 @@ "@jsx-email/doiuse-email/htmlparser2/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], + "@malept/flatpak-bundler/fs-extra/jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="], + "@modelcontextprotocol/sdk/express/accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], "@modelcontextprotocol/sdk/express/body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="], @@ -5309,6 +5856,8 @@ "@octokit/rest/@octokit/core/before-after-hook": ["before-after-hook@4.0.0", "", {}, "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ=="], + "@opencode-ai/desktop-electron/@actions/artifact/@actions/http-client": ["@actions/http-client@2.2.3", "", { "dependencies": { "tunnel": "^0.0.6", "undici": "^5.25.4" } }, "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA=="], + "@opencode-ai/desktop/@actions/artifact/@actions/http-client": ["@actions/http-client@2.2.3", "", { "dependencies": { "tunnel": "^0.0.6", "undici": "^5.25.4" } }, "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA=="], "@opencode-ai/web/@shikijs/transformers/@shikijs/core": ["@shikijs/core@3.20.0", "", { "dependencies": { "@shikijs/types": "3.20.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-f2ED7HYV4JEk827mtMDwe/yQ25pRiXZmtHjWF8uzZKuKiEsJR7Ce1nuQ+HhV9FzDcbIo4ObBCD9GPTzNuy9S1g=="], @@ -5357,10 +5906,16 @@ "ai-gateway-provider/@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="], + "ajv-keywords/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], + "ansi-align/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "ansi-align/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "app-builder-lib/@electron/get/fs-extra": ["fs-extra@8.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="], + + "app-builder-lib/@electron/get/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + "archiver-utils/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], "archiver-utils/glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], @@ -5387,12 +5942,40 @@ "c12/chokidar/readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="], + "cacache/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], + + "cacache/glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + + "cacache/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], + + "cli-truncate/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "cli-truncate/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "crc/buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + + "cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "dir-compare/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], + + "dir-compare/p-limit/yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], + + "dmg-license/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], + "editorconfig/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + "electron-builder/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], + + "electron-builder/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "electron-winstaller/fs-extra/universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="], + "esbuild-plugin-copy/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], "express/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], + "filelist/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + "finalhandler/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], "form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], @@ -5405,10 +5988,16 @@ "js-beautify/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], + "lazystream/readable-stream/core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="], + "lazystream/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], "lazystream/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], + "motion/framer-motion/motion-dom": ["motion-dom@12.34.5", "", { "dependencies": { "motion-utils": "^12.29.2" } }, "sha512-k33CsnxO2K3gBRMUZT+vPmc4Utlb5menKdG0RyVNLtlqRaaJPRWlE9fXl8NTtfZ5z3G8TDvqSu0MENLqSTaHZA=="], + + "node-gyp/nopt/abbrev": ["abbrev@3.0.1", "", {}, "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg=="], + "opencode/@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="], "opencode/@ai-sdk/openai-compatible/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="], @@ -5423,12 +6012,20 @@ "opencontrol/@modelcontextprotocol/sdk/zod-to-json-schema": ["zod-to-json-schema@3.25.1", "", { "peerDependencies": { "zod": "^3.25 || ^4" } }, "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA=="], + "ora/bl/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], + + "ora/bl/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], + + "ora/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "pkg-up/find-up/locate-path": ["locate-path@3.0.0", "", { "dependencies": { "p-locate": "^3.0.0", "path-exists": "^3.0.0" } }, "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A=="], "readable-stream/buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], "readdir-glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + "restore-cursor/onetime/mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="], + "rimraf/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], "rimraf/glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], @@ -5493,6 +6090,8 @@ "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "temp/rimraf/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], + "tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="], "tsx/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.3", "", { "os": "android", "cpu": "arm" }, "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA=="], @@ -5673,6 +6272,18 @@ "@aws-sdk/token-providers/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.3.6", "", { "dependencies": { "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA=="], + "@electron/asar/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "@electron/rebuild/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "@electron/rebuild/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "@electron/rebuild/yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "@electron/rebuild/yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "@electron/universal/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "@jsx-email/cli/tailwindcss/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], "@jsx-email/cli/tailwindcss/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], @@ -5743,6 +6354,8 @@ "@octokit/rest/@octokit/core/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="], + "@opencode-ai/desktop-electron/@actions/artifact/@actions/http-client/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="], + "@opencode-ai/desktop/@actions/artifact/@actions/http-client/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="], "@slack/web-api/form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], @@ -5751,6 +6364,8 @@ "ansi-align/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "app-builder-lib/@electron/get/fs-extra/universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="], + "archiver-utils/glob/jackspeak/@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], "archiver-utils/glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], @@ -5769,10 +6384,28 @@ "babel-plugin-module-resolver/glob/path-scurry/minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], + "cacache/glob/jackspeak/@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], + + "cacache/glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + + "cli-truncate/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "dir-compare/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "editorconfig/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "electron-builder/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "electron-builder/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "electron-builder/yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "electron-builder/yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "esbuild-plugin-copy/chokidar/readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "filelist/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "gray-matter/js-yaml/argparse/sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="], "js-beautify/glob/jackspeak/@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], @@ -5803,6 +6436,8 @@ "opencontrol/@modelcontextprotocol/sdk/express/type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="], + "ora/bl/buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + "pkg-up/find-up/locate-path/p-locate": ["p-locate@3.0.0", "", { "dependencies": { "p-limit": "^2.0.0" } }, "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ=="], "pkg-up/find-up/locate-path/path-exists": ["path-exists@3.0.0", "", {}, "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ=="], @@ -5815,6 +6450,8 @@ "rimraf/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + "temp/rimraf/glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], + "tw-to-css/tailwindcss/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], "tw-to-css/tailwindcss/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], @@ -5851,6 +6488,10 @@ "@aws-sdk/token-providers/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.1.2", "", {}, "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ=="], + "@electron/rebuild/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "@electron/rebuild/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "@jsx-email/cli/tailwindcss/chokidar/readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], "@solidjs/start/shiki/@shikijs/engine-javascript/oniguruma-to-es/regex": ["regex@5.1.1", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-dN5I359AVGPnwzJm2jN1k0W9LPZ+ePvoOeVMMfqIMFz53sSwXkxaJoxr50ptnsC771lK95BnTrVSZxq0b9yCGw=="], @@ -5865,6 +6506,16 @@ "babel-plugin-module-resolver/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "cacache/glob/jackspeak/@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], + + "cacache/glob/jackspeak/@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], + + "cacache/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "electron-builder/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "electron-builder/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "js-beautify/glob/jackspeak/@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], "js-beautify/glob/jackspeak/@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], @@ -5883,6 +6534,8 @@ "rimraf/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "temp/rimraf/glob/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], + "tw-to-css/tailwindcss/chokidar/readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], "@aws-sdk/credential-provider-cognito-identity/@aws-sdk/client-cognito-identity/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.1.2", "", {}, "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ=="], @@ -5891,6 +6544,10 @@ "archiver-utils/glob/jackspeak/@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + "cacache/glob/jackspeak/@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], + + "cacache/glob/jackspeak/@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + "js-beautify/glob/jackspeak/@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], "js-beautify/glob/jackspeak/@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], @@ -5898,5 +6555,7 @@ "rimraf/glob/jackspeak/@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], "rimraf/glob/jackspeak/@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + + "temp/rimraf/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], } } diff --git a/flake.lock b/flake.lock index 9efa1883b18..59eb118fa46 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1770812194, - "narHash": "sha256-OH+lkaIKAvPXR3nITO7iYZwew2nW9Y7Xxq0yfM/UcUU=", + "lastModified": 1772091128, + "narHash": "sha256-TnrYykX8Mf/Ugtkix6V+PjW7miU2yClA6uqWl/v6KWM=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "8482c7ded03bae7550f3d69884f1e611e3bd19e8", + "rev": "3f0336406035444b4a24b942788334af5f906259", "type": "github" }, "original": { diff --git a/infra/console.ts b/infra/console.ts index de72cb072ee..128e0698632 100644 --- a/infra/console.ts +++ b/infra/console.ts @@ -118,7 +118,6 @@ const ZEN_LITE_PRICE = new sst.Linkable("ZEN_LITE_PRICE", { price: zenLitePrice.id, }, }) -const ZEN_LITE_LIMITS = new sst.Secret("ZEN_LITE_LIMITS") const zenBlackProduct = new stripe.Product("ZenBlack", { name: "OpenCode Black", @@ -142,7 +141,6 @@ const ZEN_BLACK_PRICE = new sst.Linkable("ZEN_BLACK_PRICE", { plan20: zenBlackPrice20.id, }, }) -const ZEN_BLACK_LIMITS = new sst.Secret("ZEN_BLACK_LIMITS") const ZEN_MODELS = [ new sst.Secret("ZEN_MODELS1"), @@ -215,9 +213,8 @@ new sst.cloudflare.x.SolidStart("Console", { AWS_SES_ACCESS_KEY_ID, AWS_SES_SECRET_ACCESS_KEY, ZEN_BLACK_PRICE, - ZEN_BLACK_LIMITS, ZEN_LITE_PRICE, - ZEN_LITE_LIMITS, + new sst.Secret("ZEN_LIMITS"), new sst.Secret("ZEN_SESSION_SECRET"), ...ZEN_MODELS, ...($dev diff --git a/nix/hashes.json b/nix/hashes.json index 6a0db601cdd..0ef20b235d0 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,8 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-2XLuizbG90QDUQL+1M90XxfVZxjkIQ1cFYS46nnVO7g=", - "aarch64-linux": "sha256-hlckiGAtbpAlwgcE7KgzKKRq9T2FEOSq3Q1MhuHfZ2c=", - "aarch64-darwin": "sha256-V/8Kay+5bDb/BSVgBQhSMwzmRmkNGl3U0HFMVbVcMak=", - "x86_64-darwin": "sha256-duLDF88Q/hXK5jwBy4dVxMSiTTS0R4obp9MlTuOF/Pw=" + "x86_64-linux": "sha256-jtBYpfiE9g0otqZEtOksW1Nbg+O8CJP9OEOEhsa7sa8=", + "aarch64-linux": "sha256-m+YNZIB7I7EMPyfqkKsvDvmBX9R1szmEKxXpxTNFLH8=", + "aarch64-darwin": "sha256-1gVmtkC1/I8sdHZcaeSFJheySVlpCyKCjf9zbVsVqAQ=", + "x86_64-darwin": "sha256-Tvk5YL6Z0xRul4jopbGme/997iHBylXC0Cq3RnjQb+I=" } } diff --git a/nix/node_modules.nix b/nix/node_modules.nix index e918846c244..6c188c07cf7 100644 --- a/nix/node_modules.nix +++ b/nix/node_modules.nix @@ -31,6 +31,7 @@ stdenvNoCC.mkDerivation { ../package.json ../patches ../install # required by desktop build (cli.rs include_str!) + ../.github/TEAM_MEMBERS # required by @opencode-ai/script ] ); }; diff --git a/package.json b/package.json index bd9dbac414c..f2c4dac99f4 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "dev": "bun run --cwd packages/opencode --conditions=browser src/index.ts", "dev:desktop": "bun --cwd packages/desktop tauri dev", "dev:web": "bun --cwd packages/app dev", + "dev:storybook": "bun --cwd packages/storybook storybook", "typecheck": "bun turbo typecheck", "prepare": "husky", "random": "echo 'Random script'", @@ -98,7 +99,8 @@ "protobufjs", "tree-sitter", "tree-sitter-bash", - "web-tree-sitter" + "web-tree-sitter", + "electron" ], "overrides": { "@types/bun": "catalog:", diff --git a/packages/app/create-effect-simplification-spec.md b/packages/app/create-effect-simplification-spec.md new file mode 100644 index 00000000000..cc101ab0592 --- /dev/null +++ b/packages/app/create-effect-simplification-spec.md @@ -0,0 +1,515 @@ +# CreateEffect Simplification Implementation Spec + +Reduce reactive misuse across `packages/app`. + +--- + +## Context + +This work targets `packages/app/src`, which currently has 101 `createEffect` calls across 37 files. + +The biggest clusters are `pages/session.tsx` (19), `pages/layout.tsx` (13), `pages/session/file-tabs.tsx` (6), and several context providers that mirror one store into another. + +Key issues from the audit: + +- Derived state is being written through effects instead of computed directly +- Session and file resets are handled by watch-and-clear effects instead of keyed state boundaries +- User-driven actions are hidden inside reactive effects +- Context layers mirror and hydrate child stores with multiple sync effects +- Several areas repeat the same imperative trigger pattern in multiple effects + +Keep the implementation focused on removing unnecessary effects, not on broad UI redesign. + +## Goals + +- Cut high-churn `createEffect` usage in the hottest files first +- Replace effect-driven derived state with reactive derivation +- Replace reset-on-key effects with keyed ownership boundaries +- Move event-driven work to direct actions and write paths +- Remove mirrored store hydration where a single source of truth can exist +- Leave necessary external sync effects in place, but make them narrower and clearer + +## Non-Goals + +- Do not rewrite unrelated component structure just to reduce the count +- Do not change product behavior, navigation flow, or persisted data shape unless required for a cleaner write boundary +- Do not remove effects that bridge to DOM, editors, polling, or external APIs unless there is a clearly safer equivalent +- Do not attempt a repo-wide cleanup outside `packages/app` + +## Effect Taxonomy And Replacement Rules + +Use these rules during implementation. + +### Prefer `createMemo` + +Use `createMemo` when the target value is pure derived state from other signals or stores. + +Do this when an effect only reads reactive inputs and writes another reactive value that could be computed instead. + +Apply this to: + +- `packages/app/src/pages/session.tsx:141` +- `packages/app/src/pages/layout.tsx:557` +- `packages/app/src/components/terminal.tsx:261` +- `packages/app/src/components/session/session-header.tsx:309` + +Rules: + +- If no external system is touched, do not use `createEffect` +- Derive once, then read the memo where needed +- If normalization is required, prefer normalizing at the write boundary before falling back to a memo + +### Prefer Keyed Remounts + +Use keyed remounts when local UI state should reset because an identity changed. + +Do this with `sessionKey`, `scope()`, or another stable identity instead of watching the key and manually clearing signals. + +Apply this to: + +- `packages/app/src/pages/session.tsx:325` +- `packages/app/src/pages/session.tsx:336` +- `packages/app/src/pages/session.tsx:477` +- `packages/app/src/pages/session.tsx:869` +- `packages/app/src/pages/session.tsx:963` +- `packages/app/src/pages/session/message-timeline.tsx:149` +- `packages/app/src/context/file.tsx:100` + +Rules: + +- If the desired behavior is "new identity, fresh local state," key the owner subtree +- Keep state local to the keyed boundary so teardown and recreation handle the reset naturally + +### Prefer Event Handlers And Actions + +Use direct handlers, store actions, and async command functions when work happens because a user clicked, selected, reloaded, or navigated. + +Do this when an effect is just watching for a flag change, command token, or event-bus signal to trigger imperative logic. + +Apply this to: + +- `packages/app/src/pages/layout.tsx:484` +- `packages/app/src/pages/layout.tsx:652` +- `packages/app/src/pages/layout.tsx:776` +- `packages/app/src/pages/layout.tsx:1489` +- `packages/app/src/pages/layout.tsx:1519` +- `packages/app/src/components/file-tree.tsx:328` +- `packages/app/src/pages/session/terminal-panel.tsx:55` +- `packages/app/src/context/global-sync.tsx:148` +- Duplicated trigger sets in: + - `packages/app/src/pages/session/review-tab.tsx:122` + - `packages/app/src/pages/session/review-tab.tsx:130` + - `packages/app/src/pages/session/review-tab.tsx:138` + - `packages/app/src/pages/session/file-tabs.tsx:367` + - `packages/app/src/pages/session/file-tabs.tsx:378` + - `packages/app/src/pages/session/file-tabs.tsx:389` + - `packages/app/src/pages/session/use-session-hash-scroll.ts:144` + - `packages/app/src/pages/session/use-session-hash-scroll.ts:149` + - `packages/app/src/pages/session/use-session-hash-scroll.ts:167` + +Rules: + +- If the trigger is user intent, call the action at the source of that intent +- If the same imperative work is triggered from multiple places, extract one function and call it directly + +### Prefer `onMount` And `onCleanup` + +Use `onMount` and `onCleanup` for lifecycle-only setup and teardown. + +This is the right fit for subscriptions, one-time wiring, timers, and imperative integration that should not rerun for ordinary reactive changes. + +Use this when: + +- Setup should happen once per owner lifecycle +- Cleanup should always pair with teardown +- The work is not conceptually derived state + +### Keep `createEffect` When It Is A Real Bridge + +Keep `createEffect` when it synchronizes reactive data to an external imperative sink. + +Examples that should remain, though they may be narrowed or split: + +- DOM/editor sync in `packages/app/src/components/prompt-input.tsx:690` +- Scroll sync in `packages/app/src/pages/session.tsx:685` +- Scroll/hash sync in `packages/app/src/pages/session/use-session-hash-scroll.ts:149` +- External sync in: + - `packages/app/src/context/language.tsx:207` + - `packages/app/src/context/settings.tsx:110` + - `packages/app/src/context/sdk.tsx:26` +- Polling in: + - `packages/app/src/components/status-popover.tsx:59` + - `packages/app/src/components/dialog-select-server.tsx:273` + +Rules: + +- Keep the effect single-purpose +- Make dependencies explicit and narrow +- Avoid writing back into the same reactive graph unless absolutely required + +## Implementation Plan + +### Phase 0: Classification Pass + +Before changing code, tag each targeted effect as one of: derive, reset, event, lifecycle, or external bridge. + +Acceptance criteria: + +- Every targeted effect in this spec is tagged with a replacement strategy before refactoring starts +- Shared helpers to be introduced are identified up front to avoid repeating patterns + +### Phase 1: Derived-State Cleanup + +Tackle highest-value, lowest-risk derived-state cleanup first. + +Priority items: + +- Normalize tabs at write boundaries and remove `packages/app/src/pages/session.tsx:141` +- Stop syncing `workspaceOrder` in `packages/app/src/pages/layout.tsx:557` +- Make prompt slash filtering reactive so `packages/app/src/components/prompt-input.tsx:652` can be removed +- Replace other obvious derived-state effects in terminal and session header + +Acceptance criteria: + +- No behavior change in tab ordering, prompt filtering, terminal display, or header state +- Targeted derived-state effects are deleted, not just moved + +### Phase 2: Keyed Reset Cleanup + +Replace reset-on-key effects with keyed ownership boundaries. + +Priority items: + +- Key session-scoped UI and state by `sessionKey` +- Key file-scoped state by `scope()` +- Remove manual clear-and-reseed effects in session and file context + +Acceptance criteria: + +- Switching session or file scope recreates the intended local state cleanly +- No stale state leaks across session or scope changes +- Target reset effects are deleted + +### Phase 3: Event-Driven Work Extraction + +Move event-driven work out of reactive effects. + +Priority items: + +- Replace `globalStore.reload` effect dispatching with direct calls +- Split mixed-responsibility effect in `packages/app/src/pages/layout.tsx:1489` +- Collapse duplicated imperative trigger triplets into single functions +- Move file-tree and terminal-panel imperative work to explicit handlers + +Acceptance criteria: + +- User-triggered behavior still fires exactly once per intended action +- No effect remains whose only job is to notice a command-like state and trigger an imperative function + +### Phase 4: Context Ownership Cleanup + +Remove mirrored child-store hydration patterns. + +Priority items: + +- Remove child-store hydration mirrors in `packages/app/src/context/global-sync/child-store.ts:184`, `:190`, `:193` +- Simplify mirror logic in `packages/app/src/context/global-sync.tsx:130`, `:138` +- Revisit `packages/app/src/context/layout.tsx:424` if it still mirrors instead of deriving + +Acceptance criteria: + +- There is one clear source of truth for each synced value +- Child stores no longer need effect-based hydration to stay consistent +- Initialization and updates both work without manual mirror effects + +### Phase 5: Cleanup And Keeper Review + +Clean up remaining targeted hotspots and narrow the effects that should stay. + +Acceptance criteria: + +- Remaining `createEffect` calls in touched files are all true bridges or clearly justified lifecycle sync +- Mixed-responsibility effects are split into smaller units where still needed + +## Detailed Work Items By Area + +### 1. Normalize Tab State + +Files: + +- `packages/app/src/pages/session.tsx:141` + +Work: + +- Move tab normalization into the functions that create, load, or update tab state +- Make readers consume already-normalized tab data +- Remove the effect that rewrites derived tab state after the fact + +Rationale: + +- Tabs should become valid when written, not be repaired later +- This removes a feedback loop and makes state easier to trust + +Acceptance criteria: + +- The effect at `packages/app/src/pages/session.tsx:141` is removed +- Newly created and restored tabs are normalized before they enter local state +- Tab rendering still matches current behavior for valid and edge-case inputs + +### 2. Key Session-Owned State + +Files: + +- `packages/app/src/pages/session.tsx:325` +- `packages/app/src/pages/session.tsx:336` +- `packages/app/src/pages/session.tsx:477` +- `packages/app/src/pages/session.tsx:869` +- `packages/app/src/pages/session.tsx:963` +- `packages/app/src/pages/session/message-timeline.tsx:149` + +Work: + +- Identify state that should reset when `sessionKey` changes +- Move that state under a keyed subtree or keyed owner boundary +- Remove effects that watch `sessionKey` just to clear local state, refs, or temporary UI flags + +Rationale: + +- Session identity already defines the lifetime of this UI state +- Keyed ownership makes reset behavior automatic and easier to reason about + +Acceptance criteria: + +- The targeted reset effects are removed +- Changing sessions resets only the intended session-local state +- Scroll and editor state that should persist are not accidentally reset + +### 3. Derive Workspace Order + +Files: + +- `packages/app/src/pages/layout.tsx:557` + +Work: + +- Stop writing `workspaceOrder` from live workspace data in an effect +- Represent user overrides separately from live workspace data +- Compute effective order from current data plus overrides with a memo or pure helper + +Rationale: + +- Persisted user intent and live source data should not mirror each other through an effect +- A computed effective order avoids drift and racey resync behavior + +Acceptance criteria: + +- The effect at `packages/app/src/pages/layout.tsx:557` is removed +- Workspace order updates correctly when workspaces appear, disappear, or are reordered by the user +- User overrides persist without requiring a sync-back effect + +### 4. Remove Child-Store Mirrors + +Files: + +- `packages/app/src/context/global-sync.tsx:130` +- `packages/app/src/context/global-sync.tsx:138` +- `packages/app/src/context/global-sync.tsx:148` +- `packages/app/src/context/global-sync/child-store.ts:184` +- `packages/app/src/context/global-sync/child-store.ts:190` +- `packages/app/src/context/global-sync/child-store.ts:193` +- `packages/app/src/context/layout.tsx:424` + +Work: + +- Trace the actual ownership of global and child store values +- Replace hydration and mirror effects with explicit initialization and direct updates +- Remove the `globalStore.reload` event-bus pattern and call the needed reload paths directly + +Rationale: + +- Mirrors make it hard to tell which state is authoritative +- Event-bus style state toggles hide control flow and create accidental reruns + +Acceptance criteria: + +- Child store hydration no longer depends on effect-based copying +- Reload work can be followed from the event source to the handler without a reactive relay +- State remains correct on first load, child creation, and subsequent updates + +### 5. Key File-Scoped State + +Files: + +- `packages/app/src/context/file.tsx:100` + +Work: + +- Move file-scoped local state under a boundary keyed by `scope()` +- Remove any effect that watches `scope()` only to reset file-local state + +Rationale: + +- File scope changes are identity changes +- Keyed ownership gives a cleaner reset than manual clear logic + +Acceptance criteria: + +- The effect at `packages/app/src/context/file.tsx:100` is removed +- Switching scopes resets only scope-local state +- No previous-scope data appears after a scope change + +### 6. Split Layout Side Effects + +Files: + +- `packages/app/src/pages/layout.tsx:1489` +- Related event-driven effects near `packages/app/src/pages/layout.tsx:484`, `:652`, `:776`, `:1519` + +Work: + +- Break the mixed-responsibility effect at `:1489` into direct actions and smaller bridge effects only where required +- Move user-triggered branches into the actual command or handler that causes them +- Remove any branch that only exists because one effect is handling unrelated concerns + +Rationale: + +- Mixed effects hide cause and make reruns hard to predict +- Smaller units reduce accidental coupling and make future cleanup safer + +Acceptance criteria: + +- The effect at `packages/app/src/pages/layout.tsx:1489` no longer mixes unrelated responsibilities +- Event-driven branches execute from direct handlers +- Remaining effects in this area each have one clear external sync purpose + +### 7. Remove Duplicate Triggers + +Files: + +- `packages/app/src/pages/session/review-tab.tsx:122` +- `packages/app/src/pages/session/review-tab.tsx:130` +- `packages/app/src/pages/session/review-tab.tsx:138` +- `packages/app/src/pages/session/file-tabs.tsx:367` +- `packages/app/src/pages/session/file-tabs.tsx:378` +- `packages/app/src/pages/session/file-tabs.tsx:389` +- `packages/app/src/pages/session/use-session-hash-scroll.ts:144` +- `packages/app/src/pages/session/use-session-hash-scroll.ts:149` +- `packages/app/src/pages/session/use-session-hash-scroll.ts:167` + +Work: + +- Extract one explicit imperative function per behavior +- Call that function from each source event instead of replicating the same effect pattern multiple times +- Preserve the scroll-sync effect that is truly syncing with the DOM, but remove duplicate trigger scaffolding around it + +Rationale: + +- Duplicate triggers make it easy to miss a case or fire twice +- One named action is easier to test and reason about + +Acceptance criteria: + +- Repeated imperative effect triplets are collapsed into shared functions +- Scroll behavior still works, including hash-based navigation +- No duplicate firing is introduced + +### 8. Make Prompt Filtering Reactive + +Files: + +- `packages/app/src/components/prompt-input.tsx:652` +- Keep `packages/app/src/components/prompt-input.tsx:690` as needed + +Work: + +- Convert slash filtering into a pure reactive derivation from the current input and candidate command list +- Keep only the editor or DOM bridge effect if it is still needed for imperative syncing + +Rationale: + +- Filtering is classic derived state +- It should not need an effect if it can be computed from current inputs + +Acceptance criteria: + +- The effect at `packages/app/src/components/prompt-input.tsx:652` is removed +- Filtered slash-command results update correctly as the input changes +- The editor sync effect at `:690` still behaves correctly + +### 9. Clean Up Smaller Derived-State Cases + +Files: + +- `packages/app/src/components/terminal.tsx:261` +- `packages/app/src/components/session/session-header.tsx:309` + +Work: + +- Replace effect-written local state with memos or inline derivation +- Remove intermediate setters when the value can be computed directly + +Rationale: + +- These are low-risk wins that reinforce the same pattern +- They also help keep follow-up cleanup consistent + +Acceptance criteria: + +- Targeted effects are removed +- UI output remains unchanged under the same inputs + +## Verification And Regression Checks + +Run focused checks after each phase, not only at the end. + +### Suggested Verification + +- Switch between sessions rapidly and confirm local session UI resets only where intended +- Open, close, and reorder tabs and confirm order and normalization remain stable +- Change workspaces, reload workspace data, and verify effective ordering is correct +- Change file scope and confirm stale file state does not bleed across scopes +- Trigger layout actions that previously depended on effects and confirm they still fire once +- Use slash commands in the prompt and verify filtering updates as you type +- Test review tab, file tab, and hash-scroll flows for duplicate or missing triggers +- Verify global sync initialization, reload, and child-store creation paths + +### Regression Checks + +- No accidental infinite reruns +- No double-firing network or command actions +- No lost cleanup for listeners, timers, or scroll handlers +- No preserved stale state after identity changes +- No removed effect that was actually bridging to DOM or an external API + +If available, add or update tests around pure helpers introduced during this cleanup. + +Favor tests for derived ordering, normalization, and action extraction, since those are easiest to lock down. + +## Definition Of Done + +This work is done when all of the following are true: + +- The highest-leverage targets in this spec are implemented +- Each removed effect has been replaced by a clearer pattern: memo, keyed boundary, direct action, or lifecycle hook +- The "should remain" effects still exist only where they serve a real external sync purpose +- Touched files have fewer mixed-responsibility effects and clearer ownership of state +- Manual verification covers session switching, file scope changes, workspace ordering, prompt filtering, and reload flows +- No behavior regressions are found in the targeted areas + +A reduced raw `createEffect` count is helpful, but it is not the main success metric. + +The main success metric is clearer ownership and fewer effect-driven state repairs. + +## Risks And Rollout Notes + +Main risks: + +- Keyed remounts can reset too much if state boundaries are drawn too high +- Store mirror removal can break initialization order if ownership is not mapped first +- Moving event work out of effects can accidentally skip triggers that were previously implicit + +Rollout notes: + +- Land in small phases, with each phase keeping the app behaviorally stable +- Prefer isolated PRs by phase or by file cluster, especially for context-store changes +- Review each remaining effect in touched files and leave it only if it clearly bridges to something external diff --git a/packages/app/e2e/projects/projects-switch.spec.ts b/packages/app/e2e/projects/projects-switch.spec.ts index 74b3890888f..81cca6988d2 100644 --- a/packages/app/e2e/projects/projects-switch.spec.ts +++ b/packages/app/e2e/projects/projects-switch.spec.ts @@ -92,14 +92,19 @@ test("switching back to a project opens the latest workspace session", async ({ await expect(page).toHaveURL(new RegExp(`/${workspaceSlug}/session(?:[/?#]|$)`)) - const created = await createSdk(workspaceDir) - .session.create() - .then((x) => x.data?.id) - if (!created) throw new Error(`Failed to create session for workspace: ${workspaceDir}`) + // Create a session by sending a prompt + const prompt = page.locator(promptSelector) + await expect(prompt).toBeVisible() + await prompt.fill("test") + await page.keyboard.press("Enter") + + // Wait for the URL to update with the new session ID + await expect.poll(() => sessionIDFromUrl(page.url()) ?? "", { timeout: 15_000 }).not.toBe("") + + const created = sessionIDFromUrl(page.url()) + if (!created) throw new Error(`Failed to get session ID from url: ${page.url()}`) sessionID = created - await page.goto(sessionPath(workspaceDir, created)) - await expect(page.locator(promptSelector)).toBeVisible() await expect(page).toHaveURL(new RegExp(`/${workspaceSlug}/session/${created}(?:[/?#]|$)`)) await openSidebar(page) diff --git a/packages/app/e2e/session/session-composer-dock.spec.ts b/packages/app/e2e/session/session-composer-dock.spec.ts index deb87a06209..4cf075fc9a1 100644 --- a/packages/app/e2e/session/session-composer-dock.spec.ts +++ b/packages/app/e2e/session/session-composer-dock.spec.ts @@ -142,6 +142,17 @@ test("default dock shows prompt input", async ({ page, sdk, gotoSession }) => { }) }) +test("auto-accept toggle works before first submit", async ({ page, gotoSession }) => { + await gotoSession() + + const button = page.locator('[data-action="prompt-permissions"]').first() + await expect(button).toBeVisible() + await expect(button).toHaveAttribute("aria-pressed", "false") + + await setAutoAccept(page, true) + await setAutoAccept(page, false) +}) + test("blocked question flow unblocks after submit", async ({ page, sdk, gotoSession }) => { await withDockSession(sdk, "e2e composer dock question", async (session) => { await withDockSeed(sdk, session.id, async () => { diff --git a/packages/app/package.json b/packages/app/package.json index 446c14e9671..6f5fdeb2fbe 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/app", - "version": "1.2.15", + "version": "1.2.16", "description": "", "type": "module", "exports": { diff --git a/packages/app/script/e2e-local.ts b/packages/app/script/e2e-local.ts index 112e2bc60a1..9a83411b1d2 100644 --- a/packages/app/script/e2e-local.ts +++ b/packages/app/script/e2e-local.ts @@ -145,6 +145,7 @@ try { Object.assign(process.env, serverEnv) process.env.AGENT = "1" process.env.OPENCODE = "1" + process.env.OPENCODE_PID = String(process.pid) const log = await import("../../opencode/src/util/log") const install = await import("../../opencode/src/installation") diff --git a/packages/app/src/app.tsx b/packages/app/src/app.tsx index 4a25e8d9483..52a1dac6a2f 100644 --- a/packages/app/src/app.tsx +++ b/packages/app/src/app.tsx @@ -7,8 +7,8 @@ import { MarkedProvider } from "@opencode-ai/ui/context/marked" import { Font } from "@opencode-ai/ui/font" import { ThemeProvider } from "@opencode-ai/ui/theme" import { MetaProvider } from "@solidjs/meta" -import { Navigate, Route, Router } from "@solidjs/router" -import { ErrorBoundary, type JSX, lazy, type ParentProps, Show, Suspense } from "solid-js" +import { BaseRouterProps, Navigate, Route, Router } from "@solidjs/router" +import { Component, ErrorBoundary, type JSX, lazy, type ParentProps, Show, Suspense } from "solid-js" import { CommandProvider } from "@/context/command" import { CommentsProvider } from "@/context/comments" import { FileProvider } from "@/context/file" @@ -28,6 +28,7 @@ import { TerminalProvider } from "@/context/terminal" import DirectoryLayout from "@/pages/directory-layout" import Layout from "@/pages/layout" import { ErrorPage } from "./pages/error" +import { Dynamic } from "solid-js/web" const Home = lazy(() => import("@/pages/home")) const Session = lazy(() => import("@/pages/session")) @@ -144,13 +145,15 @@ export function AppInterface(props: { children?: JSX.Element defaultServer: ServerConnection.Key servers?: Array + router?: Component }) { return ( - {routerProps.children}} > @@ -158,7 +161,7 @@ export function AppInterface(props: { - + diff --git a/packages/app/src/components/dialog-connect-provider.tsx b/packages/app/src/components/dialog-connect-provider.tsx index 90f4f41f7c6..b042205cf4d 100644 --- a/packages/app/src/components/dialog-connect-provider.tsx +++ b/packages/app/src/components/dialog-connect-provider.tsx @@ -4,7 +4,6 @@ import { useDialog } from "@opencode-ai/ui/context/dialog" import { Dialog } from "@opencode-ai/ui/dialog" import { Icon } from "@opencode-ai/ui/icon" import { IconButton } from "@opencode-ai/ui/icon-button" -import type { IconName } from "@opencode-ai/ui/icons/provider" import { List, type ListRef } from "@opencode-ai/ui/list" import { ProviderIcon } from "@opencode-ai/ui/provider-icon" import { Spinner } from "@opencode-ai/ui/spinner" @@ -447,7 +446,7 @@ export function DialogConnectProvider(props: { provider: string }) { >
- +
diff --git a/packages/app/src/components/dialog-select-model-unpaid.tsx b/packages/app/src/components/dialog-select-model-unpaid.tsx index 5ca29a520a0..bcee3f501f5 100644 --- a/packages/app/src/components/dialog-select-model-unpaid.tsx +++ b/packages/app/src/components/dialog-select-model-unpaid.tsx @@ -1,7 +1,6 @@ import { Button } from "@opencode-ai/ui/button" import { useDialog } from "@opencode-ai/ui/context/dialog" import { Dialog } from "@opencode-ai/ui/dialog" -import type { IconName } from "@opencode-ai/ui/icons/provider" import { List, type ListRef } from "@opencode-ai/ui/list" import { ProviderIcon } from "@opencode-ai/ui/provider-icon" import { Tag } from "@opencode-ai/ui/tag" @@ -95,7 +94,7 @@ export const DialogSelectModelUnpaid: Component = () => { > {(i) => (
- + {i.name}
{language.t("dialog.provider.opencode.tagline")}
diff --git a/packages/app/src/components/dialog-select-provider.tsx b/packages/app/src/components/dialog-select-provider.tsx index 76e718bb001..e53738399ab 100644 --- a/packages/app/src/components/dialog-select-provider.tsx +++ b/packages/app/src/components/dialog-select-provider.tsx @@ -5,18 +5,12 @@ import { Dialog } from "@opencode-ai/ui/dialog" import { List } from "@opencode-ai/ui/list" import { Tag } from "@opencode-ai/ui/tag" import { ProviderIcon } from "@opencode-ai/ui/provider-icon" -import { iconNames, type IconName } from "@opencode-ai/ui/icons/provider" import { DialogConnectProvider } from "./dialog-connect-provider" import { useLanguage } from "@/context/language" import { DialogCustomProvider } from "./dialog-custom-provider" const CUSTOM_ID = "_custom" -function icon(id: string): IconName { - if (iconNames.includes(id as IconName)) return id as IconName - return "synthetic" -} - export const DialogSelectProvider: Component = () => { const dialog = useDialog() const providers = useProviders() @@ -69,7 +63,7 @@ export const DialogSelectProvider: Component = () => { > {(i) => (
- + {i.name}
{language.t("dialog.provider.opencode.tagline")}
diff --git a/packages/app/src/components/file-tree.tsx b/packages/app/src/components/file-tree.tsx index 3840f18ed87..930832fb655 100644 --- a/packages/app/src/components/file-tree.tsx +++ b/packages/app/src/components/file-tree.tsx @@ -325,12 +325,6 @@ export default function FileTree(props: { ), ) - createEffect(() => { - const dir = file.tree.state(props.path) - if (!shouldListExpanded({ level, dir })) return - void file.tree.list(props.path) - }) - const nodes = createMemo(() => { const nodes = file.tree.children(props.path) const current = filter() diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx index 3ba3763b8c5..c9c8bc6b441 100644 --- a/packages/app/src/components/prompt-input.tsx +++ b/packages/app/src/components/prompt-input.tsx @@ -1,4 +1,5 @@ import { useFilteredList } from "@opencode-ai/ui/hooks" +import { useSpring } from "@opencode-ai/ui/motion-spring" import { createEffect, on, Component, Show, onCleanup, Switch, Match, createMemo, createSignal } from "solid-js" import { createStore } from "solid-js/store" import { createFocusSignal } from "@solid-primitives/active-element" @@ -23,7 +24,6 @@ import { Button } from "@opencode-ai/ui/button" import { DockShellForm, DockTray } from "@opencode-ai/ui/dock-surface" import { Icon } from "@opencode-ai/ui/icon" import { ProviderIcon } from "@opencode-ai/ui/provider-icon" -import type { IconName } from "@opencode-ai/ui/icons/provider" import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip" import { IconButton } from "@opencode-ai/ui/icon-button" import { Select } from "@opencode-ai/ui/select" @@ -244,6 +244,7 @@ export const PromptInput: Component = (props) => { draggingType: "image" | "@mention" | null mode: "normal" | "shell" applyingHistory: boolean + pendingAutoAccept: boolean }>({ popover: null, historyIndex: -1, @@ -252,8 +253,11 @@ export const PromptInput: Component = (props) => { draggingType: null, mode: "normal", applyingHistory: false, + pendingAutoAccept: false, }) + const buttonsSpring = useSpring(() => (store.mode === "normal" ? 1 : 0), { visualDuration: 0.2, bounce: 0 }) + const commentCount = createMemo(() => { if (store.mode === "shell") return 0 return prompt.context.items().filter((item) => !!item.comment?.trim()).length @@ -302,6 +306,12 @@ export const PromptInput: Component = (props) => { }), ) + createEffect( + on(sessionKey, () => { + setStore("pendingAutoAccept", false) + }), + ) + const historyComments = () => { const byID = new Map(comments.all().map((item) => [`${item.file}\n${item.id}`, item] as const)) return prompt.context.items().flatMap((item) => { @@ -592,7 +602,6 @@ export const PromptInput: Component = (props) => { setActive: setSlashActive, onInput: slashOnInput, onKeyDown: slashOnKeyDown, - refetch: slashRefetch, } = useFilteredList({ items: slashCommands, key: (x) => x?.id, @@ -649,14 +658,6 @@ export const PromptInput: Component = (props) => { } } - createEffect( - on( - () => sync.data.command, - () => slashRefetch(), - { defer: true }, - ), - ) - // Auto-scroll active command into view when navigating with keyboard createEffect(() => { const activeId = slashActive() @@ -957,10 +958,18 @@ export const PromptInput: Component = (props) => { readClipboardImage: platform.readClipboardImage, }) + const variants = createMemo(() => ["default", ...local.model.variant.list()]) + const accepting = createMemo(() => { + const id = params.id + if (!id) return store.pendingAutoAccept + return permission.isAutoAccepting(id, sdk.directory) + }) + const { abort, handleSubmit } = createPromptSubmit({ info, imageAttachments, commentCount, + autoAccept: () => accepting(), mode: () => store.mode, working, editor: () => editorRef, @@ -1125,13 +1134,6 @@ export const PromptInput: Component = (props) => { } } - const variants = createMemo(() => ["default", ...local.model.variant.list()]) - const accepting = createMemo(() => { - const id = params.id - if (!id) return false - return permission.isAutoAccepting(id, sdk.directory) - }) - return (
= (props) => {
0.5 ? "auto" : "none", }} > = (props) => { type="button" variant="ghost" class="size-8 p-0" + style={{ + opacity: buttonsSpring(), + transform: `scale(${0.95 + buttonsSpring() * 0.05})`, + filter: `blur(${(1 - buttonsSpring()) * 2}px)`, + }} onClick={pick} disabled={store.mode !== "normal"} tabIndex={store.mode === "normal" ? undefined : -1} @@ -1304,6 +1310,11 @@ export const PromptInput: Component = (props) => { icon={working() ? "stop" : "arrow-up"} variant="primary" class="size-8" + style={{ + opacity: buttonsSpring(), + transform: `scale(${0.95 + buttonsSpring() * 0.05})`, + filter: `blur(${(1 - buttonsSpring()) * 2}px)`, + }} aria-label={working() ? language.t("prompt.action.stop") : language.t("prompt.action.send")} /> @@ -1323,9 +1334,11 @@ export const PromptInput: Component = (props) => { -
- - -
- + { + setTitle("pendingRename", true) + setTitle("menuOpen", false) + }} + > + {language.t("common.rename")} + + void archiveSession(id())}> + {language.t("common.archive")} + + + dialog.show(() => )} + > + {language.t("common.delete")} + + + + +
+ )} +
+
- - {(message) => { - const comments = createMemo(() => messageComments(sync.data.part[message.id] ?? [])) - return ( -
{ - props.onRegisterMessage(el, message.id) - onCleanup(() => props.onUnregisterMessage(message.id)) - }} - classList={{ - "min-w-0 w-full max-w-full": true, - "md:max-w-200 2xl:max-w-[1000px]": props.centered, - }} + +
+ 0 || props.historyMore}> +
+ +
+
+ + {(messageID) => { + const active = createMemo(() => activeMessageID() === messageID) + const queued = createMemo(() => { + if (active()) return false + const activeID = activeMessageID() + if (activeID) return messageID > activeID + return false + }) + const comments = createMemo(() => messageComments(sync.data.part[messageID] ?? []), [], { + equals: (a, b) => JSON.stringify(a) === JSON.stringify(b), + }) + const commentCount = createMemo(() => comments().length) + return ( +
{ + props.onRegisterMessage(el, messageID) + onCleanup(() => props.onUnregisterMessage(messageID)) + }} + classList={{ + "min-w-0 w-full max-w-full": true, + "md:max-w-200 2xl:max-w-[1000px]": props.centered, + }} + > + 0}> +
+
+
+ + {(commentAccessor: () => MessageComment) => { + const comment = createMemo(() => commentAccessor()) + return ( +
+
+ + {getFilename(comment().path)} + + {(selection) => ( + + {selection().startLine === selection().endLine + ? `:${selection().startLine}` + : `:${selection().startLine}-${selection().endLine}`} + + )} + +
+
+ {comment().comment} +
+
+ ) + }} +
+
-
- - -
- ) - }} - + + +
+ ) + }} +
+
diff --git a/packages/app/src/pages/session/review-tab.tsx b/packages/app/src/pages/session/review-tab.tsx index 1b285407b6d..142ee7ad929 100644 --- a/packages/app/src/pages/session/review-tab.tsx +++ b/packages/app/src/pages/session/review-tab.tsx @@ -1,4 +1,4 @@ -import { createEffect, on, onCleanup, type JSX } from "solid-js" +import { createEffect, onCleanup, type JSX } from "solid-js" import type { FileDiff } from "@opencode-ai/sdk/v2" import { SessionReview } from "@opencode-ai/ui/session-review" import type { @@ -119,32 +119,12 @@ export function SessionReviewTab(props: SessionReviewTabProps) { }) } - createEffect( - on( - () => props.diffs().length, - () => queueRestore(), - { defer: true }, - ), - ) - - createEffect( - on( - () => props.diffStyle, - () => queueRestore(), - { defer: true }, - ), - ) - - createEffect( - on( - () => layout.ready(), - (ready) => { - if (!ready) return - queueRestore() - }, - { defer: true }, - ), - ) + createEffect(() => { + props.diffs().length + props.diffStyle + if (!layout.ready()) return + queueRestore() + }) onCleanup(() => { if (restoreFrame !== undefined) cancelAnimationFrame(restoreFrame) @@ -176,7 +156,7 @@ export function SessionReviewTab(props: SessionReviewTabProps) { open={props.view().review.open()} onOpenChange={props.view().review.setOpen} classes={{ - root: props.classes?.root ?? "pb-6 pr-3", + root: props.classes?.root ?? "pr-3", header: props.classes?.header ?? "px-3", container: props.classes?.container ?? "pl-3", }} diff --git a/packages/app/src/pages/session/terminal-panel.tsx b/packages/app/src/pages/session/terminal-panel.tsx index 27ea4e6f317..cc4c17ee216 100644 --- a/packages/app/src/pages/session/terminal-panel.tsx +++ b/packages/app/src/pages/session/terminal-panel.tsx @@ -56,9 +56,9 @@ export function TerminalPanel() { on( () => terminal.all().length, (count, prevCount) => { - if (prevCount !== undefined && prevCount > 0 && count === 0) { - if (opened()) view().terminal.toggle() - } + if (prevCount === undefined || prevCount <= 0 || count !== 0) return + if (!opened()) return + close() }, ), ) @@ -102,7 +102,7 @@ export function TerminalPanel() { const all = createMemo(() => terminal.all()) const ids = createMemo(() => all().map((pty) => pty.id)) - const byId = createMemo(() => new Map(all().map((pty) => [pty.id, pty]))) + const byId = createMemo(() => new Map(all().map((pty) => [pty.id, { ...pty }]))) const handleTerminalDragStart = (event: unknown) => { const id = getDraggableId(event) @@ -189,7 +189,13 @@ export function TerminalPanel() { > - {(pty) => } + + {(id) => ( + + {(pty) => } + + )} +
{ @@ -19,7 +19,6 @@ export const useSessionHashScroll = (input: { setPendingMessage: (value: string | undefined) => void setActiveMessage: (message: UserMessage | undefined) => void setTurnStart: (value: number) => void - scheduleTurnBackfill: () => void autoScroll: { pause: () => void; forceScrollToBottom: () => void } scroller: () => HTMLDivElement | undefined anchor: (id: string) => string @@ -29,6 +28,7 @@ export const useSessionHashScroll = (input: { const visibleUserMessages = createMemo(() => input.visibleUserMessages()) const messageById = createMemo(() => new Map(visibleUserMessages().map((m) => [m.id, m]))) const messageIndex = createMemo(() => new Map(visibleUserMessages().map((m, i) => [m.id, i]))) + let pendingKey = "" const clearMessageHash = () => { if (!window.location.hash) return @@ -58,7 +58,6 @@ export const useSessionHashScroll = (input: { const index = messageIndex().get(message.id) ?? -1 if (index !== -1 && index < input.turnStart()) { input.setTurnStart(index) - input.scheduleTurnBackfill() requestAnimationFrame(() => { const el = document.getElementById(input.anchor(message.id)) @@ -132,15 +131,6 @@ export const useSessionHashScroll = (input: { if (el) input.scheduleScrollState(el) } - createEffect( - on(input.sessionKey, (key) => { - if (!input.sessionID()) return - const messageID = input.consumePendingMessage(key) - if (!messageID) return - input.setPendingMessage(messageID) - }), - ) - createEffect(() => { if (!input.sessionID() || !input.messagesReady()) return requestAnimationFrame(() => applyHash("auto")) @@ -152,7 +142,20 @@ export const useSessionHashScroll = (input: { visibleUserMessages() input.turnStart() - const targetId = input.pendingMessage() ?? messageIdFromHash(window.location.hash) + let targetId = input.pendingMessage() + if (!targetId) { + const key = input.sessionKey() + if (pendingKey !== key) { + pendingKey = key + const next = input.consumePendingMessage(key) + if (next) { + input.setPendingMessage(next) + targetId = next + } + } + } + + if (!targetId) targetId = messageIdFromHash(window.location.hash) if (!targetId) return if (input.currentMessageId() === targetId) return @@ -164,9 +167,16 @@ export const useSessionHashScroll = (input: { requestAnimationFrame(() => scrollToMessage(msg, "auto")) }) - createEffect(() => { - if (!input.sessionID() || !input.messagesReady()) return - const handler = () => requestAnimationFrame(() => applyHash("auto")) + onMount(() => { + if (typeof window !== "undefined" && "scrollRestoration" in window.history) { + window.history.scrollRestoration = "manual" + } + + const handler = () => { + if (!input.sessionID() || !input.messagesReady()) return + requestAnimationFrame(() => applyHash("auto")) + } + window.addEventListener("hashchange", handler) onCleanup(() => window.removeEventListener("hashchange", handler)) }) diff --git a/packages/app/src/utils/notification-click.test.ts b/packages/app/src/utils/notification-click.test.ts index 76535f83a8e..fa81b0e0251 100644 --- a/packages/app/src/utils/notification-click.test.ts +++ b/packages/app/src/utils/notification-click.test.ts @@ -1,26 +1,27 @@ -import { describe, expect, test } from "bun:test" -import { handleNotificationClick } from "./notification-click" +import { afterEach, describe, expect, test } from "bun:test" +import { handleNotificationClick, setNavigate } from "./notification-click" describe("notification click", () => { - test("focuses and navigates when href exists", () => { + afterEach(() => { + setNavigate(undefined as any) + }) + + test("navigates via registered navigate function", () => { const calls: string[] = [] - handleNotificationClick("/abc/session/123", { - focus: () => calls.push("focus"), - location: { - assign: (href) => calls.push(href), - }, - }) - expect(calls).toEqual(["focus", "/abc/session/123"]) + setNavigate((href) => calls.push(href)) + handleNotificationClick("/abc/session/123") + expect(calls).toEqual(["/abc/session/123"]) }) - test("only focuses when href is missing", () => { + test("does not navigate when href is missing", () => { const calls: string[] = [] - handleNotificationClick(undefined, { - focus: () => calls.push("focus"), - location: { - assign: (href) => calls.push(href), - }, - }) - expect(calls).toEqual(["focus"]) + setNavigate((href) => calls.push(href)) + handleNotificationClick(undefined) + expect(calls).toEqual([]) + }) + + test("falls back to location.assign without registered navigate", () => { + handleNotificationClick("/abc/session/123") + // falls back to window.location.assign — no error thrown }) }) diff --git a/packages/app/src/utils/notification-click.ts b/packages/app/src/utils/notification-click.ts index 1234cd1d629..94086c59593 100644 --- a/packages/app/src/utils/notification-click.ts +++ b/packages/app/src/utils/notification-click.ts @@ -1,12 +1,12 @@ -type WindowTarget = { - focus: () => void - location: { - assign: (href: string) => void - } +let nav: ((href: string) => void) | undefined + +export const setNavigate = (fn: (href: string) => void) => { + nav = fn } -export const handleNotificationClick = (href?: string, target: WindowTarget = window) => { - target.focus() +export const handleNotificationClick = (href?: string) => { + window.focus() if (!href) return - target.location.assign(href) + if (nav) nav(href) + else window.location.assign(href) } diff --git a/packages/console/app/package.json b/packages/console/app/package.json index ad5813ced67..c2e847991ce 100644 --- a/packages/console/app/package.json +++ b/packages/console/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-app", - "version": "1.2.15", + "version": "1.2.16", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/console/app/src/i18n/tr.ts b/packages/console/app/src/i18n/tr.ts index 11e6ed58d4a..bd7a3042d76 100644 --- a/packages/console/app/src/i18n/tr.ts +++ b/packages/console/app/src/i18n/tr.ts @@ -102,7 +102,7 @@ export const dict = { "temp.logoDarkAlt": "opencode koyu logo", "home.banner.badge": "Yeni", - "home.banner.text": "Masaüstü uygulaması beta olarak kullanılabilir", + "home.banner.text": "Masaüstü uygulaması beta olarak mevcut", "home.banner.platforms": "macOS, Windows ve Linux'ta", "home.banner.downloadNow": "Şimdi indir", "home.banner.downloadBetaNow": "Masaüstü betayı şimdi indir", @@ -139,7 +139,7 @@ export const dict = { "home.growth.contributors": "Katılımcılar", "home.growth.monthlyDevs": "Aylık Geliştiriciler", - "home.privacy.title": "Önce gizlilik için tasarlandı", + "home.privacy.title": "Gizlilik öncelikli tasarlandı", "home.privacy.body": "OpenCode kodunuzu veya bağlam verilerinizi saklamaz; bu sayede gizliliğe duyarlı ortamlarda çalışabilir.", "home.privacy.learnMore": "Hakkında daha fazla bilgi:", @@ -157,12 +157,12 @@ export const dict = { "home.faq.a3.p2.afterZen": " hesabı oluşturabilirsiniz.", "home.faq.a3.p3": "Zen'i öneriyoruz, ancak OpenCode OpenAI, Anthropic, xAI gibi popüler sağlayıcılarla da çalışır.", "home.faq.a3.p4.beforeLocal": "Hatta", - "home.faq.a3.p4.localLink": "yerel modellerinizi", + "home.faq.a3.p4.localLink": "yerel modellerinizi bağlayabilirsiniz", "home.faq.q4": "Mevcut AI aboneliklerimi OpenCode ile kullanabilir miyim?", "home.faq.a4.p1": "Evet. OpenCode tüm büyük sağlayıcıların aboneliklerini destekler. Claude Pro/Max, ChatGPT Plus/Pro veya GitHub Copilot kullanabilirsiniz.", "home.faq.q5": "OpenCode'u sadece terminalde mi kullanabilirim?", - "home.faq.a5.beforeDesktop": "Artık hayır! OpenCode şimdi", + "home.faq.a5.beforeDesktop": "Artık hayır! OpenCode artık sizin bu cihazlarınıza", "home.faq.a5.desktop": "masaüstü", "home.faq.a5.and": "ve", "home.faq.a5.web": "web", @@ -178,10 +178,10 @@ export const dict = { "home.faq.a7.p2.shareLink": "paylaşım sayfaları", "home.faq.q8": "OpenCode açık kaynak mı?", "home.faq.a8.p1": "Evet, OpenCode tamamen açık kaynaktır. Kaynak kodu", - "home.faq.a8.p2": "altında", + "home.faq.a8.p2": "'da", "home.faq.a8.mitLicense": "MIT Lisansı", "home.faq.a8.p3": - ", yani herkes kullanabilir, değiştirebilir veya geliştirmeye katkıda bulunabilir. Topluluktan herkes issue açabilir, pull request gönderebilir ve işlevselliği genişletebilir.", + "altında herkese açıktır, yani herkes kullanabilir, değiştirebilir veya geliştirmeye katkıda bulunabilir. Topluluktan herkes issue açabilir, pull request gönderebilir ve işlevselliği genişletebilir.", "home.zenCta.title": "Kodlama ajanları için güvenilir, optimize modeller", "home.zenCta.body": diff --git a/packages/console/app/src/routes/zen/util/handler.ts b/packages/console/app/src/routes/zen/util/handler.ts index d3a25c5f661..429ce001855 100644 --- a/packages/console/app/src/routes/zen/util/handler.ts +++ b/packages/console/app/src/routes/zen/util/handler.ts @@ -97,9 +97,9 @@ export async function handler( const zenData = ZenData.list(opts.modelList) const modelInfo = validateModel(zenData, model) const dataDumper = createDataDumper(sessionId, requestId, projectId) - const trialLimiter = createTrialLimiter(modelInfo.trial, ip, ocClient) - const isTrial = await trialLimiter?.isTrial() - const rateLimiter = createRateLimiter(modelInfo.rateLimit, ip, input.request) + const trialLimiter = createTrialLimiter(modelInfo.trialProvider, ip) + const trialProvider = await trialLimiter?.check() + const rateLimiter = createRateLimiter(modelInfo.allowAnonymous, ip, input.request) await rateLimiter?.check() const stickyTracker = createStickyTracker(modelInfo.stickyProvider, sessionId) const stickyProvider = await stickyTracker?.get() @@ -114,7 +114,7 @@ export async function handler( authInfo, modelInfo, sessionId, - isTrial ?? false, + trialProvider, retry, stickyProvider, ) @@ -144,9 +144,6 @@ export async function handler( Object.entries(providerInfo.headerMappings ?? {}).forEach(([k, v]) => { headers.set(k, headers.get(v)!) }) - Object.entries(providerInfo.headers ?? {}).forEach(([k, v]) => { - headers.set(k, v) - }) headers.delete("host") headers.delete("content-length") headers.delete("x-opencode-request") @@ -295,18 +292,13 @@ export async function handler( part = part.trim() usageParser.parse(part) - if (providerInfo.responseModifier) { - for (const [k, v] of Object.entries(providerInfo.responseModifier)) { - part = part.replace(k, v) - } - c.enqueue(encoder.encode(part + "\n\n")) - } else if (providerInfo.format !== opts.format) { + if (providerInfo.format !== opts.format) { part = streamConverter(part) c.enqueue(encoder.encode(part + "\n\n")) } } - if (!providerInfo.responseModifier && providerInfo.format === opts.format) { + if (providerInfo.format === opts.format) { c.enqueue(value) } @@ -398,7 +390,7 @@ export async function handler( authInfo: AuthInfo, modelInfo: ModelInfo, sessionId: string, - isTrial: boolean, + trialProvider: string | undefined, retry: RetryOptions, stickyProvider: string | undefined, ) { @@ -407,8 +399,8 @@ export async function handler( return modelInfo.providers.find((provider) => provider.id === modelInfo.byokProvider) } - if (isTrial) { - return modelInfo.providers.find((provider) => provider.id === modelInfo.trial!.provider) + if (trialProvider) { + return modelInfo.providers.find((provider) => provider.id === trialProvider) } if (stickyProvider) { diff --git a/packages/console/app/src/routes/zen/util/provider/anthropic.ts b/packages/console/app/src/routes/zen/util/provider/anthropic.ts index e2803459e08..95c50fbdbf6 100644 --- a/packages/console/app/src/routes/zen/util/provider/anthropic.ts +++ b/packages/console/app/src/routes/zen/util/provider/anthropic.ts @@ -43,7 +43,7 @@ export const anthropicHelper: ProviderHelper = ({ reqModel, providerModel }) => ...(isBedrock ? { anthropic_version: "bedrock-2023-05-31", - anthropic_beta: supports1m ? "context-1m-2025-08-07" : undefined, + anthropic_beta: supports1m ? ["context-1m-2025-08-07"] : undefined, model: undefined, stream: undefined, } diff --git a/packages/console/app/src/routes/zen/util/rateLimiter.ts b/packages/console/app/src/routes/zen/util/rateLimiter.ts index 6325a7b4df9..019e68754de 100644 --- a/packages/console/app/src/routes/zen/util/rateLimiter.ts +++ b/packages/console/app/src/routes/zen/util/rateLimiter.ts @@ -2,29 +2,28 @@ import { Database, eq, and, sql, inArray } from "@opencode-ai/console-core/drizz import { IpRateLimitTable } from "@opencode-ai/console-core/schema/ip.sql.js" import { FreeUsageLimitError } from "./error" import { logger } from "./logger" -import { ZenData } from "@opencode-ai/console-core/model.js" import { i18n } from "~/i18n" import { localeFromRequest } from "~/lib/language" +import { Subscription } from "@opencode-ai/console-core/subscription.js" -export function createRateLimiter(limit: ZenData.RateLimit | undefined, rawIp: string, request: Request) { - if (!limit) return +export function createRateLimiter(allowAnonymous: boolean | undefined, rawIp: string, request: Request) { + if (!allowAnonymous) return const dict = i18n(localeFromRequest(request)) - const limitValue = limit.checkHeader && !request.headers.get(limit.checkHeader) ? limit.fallbackValue! : limit.value + const limits = Subscription.getFreeLimits() + const limitValue = + limits.checkHeader && !request.headers.get(limits.checkHeader) ? limits.fallbackValue : limits.dailyRequests const ip = !rawIp.length ? "unknown" : rawIp const now = Date.now() - const intervals = - limit.period === "day" - ? [buildYYYYMMDD(now)] - : [buildYYYYMMDDHH(now), buildYYYYMMDDHH(now - 3_600_000), buildYYYYMMDDHH(now - 7_200_000)] + const interval = buildYYYYMMDD(now) return { track: async () => { await Database.use((tx) => tx .insert(IpRateLimitTable) - .values({ ip, interval: intervals[0], count: 1 }) + .values({ ip, interval, count: 1 }) .onDuplicateKeyUpdate({ set: { count: sql`${IpRateLimitTable.count} + 1` } }), ) }, @@ -33,15 +32,12 @@ export function createRateLimiter(limit: ZenData.RateLimit | undefined, rawIp: s tx .select({ interval: IpRateLimitTable.interval, count: IpRateLimitTable.count }) .from(IpRateLimitTable) - .where(and(eq(IpRateLimitTable.ip, ip), inArray(IpRateLimitTable.interval, intervals))), + .where(and(eq(IpRateLimitTable.ip, ip), inArray(IpRateLimitTable.interval, [interval]))), ) const total = rows.reduce((sum, r) => sum + r.count, 0) logger.debug(`rate limit total: ${total}`) if (total >= limitValue) - throw new FreeUsageLimitError( - dict["zen.api.error.rateLimitExceeded"], - limit.period === "day" ? getRetryAfterDay(now) : getRetryAfterHour(rows, intervals, limitValue, now), - ) + throw new FreeUsageLimitError(dict["zen.api.error.rateLimitExceeded"], getRetryAfterDay(now)) }, } } @@ -50,37 +46,9 @@ export function getRetryAfterDay(now: number) { return Math.ceil((86_400_000 - (now % 86_400_000)) / 1000) } -export function getRetryAfterHour( - rows: { interval: string; count: number }[], - intervals: string[], - limit: number, - now: number, -) { - const counts = new Map(rows.map((r) => [r.interval, r.count])) - // intervals are ordered newest to oldest: [current, -1h, -2h] - // simulate dropping oldest intervals one at a time - let running = intervals.reduce((sum, i) => sum + (counts.get(i) ?? 0), 0) - for (let i = intervals.length - 1; i >= 0; i--) { - running -= counts.get(intervals[i]) ?? 0 - if (running < limit) { - // interval at index i rolls out of the window (intervals.length - i) hours from the current hour start - const hours = intervals.length - i - return Math.ceil((hours * 3_600_000 - (now % 3_600_000)) / 1000) - } - } - return Math.ceil((3_600_000 - (now % 3_600_000)) / 1000) -} - function buildYYYYMMDD(timestamp: number) { return new Date(timestamp) .toISOString() .replace(/[^0-9]/g, "") .substring(0, 8) } - -function buildYYYYMMDDHH(timestamp: number) { - return new Date(timestamp) - .toISOString() - .replace(/[^0-9]/g, "") - .substring(0, 10) -} diff --git a/packages/console/app/src/routes/zen/util/trialLimiter.ts b/packages/console/app/src/routes/zen/util/trialLimiter.ts index 531e5cf0c30..1ae0ab32924 100644 --- a/packages/console/app/src/routes/zen/util/trialLimiter.ts +++ b/packages/console/app/src/routes/zen/util/trialLimiter.ts @@ -1,21 +1,18 @@ import { Database, eq, sql } from "@opencode-ai/console-core/drizzle/index.js" import { IpTable } from "@opencode-ai/console-core/schema/ip.sql.js" import { UsageInfo } from "./provider/provider" -import { ZenData } from "@opencode-ai/console-core/model.js" +import { Subscription } from "@opencode-ai/console-core/subscription.js" -export function createTrialLimiter(trial: ZenData.Trial | undefined, ip: string, client: string) { - if (!trial) return +export function createTrialLimiter(trialProvider: string | undefined, ip: string) { + if (!trialProvider) return if (!ip) return - const limit = - trial.limits.find((limit) => limit.client === client)?.limit ?? - trial.limits.find((limit) => limit.client === undefined)?.limit - if (!limit) return + const limit = Subscription.getFreeLimits().promoTokens let _isTrial: boolean return { - isTrial: async () => { + check: async () => { const data = await Database.use((tx) => tx .select({ @@ -27,7 +24,7 @@ export function createTrialLimiter(trial: ZenData.Trial | undefined, ip: string, ) _isTrial = (data?.usage ?? 0) < limit - return _isTrial + return _isTrial ? trialProvider : undefined }, track: async (usageInfo: UsageInfo) => { if (!_isTrial) return diff --git a/packages/console/app/test/rateLimiter.test.ts b/packages/console/app/test/rateLimiter.test.ts index 864f907d669..5cc97dccf6c 100644 --- a/packages/console/app/test/rateLimiter.test.ts +++ b/packages/console/app/test/rateLimiter.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test" -import { getRetryAfterDay, getRetryAfterHour } from "../src/routes/zen/util/rateLimiter" +import { getRetryAfterDay } from "../src/routes/zen/util/rateLimiter" describe("getRetryAfterDay", () => { test("returns full day at midnight UTC", () => { @@ -17,76 +17,3 @@ describe("getRetryAfterDay", () => { expect(getRetryAfterDay(almost)).toBe(1) }) }) - -describe("getRetryAfterHour", () => { - // 14:30:00 UTC — 30 minutes into the current hour - const now = Date.UTC(2026, 0, 15, 14, 30, 0, 0) - const intervals = ["2026011514", "2026011513", "2026011512"] - - test("waits 3 hours when all usage is in current hour", () => { - const rows = [{ interval: "2026011514", count: 10 }] - // only current hour has usage — it won't leave the window for 3 hours from hour start - // 3 * 3600 - 1800 = 9000s - expect(getRetryAfterHour(rows, intervals, 10, now)).toBe(9000) - }) - - test("waits 1 hour when dropping oldest interval is sufficient", () => { - const rows = [ - { interval: "2026011514", count: 2 }, - { interval: "2026011512", count: 10 }, - ] - // total=12, drop oldest (-2h, count=10) -> 2 < 10 - // hours = 3 - 2 = 1 -> 1 * 3600 - 1800 = 1800s - expect(getRetryAfterHour(rows, intervals, 10, now)).toBe(1800) - }) - - test("waits 2 hours when usage spans oldest two intervals", () => { - const rows = [ - { interval: "2026011513", count: 8 }, - { interval: "2026011512", count: 5 }, - ] - // total=13, drop -2h (5) -> 8, 8 >= 8, drop -1h (8) -> 0 < 8 - // hours = 3 - 1 = 2 -> 2 * 3600 - 1800 = 5400s - expect(getRetryAfterHour(rows, intervals, 8, now)).toBe(5400) - }) - - test("waits 1 hour when oldest interval alone pushes over limit", () => { - const rows = [ - { interval: "2026011514", count: 1 }, - { interval: "2026011513", count: 1 }, - { interval: "2026011512", count: 10 }, - ] - // total=12, drop -2h (10) -> 2 < 10 - // hours = 3 - 2 = 1 -> 1800s - expect(getRetryAfterHour(rows, intervals, 10, now)).toBe(1800) - }) - - test("waits 2 hours when middle interval keeps total over limit", () => { - const rows = [ - { interval: "2026011514", count: 4 }, - { interval: "2026011513", count: 4 }, - { interval: "2026011512", count: 4 }, - ] - // total=12, drop -2h (4) -> 8, 8 >= 5, drop -1h (4) -> 4 < 5 - // hours = 3 - 1 = 2 -> 5400s - expect(getRetryAfterHour(rows, intervals, 5, now)).toBe(5400) - }) - - test("rounds up to nearest second", () => { - const offset = Date.UTC(2026, 0, 15, 14, 30, 0, 500) - const rows = [ - { interval: "2026011514", count: 2 }, - { interval: "2026011512", count: 10 }, - ] - // hours=1 -> 3_600_000 - 1_800_500 = 1_799_500ms -> ceil(1799.5) = 1800 - expect(getRetryAfterHour(rows, intervals, 10, offset)).toBe(1800) - }) - - test("fallback returns time until next hour when rows are empty", () => { - // edge case: rows empty but function called (shouldn't happen in practice) - // loop drops all zeros, running stays 0 which is < any positive limit on first iteration - const rows: { interval: string; count: number }[] = [] - // drop -2h (0) -> 0 < 1 -> hours = 3 - 2 = 1 -> 1800s - expect(getRetryAfterHour(rows, intervals, 1, now)).toBe(1800) - }) -}) diff --git a/packages/console/core/package.json b/packages/console/core/package.json index 8f65e0c4578..40d2f8f016a 100644 --- a/packages/console/core/package.json +++ b/packages/console/core/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/console-core", - "version": "1.2.15", + "version": "1.2.16", "private": true, "type": "module", "license": "MIT", @@ -34,12 +34,9 @@ "promote-models-to-prod": "script/promote-models.ts production", "pull-models-from-dev": "script/pull-models.ts dev", "pull-models-from-prod": "script/pull-models.ts production", - "update-black": "script/update-black.ts", - "promote-black-to-dev": "script/promote-black.ts dev", - "promote-black-to-prod": "script/promote-black.ts production", - "update-lite": "script/update-lite.ts", - "promote-lite-to-dev": "script/promote-lite.ts dev", - "promote-lite-to-prod": "script/promote-lite.ts production", + "update-limits": "script/update-limits.ts", + "promote-limits-to-dev": "script/promote-limits.ts dev", + "promote-limits-to-prod": "script/promote-limits.ts production", "typecheck": "tsgo --noEmit" }, "devDependencies": { diff --git a/packages/console/core/script/black-stats.ts b/packages/console/core/script/black-stats.ts new file mode 100644 index 00000000000..de7cf5e415f --- /dev/null +++ b/packages/console/core/script/black-stats.ts @@ -0,0 +1,312 @@ +import { Database, and, eq, inArray, isNotNull, sql } from "../src/drizzle/index.js" +import { BillingTable, BlackPlans, SubscriptionTable, UsageTable } from "../src/schema/billing.sql.js" + +if (process.argv.length < 3) { + console.error("Usage: bun black-stats.ts ") + process.exit(1) +} +const plan = process.argv[2] as (typeof BlackPlans)[number] +if (!BlackPlans.includes(plan)) { + console.error("Usage: bun black-stats.ts ") + process.exit(1) +} +const cutoff = new Date(Date.UTC(2026, 1, 0, 23, 59, 59, 999)) + +// get workspaces +const workspaces = await Database.use((tx) => + tx + .select({ workspaceID: BillingTable.workspaceID }) + .from(BillingTable) + .where( + and(isNotNull(BillingTable.subscriptionID), sql`JSON_UNQUOTE(JSON_EXTRACT(subscription, '$.plan')) = ${plan}`), + ), +) +if (workspaces.length === 0) throw new Error(`No active Black ${plan} subscriptions found`) + +const week = sql`YEARWEEK(${UsageTable.timeCreated}, 3)` +const workspaceIDs = workspaces.map((row) => row.workspaceID) +// Get subscription spend +const spend = await Database.use((tx) => + tx + .select({ + workspaceID: UsageTable.workspaceID, + week, + amount: sql`COALESCE(SUM(${UsageTable.cost}), 0)`, + }) + .from(UsageTable) + .where( + and(inArray(UsageTable.workspaceID, workspaceIDs), sql`JSON_UNQUOTE(JSON_EXTRACT(enrichment, '$.plan')) = 'sub'`), + ) + .groupBy(UsageTable.workspaceID, week), +) + +// Get pay per use spend +const ppu = await Database.use((tx) => + tx + .select({ + workspaceID: UsageTable.workspaceID, + week, + amount: sql`COALESCE(SUM(${UsageTable.cost}), 0)`, + }) + .from(UsageTable) + .where( + and( + inArray(UsageTable.workspaceID, workspaceIDs), + sql`(${UsageTable.enrichment} IS NULL OR JSON_UNQUOTE(JSON_EXTRACT(enrichment, '$.plan')) != 'sub')`, + ), + ) + .groupBy(UsageTable.workspaceID, week), +) + +const models = await Database.use((tx) => + tx + .select({ + workspaceID: UsageTable.workspaceID, + model: UsageTable.model, + amount: sql`COALESCE(SUM(${UsageTable.cost}), 0)`, + }) + .from(UsageTable) + .where( + and(inArray(UsageTable.workspaceID, workspaceIDs), sql`JSON_UNQUOTE(JSON_EXTRACT(enrichment, '$.plan')) = 'sub'`), + ) + .groupBy(UsageTable.workspaceID, UsageTable.model), +) + +const tokens = await Database.use((tx) => + tx + .select({ + workspaceID: UsageTable.workspaceID, + week, + input: sql`COALESCE(SUM(${UsageTable.inputTokens}), 0)`, + cacheRead: sql`COALESCE(SUM(${UsageTable.cacheReadTokens}), 0)`, + output: sql`COALESCE(SUM(${UsageTable.outputTokens}), 0) + COALESCE(SUM(${UsageTable.reasoningTokens}), 0)`, + }) + .from(UsageTable) + .where( + and(inArray(UsageTable.workspaceID, workspaceIDs), sql`JSON_UNQUOTE(JSON_EXTRACT(enrichment, '$.plan')) = 'sub'`), + ) + .groupBy(UsageTable.workspaceID, week), +) + +const allWeeks = [...spend, ...ppu].map((row) => row.week) +const weeks = [...new Set(allWeeks)].sort((a, b) => a - b) +const spendMap = new Map>() +const totals = new Map() +const ppuMap = new Map>() +const ppuTotals = new Map() +const modelMap = new Map() +const tokenMap = new Map>() + +for (const row of spend) { + const workspace = spendMap.get(row.workspaceID) ?? new Map() + const total = totals.get(row.workspaceID) ?? 0 + const amount = toNumber(row.amount) + workspace.set(row.week, amount) + totals.set(row.workspaceID, total + amount) + spendMap.set(row.workspaceID, workspace) +} + +for (const row of ppu) { + const workspace = ppuMap.get(row.workspaceID) ?? new Map() + const total = ppuTotals.get(row.workspaceID) ?? 0 + const amount = toNumber(row.amount) + workspace.set(row.week, amount) + ppuTotals.set(row.workspaceID, total + amount) + ppuMap.set(row.workspaceID, workspace) +} + +for (const row of models) { + const current = modelMap.get(row.workspaceID) ?? [] + current.push({ model: row.model, amount: toNumber(row.amount) }) + modelMap.set(row.workspaceID, current) +} + +for (const row of tokens) { + const workspace = tokenMap.get(row.workspaceID) ?? new Map() + workspace.set(row.week, { + input: toNumber(row.input), + cacheRead: toNumber(row.cacheRead), + output: toNumber(row.output), + }) + tokenMap.set(row.workspaceID, workspace) +} + +const users = await Database.use((tx) => + tx + .select({ + workspaceID: SubscriptionTable.workspaceID, + subscribed: SubscriptionTable.timeCreated, + subscription: BillingTable.subscription, + }) + .from(SubscriptionTable) + .innerJoin(BillingTable, eq(SubscriptionTable.workspaceID, BillingTable.workspaceID)) + .where( + and(inArray(SubscriptionTable.workspaceID, workspaceIDs), sql`${SubscriptionTable.timeCreated} <= ${cutoff}`), + ), +) + +const counts = new Map() +for (const user of users) { + const current = counts.get(user.workspaceID) ?? 0 + counts.set(user.workspaceID, current + 1) +} + +const rows = users + .map((user) => { + const workspace = spendMap.get(user.workspaceID) ?? new Map() + const ppuWorkspace = ppuMap.get(user.workspaceID) ?? new Map() + const count = counts.get(user.workspaceID) ?? 1 + const amount = (totals.get(user.workspaceID) ?? 0) / count + const ppuAmount = (ppuTotals.get(user.workspaceID) ?? 0) / count + const monthStart = user.subscribed ? startOfMonth(user.subscribed) : null + const modelRows = (modelMap.get(user.workspaceID) ?? []).sort((a, b) => b.amount - a.amount).slice(0, 3) + const modelTotal = totals.get(user.workspaceID) ?? 0 + const modelCells = modelRows.map((row) => ({ + model: row.model, + percent: modelTotal > 0 ? `${((row.amount / modelTotal) * 100).toFixed(1)}%` : "0.0%", + })) + const modelData = [0, 1, 2].map((index) => modelCells[index] ?? { model: "-", percent: "-" }) + const weekly = Object.fromEntries( + weeks.map((item) => { + const value = (workspace.get(item) ?? 0) / count + const beforeMonth = monthStart ? isoWeekStart(item) < monthStart : false + return [formatWeek(item), beforeMonth ? "-" : formatMicroCents(value)] + }), + ) + const ppuWeekly = Object.fromEntries( + weeks.map((item) => { + const value = (ppuWorkspace.get(item) ?? 0) / count + const beforeMonth = monthStart ? isoWeekStart(item) < monthStart : false + return [formatWeek(item), beforeMonth ? "-" : formatMicroCents(value)] + }), + ) + const tokenWorkspace = tokenMap.get(user.workspaceID) ?? new Map() + const weeklyTokens = Object.fromEntries( + weeks.map((item) => { + const t = tokenWorkspace.get(item) ?? { input: 0, cacheRead: 0, output: 0 } + const beforeMonth = monthStart ? isoWeekStart(item) < monthStart : false + return [ + formatWeek(item), + beforeMonth + ? { input: "-", cacheRead: "-", output: "-" } + : { + input: Math.round(t.input / count), + cacheRead: Math.round(t.cacheRead / count), + output: Math.round(t.output / count), + }, + ] + }), + ) + return { + workspaceID: user.workspaceID, + useBalance: user.subscription?.useBalance ?? false, + subscribed: formatDate(user.subscribed), + subscribedAt: user.subscribed?.getTime() ?? 0, + amount, + ppuAmount, + models: modelData, + weekly, + ppuWeekly, + weeklyTokens, + } + }) + .sort((a, b) => a.subscribedAt - b.subscribedAt) + +console.log(`Black ${plan} subscribers: ${rows.length}`) +const header = [ + "workspaceID", + "subscribed", + "useCredit", + "subTotal", + "ppuTotal", + "model1", + "model1%", + "model2", + "model2%", + "model3", + "model3%", + ...weeks.flatMap((item) => [ + formatWeek(item) + " sub", + formatWeek(item) + " ppu", + formatWeek(item) + " input", + formatWeek(item) + " cache", + formatWeek(item) + " output", + ]), +] +const lines = [header.map(csvCell).join(",")] +for (const row of rows) { + const model1 = row.models[0] + const model2 = row.models[1] + const model3 = row.models[2] + const cells = [ + row.workspaceID, + row.subscribed ?? "", + row.useBalance ? "yes" : "no", + formatMicroCents(row.amount), + formatMicroCents(row.ppuAmount), + model1.model, + model1.percent, + model2.model, + model2.percent, + model3.model, + model3.percent, + ...weeks.flatMap((item) => { + const t = row.weeklyTokens[formatWeek(item)] ?? { input: "-", cacheRead: "-", output: "-" } + return [ + row.weekly[formatWeek(item)] ?? "", + row.ppuWeekly[formatWeek(item)] ?? "", + String(t.input), + String(t.cacheRead), + String(t.output), + ] + }), + ] + lines.push(cells.map(csvCell).join(",")) +} +const output = `${lines.join("\n")}\n` +const file = Bun.file(`black-stats-${plan}.csv`) +await file.write(output) +console.log(`Wrote ${lines.length - 1} rows to ${file.name}`) +const total = rows.reduce((sum, row) => sum + row.amount, 0) +const average = rows.length === 0 ? 0 : total / rows.length +console.log(`Average spending per user: ${formatMicroCents(average)}`) + +function formatMicroCents(value: number) { + return `$${(value / 100000000).toFixed(2)}` +} + +function formatDate(value: Date | null | undefined) { + if (!value) return null + return value.toISOString().split("T")[0] +} + +function formatWeek(value: number) { + return formatDate(isoWeekStart(value)) ?? "" +} + +function startOfMonth(value: Date) { + return new Date(Date.UTC(value.getUTCFullYear(), value.getUTCMonth(), 1)) +} + +function isoWeekStart(value: number) { + const year = Math.floor(value / 100) + const weekNumber = value % 100 + const jan4 = new Date(Date.UTC(year, 0, 4)) + const day = jan4.getUTCDay() || 7 + const weekStart = new Date(Date.UTC(year, 0, 4 - (day - 1))) + weekStart.setUTCDate(weekStart.getUTCDate() + (weekNumber - 1) * 7) + return weekStart +} + +function toNumber(value: unknown) { + if (typeof value === "number") return value + if (typeof value === "bigint") return Number(value) + if (typeof value === "string") return Number(value) + return 0 +} + +function csvCell(value: string | number) { + const text = String(value) + if (!/[",\n]/.test(text)) return text + return `"${text.replace(/"/g, '""')}"` +} diff --git a/packages/console/core/script/promote-black.ts b/packages/console/core/script/promote-black.ts deleted file mode 100755 index 4338d0e4216..00000000000 --- a/packages/console/core/script/promote-black.ts +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env bun - -import { $ } from "bun" -import path from "path" -import { BlackData } from "../src/black" - -const stage = process.argv[2] -if (!stage) throw new Error("Stage is required") - -const root = path.resolve(process.cwd(), "..", "..", "..") - -// read the secret -const ret = await $`bun sst secret list`.cwd(root).text() -const lines = ret.split("\n") -const value = lines.find((line) => line.startsWith("ZEN_BLACK_LIMITS"))?.split("=")[1] -if (!value) throw new Error("ZEN_BLACK_LIMITS not found") - -// validate value -BlackData.validate(JSON.parse(value)) - -// update the secret -await $`bun sst secret set ZEN_BLACK_LIMITS ${value} --stage ${stage}` diff --git a/packages/console/core/script/promote-lite.ts b/packages/console/core/script/promote-limits.ts similarity index 55% rename from packages/console/core/script/promote-lite.ts rename to packages/console/core/script/promote-limits.ts index 8fd58c8059b..f488aba02d4 100755 --- a/packages/console/core/script/promote-lite.ts +++ b/packages/console/core/script/promote-limits.ts @@ -2,7 +2,7 @@ import { $ } from "bun" import path from "path" -import { LiteData } from "../src/lite" +import { Subscription } from "../src/subscription" const stage = process.argv[2] if (!stage) throw new Error("Stage is required") @@ -12,11 +12,11 @@ const root = path.resolve(process.cwd(), "..", "..", "..") // read the secret const ret = await $`bun sst secret list`.cwd(root).text() const lines = ret.split("\n") -const value = lines.find((line) => line.startsWith("ZEN_LITE_LIMITS"))?.split("=")[1] -if (!value) throw new Error("ZEN_LITE_LIMITS not found") +const value = lines.find((line) => line.startsWith("ZEN_LIMITS"))?.split("=")[1] +if (!value) throw new Error("ZEN_LIMITS not found") // validate value -LiteData.validate(JSON.parse(value)) +Subscription.validate(JSON.parse(value)) // update the secret -await $`bun sst secret set ZEN_LITE_LIMITS ${value} --stage ${stage}` +await $`bun sst secret set ZEN_LIMITS ${value} --stage ${stage}` diff --git a/packages/console/core/script/update-black.ts b/packages/console/core/script/update-black.ts deleted file mode 100755 index 695a5d3ce47..00000000000 --- a/packages/console/core/script/update-black.ts +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env bun - -import { $ } from "bun" -import path from "path" -import os from "os" -import { BlackData } from "../src/black" - -const root = path.resolve(process.cwd(), "..", "..", "..") -const secrets = await $`bun sst secret list`.cwd(root).text() - -// read value -const lines = secrets.split("\n") -const oldValue = lines.find((line) => line.startsWith("ZEN_BLACK_LIMITS"))?.split("=")[1] ?? "{}" -if (!oldValue) throw new Error("ZEN_BLACK_LIMITS not found") - -// store the prettified json to a temp file -const filename = `black-${Date.now()}.json` -const tempFile = Bun.file(path.join(os.tmpdir(), filename)) -await tempFile.write(JSON.stringify(JSON.parse(oldValue), null, 2)) -console.log("tempFile", tempFile.name) - -// open temp file in vim and read the file on close -await $`vim ${tempFile.name}` -const newValue = JSON.stringify(JSON.parse(await tempFile.text())) -BlackData.validate(JSON.parse(newValue)) - -// update the secret -await $`bun sst secret set ZEN_BLACK_LIMITS ${newValue}` diff --git a/packages/console/core/script/update-lite.ts b/packages/console/core/script/update-limits.ts similarity index 65% rename from packages/console/core/script/update-lite.ts rename to packages/console/core/script/update-limits.ts index 2f3e6683572..8f257931261 100755 --- a/packages/console/core/script/update-lite.ts +++ b/packages/console/core/script/update-limits.ts @@ -3,18 +3,18 @@ import { $ } from "bun" import path from "path" import os from "os" -import { LiteData } from "../src/lite" +import { Subscription } from "../src/subscription" const root = path.resolve(process.cwd(), "..", "..", "..") const secrets = await $`bun sst secret list`.cwd(root).text() // read value const lines = secrets.split("\n") -const oldValue = lines.find((line) => line.startsWith("ZEN_LITE_LIMITS"))?.split("=")[1] ?? "{}" -if (!oldValue) throw new Error("ZEN_LITE_LIMITS not found") +const oldValue = lines.find((line) => line.startsWith("ZEN_LIMITS"))?.split("=")[1] ?? "{}" +if (!oldValue) throw new Error("ZEN_LIMITS not found") // store the prettified json to a temp file -const filename = `lite-${Date.now()}.json` +const filename = `limits-${Date.now()}.json` const tempFile = Bun.file(path.join(os.tmpdir(), filename)) await tempFile.write(JSON.stringify(JSON.parse(oldValue), null, 2)) console.log("tempFile", tempFile.name) @@ -22,7 +22,7 @@ console.log("tempFile", tempFile.name) // open temp file in vim and read the file on close await $`vim ${tempFile.name}` const newValue = JSON.stringify(JSON.parse(await tempFile.text())) -LiteData.validate(JSON.parse(newValue)) +Subscription.validate(JSON.parse(newValue)) // update the secret -await $`bun sst secret set ZEN_LITE_LIMITS ${newValue}` +await $`bun sst secret set ZEN_LIMITS ${newValue}` diff --git a/packages/console/core/src/black.ts b/packages/console/core/src/black.ts index a18c5258d04..1908403a276 100644 --- a/packages/console/core/src/black.ts +++ b/packages/console/core/src/black.ts @@ -2,37 +2,15 @@ import { z } from "zod" import { fn } from "./util/fn" import { Resource } from "@opencode-ai/console-resource" import { BlackPlans } from "./schema/billing.sql" +import { Subscription } from "./subscription" export namespace BlackData { - const Schema = z.object({ - "200": z.object({ - fixedLimit: z.number().int(), - rollingLimit: z.number().int(), - rollingWindow: z.number().int(), - }), - "100": z.object({ - fixedLimit: z.number().int(), - rollingLimit: z.number().int(), - rollingWindow: z.number().int(), - }), - "20": z.object({ - fixedLimit: z.number().int(), - rollingLimit: z.number().int(), - rollingWindow: z.number().int(), - }), - }) - - export const validate = fn(Schema, (input) => { - return input - }) - export const getLimits = fn( z.object({ plan: z.enum(BlackPlans), }), ({ plan }) => { - const json = JSON.parse(Resource.ZEN_BLACK_LIMITS.value) - return Schema.parse(json)[plan] + return Subscription.getLimits()["black"][plan] }, ) diff --git a/packages/console/core/src/lite.ts b/packages/console/core/src/lite.ts index 49d23e59ec0..c6f7d5a3e41 100644 --- a/packages/console/core/src/lite.ts +++ b/packages/console/core/src/lite.ts @@ -1,22 +1,11 @@ import { z } from "zod" import { fn } from "./util/fn" import { Resource } from "@opencode-ai/console-resource" +import { Subscription } from "./subscription" export namespace LiteData { - const Schema = z.object({ - rollingLimit: z.number().int(), - rollingWindow: z.number().int(), - weeklyLimit: z.number().int(), - monthlyLimit: z.number().int(), - }) - - export const validate = fn(Schema, (input) => { - return input - }) - export const getLimits = fn(z.void(), () => { - const json = JSON.parse(Resource.ZEN_LITE_LIMITS.value) - return Schema.parse(json) + return Subscription.getLimits()["lite"] }) export const productID = fn(z.void(), () => Resource.ZEN_LITE_PRICE.product) diff --git a/packages/console/core/src/model.ts b/packages/console/core/src/model.ts index e868b176e8a..e4fa0224942 100644 --- a/packages/console/core/src/model.ts +++ b/packages/console/core/src/model.ts @@ -9,24 +9,7 @@ import { Resource } from "@opencode-ai/console-resource" export namespace ZenData { const FormatSchema = z.enum(["anthropic", "google", "openai", "oa-compat"]) - const TrialSchema = z.object({ - provider: z.string(), - limits: z.array( - z.object({ - limit: z.number(), - client: z.enum(["cli", "desktop"]).optional(), - }), - ), - }) - const RateLimitSchema = z.object({ - period: z.enum(["day", "rolling"]), - value: z.number().int(), - checkHeader: z.string().optional(), - fallbackValue: z.number().int().optional(), - }) export type Format = z.infer - export type Trial = z.infer - export type RateLimit = z.infer const ModelCostSchema = z.object({ input: z.number(), @@ -43,8 +26,7 @@ export namespace ZenData { allowAnonymous: z.boolean().optional(), byokProvider: z.enum(["openai", "anthropic", "google"]).optional(), stickyProvider: z.enum(["strict", "prefer"]).optional(), - trial: TrialSchema.optional(), - rateLimit: RateLimitSchema.optional(), + trialProvider: z.string().optional(), fallbackProvider: z.string().optional(), providers: z.array( z.object({ @@ -63,19 +45,12 @@ export namespace ZenData { format: FormatSchema.optional(), headerMappings: z.record(z.string(), z.string()).optional(), payloadModifier: z.record(z.string(), z.any()).optional(), - family: z.string().optional(), - }) - - const ProviderFamilySchema = z.object({ - headers: z.record(z.string(), z.string()).optional(), - responseModifier: z.record(z.string(), z.string()).optional(), }) const ModelsSchema = z.object({ models: z.record(z.string(), z.union([ModelSchema, z.array(ModelSchema.extend({ formatFilter: FormatSchema }))])), liteModels: z.record(z.string(), ModelSchema), providers: z.record(z.string(), ProviderSchema), - providerFamilies: z.record(z.string(), ProviderFamilySchema), }) export const validate = fn(ModelsSchema, (input) => { @@ -115,15 +90,10 @@ export namespace ZenData { Resource.ZEN_MODELS29.value + Resource.ZEN_MODELS30.value, ) - const { models, liteModels, providers, providerFamilies } = ModelsSchema.parse(json) + const { models, liteModels, providers } = ModelsSchema.parse(json) return { models: modelList === "lite" ? liteModels : models, - providers: Object.fromEntries( - Object.entries(providers).map(([id, provider]) => [ - id, - { ...provider, ...(provider.family ? providerFamilies[provider.family] : {}) }, - ]), - ), + providers, } }) } diff --git a/packages/console/core/src/subscription.ts b/packages/console/core/src/subscription.ts index 879f940e0eb..9d6c3ce2b58 100644 --- a/packages/console/core/src/subscription.ts +++ b/packages/console/core/src/subscription.ts @@ -2,8 +2,54 @@ import { z } from "zod" import { fn } from "./util/fn" import { centsToMicroCents } from "./util/price" import { getWeekBounds, getMonthlyBounds } from "./util/date" +import { Resource } from "@opencode-ai/console-resource" export namespace Subscription { + const LimitsSchema = z.object({ + free: z.object({ + promoTokens: z.number().int(), + dailyRequests: z.number().int(), + checkHeader: z.string(), + fallbackValue: z.number().int(), + }), + lite: z.object({ + rollingLimit: z.number().int(), + rollingWindow: z.number().int(), + weeklyLimit: z.number().int(), + monthlyLimit: z.number().int(), + }), + black: z.object({ + "20": z.object({ + fixedLimit: z.number().int(), + rollingLimit: z.number().int(), + rollingWindow: z.number().int(), + }), + "100": z.object({ + fixedLimit: z.number().int(), + rollingLimit: z.number().int(), + rollingWindow: z.number().int(), + }), + "200": z.object({ + fixedLimit: z.number().int(), + rollingLimit: z.number().int(), + rollingWindow: z.number().int(), + }), + }), + }) + + export const validate = fn(LimitsSchema, (input) => { + return input + }) + + export const getLimits = fn(z.void(), () => { + const json = JSON.parse(Resource.ZEN_LIMITS.value) + return LimitsSchema.parse(json) + }) + + export const getFreeLimits = fn(z.void(), () => { + return getLimits()["free"] + }) + export const analyzeRollingUsage = fn( z.object({ limit: z.number().int(), diff --git a/packages/console/core/sst-env.d.ts b/packages/console/core/sst-env.d.ts index edff904e015..23ae6e44bfb 100644 --- a/packages/console/core/sst-env.d.ts +++ b/packages/console/core/sst-env.d.ts @@ -119,10 +119,6 @@ declare module "sst" { "type": "sst.cloudflare.StaticSite" "url": string } - "ZEN_BLACK_LIMITS": { - "type": "sst.sst.Secret" - "value": string - } "ZEN_BLACK_PRICE": { "plan100": string "plan20": string @@ -130,7 +126,7 @@ declare module "sst" { "product": string "type": "sst.sst.Linkable" } - "ZEN_LITE_LIMITS": { + "ZEN_LIMITS": { "type": "sst.sst.Secret" "value": string } diff --git a/packages/console/function/package.json b/packages/console/function/package.json index 6cdf752432c..009a1583a9d 100644 --- a/packages/console/function/package.json +++ b/packages/console/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-function", - "version": "1.2.15", + "version": "1.2.16", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/console/function/sst-env.d.ts b/packages/console/function/sst-env.d.ts index edff904e015..23ae6e44bfb 100644 --- a/packages/console/function/sst-env.d.ts +++ b/packages/console/function/sst-env.d.ts @@ -119,10 +119,6 @@ declare module "sst" { "type": "sst.cloudflare.StaticSite" "url": string } - "ZEN_BLACK_LIMITS": { - "type": "sst.sst.Secret" - "value": string - } "ZEN_BLACK_PRICE": { "plan100": string "plan20": string @@ -130,7 +126,7 @@ declare module "sst" { "product": string "type": "sst.sst.Linkable" } - "ZEN_LITE_LIMITS": { + "ZEN_LIMITS": { "type": "sst.sst.Secret" "value": string } diff --git a/packages/console/mail/package.json b/packages/console/mail/package.json index 09344f7fa23..fefb80796b3 100644 --- a/packages/console/mail/package.json +++ b/packages/console/mail/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-mail", - "version": "1.2.15", + "version": "1.2.16", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", diff --git a/packages/console/resource/sst-env.d.ts b/packages/console/resource/sst-env.d.ts index edff904e015..23ae6e44bfb 100644 --- a/packages/console/resource/sst-env.d.ts +++ b/packages/console/resource/sst-env.d.ts @@ -119,10 +119,6 @@ declare module "sst" { "type": "sst.cloudflare.StaticSite" "url": string } - "ZEN_BLACK_LIMITS": { - "type": "sst.sst.Secret" - "value": string - } "ZEN_BLACK_PRICE": { "plan100": string "plan20": string @@ -130,7 +126,7 @@ declare module "sst" { "product": string "type": "sst.sst.Linkable" } - "ZEN_LITE_LIMITS": { + "ZEN_LIMITS": { "type": "sst.sst.Secret" "value": string } diff --git a/packages/desktop-electron/.gitignore b/packages/desktop-electron/.gitignore new file mode 100644 index 00000000000..ac9d8db9694 --- /dev/null +++ b/packages/desktop-electron/.gitignore @@ -0,0 +1,28 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? +out/ + +resources/opencode-cli* +resources/icons diff --git a/packages/desktop-electron/AGENTS.md b/packages/desktop-electron/AGENTS.md new file mode 100644 index 00000000000..7805ea835f5 --- /dev/null +++ b/packages/desktop-electron/AGENTS.md @@ -0,0 +1,4 @@ +# Desktop package notes + +- Renderer process should only call `window.api` from `src/preload`. +- Main process should register IPC handlers in `src/main/ipc.ts`. diff --git a/packages/desktop-electron/README.md b/packages/desktop-electron/README.md new file mode 100644 index 00000000000..ebaf4882231 --- /dev/null +++ b/packages/desktop-electron/README.md @@ -0,0 +1,32 @@ +# OpenCode Desktop + +Native OpenCode desktop app, built with Tauri v2. + +## Development + +From the repo root: + +```bash +bun install +bun run --cwd packages/desktop tauri dev +``` + +This starts the Vite dev server on http://localhost:1420 and opens the native window. + +If you only want the web dev server (no native shell): + +```bash +bun run --cwd packages/desktop dev +``` + +## Build + +To create a production `dist/` and build the native app bundle: + +```bash +bun run --cwd packages/desktop tauri build +``` + +## Prerequisites + +Running the desktop app requires additional Tauri dependencies (Rust toolchain, platform-specific libraries). See the [Tauri prerequisites](https://v2.tauri.app/start/prerequisites/) for setup instructions. diff --git a/packages/desktop-electron/electron-builder.config.ts b/packages/desktop-electron/electron-builder.config.ts new file mode 100644 index 00000000000..e6b4bcd2b97 --- /dev/null +++ b/packages/desktop-electron/electron-builder.config.ts @@ -0,0 +1,97 @@ +import type { Configuration } from "electron-builder" + +const channel = (() => { + const raw = process.env.OPENCODE_CHANNEL + if (raw === "dev" || raw === "beta" || raw === "prod") return raw + return "dev" +})() + +const getBase = (): Configuration => ({ + artifactName: "opencode-electron-${os}-${arch}.${ext}", + directories: { + output: "dist", + buildResources: "resources", + }, + files: ["out/**/*", "resources/**/*"], + extraResources: [ + { + from: "resources/", + to: "", + filter: ["opencode-cli*"], + }, + { + from: "native/", + to: "native/", + filter: ["index.js", "index.d.ts", "build/Release/mac_window.node", "swift-build/**"], + }, + ], + mac: { + category: "public.app-category.developer-tools", + icon: `resources/icons/icon.icns`, + hardenedRuntime: true, + gatekeeperAssess: false, + entitlements: "resources/entitlements.plist", + entitlementsInherit: "resources/entitlements.plist", + notarize: true, + target: ["dmg", "zip"], + }, + dmg: { + sign: true, + }, + protocols: { + name: "OpenCode", + schemes: ["opencode"], + }, + win: { + icon: `resources/icons/icon.ico`, + target: ["nsis"], + }, + nsis: { + oneClick: false, + allowToChangeInstallationDirectory: true, + installerIcon: `resources/icons/icon.ico`, + installerHeaderIcon: `resources/icons/icon.ico`, + }, + linux: { + icon: `resources/icons`, + category: "Development", + target: ["AppImage", "deb", "rpm"], + }, +}) + +function getConfig() { + const base = getBase() + + switch (channel) { + case "dev": { + return { + ...base, + appId: "ai.opencode.desktop.dev", + productName: "OpenCode Dev", + rpm: { packageName: "opencode-dev" }, + } + } + case "beta": { + return { + ...base, + appId: "ai.opencode.desktop.beta", + productName: "OpenCode Beta", + protocols: { name: "OpenCode Beta", schemes: ["opencode"] }, + publish: { provider: "github", owner: "anomalyco", repo: "opencode-beta", channel: "latest" }, + rpm: { packageName: "opencode-beta" }, + } + } + case "prod": { + return { + ...base, + appId: "ai.opencode.desktop", + productName: "OpenCode", + protocols: { name: "OpenCode", schemes: ["opencode"] }, + publish: { provider: "github", owner: "anomalyco", repo: "opencode", channel: "latest" }, + rpm: { packageName: "opencode" }, + } + } + } +} + +export default getConfig() diff --git a/packages/desktop-electron/electron.vite.config.ts b/packages/desktop-electron/electron.vite.config.ts new file mode 100644 index 00000000000..80c1d6b704c --- /dev/null +++ b/packages/desktop-electron/electron.vite.config.ts @@ -0,0 +1,41 @@ +import { defineConfig } from "electron-vite" +import appPlugin from "@opencode-ai/app/vite" + +const channel = (() => { + const raw = process.env.OPENCODE_CHANNEL + if (raw === "dev" || raw === "beta" || raw === "prod") return raw + return "dev" +})() + +export default defineConfig({ + main: { + define: { + "import.meta.env.OPENCODE_CHANNEL": JSON.stringify(channel), + }, + build: { + rollupOptions: { + input: { index: "src/main/index.ts" }, + }, + }, + }, + preload: { + build: { + rollupOptions: { + input: { index: "src/preload/index.ts" }, + }, + }, + }, + renderer: { + plugins: [appPlugin], + publicDir: "../app/public", + root: "src/renderer", + build: { + rollupOptions: { + input: { + main: "src/renderer/index.html", + loading: "src/renderer/loading.html", + }, + }, + }, + }, +}) diff --git a/packages/desktop-electron/icons/README.md b/packages/desktop-electron/icons/README.md new file mode 100644 index 00000000000..fa219a77ef1 --- /dev/null +++ b/packages/desktop-electron/icons/README.md @@ -0,0 +1,11 @@ +# Tauri Icons + +Here's the process I've been using to create icons: + +- Save source image as `app-icon.png` in `packages/desktop` +- `cd` to `packages/desktop` +- Run `bun tauri icon -o src-tauri/icons/{environment}` +- Use [Image2Icon](https://img2icnsapp.com/)'s 'Big Sur Icon' preset to generate an `icon.icns` file and place it in the appropriate icons folder + +The Image2Icon step is necessary as the `icon.icns` generated by `app-icon.png` does not apply the shadow/padding expected by macOS, +so app icons appear larger than expected. diff --git a/packages/desktop-electron/icons/beta/128x128.png b/packages/desktop-electron/icons/beta/128x128.png new file mode 100644 index 00000000000..751e80f1fda Binary files /dev/null and b/packages/desktop-electron/icons/beta/128x128.png differ diff --git a/packages/desktop-electron/icons/beta/128x128@2x.png b/packages/desktop-electron/icons/beta/128x128@2x.png new file mode 100644 index 00000000000..fe330df419a Binary files /dev/null and b/packages/desktop-electron/icons/beta/128x128@2x.png differ diff --git a/packages/desktop-electron/icons/beta/32x32.png b/packages/desktop-electron/icons/beta/32x32.png new file mode 100644 index 00000000000..2703048eed1 Binary files /dev/null and b/packages/desktop-electron/icons/beta/32x32.png differ diff --git a/packages/desktop-electron/icons/beta/64x64.png b/packages/desktop-electron/icons/beta/64x64.png new file mode 100644 index 00000000000..ecd7fe3142c Binary files /dev/null and b/packages/desktop-electron/icons/beta/64x64.png differ diff --git a/packages/desktop-electron/icons/beta/Square107x107Logo.png b/packages/desktop-electron/icons/beta/Square107x107Logo.png new file mode 100644 index 00000000000..e6ea73f4da2 Binary files /dev/null and b/packages/desktop-electron/icons/beta/Square107x107Logo.png differ diff --git a/packages/desktop-electron/icons/beta/Square142x142Logo.png b/packages/desktop-electron/icons/beta/Square142x142Logo.png new file mode 100644 index 00000000000..74ae729c42b Binary files /dev/null and b/packages/desktop-electron/icons/beta/Square142x142Logo.png differ diff --git a/packages/desktop-electron/icons/beta/Square150x150Logo.png b/packages/desktop-electron/icons/beta/Square150x150Logo.png new file mode 100644 index 00000000000..0b109b8f4ae Binary files /dev/null and b/packages/desktop-electron/icons/beta/Square150x150Logo.png differ diff --git a/packages/desktop-electron/icons/beta/Square284x284Logo.png b/packages/desktop-electron/icons/beta/Square284x284Logo.png new file mode 100644 index 00000000000..0261ded42c7 Binary files /dev/null and b/packages/desktop-electron/icons/beta/Square284x284Logo.png differ diff --git a/packages/desktop-electron/icons/beta/Square30x30Logo.png b/packages/desktop-electron/icons/beta/Square30x30Logo.png new file mode 100644 index 00000000000..34158f10a49 Binary files /dev/null and b/packages/desktop-electron/icons/beta/Square30x30Logo.png differ diff --git a/packages/desktop-electron/icons/beta/Square310x310Logo.png b/packages/desktop-electron/icons/beta/Square310x310Logo.png new file mode 100644 index 00000000000..f18bfada4cd Binary files /dev/null and b/packages/desktop-electron/icons/beta/Square310x310Logo.png differ diff --git a/packages/desktop-electron/icons/beta/Square44x44Logo.png b/packages/desktop-electron/icons/beta/Square44x44Logo.png new file mode 100644 index 00000000000..6d1cc06c086 Binary files /dev/null and b/packages/desktop-electron/icons/beta/Square44x44Logo.png differ diff --git a/packages/desktop-electron/icons/beta/Square71x71Logo.png b/packages/desktop-electron/icons/beta/Square71x71Logo.png new file mode 100644 index 00000000000..a26084dc2fc Binary files /dev/null and b/packages/desktop-electron/icons/beta/Square71x71Logo.png differ diff --git a/packages/desktop-electron/icons/beta/Square89x89Logo.png b/packages/desktop-electron/icons/beta/Square89x89Logo.png new file mode 100644 index 00000000000..58b0eb6053c Binary files /dev/null and b/packages/desktop-electron/icons/beta/Square89x89Logo.png differ diff --git a/packages/desktop-electron/icons/beta/StoreLogo.png b/packages/desktop-electron/icons/beta/StoreLogo.png new file mode 100644 index 00000000000..648fd2114d7 Binary files /dev/null and b/packages/desktop-electron/icons/beta/StoreLogo.png differ diff --git a/packages/desktop-electron/icons/beta/android/mipmap-anydpi-v26/ic_launcher.xml b/packages/desktop-electron/icons/beta/android/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 00000000000..2ffbf24b689 --- /dev/null +++ b/packages/desktop-electron/icons/beta/android/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/packages/desktop-electron/icons/beta/android/mipmap-hdpi/ic_launcher.png b/packages/desktop-electron/icons/beta/android/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000000..39d1dd0d519 Binary files /dev/null and b/packages/desktop-electron/icons/beta/android/mipmap-hdpi/ic_launcher.png differ diff --git a/packages/desktop-electron/icons/beta/android/mipmap-hdpi/ic_launcher_foreground.png b/packages/desktop-electron/icons/beta/android/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 00000000000..84908e71c1f Binary files /dev/null and b/packages/desktop-electron/icons/beta/android/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/packages/desktop-electron/icons/beta/android/mipmap-hdpi/ic_launcher_round.png b/packages/desktop-electron/icons/beta/android/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 00000000000..a6b8cb61624 Binary files /dev/null and b/packages/desktop-electron/icons/beta/android/mipmap-hdpi/ic_launcher_round.png differ diff --git a/packages/desktop-electron/icons/beta/android/mipmap-mdpi/ic_launcher.png b/packages/desktop-electron/icons/beta/android/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000000..6522e0fba8a Binary files /dev/null and b/packages/desktop-electron/icons/beta/android/mipmap-mdpi/ic_launcher.png differ diff --git a/packages/desktop-electron/icons/beta/android/mipmap-mdpi/ic_launcher_foreground.png b/packages/desktop-electron/icons/beta/android/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 00000000000..b3449bd4f3f Binary files /dev/null and b/packages/desktop-electron/icons/beta/android/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/packages/desktop-electron/icons/beta/android/mipmap-mdpi/ic_launcher_round.png b/packages/desktop-electron/icons/beta/android/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 00000000000..7aa97d82761 Binary files /dev/null and b/packages/desktop-electron/icons/beta/android/mipmap-mdpi/ic_launcher_round.png differ diff --git a/packages/desktop-electron/icons/beta/android/mipmap-xhdpi/ic_launcher.png b/packages/desktop-electron/icons/beta/android/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000000..82bc9d22a69 Binary files /dev/null and b/packages/desktop-electron/icons/beta/android/mipmap-xhdpi/ic_launcher.png differ diff --git a/packages/desktop-electron/icons/beta/android/mipmap-xhdpi/ic_launcher_foreground.png b/packages/desktop-electron/icons/beta/android/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000000..6b031ce8515 Binary files /dev/null and b/packages/desktop-electron/icons/beta/android/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/packages/desktop-electron/icons/beta/android/mipmap-xhdpi/ic_launcher_round.png b/packages/desktop-electron/icons/beta/android/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 00000000000..34859de5ef0 Binary files /dev/null and b/packages/desktop-electron/icons/beta/android/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/packages/desktop-electron/icons/beta/android/mipmap-xxhdpi/ic_launcher.png b/packages/desktop-electron/icons/beta/android/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000000..4cdb71d62b6 Binary files /dev/null and b/packages/desktop-electron/icons/beta/android/mipmap-xxhdpi/ic_launcher.png differ diff --git a/packages/desktop-electron/icons/beta/android/mipmap-xxhdpi/ic_launcher_foreground.png b/packages/desktop-electron/icons/beta/android/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000000..a64be6ada1d Binary files /dev/null and b/packages/desktop-electron/icons/beta/android/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/packages/desktop-electron/icons/beta/android/mipmap-xxhdpi/ic_launcher_round.png b/packages/desktop-electron/icons/beta/android/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 00000000000..2de3c27342a Binary files /dev/null and b/packages/desktop-electron/icons/beta/android/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/packages/desktop-electron/icons/beta/android/mipmap-xxxhdpi/ic_launcher.png b/packages/desktop-electron/icons/beta/android/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000000..0ead288664d Binary files /dev/null and b/packages/desktop-electron/icons/beta/android/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/packages/desktop-electron/icons/beta/android/mipmap-xxxhdpi/ic_launcher_foreground.png b/packages/desktop-electron/icons/beta/android/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000000..bdd1748258a Binary files /dev/null and b/packages/desktop-electron/icons/beta/android/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/packages/desktop-electron/icons/beta/android/mipmap-xxxhdpi/ic_launcher_round.png b/packages/desktop-electron/icons/beta/android/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 00000000000..69f74758ecf Binary files /dev/null and b/packages/desktop-electron/icons/beta/android/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/packages/desktop-electron/icons/beta/android/values/ic_launcher_background.xml b/packages/desktop-electron/icons/beta/android/values/ic_launcher_background.xml new file mode 100644 index 00000000000..ea9c223a6cb --- /dev/null +++ b/packages/desktop-electron/icons/beta/android/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #fff + \ No newline at end of file diff --git a/packages/desktop-electron/icons/beta/icon.icns b/packages/desktop-electron/icons/beta/icon.icns new file mode 100644 index 00000000000..f98de5da88b Binary files /dev/null and b/packages/desktop-electron/icons/beta/icon.icns differ diff --git a/packages/desktop-electron/icons/beta/icon.ico b/packages/desktop-electron/icons/beta/icon.ico new file mode 100644 index 00000000000..df8588c8e4c Binary files /dev/null and b/packages/desktop-electron/icons/beta/icon.ico differ diff --git a/packages/desktop-electron/icons/beta/icon.png b/packages/desktop-electron/icons/beta/icon.png new file mode 100644 index 00000000000..5313049562a Binary files /dev/null and b/packages/desktop-electron/icons/beta/icon.png differ diff --git a/packages/desktop-electron/icons/beta/ios/AppIcon-20x20@1x.png b/packages/desktop-electron/icons/beta/ios/AppIcon-20x20@1x.png new file mode 100644 index 00000000000..e8ebb28efe1 Binary files /dev/null and b/packages/desktop-electron/icons/beta/ios/AppIcon-20x20@1x.png differ diff --git a/packages/desktop-electron/icons/beta/ios/AppIcon-20x20@2x-1.png b/packages/desktop-electron/icons/beta/ios/AppIcon-20x20@2x-1.png new file mode 100644 index 00000000000..50c8015dea4 Binary files /dev/null and b/packages/desktop-electron/icons/beta/ios/AppIcon-20x20@2x-1.png differ diff --git a/packages/desktop-electron/icons/beta/ios/AppIcon-20x20@2x.png b/packages/desktop-electron/icons/beta/ios/AppIcon-20x20@2x.png new file mode 100644 index 00000000000..50c8015dea4 Binary files /dev/null and b/packages/desktop-electron/icons/beta/ios/AppIcon-20x20@2x.png differ diff --git a/packages/desktop-electron/icons/beta/ios/AppIcon-20x20@3x.png b/packages/desktop-electron/icons/beta/ios/AppIcon-20x20@3x.png new file mode 100644 index 00000000000..6e290dbc689 Binary files /dev/null and b/packages/desktop-electron/icons/beta/ios/AppIcon-20x20@3x.png differ diff --git a/packages/desktop-electron/icons/beta/ios/AppIcon-29x29@1x.png b/packages/desktop-electron/icons/beta/ios/AppIcon-29x29@1x.png new file mode 100644 index 00000000000..4ef554b4de3 Binary files /dev/null and b/packages/desktop-electron/icons/beta/ios/AppIcon-29x29@1x.png differ diff --git a/packages/desktop-electron/icons/beta/ios/AppIcon-29x29@2x-1.png b/packages/desktop-electron/icons/beta/ios/AppIcon-29x29@2x-1.png new file mode 100644 index 00000000000..b9ddfd47c88 Binary files /dev/null and b/packages/desktop-electron/icons/beta/ios/AppIcon-29x29@2x-1.png differ diff --git a/packages/desktop-electron/icons/beta/ios/AppIcon-29x29@2x.png b/packages/desktop-electron/icons/beta/ios/AppIcon-29x29@2x.png new file mode 100644 index 00000000000..b9ddfd47c88 Binary files /dev/null and b/packages/desktop-electron/icons/beta/ios/AppIcon-29x29@2x.png differ diff --git a/packages/desktop-electron/icons/beta/ios/AppIcon-29x29@3x.png b/packages/desktop-electron/icons/beta/ios/AppIcon-29x29@3x.png new file mode 100644 index 00000000000..052322d6821 Binary files /dev/null and b/packages/desktop-electron/icons/beta/ios/AppIcon-29x29@3x.png differ diff --git a/packages/desktop-electron/icons/beta/ios/AppIcon-40x40@1x.png b/packages/desktop-electron/icons/beta/ios/AppIcon-40x40@1x.png new file mode 100644 index 00000000000..50c8015dea4 Binary files /dev/null and b/packages/desktop-electron/icons/beta/ios/AppIcon-40x40@1x.png differ diff --git a/packages/desktop-electron/icons/beta/ios/AppIcon-40x40@2x-1.png b/packages/desktop-electron/icons/beta/ios/AppIcon-40x40@2x-1.png new file mode 100644 index 00000000000..9317b25001c Binary files /dev/null and b/packages/desktop-electron/icons/beta/ios/AppIcon-40x40@2x-1.png differ diff --git a/packages/desktop-electron/icons/beta/ios/AppIcon-40x40@2x.png b/packages/desktop-electron/icons/beta/ios/AppIcon-40x40@2x.png new file mode 100644 index 00000000000..9317b25001c Binary files /dev/null and b/packages/desktop-electron/icons/beta/ios/AppIcon-40x40@2x.png differ diff --git a/packages/desktop-electron/icons/beta/ios/AppIcon-40x40@3x.png b/packages/desktop-electron/icons/beta/ios/AppIcon-40x40@3x.png new file mode 100644 index 00000000000..6b921a17e34 Binary files /dev/null and b/packages/desktop-electron/icons/beta/ios/AppIcon-40x40@3x.png differ diff --git a/packages/desktop-electron/icons/beta/ios/AppIcon-512@2x.png b/packages/desktop-electron/icons/beta/ios/AppIcon-512@2x.png new file mode 100644 index 00000000000..b83131d64bc Binary files /dev/null and b/packages/desktop-electron/icons/beta/ios/AppIcon-512@2x.png differ diff --git a/packages/desktop-electron/icons/beta/ios/AppIcon-60x60@2x.png b/packages/desktop-electron/icons/beta/ios/AppIcon-60x60@2x.png new file mode 100644 index 00000000000..6b921a17e34 Binary files /dev/null and b/packages/desktop-electron/icons/beta/ios/AppIcon-60x60@2x.png differ diff --git a/packages/desktop-electron/icons/beta/ios/AppIcon-60x60@3x.png b/packages/desktop-electron/icons/beta/ios/AppIcon-60x60@3x.png new file mode 100644 index 00000000000..685004995cc Binary files /dev/null and b/packages/desktop-electron/icons/beta/ios/AppIcon-60x60@3x.png differ diff --git a/packages/desktop-electron/icons/beta/ios/AppIcon-76x76@1x.png b/packages/desktop-electron/icons/beta/ios/AppIcon-76x76@1x.png new file mode 100644 index 00000000000..1ffceb752a5 Binary files /dev/null and b/packages/desktop-electron/icons/beta/ios/AppIcon-76x76@1x.png differ diff --git a/packages/desktop-electron/icons/beta/ios/AppIcon-76x76@2x.png b/packages/desktop-electron/icons/beta/ios/AppIcon-76x76@2x.png new file mode 100644 index 00000000000..81c4178c912 Binary files /dev/null and b/packages/desktop-electron/icons/beta/ios/AppIcon-76x76@2x.png differ diff --git a/packages/desktop-electron/icons/beta/ios/AppIcon-83.5x83.5@2x.png b/packages/desktop-electron/icons/beta/ios/AppIcon-83.5x83.5@2x.png new file mode 100644 index 00000000000..d5453adffbd Binary files /dev/null and b/packages/desktop-electron/icons/beta/ios/AppIcon-83.5x83.5@2x.png differ diff --git a/packages/desktop-electron/icons/dev/128x128.png b/packages/desktop-electron/icons/dev/128x128.png new file mode 100644 index 00000000000..d7fc4db1498 Binary files /dev/null and b/packages/desktop-electron/icons/dev/128x128.png differ diff --git a/packages/desktop-electron/icons/dev/128x128@2x.png b/packages/desktop-electron/icons/dev/128x128@2x.png new file mode 100644 index 00000000000..59188230647 Binary files /dev/null and b/packages/desktop-electron/icons/dev/128x128@2x.png differ diff --git a/packages/desktop-electron/icons/dev/32x32.png b/packages/desktop-electron/icons/dev/32x32.png new file mode 100644 index 00000000000..53925cc4f54 Binary files /dev/null and b/packages/desktop-electron/icons/dev/32x32.png differ diff --git a/packages/desktop-electron/icons/dev/64x64.png b/packages/desktop-electron/icons/dev/64x64.png new file mode 100644 index 00000000000..a88ef15c64a Binary files /dev/null and b/packages/desktop-electron/icons/dev/64x64.png differ diff --git a/packages/desktop-electron/icons/dev/Square107x107Logo.png b/packages/desktop-electron/icons/dev/Square107x107Logo.png new file mode 100644 index 00000000000..0de29ec828c Binary files /dev/null and b/packages/desktop-electron/icons/dev/Square107x107Logo.png differ diff --git a/packages/desktop-electron/icons/dev/Square142x142Logo.png b/packages/desktop-electron/icons/dev/Square142x142Logo.png new file mode 100644 index 00000000000..af62e8e1e9e Binary files /dev/null and b/packages/desktop-electron/icons/dev/Square142x142Logo.png differ diff --git a/packages/desktop-electron/icons/dev/Square150x150Logo.png b/packages/desktop-electron/icons/dev/Square150x150Logo.png new file mode 100644 index 00000000000..2b19dc39cc0 Binary files /dev/null and b/packages/desktop-electron/icons/dev/Square150x150Logo.png differ diff --git a/packages/desktop-electron/icons/dev/Square284x284Logo.png b/packages/desktop-electron/icons/dev/Square284x284Logo.png new file mode 100644 index 00000000000..eda6d9901f3 Binary files /dev/null and b/packages/desktop-electron/icons/dev/Square284x284Logo.png differ diff --git a/packages/desktop-electron/icons/dev/Square30x30Logo.png b/packages/desktop-electron/icons/dev/Square30x30Logo.png new file mode 100644 index 00000000000..dad821ba84f Binary files /dev/null and b/packages/desktop-electron/icons/dev/Square30x30Logo.png differ diff --git a/packages/desktop-electron/icons/dev/Square310x310Logo.png b/packages/desktop-electron/icons/dev/Square310x310Logo.png new file mode 100644 index 00000000000..555b3b19790 Binary files /dev/null and b/packages/desktop-electron/icons/dev/Square310x310Logo.png differ diff --git a/packages/desktop-electron/icons/dev/Square44x44Logo.png b/packages/desktop-electron/icons/dev/Square44x44Logo.png new file mode 100644 index 00000000000..9f8ad001f78 Binary files /dev/null and b/packages/desktop-electron/icons/dev/Square44x44Logo.png differ diff --git a/packages/desktop-electron/icons/dev/Square71x71Logo.png b/packages/desktop-electron/icons/dev/Square71x71Logo.png new file mode 100644 index 00000000000..43feb784882 Binary files /dev/null and b/packages/desktop-electron/icons/dev/Square71x71Logo.png differ diff --git a/packages/desktop-electron/icons/dev/Square89x89Logo.png b/packages/desktop-electron/icons/dev/Square89x89Logo.png new file mode 100644 index 00000000000..628cc597f06 Binary files /dev/null and b/packages/desktop-electron/icons/dev/Square89x89Logo.png differ diff --git a/packages/desktop-electron/icons/dev/StoreLogo.png b/packages/desktop-electron/icons/dev/StoreLogo.png new file mode 100644 index 00000000000..8d3aa53cffd Binary files /dev/null and b/packages/desktop-electron/icons/dev/StoreLogo.png differ diff --git a/packages/desktop-electron/icons/dev/android/mipmap-anydpi-v26/ic_launcher.xml b/packages/desktop-electron/icons/dev/android/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 00000000000..2ffbf24b689 --- /dev/null +++ b/packages/desktop-electron/icons/dev/android/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/packages/desktop-electron/icons/dev/android/mipmap-hdpi/ic_launcher.png b/packages/desktop-electron/icons/dev/android/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000000..b355e37fea6 Binary files /dev/null and b/packages/desktop-electron/icons/dev/android/mipmap-hdpi/ic_launcher.png differ diff --git a/packages/desktop-electron/icons/dev/android/mipmap-hdpi/ic_launcher_foreground.png b/packages/desktop-electron/icons/dev/android/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 00000000000..c33f8713bc1 Binary files /dev/null and b/packages/desktop-electron/icons/dev/android/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/packages/desktop-electron/icons/dev/android/mipmap-hdpi/ic_launcher_round.png b/packages/desktop-electron/icons/dev/android/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 00000000000..04e37aa6544 Binary files /dev/null and b/packages/desktop-electron/icons/dev/android/mipmap-hdpi/ic_launcher_round.png differ diff --git a/packages/desktop-electron/icons/dev/android/mipmap-mdpi/ic_launcher.png b/packages/desktop-electron/icons/dev/android/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000000..98e53cd220a Binary files /dev/null and b/packages/desktop-electron/icons/dev/android/mipmap-mdpi/ic_launcher.png differ diff --git a/packages/desktop-electron/icons/dev/android/mipmap-mdpi/ic_launcher_foreground.png b/packages/desktop-electron/icons/dev/android/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 00000000000..40fe6e37863 Binary files /dev/null and b/packages/desktop-electron/icons/dev/android/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/packages/desktop-electron/icons/dev/android/mipmap-mdpi/ic_launcher_round.png b/packages/desktop-electron/icons/dev/android/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 00000000000..4814f1ddf5b Binary files /dev/null and b/packages/desktop-electron/icons/dev/android/mipmap-mdpi/ic_launcher_round.png differ diff --git a/packages/desktop-electron/icons/dev/android/mipmap-xhdpi/ic_launcher.png b/packages/desktop-electron/icons/dev/android/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000000..608493283e4 Binary files /dev/null and b/packages/desktop-electron/icons/dev/android/mipmap-xhdpi/ic_launcher.png differ diff --git a/packages/desktop-electron/icons/dev/android/mipmap-xhdpi/ic_launcher_foreground.png b/packages/desktop-electron/icons/dev/android/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000000..898066a3fc0 Binary files /dev/null and b/packages/desktop-electron/icons/dev/android/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/packages/desktop-electron/icons/dev/android/mipmap-xhdpi/ic_launcher_round.png b/packages/desktop-electron/icons/dev/android/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 00000000000..64035c0f3c4 Binary files /dev/null and b/packages/desktop-electron/icons/dev/android/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/packages/desktop-electron/icons/dev/android/mipmap-xxhdpi/ic_launcher.png b/packages/desktop-electron/icons/dev/android/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000000..f47691bf428 Binary files /dev/null and b/packages/desktop-electron/icons/dev/android/mipmap-xxhdpi/ic_launcher.png differ diff --git a/packages/desktop-electron/icons/dev/android/mipmap-xxhdpi/ic_launcher_foreground.png b/packages/desktop-electron/icons/dev/android/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000000..dba6f5635b7 Binary files /dev/null and b/packages/desktop-electron/icons/dev/android/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/packages/desktop-electron/icons/dev/android/mipmap-xxhdpi/ic_launcher_round.png b/packages/desktop-electron/icons/dev/android/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 00000000000..764702604e3 Binary files /dev/null and b/packages/desktop-electron/icons/dev/android/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/packages/desktop-electron/icons/dev/android/mipmap-xxxhdpi/ic_launcher.png b/packages/desktop-electron/icons/dev/android/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000000..2e8430a604c Binary files /dev/null and b/packages/desktop-electron/icons/dev/android/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/packages/desktop-electron/icons/dev/android/mipmap-xxxhdpi/ic_launcher_foreground.png b/packages/desktop-electron/icons/dev/android/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000000..db953d128c6 Binary files /dev/null and b/packages/desktop-electron/icons/dev/android/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/packages/desktop-electron/icons/dev/android/mipmap-xxxhdpi/ic_launcher_round.png b/packages/desktop-electron/icons/dev/android/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 00000000000..d5c9ba6a8d1 Binary files /dev/null and b/packages/desktop-electron/icons/dev/android/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/packages/desktop-electron/icons/dev/android/values/ic_launcher_background.xml b/packages/desktop-electron/icons/dev/android/values/ic_launcher_background.xml new file mode 100644 index 00000000000..ea9c223a6cb --- /dev/null +++ b/packages/desktop-electron/icons/dev/android/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #fff + \ No newline at end of file diff --git a/packages/desktop-electron/icons/dev/icon.icns b/packages/desktop-electron/icons/dev/icon.icns new file mode 100644 index 00000000000..d73a94904ad Binary files /dev/null and b/packages/desktop-electron/icons/dev/icon.icns differ diff --git a/packages/desktop-electron/icons/dev/icon.ico b/packages/desktop-electron/icons/dev/icon.ico new file mode 100644 index 00000000000..bec385d9aad Binary files /dev/null and b/packages/desktop-electron/icons/dev/icon.ico differ diff --git a/packages/desktop-electron/icons/dev/icon.png b/packages/desktop-electron/icons/dev/icon.png new file mode 100644 index 00000000000..6de37ea2942 Binary files /dev/null and b/packages/desktop-electron/icons/dev/icon.png differ diff --git a/packages/desktop-electron/icons/dev/ios/AppIcon-20x20@1x.png b/packages/desktop-electron/icons/dev/ios/AppIcon-20x20@1x.png new file mode 100644 index 00000000000..0e823043e76 Binary files /dev/null and b/packages/desktop-electron/icons/dev/ios/AppIcon-20x20@1x.png differ diff --git a/packages/desktop-electron/icons/dev/ios/AppIcon-20x20@2x-1.png b/packages/desktop-electron/icons/dev/ios/AppIcon-20x20@2x-1.png new file mode 100644 index 00000000000..54e4b2aacab Binary files /dev/null and b/packages/desktop-electron/icons/dev/ios/AppIcon-20x20@2x-1.png differ diff --git a/packages/desktop-electron/icons/dev/ios/AppIcon-20x20@2x.png b/packages/desktop-electron/icons/dev/ios/AppIcon-20x20@2x.png new file mode 100644 index 00000000000..54e4b2aacab Binary files /dev/null and b/packages/desktop-electron/icons/dev/ios/AppIcon-20x20@2x.png differ diff --git a/packages/desktop-electron/icons/dev/ios/AppIcon-20x20@3x.png b/packages/desktop-electron/icons/dev/ios/AppIcon-20x20@3x.png new file mode 100644 index 00000000000..645b01561a1 Binary files /dev/null and b/packages/desktop-electron/icons/dev/ios/AppIcon-20x20@3x.png differ diff --git a/packages/desktop-electron/icons/dev/ios/AppIcon-29x29@1x.png b/packages/desktop-electron/icons/dev/ios/AppIcon-29x29@1x.png new file mode 100644 index 00000000000..054225c6e9f Binary files /dev/null and b/packages/desktop-electron/icons/dev/ios/AppIcon-29x29@1x.png differ diff --git a/packages/desktop-electron/icons/dev/ios/AppIcon-29x29@2x-1.png b/packages/desktop-electron/icons/dev/ios/AppIcon-29x29@2x-1.png new file mode 100644 index 00000000000..0b1b2e0b7fe Binary files /dev/null and b/packages/desktop-electron/icons/dev/ios/AppIcon-29x29@2x-1.png differ diff --git a/packages/desktop-electron/icons/dev/ios/AppIcon-29x29@2x.png b/packages/desktop-electron/icons/dev/ios/AppIcon-29x29@2x.png new file mode 100644 index 00000000000..0b1b2e0b7fe Binary files /dev/null and b/packages/desktop-electron/icons/dev/ios/AppIcon-29x29@2x.png differ diff --git a/packages/desktop-electron/icons/dev/ios/AppIcon-29x29@3x.png b/packages/desktop-electron/icons/dev/ios/AppIcon-29x29@3x.png new file mode 100644 index 00000000000..d2c42592b3d Binary files /dev/null and b/packages/desktop-electron/icons/dev/ios/AppIcon-29x29@3x.png differ diff --git a/packages/desktop-electron/icons/dev/ios/AppIcon-40x40@1x.png b/packages/desktop-electron/icons/dev/ios/AppIcon-40x40@1x.png new file mode 100644 index 00000000000..54e4b2aacab Binary files /dev/null and b/packages/desktop-electron/icons/dev/ios/AppIcon-40x40@1x.png differ diff --git a/packages/desktop-electron/icons/dev/ios/AppIcon-40x40@2x-1.png b/packages/desktop-electron/icons/dev/ios/AppIcon-40x40@2x-1.png new file mode 100644 index 00000000000..471ed2eec34 Binary files /dev/null and b/packages/desktop-electron/icons/dev/ios/AppIcon-40x40@2x-1.png differ diff --git a/packages/desktop-electron/icons/dev/ios/AppIcon-40x40@2x.png b/packages/desktop-electron/icons/dev/ios/AppIcon-40x40@2x.png new file mode 100644 index 00000000000..471ed2eec34 Binary files /dev/null and b/packages/desktop-electron/icons/dev/ios/AppIcon-40x40@2x.png differ diff --git a/packages/desktop-electron/icons/dev/ios/AppIcon-40x40@3x.png b/packages/desktop-electron/icons/dev/ios/AppIcon-40x40@3x.png new file mode 100644 index 00000000000..1a490cbf16f Binary files /dev/null and b/packages/desktop-electron/icons/dev/ios/AppIcon-40x40@3x.png differ diff --git a/packages/desktop-electron/icons/dev/ios/AppIcon-512@2x.png b/packages/desktop-electron/icons/dev/ios/AppIcon-512@2x.png new file mode 100644 index 00000000000..f53b404e5f8 Binary files /dev/null and b/packages/desktop-electron/icons/dev/ios/AppIcon-512@2x.png differ diff --git a/packages/desktop-electron/icons/dev/ios/AppIcon-60x60@2x.png b/packages/desktop-electron/icons/dev/ios/AppIcon-60x60@2x.png new file mode 100644 index 00000000000..1a490cbf16f Binary files /dev/null and b/packages/desktop-electron/icons/dev/ios/AppIcon-60x60@2x.png differ diff --git a/packages/desktop-electron/icons/dev/ios/AppIcon-60x60@3x.png b/packages/desktop-electron/icons/dev/ios/AppIcon-60x60@3x.png new file mode 100644 index 00000000000..bdc759eefe2 Binary files /dev/null and b/packages/desktop-electron/icons/dev/ios/AppIcon-60x60@3x.png differ diff --git a/packages/desktop-electron/icons/dev/ios/AppIcon-76x76@1x.png b/packages/desktop-electron/icons/dev/ios/AppIcon-76x76@1x.png new file mode 100644 index 00000000000..d22096a2dfe Binary files /dev/null and b/packages/desktop-electron/icons/dev/ios/AppIcon-76x76@1x.png differ diff --git a/packages/desktop-electron/icons/dev/ios/AppIcon-76x76@2x.png b/packages/desktop-electron/icons/dev/ios/AppIcon-76x76@2x.png new file mode 100644 index 00000000000..d675773d17e Binary files /dev/null and b/packages/desktop-electron/icons/dev/ios/AppIcon-76x76@2x.png differ diff --git a/packages/desktop-electron/icons/dev/ios/AppIcon-83.5x83.5@2x.png b/packages/desktop-electron/icons/dev/ios/AppIcon-83.5x83.5@2x.png new file mode 100644 index 00000000000..31698afce20 Binary files /dev/null and b/packages/desktop-electron/icons/dev/ios/AppIcon-83.5x83.5@2x.png differ diff --git a/packages/desktop-electron/icons/prod/128x128.png b/packages/desktop-electron/icons/prod/128x128.png new file mode 100644 index 00000000000..caf7b02eb36 Binary files /dev/null and b/packages/desktop-electron/icons/prod/128x128.png differ diff --git a/packages/desktop-electron/icons/prod/128x128@2x.png b/packages/desktop-electron/icons/prod/128x128@2x.png new file mode 100644 index 00000000000..47fe4c61ea2 Binary files /dev/null and b/packages/desktop-electron/icons/prod/128x128@2x.png differ diff --git a/packages/desktop-electron/icons/prod/32x32.png b/packages/desktop-electron/icons/prod/32x32.png new file mode 100644 index 00000000000..5868bcc9331 Binary files /dev/null and b/packages/desktop-electron/icons/prod/32x32.png differ diff --git a/packages/desktop-electron/icons/prod/64x64.png b/packages/desktop-electron/icons/prod/64x64.png new file mode 100644 index 00000000000..1ed7425d855 Binary files /dev/null and b/packages/desktop-electron/icons/prod/64x64.png differ diff --git a/packages/desktop-electron/icons/prod/Square107x107Logo.png b/packages/desktop-electron/icons/prod/Square107x107Logo.png new file mode 100644 index 00000000000..1db249bf720 Binary files /dev/null and b/packages/desktop-electron/icons/prod/Square107x107Logo.png differ diff --git a/packages/desktop-electron/icons/prod/Square142x142Logo.png b/packages/desktop-electron/icons/prod/Square142x142Logo.png new file mode 100644 index 00000000000..1961c340816 Binary files /dev/null and b/packages/desktop-electron/icons/prod/Square142x142Logo.png differ diff --git a/packages/desktop-electron/icons/prod/Square150x150Logo.png b/packages/desktop-electron/icons/prod/Square150x150Logo.png new file mode 100644 index 00000000000..abc507347e6 Binary files /dev/null and b/packages/desktop-electron/icons/prod/Square150x150Logo.png differ diff --git a/packages/desktop-electron/icons/prod/Square284x284Logo.png b/packages/desktop-electron/icons/prod/Square284x284Logo.png new file mode 100644 index 00000000000..51e2a1b9fea Binary files /dev/null and b/packages/desktop-electron/icons/prod/Square284x284Logo.png differ diff --git a/packages/desktop-electron/icons/prod/Square30x30Logo.png b/packages/desktop-electron/icons/prod/Square30x30Logo.png new file mode 100644 index 00000000000..066a1fd0c87 Binary files /dev/null and b/packages/desktop-electron/icons/prod/Square30x30Logo.png differ diff --git a/packages/desktop-electron/icons/prod/Square310x310Logo.png b/packages/desktop-electron/icons/prod/Square310x310Logo.png new file mode 100644 index 00000000000..2a85c8e9522 Binary files /dev/null and b/packages/desktop-electron/icons/prod/Square310x310Logo.png differ diff --git a/packages/desktop-electron/icons/prod/Square44x44Logo.png b/packages/desktop-electron/icons/prod/Square44x44Logo.png new file mode 100644 index 00000000000..c855b80632b Binary files /dev/null and b/packages/desktop-electron/icons/prod/Square44x44Logo.png differ diff --git a/packages/desktop-electron/icons/prod/Square71x71Logo.png b/packages/desktop-electron/icons/prod/Square71x71Logo.png new file mode 100644 index 00000000000..c8168f71119 Binary files /dev/null and b/packages/desktop-electron/icons/prod/Square71x71Logo.png differ diff --git a/packages/desktop-electron/icons/prod/Square89x89Logo.png b/packages/desktop-electron/icons/prod/Square89x89Logo.png new file mode 100644 index 00000000000..19ec1777dec Binary files /dev/null and b/packages/desktop-electron/icons/prod/Square89x89Logo.png differ diff --git a/packages/desktop-electron/icons/prod/StoreLogo.png b/packages/desktop-electron/icons/prod/StoreLogo.png new file mode 100644 index 00000000000..3fd053d349a Binary files /dev/null and b/packages/desktop-electron/icons/prod/StoreLogo.png differ diff --git a/packages/desktop-electron/icons/prod/android/mipmap-anydpi-v26/ic_launcher.xml b/packages/desktop-electron/icons/prod/android/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 00000000000..2ffbf24b689 --- /dev/null +++ b/packages/desktop-electron/icons/prod/android/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/packages/desktop-electron/icons/prod/android/mipmap-hdpi/ic_launcher.png b/packages/desktop-electron/icons/prod/android/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000000..4f3ea0e3672 Binary files /dev/null and b/packages/desktop-electron/icons/prod/android/mipmap-hdpi/ic_launcher.png differ diff --git a/packages/desktop-electron/icons/prod/android/mipmap-hdpi/ic_launcher_foreground.png b/packages/desktop-electron/icons/prod/android/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 00000000000..7db80699bcc Binary files /dev/null and b/packages/desktop-electron/icons/prod/android/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/packages/desktop-electron/icons/prod/android/mipmap-hdpi/ic_launcher_round.png b/packages/desktop-electron/icons/prod/android/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 00000000000..a54ebe65286 Binary files /dev/null and b/packages/desktop-electron/icons/prod/android/mipmap-hdpi/ic_launcher_round.png differ diff --git a/packages/desktop-electron/icons/prod/android/mipmap-mdpi/ic_launcher.png b/packages/desktop-electron/icons/prod/android/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000000..9337ccfa3fc Binary files /dev/null and b/packages/desktop-electron/icons/prod/android/mipmap-mdpi/ic_launcher.png differ diff --git a/packages/desktop-electron/icons/prod/android/mipmap-mdpi/ic_launcher_foreground.png b/packages/desktop-electron/icons/prod/android/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 00000000000..0bfc1082e68 Binary files /dev/null and b/packages/desktop-electron/icons/prod/android/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/packages/desktop-electron/icons/prod/android/mipmap-mdpi/ic_launcher_round.png b/packages/desktop-electron/icons/prod/android/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 00000000000..5b02ec732e3 Binary files /dev/null and b/packages/desktop-electron/icons/prod/android/mipmap-mdpi/ic_launcher_round.png differ diff --git a/packages/desktop-electron/icons/prod/android/mipmap-xhdpi/ic_launcher.png b/packages/desktop-electron/icons/prod/android/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000000..322aeaeaaad Binary files /dev/null and b/packages/desktop-electron/icons/prod/android/mipmap-xhdpi/ic_launcher.png differ diff --git a/packages/desktop-electron/icons/prod/android/mipmap-xhdpi/ic_launcher_foreground.png b/packages/desktop-electron/icons/prod/android/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000000..ca1e336cc34 Binary files /dev/null and b/packages/desktop-electron/icons/prod/android/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/packages/desktop-electron/icons/prod/android/mipmap-xhdpi/ic_launcher_round.png b/packages/desktop-electron/icons/prod/android/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 00000000000..f7111079920 Binary files /dev/null and b/packages/desktop-electron/icons/prod/android/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/packages/desktop-electron/icons/prod/android/mipmap-xxhdpi/ic_launcher.png b/packages/desktop-electron/icons/prod/android/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000000..287a6b500b6 Binary files /dev/null and b/packages/desktop-electron/icons/prod/android/mipmap-xxhdpi/ic_launcher.png differ diff --git a/packages/desktop-electron/icons/prod/android/mipmap-xxhdpi/ic_launcher_foreground.png b/packages/desktop-electron/icons/prod/android/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000000..9d3d06a867b Binary files /dev/null and b/packages/desktop-electron/icons/prod/android/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/packages/desktop-electron/icons/prod/android/mipmap-xxhdpi/ic_launcher_round.png b/packages/desktop-electron/icons/prod/android/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 00000000000..d4b6fde1b81 Binary files /dev/null and b/packages/desktop-electron/icons/prod/android/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/packages/desktop-electron/icons/prod/android/mipmap-xxxhdpi/ic_launcher.png b/packages/desktop-electron/icons/prod/android/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000000..bde8d75967a Binary files /dev/null and b/packages/desktop-electron/icons/prod/android/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/packages/desktop-electron/icons/prod/android/mipmap-xxxhdpi/ic_launcher_foreground.png b/packages/desktop-electron/icons/prod/android/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000000..03df7809da9 Binary files /dev/null and b/packages/desktop-electron/icons/prod/android/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/packages/desktop-electron/icons/prod/android/mipmap-xxxhdpi/ic_launcher_round.png b/packages/desktop-electron/icons/prod/android/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 00000000000..62363be0470 Binary files /dev/null and b/packages/desktop-electron/icons/prod/android/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/packages/desktop-electron/icons/prod/android/values/ic_launcher_background.xml b/packages/desktop-electron/icons/prod/android/values/ic_launcher_background.xml new file mode 100644 index 00000000000..ea9c223a6cb --- /dev/null +++ b/packages/desktop-electron/icons/prod/android/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #fff + \ No newline at end of file diff --git a/packages/desktop-electron/icons/prod/icon.icns b/packages/desktop-electron/icons/prod/icon.icns new file mode 100644 index 00000000000..be910ad5f93 Binary files /dev/null and b/packages/desktop-electron/icons/prod/icon.icns differ diff --git a/packages/desktop-electron/icons/prod/icon.ico b/packages/desktop-electron/icons/prod/icon.ico new file mode 100644 index 00000000000..ff88d21e4c5 Binary files /dev/null and b/packages/desktop-electron/icons/prod/icon.ico differ diff --git a/packages/desktop-electron/icons/prod/icon.png b/packages/desktop-electron/icons/prod/icon.png new file mode 100644 index 00000000000..0ecbb6d5f8f Binary files /dev/null and b/packages/desktop-electron/icons/prod/icon.png differ diff --git a/packages/desktop-electron/icons/prod/ios/AppIcon-20x20@1x.png b/packages/desktop-electron/icons/prod/ios/AppIcon-20x20@1x.png new file mode 100644 index 00000000000..eb137e164af Binary files /dev/null and b/packages/desktop-electron/icons/prod/ios/AppIcon-20x20@1x.png differ diff --git a/packages/desktop-electron/icons/prod/ios/AppIcon-20x20@2x-1.png b/packages/desktop-electron/icons/prod/ios/AppIcon-20x20@2x-1.png new file mode 100644 index 00000000000..aa76ab10bae Binary files /dev/null and b/packages/desktop-electron/icons/prod/ios/AppIcon-20x20@2x-1.png differ diff --git a/packages/desktop-electron/icons/prod/ios/AppIcon-20x20@2x.png b/packages/desktop-electron/icons/prod/ios/AppIcon-20x20@2x.png new file mode 100644 index 00000000000..aa76ab10bae Binary files /dev/null and b/packages/desktop-electron/icons/prod/ios/AppIcon-20x20@2x.png differ diff --git a/packages/desktop-electron/icons/prod/ios/AppIcon-20x20@3x.png b/packages/desktop-electron/icons/prod/ios/AppIcon-20x20@3x.png new file mode 100644 index 00000000000..c58ea3d49bd Binary files /dev/null and b/packages/desktop-electron/icons/prod/ios/AppIcon-20x20@3x.png differ diff --git a/packages/desktop-electron/icons/prod/ios/AppIcon-29x29@1x.png b/packages/desktop-electron/icons/prod/ios/AppIcon-29x29@1x.png new file mode 100644 index 00000000000..0eeb4d9bf9e Binary files /dev/null and b/packages/desktop-electron/icons/prod/ios/AppIcon-29x29@1x.png differ diff --git a/packages/desktop-electron/icons/prod/ios/AppIcon-29x29@2x-1.png b/packages/desktop-electron/icons/prod/ios/AppIcon-29x29@2x-1.png new file mode 100644 index 00000000000..32601c70a14 Binary files /dev/null and b/packages/desktop-electron/icons/prod/ios/AppIcon-29x29@2x-1.png differ diff --git a/packages/desktop-electron/icons/prod/ios/AppIcon-29x29@2x.png b/packages/desktop-electron/icons/prod/ios/AppIcon-29x29@2x.png new file mode 100644 index 00000000000..32601c70a14 Binary files /dev/null and b/packages/desktop-electron/icons/prod/ios/AppIcon-29x29@2x.png differ diff --git a/packages/desktop-electron/icons/prod/ios/AppIcon-29x29@3x.png b/packages/desktop-electron/icons/prod/ios/AppIcon-29x29@3x.png new file mode 100644 index 00000000000..a372c4a111f Binary files /dev/null and b/packages/desktop-electron/icons/prod/ios/AppIcon-29x29@3x.png differ diff --git a/packages/desktop-electron/icons/prod/ios/AppIcon-40x40@1x.png b/packages/desktop-electron/icons/prod/ios/AppIcon-40x40@1x.png new file mode 100644 index 00000000000..aa76ab10bae Binary files /dev/null and b/packages/desktop-electron/icons/prod/ios/AppIcon-40x40@1x.png differ diff --git a/packages/desktop-electron/icons/prod/ios/AppIcon-40x40@2x-1.png b/packages/desktop-electron/icons/prod/ios/AppIcon-40x40@2x-1.png new file mode 100644 index 00000000000..e82ce2765f1 Binary files /dev/null and b/packages/desktop-electron/icons/prod/ios/AppIcon-40x40@2x-1.png differ diff --git a/packages/desktop-electron/icons/prod/ios/AppIcon-40x40@2x.png b/packages/desktop-electron/icons/prod/ios/AppIcon-40x40@2x.png new file mode 100644 index 00000000000..e82ce2765f1 Binary files /dev/null and b/packages/desktop-electron/icons/prod/ios/AppIcon-40x40@2x.png differ diff --git a/packages/desktop-electron/icons/prod/ios/AppIcon-40x40@3x.png b/packages/desktop-electron/icons/prod/ios/AppIcon-40x40@3x.png new file mode 100644 index 00000000000..15ad5936285 Binary files /dev/null and b/packages/desktop-electron/icons/prod/ios/AppIcon-40x40@3x.png differ diff --git a/packages/desktop-electron/icons/prod/ios/AppIcon-512@2x.png b/packages/desktop-electron/icons/prod/ios/AppIcon-512@2x.png new file mode 100644 index 00000000000..2260671c001 Binary files /dev/null and b/packages/desktop-electron/icons/prod/ios/AppIcon-512@2x.png differ diff --git a/packages/desktop-electron/icons/prod/ios/AppIcon-60x60@2x.png b/packages/desktop-electron/icons/prod/ios/AppIcon-60x60@2x.png new file mode 100644 index 00000000000..15ad5936285 Binary files /dev/null and b/packages/desktop-electron/icons/prod/ios/AppIcon-60x60@2x.png differ diff --git a/packages/desktop-electron/icons/prod/ios/AppIcon-60x60@3x.png b/packages/desktop-electron/icons/prod/ios/AppIcon-60x60@3x.png new file mode 100644 index 00000000000..5c66bd3b181 Binary files /dev/null and b/packages/desktop-electron/icons/prod/ios/AppIcon-60x60@3x.png differ diff --git a/packages/desktop-electron/icons/prod/ios/AppIcon-76x76@1x.png b/packages/desktop-electron/icons/prod/ios/AppIcon-76x76@1x.png new file mode 100644 index 00000000000..a5b05f3b50f Binary files /dev/null and b/packages/desktop-electron/icons/prod/ios/AppIcon-76x76@1x.png differ diff --git a/packages/desktop-electron/icons/prod/ios/AppIcon-76x76@2x.png b/packages/desktop-electron/icons/prod/ios/AppIcon-76x76@2x.png new file mode 100644 index 00000000000..9c0615d411d Binary files /dev/null and b/packages/desktop-electron/icons/prod/ios/AppIcon-76x76@2x.png differ diff --git a/packages/desktop-electron/icons/prod/ios/AppIcon-83.5x83.5@2x.png b/packages/desktop-electron/icons/prod/ios/AppIcon-83.5x83.5@2x.png new file mode 100644 index 00000000000..6b792b36ad3 Binary files /dev/null and b/packages/desktop-electron/icons/prod/ios/AppIcon-83.5x83.5@2x.png differ diff --git a/packages/desktop-electron/package.json b/packages/desktop-electron/package.json new file mode 100644 index 00000000000..c4a64aff7d3 --- /dev/null +++ b/packages/desktop-electron/package.json @@ -0,0 +1,52 @@ +{ + "name": "@opencode-ai/desktop-electron", + "private": true, + "version": "1.2.6", + "type": "module", + "license": "MIT", + "homepage": "https://opencode.ai", + "author": { + "name": "OpenCode", + "email": "hello@opencode.ai" + }, + "scripts": { + "typecheck": "tsgo -b", + "predev": "bun ./scripts/predev.ts", + "dev": "electron-vite dev", + "prebuild": "bun ./scripts/copy-icons.ts", + "build": "electron-vite build", + "preview": "electron-vite preview", + "package": "electron-builder --config electron-builder.config.ts", + "package:mac": "electron-builder --mac --config electron-builder.config.ts", + "package:win": "electron-builder --win --config electron-builder.config.ts", + "package:linux": "electron-builder --linux --config electron-builder.config.ts", + "native:build": "bun install --cwd native" + }, + "main": "./out/main/index.js", + "dependencies": { + "@opencode-ai/app": "workspace:*", + "@opencode-ai/ui": "workspace:*", + "@solid-primitives/i18n": "2.2.1", + "@solid-primitives/storage": "catalog:", + "@solidjs/meta": "catalog:", + "@solidjs/router": "0.15.4", + "electron-log": "^5", + "electron-store": "^10", + "electron-updater": "^6", + "electron-window-state": "^5.0.3", + "marked": "^15", + "solid-js": "catalog:", + "tree-kill": "^1.2.2" + }, + "devDependencies": { + "@actions/artifact": "4.0.0", + "@types/bun": "catalog:", + "@types/node": "catalog:", + "@typescript/native-preview": "catalog:", + "electron": "40.4.1", + "electron-builder": "^26", + "electron-vite": "^5", + "typescript": "~5.6.2", + "vite": "catalog:" + } +} diff --git a/packages/desktop-electron/resources/entitlements.plist b/packages/desktop-electron/resources/entitlements.plist new file mode 100644 index 00000000000..61d6c38cef3 --- /dev/null +++ b/packages/desktop-electron/resources/entitlements.plist @@ -0,0 +1,30 @@ + + + + + com.apple.security.cs.allow-jit + + com.apple.security.cs.allow-unsigned-executable-memory + + com.apple.security.cs.disable-executable-page-protection + + com.apple.security.cs.allow-dyld-environment-variables + + com.apple.security.cs.disable-library-validation + + com.apple.security.automation.apple-events + + com.apple.security.device.audio-input + + com.apple.security.device.camera + + com.apple.security.personal-information.addressbook + + com.apple.security.personal-information.calendars + + com.apple.security.personal-information.location + + com.apple.security.personal-information.photos-library + + + diff --git a/packages/desktop-electron/scripts/copy-bundles.ts b/packages/desktop-electron/scripts/copy-bundles.ts new file mode 100644 index 00000000000..6ef3335eb79 --- /dev/null +++ b/packages/desktop-electron/scripts/copy-bundles.ts @@ -0,0 +1,12 @@ +import { $ } from "bun" +import * as path from "node:path" + +import { RUST_TARGET } from "./utils" + +if (!RUST_TARGET) throw new Error("RUST_TARGET not defined") + +const BUNDLE_DIR = "dist" +const BUNDLES_OUT_DIR = path.join(process.cwd(), "dist/bundles") + +await $`mkdir -p ${BUNDLES_OUT_DIR}` +await $`cp -r ${BUNDLE_DIR}/* ${BUNDLES_OUT_DIR}` diff --git a/packages/desktop-electron/scripts/copy-icons.ts b/packages/desktop-electron/scripts/copy-icons.ts new file mode 100644 index 00000000000..400f4275710 --- /dev/null +++ b/packages/desktop-electron/scripts/copy-icons.ts @@ -0,0 +1,12 @@ +import { $ } from "bun" +import { resolveChannel } from "./utils" + +const arg = process.argv[2] +const channel = arg === "dev" || arg === "beta" || arg === "prod" ? arg : resolveChannel() + +const src = `./icons/${channel}` +const dest = "resources/icons" + +await $`rm -rf ${dest}` +await $`cp -R ${src} ${dest}` +console.log(`Copied ${channel} icons from ${src} to ${dest}`) diff --git a/packages/desktop-electron/scripts/finalize-latest-yml.ts b/packages/desktop-electron/scripts/finalize-latest-yml.ts new file mode 100644 index 00000000000..42ec23b642c --- /dev/null +++ b/packages/desktop-electron/scripts/finalize-latest-yml.ts @@ -0,0 +1,116 @@ +#!/usr/bin/env bun + +import { $ } from "bun" +import path from "path" + +const dir = process.env.LATEST_YML_DIR! +if (!dir) throw new Error("LATEST_YML_DIR is required") + +const repo = process.env.GH_REPO +if (!repo) throw new Error("GH_REPO is required") + +const version = process.env.OPENCODE_VERSION +if (!version) throw new Error("OPENCODE_VERSION is required") + +type FileEntry = { + url: string + sha512: string + size: number + blockMapSize?: number +} + +type LatestYml = { + version: string + files: FileEntry[] + releaseDate: string +} + +function parse(content: string): LatestYml { + const lines = content.split("\n") + let version = "" + let releaseDate = "" + const files: FileEntry[] = [] + let current: Partial | undefined + + const flush = () => { + if (current?.url && current.sha512 && current.size) files.push(current as FileEntry) + current = undefined + } + + for (const line of lines) { + const indented = line.startsWith(" ") || line.startsWith(" -") + if (line.startsWith("version:")) version = line.slice("version:".length).trim() + else if (line.startsWith("releaseDate:")) + releaseDate = line.slice("releaseDate:".length).trim().replace(/^'|'$/g, "") + else if (line.trim().startsWith("- url:")) { + flush() + current = { url: line.trim().slice("- url:".length).trim() } + } else if (indented && current && line.trim().startsWith("sha512:")) + current.sha512 = line.trim().slice("sha512:".length).trim() + else if (indented && current && line.trim().startsWith("size:")) + current.size = Number(line.trim().slice("size:".length).trim()) + else if (indented && current && line.trim().startsWith("blockMapSize:")) + current.blockMapSize = Number(line.trim().slice("blockMapSize:".length).trim()) + else if (!indented && current) flush() + } + flush() + + return { version, files, releaseDate } +} + +function serialize(data: LatestYml) { + const lines = [`version: ${data.version}`, "files:"] + for (const file of data.files) { + lines.push(` - url: ${file.url}`) + lines.push(` sha512: ${file.sha512}`) + lines.push(` size: ${file.size}`) + if (file.blockMapSize) lines.push(` blockMapSize: ${file.blockMapSize}`) + } + lines.push(`releaseDate: '${data.releaseDate}'`) + return lines.join("\n") + "\n" +} + +async function read(subdir: string, filename: string): Promise { + const file = Bun.file(path.join(dir, subdir, filename)) + if (!(await file.exists())) return undefined + return parse(await file.text()) +} + +const output: Record = {} + +// Windows: single arch, pass through +const win = await read("latest-yml-x86_64-pc-windows-msvc", "latest.yml") +if (win) output["latest.yml"] = serialize(win) + +// Linux x64: pass through +const linuxX64 = await read("latest-yml-x86_64-unknown-linux-gnu", "latest-linux.yml") +if (linuxX64) output["latest-linux.yml"] = serialize(linuxX64) + +// Linux arm64: pass through +const linuxArm64 = await read("latest-yml-aarch64-unknown-linux-gnu", "latest-linux-arm64.yml") +if (linuxArm64) output["latest-linux-arm64.yml"] = serialize(linuxArm64) + +// macOS: merge arm64 + x64 into single file +const macX64 = await read("latest-yml-x86_64-apple-darwin", "latest-mac.yml") +const macArm64 = await read("latest-yml-aarch64-apple-darwin", "latest-mac.yml") +if (macX64 || macArm64) { + const base = macArm64 ?? macX64! + output["latest-mac.yml"] = serialize({ + version: base.version, + files: [...(macArm64?.files ?? []), ...(macX64?.files ?? [])], + releaseDate: base.releaseDate, + }) +} + +// Upload to release +const tag = `v${version}` +const tmp = process.env.RUNNER_TEMP ?? "/tmp" + +for (const [filename, content] of Object.entries(output)) { + const filepath = path.join(tmp, filename) + await Bun.write(filepath, content) + await $`gh release upload ${tag} ${filepath} --clobber --repo ${repo}` + console.log(`uploaded ${filename}`) +} + +console.log("finalized latest yml files") diff --git a/packages/desktop-electron/scripts/predev.ts b/packages/desktop-electron/scripts/predev.ts new file mode 100644 index 00000000000..a688d0e7f19 --- /dev/null +++ b/packages/desktop-electron/scripts/predev.ts @@ -0,0 +1,17 @@ +import { $ } from "bun" + +import { copyBinaryToSidecarFolder, getCurrentSidecar, windowsify } from "./utils" + +await $`bun ./scripts/copy-icons.ts ${process.env.OPENCODE_CHANNEL ?? "dev"}` + +const RUST_TARGET = Bun.env.RUST_TARGET + +const sidecarConfig = getCurrentSidecar(RUST_TARGET) + +const binaryPath = windowsify(`../opencode/dist/${sidecarConfig.ocBinary}/bin/opencode`) + +await (sidecarConfig.ocBinary.includes("-baseline") + ? $`cd ../opencode && bun run build --single --baseline` + : $`cd ../opencode && bun run build --single`) + +await copyBinaryToSidecarFolder(binaryPath, RUST_TARGET) diff --git a/packages/desktop-electron/scripts/prepare.ts b/packages/desktop-electron/scripts/prepare.ts new file mode 100755 index 00000000000..3764db92106 --- /dev/null +++ b/packages/desktop-electron/scripts/prepare.ts @@ -0,0 +1,24 @@ +#!/usr/bin/env bun +import { $ } from "bun" + +import { Script } from "@opencode-ai/script" +import { copyBinaryToSidecarFolder, getCurrentSidecar, resolveChannel, windowsify } from "./utils" + +const channel = resolveChannel() +await $`bun ./scripts/copy-icons.ts ${channel}` + +const pkg = await Bun.file("./package.json").json() +pkg.version = Script.version +await Bun.write("./package.json", JSON.stringify(pkg, null, 2) + "\n") +console.log(`Updated package.json version to ${Script.version}`) + +const sidecarConfig = getCurrentSidecar() + +const dir = "resources/opencode-binaries" + +await $`mkdir -p ${dir}` +await $`gh run download ${Bun.env.GITHUB_RUN_ID} -n opencode-cli`.cwd(dir) + +await copyBinaryToSidecarFolder(windowsify(`${dir}/${sidecarConfig.ocBinary}/bin/opencode`)) + +await $`rm -rf ${dir}` diff --git a/packages/desktop-electron/scripts/utils.ts b/packages/desktop-electron/scripts/utils.ts new file mode 100644 index 00000000000..4c9af1fc7ed --- /dev/null +++ b/packages/desktop-electron/scripts/utils.ts @@ -0,0 +1,69 @@ +import { $ } from "bun" + +export type Channel = "dev" | "beta" | "prod" + +export function resolveChannel(): Channel { + const raw = Bun.env.OPENCODE_CHANNEL + if (raw === "dev" || raw === "beta" || raw === "prod") return raw + return "dev" +} + +export const SIDECAR_BINARIES: Array<{ rustTarget: string; ocBinary: string; assetExt: string }> = [ + { + rustTarget: "aarch64-apple-darwin", + ocBinary: "opencode-darwin-arm64", + assetExt: "zip", + }, + { + rustTarget: "x86_64-apple-darwin", + ocBinary: "opencode-darwin-x64-baseline", + assetExt: "zip", + }, + { + rustTarget: "x86_64-pc-windows-msvc", + ocBinary: "opencode-windows-x64-baseline", + assetExt: "zip", + }, + { + rustTarget: "x86_64-unknown-linux-gnu", + ocBinary: "opencode-linux-x64-baseline", + assetExt: "tar.gz", + }, + { + rustTarget: "aarch64-unknown-linux-gnu", + ocBinary: "opencode-linux-arm64", + assetExt: "tar.gz", + }, +] + +export const RUST_TARGET = Bun.env.RUST_TARGET + +function nativeTarget() { + const { platform, arch } = process + if (platform === "darwin") return arch === "arm64" ? "aarch64-apple-darwin" : "x86_64-apple-darwin" + if (platform === "win32") return "x86_64-pc-windows-msvc" + if (platform === "linux") return arch === "arm64" ? "aarch64-unknown-linux-gnu" : "x86_64-unknown-linux-gnu" + throw new Error(`Unsupported platform: ${platform}/${arch}`) +} + +export function getCurrentSidecar(target = RUST_TARGET ?? nativeTarget()) { + const binaryConfig = SIDECAR_BINARIES.find((b) => b.rustTarget === target) + if (!binaryConfig) throw new Error(`Sidecar configuration not available for Rust target '${target}'`) + + return binaryConfig +} + +export async function copyBinaryToSidecarFolder(source: string) { + const dir = `resources` + await $`mkdir -p ${dir}` + const dest = windowsify(`${dir}/opencode-cli`) + await $`cp ${source} ${dest}` + if (process.platform === "darwin") await $`codesign --force --sign - ${dest}` + + console.log(`Copied ${source} to ${dest}`) +} + +export function windowsify(path: string) { + if (path.endsWith(".exe")) return path + return `${path}${process.platform === "win32" ? ".exe" : ""}` +} diff --git a/packages/desktop-electron/src/main/apps.ts b/packages/desktop-electron/src/main/apps.ts new file mode 100644 index 00000000000..2b460378948 --- /dev/null +++ b/packages/desktop-electron/src/main/apps.ts @@ -0,0 +1,148 @@ +import { execFileSync } from "node:child_process" +import { existsSync, readFileSync, readdirSync } from "node:fs" +import { dirname, extname, join } from "node:path" + +export function checkAppExists(appName: string): boolean { + if (process.platform === "win32") return true + if (process.platform === "linux") return true + return checkMacosApp(appName) +} + +export function resolveAppPath(appName: string): string | null { + if (process.platform !== "win32") return appName + return resolveWindowsAppPath(appName) +} + +export function wslPath(path: string, mode: "windows" | "linux" | null): string { + if (process.platform !== "win32") return path + + const flag = mode === "windows" ? "-w" : "-u" + try { + if (path.startsWith("~")) { + const suffix = path.slice(1) + const cmd = `wslpath ${flag} \"$HOME${suffix.replace(/\"/g, '\\"')}\"` + const output = execFileSync("wsl", ["-e", "sh", "-lc", cmd]) + return output.toString().trim() + } + + const output = execFileSync("wsl", ["-e", "wslpath", flag, path]) + return output.toString().trim() + } catch (error) { + throw new Error(`Failed to run wslpath: ${String(error)}`) + } +} + +function checkMacosApp(appName: string) { + const locations = [`/Applications/${appName}.app`, `/System/Applications/${appName}.app`] + + const home = process.env.HOME + if (home) locations.push(`${home}/Applications/${appName}.app`) + + if (locations.some((location) => existsSync(location))) return true + + try { + execFileSync("which", [appName]) + return true + } catch { + return false + } +} + +function resolveWindowsAppPath(appName: string): string | null { + let output: string + try { + output = execFileSync("where", [appName]).toString() + } catch { + return null + } + + const paths = output + .split(/\r?\n/) + .map((line) => line.trim()) + .filter((line) => line.length > 0) + + const hasExt = (path: string, ext: string) => extname(path).toLowerCase() === `.${ext}` + + const exe = paths.find((path) => hasExt(path, "exe")) + if (exe) return exe + + const resolveCmd = (path: string) => { + const content = readFileSync(path, "utf8") + for (const token of content.split('"').map((value: string) => value.trim())) { + const lower = token.toLowerCase() + if (!lower.includes(".exe")) continue + + const index = lower.indexOf("%~dp0") + if (index >= 0) { + const base = dirname(path) + const suffix = token.slice(index + 5) + const resolved = suffix + .replace(/\//g, "\\") + .split("\\") + .filter((part: string) => part && part !== ".") + .reduce((current: string, part: string) => { + if (part === "..") return dirname(current) + return join(current, part) + }, base) + + if (existsSync(resolved)) return resolved + } + + if (existsSync(token)) return token + } + + return null + } + + for (const path of paths) { + if (hasExt(path, "cmd") || hasExt(path, "bat")) { + const resolved = resolveCmd(path) + if (resolved) return resolved + } + + if (!extname(path)) { + const cmd = `${path}.cmd` + if (existsSync(cmd)) { + const resolved = resolveCmd(cmd) + if (resolved) return resolved + } + + const bat = `${path}.bat` + if (existsSync(bat)) { + const resolved = resolveCmd(bat) + if (resolved) return resolved + } + } + } + + const key = appName + .split("") + .filter((value: string) => /[a-z0-9]/i.test(value)) + .map((value: string) => value.toLowerCase()) + .join("") + + if (key) { + for (const path of paths) { + const dirs = [dirname(path), dirname(dirname(path)), dirname(dirname(dirname(path)))] + for (const dir of dirs) { + try { + for (const entry of readdirSync(dir)) { + const candidate = join(dir, entry) + if (!hasExt(candidate, "exe")) continue + const stem = entry.replace(/\.exe$/i, "") + const name = stem + .split("") + .filter((value: string) => /[a-z0-9]/i.test(value)) + .map((value: string) => value.toLowerCase()) + .join("") + if (name.includes(key) || key.includes(name)) return candidate + } + } catch { + continue + } + } + } + } + + return paths[0] ?? null +} diff --git a/packages/desktop-electron/src/main/cli.ts b/packages/desktop-electron/src/main/cli.ts new file mode 100644 index 00000000000..e338d39138e --- /dev/null +++ b/packages/desktop-electron/src/main/cli.ts @@ -0,0 +1,279 @@ +import { execFileSync, spawn } from "node:child_process" +import { EventEmitter } from "node:events" +import { chmodSync, readFileSync, unlinkSync, writeFileSync } from "node:fs" +import { tmpdir } from "node:os" +import { dirname, join } from "node:path" +import readline from "node:readline" +import { fileURLToPath } from "node:url" +import { app } from "electron" +import treeKill from "tree-kill" + +import { WSL_ENABLED_KEY } from "./constants" +import { store } from "./store" + +const CLI_INSTALL_DIR = ".opencode/bin" +const CLI_BINARY_NAME = "opencode" + +export type ServerConfig = { + hostname?: string + port?: number +} + +export type Config = { + server?: ServerConfig +} + +export type TerminatedPayload = { code: number | null; signal: number | null } + +export type CommandEvent = + | { type: "stdout"; value: string } + | { type: "stderr"; value: string } + | { type: "error"; value: string } + | { type: "terminated"; value: TerminatedPayload } + | { type: "sqlite"; value: SqliteMigrationProgress } + +export type SqliteMigrationProgress = { type: "InProgress"; value: number } | { type: "Done" } + +export type CommandChild = { + kill: () => void +} + +const root = dirname(fileURLToPath(import.meta.url)) + +export function getSidecarPath() { + const suffix = process.platform === "win32" ? ".exe" : "" + const path = app.isPackaged + ? join(process.resourcesPath, `opencode-cli${suffix}`) + : join(root, "../../resources", `opencode-cli${suffix}`) + console.log(`[cli] Sidecar path resolved: ${path} (isPackaged: ${app.isPackaged})`) + return path +} + +export async function getConfig(): Promise { + const { events } = spawnCommand("debug config", {}) + let output = "" + + await new Promise((resolve) => { + events.on("stdout", (line: string) => { + output += line + }) + events.on("stderr", (line: string) => { + output += line + }) + events.on("terminated", () => resolve()) + events.on("error", () => resolve()) + }) + + try { + return JSON.parse(output) as Config + } catch { + return null + } +} + +export async function installCli(): Promise { + if (process.platform === "win32") { + throw new Error("CLI installation is only supported on macOS & Linux") + } + + const sidecar = getSidecarPath() + const scriptPath = join(app.getAppPath(), "install") + const script = readFileSync(scriptPath, "utf8") + const tempScript = join(tmpdir(), "opencode-install.sh") + + writeFileSync(tempScript, script, "utf8") + chmodSync(tempScript, 0o755) + + const cmd = spawn(tempScript, ["--binary", sidecar], { stdio: "pipe" }) + return await new Promise((resolve, reject) => { + cmd.on("exit", (code: number | null) => { + try { + unlinkSync(tempScript) + } catch {} + if (code === 0) { + const installPath = getCliInstallPath() + if (installPath) return resolve(installPath) + return reject(new Error("Could not determine install path")) + } + reject(new Error("Install script failed")) + }) + }) +} + +export function syncCli() { + if (!app.isPackaged) return + const installPath = getCliInstallPath() + if (!installPath) return + + let version = "" + try { + version = execFileSync(installPath, ["--version"]).toString().trim() + } catch { + return + } + + const cli = parseVersion(version) + const appVersion = parseVersion(app.getVersion()) + if (!cli || !appVersion) return + if (compareVersions(cli, appVersion) >= 0) return + void installCli().catch(() => undefined) +} + +export function serve(hostname: string, port: number, password: string) { + const args = `--print-logs --log-level WARN serve --hostname ${hostname} --port ${port}` + const env = { + OPENCODE_SERVER_USERNAME: "opencode", + OPENCODE_SERVER_PASSWORD: password, + } + + return spawnCommand(args, env) +} + +export function spawnCommand(args: string, extraEnv: Record) { + console.log(`[cli] Spawning command with args: ${args}`) + const base = Object.fromEntries( + Object.entries(process.env).filter((entry): entry is [string, string] => typeof entry[1] === "string"), + ) + const envs = { + ...base, + OPENCODE_EXPERIMENTAL_ICON_DISCOVERY: "true", + OPENCODE_EXPERIMENTAL_FILEWATCHER: "true", + OPENCODE_CLIENT: "desktop", + XDG_STATE_HOME: app.getPath("userData"), + ...extraEnv, + } + + const { cmd, cmdArgs } = buildCommand(args, envs) + console.log(`[cli] Executing: ${cmd} ${cmdArgs.join(" ")}`) + const child = spawn(cmd, cmdArgs, { + env: envs, + detached: true, + windowsHide: true, + stdio: ["ignore", "pipe", "pipe"], + }) + console.log(`[cli] Spawned process with PID: ${child.pid}`) + + const events = new EventEmitter() + const exit = new Promise((resolve) => { + child.on("exit", (code: number | null, signal: NodeJS.Signals | null) => { + console.log(`[cli] Process exited with code: ${code}, signal: ${signal}`) + resolve({ code: code ?? null, signal: null }) + }) + child.on("error", (error: Error) => { + console.error(`[cli] Process error: ${error.message}`) + events.emit("error", error.message) + }) + }) + + const stdout = child.stdout + const stderr = child.stderr + + if (stdout) { + readline.createInterface({ input: stdout }).on("line", (line: string) => { + if (handleSqliteProgress(events, line)) return + events.emit("stdout", `${line}\n`) + }) + } + + if (stderr) { + readline.createInterface({ input: stderr }).on("line", (line: string) => { + if (handleSqliteProgress(events, line)) return + events.emit("stderr", `${line}\n`) + }) + } + + exit.then((payload) => { + events.emit("terminated", payload) + }) + + const kill = () => { + if (!child.pid) return + treeKill(child.pid) + } + + return { events, child: { kill }, exit } +} + +function handleSqliteProgress(events: EventEmitter, line: string) { + const stripped = line.startsWith("sqlite-migration:") ? line.slice("sqlite-migration:".length).trim() : null + if (!stripped) return false + if (stripped === "done") { + events.emit("sqlite", { type: "Done" }) + return true + } + const value = Number.parseInt(stripped, 10) + if (!Number.isNaN(value)) { + events.emit("sqlite", { type: "InProgress", value }) + return true + } + return false +} + +function buildCommand(args: string, env: Record) { + if (process.platform === "win32" && isWslEnabled()) { + console.log(`[cli] Using WSL mode`) + const version = app.getVersion() + const script = [ + "set -e", + 'BIN="$HOME/.opencode/bin/opencode"', + 'if [ ! -x "$BIN" ]; then', + ` curl -fsSL https://opencode.ai/install | bash -s -- --version ${shellEscape(version)} --no-modify-path`, + "fi", + `${envPrefix(env)} exec "$BIN" ${args}`, + ].join("\n") + + return { cmd: "wsl", cmdArgs: ["-e", "bash", "-lc", script] } + } + + if (process.platform === "win32") { + const sidecar = getSidecarPath() + console.log(`[cli] Windows direct mode, sidecar: ${sidecar}`) + return { cmd: sidecar, cmdArgs: args.split(" ") } + } + + const sidecar = getSidecarPath() + const shell = process.env.SHELL || "/bin/sh" + const line = shell.endsWith("/nu") ? `^\"${sidecar}\" ${args}` : `\"${sidecar}\" ${args}` + console.log(`[cli] Unix mode, shell: ${shell}, command: ${line}`) + return { cmd: shell, cmdArgs: ["-l", "-c", line] } +} + +function envPrefix(env: Record) { + const entries = Object.entries(env).map(([key, value]) => `${key}=${shellEscape(value)}`) + return entries.join(" ") +} + +function shellEscape(input: string) { + if (!input) return "''" + return `'${input.replace(/'/g, `'"'"'`)}'` +} + +function getCliInstallPath() { + const home = process.env.HOME + if (!home) return null + return join(home, CLI_INSTALL_DIR, CLI_BINARY_NAME) +} + +function isWslEnabled() { + return store.get(WSL_ENABLED_KEY) === true +} + +function parseVersion(value: string) { + const parts = value + .replace(/^v/, "") + .split(".") + .map((part) => Number.parseInt(part, 10)) + if (parts.some((part) => Number.isNaN(part))) return null + return parts +} + +function compareVersions(a: number[], b: number[]) { + const len = Math.max(a.length, b.length) + for (let i = 0; i < len; i += 1) { + const left = a[i] ?? 0 + const right = b[i] ?? 0 + if (left > right) return 1 + if (left < right) return -1 + } + return 0 +} diff --git a/packages/desktop-electron/src/main/constants.ts b/packages/desktop-electron/src/main/constants.ts new file mode 100644 index 00000000000..1e21661c1ad --- /dev/null +++ b/packages/desktop-electron/src/main/constants.ts @@ -0,0 +1,10 @@ +import { app } from "electron" + +type Channel = "dev" | "beta" | "prod" +const raw = import.meta.env.OPENCODE_CHANNEL +export const CHANNEL: Channel = raw === "dev" || raw === "beta" || raw === "prod" ? raw : "dev" + +export const SETTINGS_STORE = "opencode.settings" +export const DEFAULT_SERVER_URL_KEY = "defaultServerUrl" +export const WSL_ENABLED_KEY = "wslEnabled" +export const UPDATER_ENABLED = app.isPackaged && CHANNEL !== "dev" diff --git a/packages/desktop-electron/src/main/env.d.ts b/packages/desktop-electron/src/main/env.d.ts new file mode 100644 index 00000000000..0ee0c551df8 --- /dev/null +++ b/packages/desktop-electron/src/main/env.d.ts @@ -0,0 +1,7 @@ +interface ImportMetaEnv { + readonly OPENCODE_CHANNEL: string +} + +interface ImportMeta { + readonly env: ImportMetaEnv +} diff --git a/packages/desktop-electron/src/main/index.ts b/packages/desktop-electron/src/main/index.ts new file mode 100644 index 00000000000..03c1e128ea5 --- /dev/null +++ b/packages/desktop-electron/src/main/index.ts @@ -0,0 +1,449 @@ +import { app, BrowserWindow, dialog } from "electron" +import type { Event } from "electron" +import pkg from "electron-updater" +import { randomUUID } from "node:crypto" +import { EventEmitter } from "node:events" +import { existsSync } from "node:fs" +import { homedir } from "node:os" +import { join } from "node:path" +import { createServer } from "node:net" + +const APP_NAMES: Record = { dev: "OpenCode Dev", beta: "OpenCode Beta", prod: "OpenCode" } +const APP_IDS: Record = { + dev: "ai.opencode.desktop.dev", + beta: "ai.opencode.desktop.beta", + prod: "ai.opencode.desktop", +} +app.setName(app.isPackaged ? APP_NAMES[CHANNEL] : "OpenCode Dev") +app.setPath("userData", join(app.getPath("appData"), app.isPackaged ? APP_IDS[CHANNEL] : "ai.opencode.desktop.dev")) +const { autoUpdater } = pkg + +import { checkAppExists, resolveAppPath, wslPath } from "./apps" +import { installCli, syncCli } from "./cli" +import { CHANNEL, UPDATER_ENABLED } from "./constants" +import { registerIpcHandlers, sendDeepLinks, sendMenuCommand, sendSqliteMigrationProgress } from "./ipc" +import { initLogging } from "./logging" +import { parseMarkdown } from "./markdown" +import { createMenu } from "./menu" +import { + checkHealth, + checkHealthOrAskRetry, + getDefaultServerUrl, + getSavedServerUrl, + getWslConfig, + setDefaultServerUrl, + setWslConfig, + spawnLocalServer, +} from "./server" +import { createLoadingWindow, createMainWindow, setDockIcon } from "./windows" + +import type { InitStep, ServerReadyData, SqliteMigrationProgress, WslConfig } from "../preload/types" +import type { CommandChild } from "./cli" + +type ServerConnection = + | { variant: "existing"; url: string } + | { + variant: "cli" + url: string + password: null | string + health: { + wait: Promise + } + events: any + } + +const initEmitter = new EventEmitter() +let initStep: InitStep = { phase: "server_waiting" } + +let mainWindow: BrowserWindow | null = null +let loadingWindow: BrowserWindow | null = null +let sidecar: CommandChild | null = null +let loadingComplete = defer() + +const pendingDeepLinks: string[] = [] + +const serverReady = defer() +const logger = initLogging() + +logger.log("app starting", { version: app.getVersion(), packaged: app.isPackaged }) + +setupApp() + +function setupApp() { + ensureLoopbackNoProxy() + app.commandLine.appendSwitch("proxy-bypass-list", "<-loopback>") + + if (!app.requestSingleInstanceLock()) { + app.quit() + return + } + + app.on("second-instance", (_event: Event, argv: string[]) => { + const urls = argv.filter((arg: string) => arg.startsWith("opencode://")) + if (urls.length) { + logger.log("deep link received via second-instance", { urls }) + emitDeepLinks(urls) + } + focusMainWindow() + }) + + app.on("open-url", (event: Event, url: string) => { + event.preventDefault() + logger.log("deep link received via open-url", { url }) + emitDeepLinks([url]) + }) + + app.on("before-quit", () => { + killSidecar() + }) + + void app.whenReady().then(async () => { + // migrate() + app.setAsDefaultProtocolClient("opencode") + setDockIcon() + setupAutoUpdater() + syncCli() + await initialize() + }) +} + +function emitDeepLinks(urls: string[]) { + if (urls.length === 0) return + pendingDeepLinks.push(...urls) + if (mainWindow) sendDeepLinks(mainWindow, urls) +} + +function focusMainWindow() { + if (!mainWindow) return + mainWindow.show() + mainWindow.focus() +} + +function setInitStep(step: InitStep) { + initStep = step + logger.log("init step", { step }) + initEmitter.emit("step", step) +} + +async function setupServerConnection(): Promise { + const customUrl = await getSavedServerUrl() + + if (customUrl && (await checkHealthOrAskRetry(customUrl))) { + serverReady.resolve({ url: customUrl, password: null }) + return { variant: "existing", url: customUrl } + } + + const port = await getSidecarPort() + const hostname = "127.0.0.1" + const localUrl = `http://${hostname}:${port}` + + if (await checkHealth(localUrl)) { + serverReady.resolve({ url: localUrl, password: null }) + return { variant: "existing", url: localUrl } + } + + const password = randomUUID() + const { child, health, events } = spawnLocalServer(hostname, port, password) + sidecar = child + + return { + variant: "cli", + url: localUrl, + password, + health, + events, + } +} + +async function initialize() { + const needsMigration = !sqliteFileExists() + const sqliteDone = needsMigration ? defer() : undefined + + const loadingTask = (async () => { + logger.log("setting up server connection") + const serverConnection = await setupServerConnection() + logger.log("server connection ready", { variant: serverConnection.variant, url: serverConnection.url }) + + const cliHealthCheck = (() => { + if (serverConnection.variant == "cli") { + return async () => { + const { events, health } = serverConnection + events.on("sqlite", (progress: SqliteMigrationProgress) => { + setInitStep({ phase: "sqlite_waiting" }) + if (loadingWindow) sendSqliteMigrationProgress(loadingWindow, progress) + if (mainWindow) sendSqliteMigrationProgress(mainWindow, progress) + if (progress.type === "Done") sqliteDone?.resolve() + }) + await health.wait + serverReady.resolve({ url: serverConnection.url, password: serverConnection.password }) + } + } else { + serverReady.resolve({ url: serverConnection.url, password: null }) + return null + } + })() + + logger.log("server connection started") + + if (cliHealthCheck) { + if (needsMigration) await sqliteDone?.promise + cliHealthCheck?.() + } + + logger.log("loading task finished") + })() + + const globals = { + updaterEnabled: UPDATER_ENABLED, + wsl: getWslConfig().enabled, + deepLinks: pendingDeepLinks, + } + + const loadingWindow = await (async () => { + if (needsMigration /** TOOD: 1 second timeout */) { + // showLoading = await Promise.race([init.then(() => false).catch(() => false), delay(1000).then(() => true)]) + const loadingWindow = createLoadingWindow(globals) + await delay(1000) + return loadingWindow + } else { + logger.log("showing main window without loading window") + mainWindow = createMainWindow(globals) + wireMenu() + } + })() + + await loadingTask + setInitStep({ phase: "done" }) + + if (loadingWindow) { + await loadingComplete.promise + } + + if (!mainWindow) { + mainWindow = createMainWindow(globals) + wireMenu() + } + + loadingWindow?.close() +} + +function wireMenu() { + if (!mainWindow) return + createMenu({ + trigger: (id) => mainWindow && sendMenuCommand(mainWindow, id), + installCli: () => { + void installCli() + }, + checkForUpdates: () => { + void checkForUpdates(true) + }, + reload: () => mainWindow?.reload(), + relaunch: () => { + killSidecar() + app.relaunch() + app.exit(0) + }, + }) +} + +registerIpcHandlers({ + killSidecar: () => killSidecar(), + installCli: async () => installCli(), + awaitInitialization: async (sendStep) => { + sendStep(initStep) + const listener = (step: InitStep) => sendStep(step) + initEmitter.on("step", listener) + try { + logger.log("awaiting server ready") + const res = await serverReady.promise + logger.log("server ready", { url: res.url }) + return res + } finally { + initEmitter.off("step", listener) + } + }, + getDefaultServerUrl: () => getDefaultServerUrl(), + setDefaultServerUrl: (url) => setDefaultServerUrl(url), + getWslConfig: () => Promise.resolve(getWslConfig()), + setWslConfig: (config: WslConfig) => setWslConfig(config), + getDisplayBackend: async () => null, + setDisplayBackend: async () => undefined, + parseMarkdown: async (markdown) => parseMarkdown(markdown), + checkAppExists: async (appName) => checkAppExists(appName), + wslPath: async (path, mode) => wslPath(path, mode), + resolveAppPath: async (appName) => resolveAppPath(appName), + loadingWindowComplete: () => loadingComplete.resolve(), + runUpdater: async (alertOnFail) => checkForUpdates(alertOnFail), + checkUpdate: async () => checkUpdate(), + installUpdate: async () => installUpdate(), +}) + +function killSidecar() { + if (!sidecar) return + sidecar.kill() + sidecar = null +} + +function ensureLoopbackNoProxy() { + const loopback = ["127.0.0.1", "localhost", "::1"] + const upsert = (key: string) => { + const items = (process.env[key] ?? "") + .split(",") + .map((value: string) => value.trim()) + .filter((value: string) => Boolean(value)) + + for (const host of loopback) { + if (items.some((value: string) => value.toLowerCase() === host)) continue + items.push(host) + } + + process.env[key] = items.join(",") + } + + upsert("NO_PROXY") + upsert("no_proxy") +} + +async function getSidecarPort() { + const fromEnv = process.env.OPENCODE_PORT + if (fromEnv) { + const parsed = Number.parseInt(fromEnv, 10) + if (!Number.isNaN(parsed)) return parsed + } + + return await new Promise((resolve, reject) => { + const server = createServer() + server.on("error", reject) + server.listen(0, "127.0.0.1", () => { + const address = server.address() + if (typeof address !== "object" || !address) { + server.close() + reject(new Error("Failed to get port")) + return + } + const port = address.port + server.close(() => resolve(port)) + }) + }) +} + +function sqliteFileExists() { + const xdg = process.env.XDG_DATA_HOME + const base = xdg && xdg.length > 0 ? xdg : join(homedir(), ".local", "share") + return existsSync(join(base, "opencode", "opencode.db")) +} + +function setupAutoUpdater() { + if (!UPDATER_ENABLED) return + autoUpdater.logger = logger + autoUpdater.channel = "latest" + autoUpdater.allowPrerelease = false + autoUpdater.allowDowngrade = true + autoUpdater.autoDownload = false + autoUpdater.autoInstallOnAppQuit = true + logger.log("auto updater configured", { + channel: autoUpdater.channel, + allowPrerelease: autoUpdater.allowPrerelease, + allowDowngrade: autoUpdater.allowDowngrade, + currentVersion: app.getVersion(), + }) +} + +let updateReady = false + +async function checkUpdate() { + if (!UPDATER_ENABLED) return { updateAvailable: false } + updateReady = false + logger.log("checking for updates", { + currentVersion: app.getVersion(), + channel: autoUpdater.channel, + allowPrerelease: autoUpdater.allowPrerelease, + allowDowngrade: autoUpdater.allowDowngrade, + }) + try { + const result = await autoUpdater.checkForUpdates() + const updateInfo = result?.updateInfo + logger.log("update metadata fetched", { + releaseVersion: updateInfo?.version ?? null, + releaseDate: updateInfo?.releaseDate ?? null, + releaseName: updateInfo?.releaseName ?? null, + files: updateInfo?.files?.map((file) => file.url) ?? [], + }) + const version = result?.updateInfo?.version + if (!version) { + logger.log("no update available", { reason: "provider returned no newer version" }) + return { updateAvailable: false } + } + logger.log("update available", { version }) + await autoUpdater.downloadUpdate() + logger.log("update download completed", { version }) + updateReady = true + return { updateAvailable: true, version } + } catch (error) { + logger.error("update check failed", error) + return { updateAvailable: false, failed: true } + } +} + +async function installUpdate() { + if (!updateReady) return + killSidecar() + autoUpdater.quitAndInstall() +} + +async function checkForUpdates(alertOnFail: boolean) { + if (!UPDATER_ENABLED) return + logger.log("checkForUpdates invoked", { alertOnFail }) + const result = await checkUpdate() + if (!result.updateAvailable) { + if (result.failed) { + logger.log("no update decision", { reason: "update check failed" }) + if (!alertOnFail) return + await dialog.showMessageBox({ + type: "error", + message: "Update check failed.", + title: "Update Error", + }) + return + } + + logger.log("no update decision", { reason: "already up to date" }) + if (!alertOnFail) return + await dialog.showMessageBox({ + type: "info", + message: "You're up to date.", + title: "No Updates", + }) + return + } + + const response = await dialog.showMessageBox({ + type: "info", + message: `Update ${result.version ?? ""} downloaded. Restart now?`, + title: "Update Ready", + buttons: ["Restart", "Later"], + defaultId: 0, + cancelId: 1, + }) + logger.log("update prompt response", { + version: result.version ?? null, + restartNow: response.response === 0, + }) + if (response.response === 0) { + await installUpdate() + } +} + +function delay(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)) +} + +function defer() { + let resolve!: (value: T) => void + let reject!: (error: Error) => void + const promise = new Promise((res, rej) => { + resolve = res + reject = rej + }) + return { promise, resolve, reject } +} diff --git a/packages/desktop-electron/src/main/ipc.ts b/packages/desktop-electron/src/main/ipc.ts new file mode 100644 index 00000000000..bbb5379bb7a --- /dev/null +++ b/packages/desktop-electron/src/main/ipc.ts @@ -0,0 +1,176 @@ +import { execFile } from "node:child_process" +import { BrowserWindow, Notification, app, clipboard, dialog, ipcMain, shell } from "electron" +import type { IpcMainEvent, IpcMainInvokeEvent } from "electron" + +import type { InitStep, ServerReadyData, SqliteMigrationProgress, WslConfig } from "../preload/types" +import { getStore } from "./store" + +type Deps = { + killSidecar: () => void + installCli: () => Promise + awaitInitialization: (sendStep: (step: InitStep) => void) => Promise + getDefaultServerUrl: () => Promise | string | null + setDefaultServerUrl: (url: string | null) => Promise | void + getWslConfig: () => Promise + setWslConfig: (config: WslConfig) => Promise | void + getDisplayBackend: () => Promise + setDisplayBackend: (backend: string | null) => Promise | void + parseMarkdown: (markdown: string) => Promise | string + checkAppExists: (appName: string) => Promise | boolean + wslPath: (path: string, mode: "windows" | "linux" | null) => Promise + resolveAppPath: (appName: string) => Promise + loadingWindowComplete: () => void + runUpdater: (alertOnFail: boolean) => Promise | void + checkUpdate: () => Promise<{ updateAvailable: boolean; version?: string }> + installUpdate: () => Promise | void +} + +export function registerIpcHandlers(deps: Deps) { + ipcMain.handle("kill-sidecar", () => deps.killSidecar()) + ipcMain.handle("install-cli", () => deps.installCli()) + ipcMain.handle("await-initialization", (event: IpcMainInvokeEvent) => { + const send = (step: InitStep) => event.sender.send("init-step", step) + return deps.awaitInitialization(send) + }) + ipcMain.handle("get-default-server-url", () => deps.getDefaultServerUrl()) + ipcMain.handle("set-default-server-url", (_event: IpcMainInvokeEvent, url: string | null) => + deps.setDefaultServerUrl(url), + ) + ipcMain.handle("get-wsl-config", () => deps.getWslConfig()) + ipcMain.handle("set-wsl-config", (_event: IpcMainInvokeEvent, config: WslConfig) => deps.setWslConfig(config)) + ipcMain.handle("get-display-backend", () => deps.getDisplayBackend()) + ipcMain.handle("set-display-backend", (_event: IpcMainInvokeEvent, backend: string | null) => + deps.setDisplayBackend(backend), + ) + ipcMain.handle("parse-markdown", (_event: IpcMainInvokeEvent, markdown: string) => deps.parseMarkdown(markdown)) + ipcMain.handle("check-app-exists", (_event: IpcMainInvokeEvent, appName: string) => deps.checkAppExists(appName)) + ipcMain.handle("wsl-path", (_event: IpcMainInvokeEvent, path: string, mode: "windows" | "linux" | null) => + deps.wslPath(path, mode), + ) + ipcMain.handle("resolve-app-path", (_event: IpcMainInvokeEvent, appName: string) => deps.resolveAppPath(appName)) + ipcMain.on("loading-window-complete", () => deps.loadingWindowComplete()) + ipcMain.handle("run-updater", (_event: IpcMainInvokeEvent, alertOnFail: boolean) => deps.runUpdater(alertOnFail)) + ipcMain.handle("check-update", () => deps.checkUpdate()) + ipcMain.handle("install-update", () => deps.installUpdate()) + ipcMain.handle("store-get", (_event: IpcMainInvokeEvent, name: string, key: string) => { + const store = getStore(name) + const value = store.get(key) + if (value === undefined || value === null) return null + return typeof value === "string" ? value : JSON.stringify(value) + }) + ipcMain.handle("store-set", (_event: IpcMainInvokeEvent, name: string, key: string, value: string) => { + getStore(name).set(key, value) + }) + ipcMain.handle("store-delete", (_event: IpcMainInvokeEvent, name: string, key: string) => { + getStore(name).delete(key) + }) + ipcMain.handle("store-clear", (_event: IpcMainInvokeEvent, name: string) => { + getStore(name).clear() + }) + ipcMain.handle("store-keys", (_event: IpcMainInvokeEvent, name: string) => { + const store = getStore(name) + return Object.keys(store.store) + }) + ipcMain.handle("store-length", (_event: IpcMainInvokeEvent, name: string) => { + const store = getStore(name) + return Object.keys(store.store).length + }) + + ipcMain.handle( + "open-directory-picker", + async (_event: IpcMainInvokeEvent, opts?: { multiple?: boolean; title?: string; defaultPath?: string }) => { + const result = await dialog.showOpenDialog({ + properties: ["openDirectory", ...(opts?.multiple ? ["multiSelections" as const] : [])], + title: opts?.title ?? "Choose a folder", + defaultPath: opts?.defaultPath, + }) + if (result.canceled) return null + return opts?.multiple ? result.filePaths : result.filePaths[0] + }, + ) + + ipcMain.handle( + "open-file-picker", + async (_event: IpcMainInvokeEvent, opts?: { multiple?: boolean; title?: string; defaultPath?: string }) => { + const result = await dialog.showOpenDialog({ + properties: ["openFile", ...(opts?.multiple ? ["multiSelections" as const] : [])], + title: opts?.title ?? "Choose a file", + defaultPath: opts?.defaultPath, + }) + if (result.canceled) return null + return opts?.multiple ? result.filePaths : result.filePaths[0] + }, + ) + + ipcMain.handle( + "save-file-picker", + async (_event: IpcMainInvokeEvent, opts?: { title?: string; defaultPath?: string }) => { + const result = await dialog.showSaveDialog({ + title: opts?.title ?? "Save file", + defaultPath: opts?.defaultPath, + }) + if (result.canceled) return null + return result.filePath ?? null + }, + ) + + ipcMain.on("open-link", (_event: IpcMainEvent, url: string) => { + void shell.openExternal(url) + }) + + ipcMain.handle("open-path", async (_event: IpcMainInvokeEvent, path: string, app?: string) => { + if (!app) return shell.openPath(path) + await new Promise((resolve, reject) => { + const [cmd, args] = + process.platform === "darwin" ? (["open", ["-a", app, path]] as const) : ([app, [path]] as const) + execFile(cmd, args, (err) => (err ? reject(err) : resolve())) + }) + }) + + ipcMain.handle("read-clipboard-image", () => { + const image = clipboard.readImage() + if (image.isEmpty()) return null + const buffer = image.toPNG().buffer + const size = image.getSize() + return { buffer, width: size.width, height: size.height } + }) + + ipcMain.on("show-notification", (_event: IpcMainEvent, title: string, body?: string) => { + new Notification({ title, body }).show() + }) + + ipcMain.handle("get-window-focused", (event: IpcMainInvokeEvent) => { + const win = BrowserWindow.fromWebContents(event.sender) + return win?.isFocused() ?? false + }) + + ipcMain.handle("set-window-focus", (event: IpcMainInvokeEvent) => { + const win = BrowserWindow.fromWebContents(event.sender) + win?.focus() + }) + + ipcMain.handle("show-window", (event: IpcMainInvokeEvent) => { + const win = BrowserWindow.fromWebContents(event.sender) + win?.show() + }) + + ipcMain.on("relaunch", () => { + app.relaunch() + app.exit(0) + }) + + ipcMain.handle("get-zoom-factor", (event: IpcMainInvokeEvent) => event.sender.getZoomFactor()) + ipcMain.handle("set-zoom-factor", (event: IpcMainInvokeEvent, factor: number) => event.sender.setZoomFactor(factor)) +} + +export function sendSqliteMigrationProgress(win: BrowserWindow, progress: SqliteMigrationProgress) { + win.webContents.send("sqlite-migration-progress", progress) +} + +export function sendMenuCommand(win: BrowserWindow, id: string) { + win.webContents.send("menu-command", id) +} + +export function sendDeepLinks(win: BrowserWindow, urls: string[]) { + win.webContents.send("deep-link", urls) +} diff --git a/packages/desktop-electron/src/main/logging.ts b/packages/desktop-electron/src/main/logging.ts new file mode 100644 index 00000000000..d315b2d344f --- /dev/null +++ b/packages/desktop-electron/src/main/logging.ts @@ -0,0 +1,40 @@ +import log from "electron-log/main.js" +import { readFileSync, readdirSync, statSync, unlinkSync } from "node:fs" +import { dirname, join } from "node:path" + +const MAX_LOG_AGE_DAYS = 7 +const TAIL_LINES = 1000 + +export function initLogging() { + log.transports.file.maxSize = 5 * 1024 * 1024 + cleanup() + return log +} + +export function tail(): string { + try { + const path = log.transports.file.getFile().path + const contents = readFileSync(path, "utf8") + const lines = contents.split("\n") + return lines.slice(Math.max(0, lines.length - TAIL_LINES)).join("\n") + } catch { + return "" + } +} + +function cleanup() { + const path = log.transports.file.getFile().path + const dir = dirname(path) + const cutoff = Date.now() - MAX_LOG_AGE_DAYS * 24 * 60 * 60 * 1000 + + for (const entry of readdirSync(dir)) { + const file = join(dir, entry) + try { + const info = statSync(file) + if (!info.isFile()) continue + if (info.mtimeMs < cutoff) unlinkSync(file) + } catch { + continue + } + } +} diff --git a/packages/desktop-electron/src/main/markdown.ts b/packages/desktop-electron/src/main/markdown.ts new file mode 100644 index 00000000000..b956f487602 --- /dev/null +++ b/packages/desktop-electron/src/main/markdown.ts @@ -0,0 +1,16 @@ +import { marked, type Tokens } from "marked" + +const renderer = new marked.Renderer() + +renderer.link = ({ href, title, text }: Tokens.Link) => { + const titleAttr = title ? ` title="${title}"` : "" + return `${text}` +} + +export function parseMarkdown(input: string) { + return marked(input, { + renderer, + breaks: false, + gfm: true, + }) +} diff --git a/packages/desktop-electron/src/main/menu.ts b/packages/desktop-electron/src/main/menu.ts new file mode 100644 index 00000000000..53707ba7f25 --- /dev/null +++ b/packages/desktop-electron/src/main/menu.ts @@ -0,0 +1,116 @@ +import { BrowserWindow, Menu, shell } from "electron" + +import { UPDATER_ENABLED } from "./constants" + +type Deps = { + trigger: (id: string) => void + installCli: () => void + checkForUpdates: () => void + reload: () => void + relaunch: () => void +} + +export function createMenu(deps: Deps) { + if (process.platform !== "darwin") return + + const template: Electron.MenuItemConstructorOptions[] = [ + { + label: "OpenCode", + submenu: [ + { role: "about" }, + { + label: "Check for Updates...", + enabled: UPDATER_ENABLED, + click: () => deps.checkForUpdates(), + }, + { + label: "Install CLI...", + click: () => deps.installCli(), + }, + { + label: "Reload Webview", + click: () => deps.reload(), + }, + { + label: "Restart", + click: () => deps.relaunch(), + }, + { type: "separator" }, + { role: "hide" }, + { role: "hideOthers" }, + { role: "unhide" }, + { type: "separator" }, + { role: "quit" }, + ], + }, + { + label: "File", + submenu: [ + { label: "New Session", accelerator: "Shift+Cmd+S", click: () => deps.trigger("session.new") }, + { label: "Open Project...", accelerator: "Cmd+O", click: () => deps.trigger("project.open") }, + { type: "separator" }, + { role: "close" }, + ], + }, + { + label: "Edit", + submenu: [ + { role: "undo" }, + { role: "redo" }, + { type: "separator" }, + { role: "cut" }, + { role: "copy" }, + { role: "paste" }, + { role: "selectAll" }, + ], + }, + { + label: "View", + submenu: [ + { label: "Toggle Sidebar", accelerator: "Cmd+B", click: () => deps.trigger("sidebar.toggle") }, + { label: "Toggle Terminal", accelerator: "Ctrl+`", click: () => deps.trigger("terminal.toggle") }, + { label: "Toggle File Tree", click: () => deps.trigger("fileTree.toggle") }, + { type: "separator" }, + { label: "Back", click: () => deps.trigger("common.goBack") }, + { label: "Forward", click: () => deps.trigger("common.goForward") }, + { type: "separator" }, + { + label: "Previous Session", + accelerator: "Option+ArrowUp", + click: () => deps.trigger("session.previous"), + }, + { + label: "Next Session", + accelerator: "Option+ArrowDown", + click: () => deps.trigger("session.next"), + }, + { type: "separator" }, + { + label: "Toggle Developer Tools", + accelerator: "Alt+Cmd+I", + click: () => BrowserWindow.getFocusedWindow()?.webContents.toggleDevTools(), + }, + ], + }, + { + label: "Help", + submenu: [ + { label: "OpenCode Documentation", click: () => shell.openExternal("https://opencode.ai/docs") }, + { label: "Support Forum", click: () => shell.openExternal("https://discord.com/invite/opencode") }, + { type: "separator" }, + { type: "separator" }, + { + label: "Share Feedback", + click: () => + shell.openExternal("https://github.com/anomalyco/opencode/issues/new?template=feature_request.yml"), + }, + { + label: "Report a Bug", + click: () => shell.openExternal("https://github.com/anomalyco/opencode/issues/new?template=bug_report.yml"), + }, + ], + }, + ] + + Menu.setApplicationMenu(Menu.buildFromTemplate(template)) +} diff --git a/packages/desktop-electron/src/main/migrate.ts b/packages/desktop-electron/src/main/migrate.ts new file mode 100644 index 00000000000..bad1349eeba --- /dev/null +++ b/packages/desktop-electron/src/main/migrate.ts @@ -0,0 +1,91 @@ +import { app } from "electron" +import log from "electron-log/main.js" +import { existsSync, readdirSync, readFileSync } from "node:fs" +import { homedir } from "node:os" +import { join } from "node:path" +import { CHANNEL } from "./constants" +import { getStore, store } from "./store" + +const TAURI_MIGRATED_KEY = "tauriMigrated" + +// Resolve the directory where Tauri stored its .dat files for the given app identifier. +// Mirrors Tauri's AppLocalData / AppData resolution per OS. +function tauriDir(id: string) { + switch (process.platform) { + case "darwin": + return join(homedir(), "Library", "Application Support", id) + case "win32": + return join(process.env.APPDATA ?? join(homedir(), "AppData", "Roaming"), id) + default: + return join(process.env.XDG_DATA_HOME ?? join(homedir(), ".local", "share"), id) + } +} + +// The Tauri app identifier changes between dev/beta/prod builds. +const TAURI_APP_IDS: Record = { + dev: "ai.opencode.desktop.dev", + beta: "ai.opencode.desktop.beta", + prod: "ai.opencode.desktop", +} +function tauriAppId() { + return app.isPackaged ? TAURI_APP_IDS[CHANNEL] : "ai.opencode.desktop.dev" +} + +// Migrate a single Tauri .dat file into the corresponding electron-store. +// `opencode.settings.dat` is special: it maps to the `opencode.settings` store +// (the electron-store name without the `.dat` extension). All other .dat files +// keep their full filename as the electron-store name so they match what the +// renderer already passes via IPC (e.g. `"default.dat"`, `"opencode.global.dat"`). +function migrateFile(datPath: string, filename: string) { + let data: Record + try { + data = JSON.parse(readFileSync(datPath, "utf-8")) + } catch (err) { + log.warn("tauri migration: failed to parse", filename, err) + return + } + + // opencode.settings.dat → the electron settings store ("opencode.settings"). + // All other .dat files keep their full filename as the store name so they match + // what the renderer passes via IPC (e.g. "default.dat", "opencode.global.dat"). + const storeName = filename === "opencode.settings.dat" ? "opencode.settings" : filename + const target = getStore(storeName) + const migrated: string[] = [] + const skipped: string[] = [] + + for (const [key, value] of Object.entries(data)) { + // Don't overwrite values the user has already set in the Electron app. + if (target.has(key)) { + skipped.push(key) + continue + } + target.set(key, value) + migrated.push(key) + } + + log.log("tauri migration: migrated", filename, "→", storeName, { migrated, skipped }) +} + +export function migrate() { + if (store.get(TAURI_MIGRATED_KEY)) { + log.log("tauri migration: already done, skipping") + return + } + + const dir = tauriDir(tauriAppId()) + log.log("tauri migration: starting", { dir }) + + if (!existsSync(dir)) { + log.log("tauri migration: no tauri data directory found, nothing to migrate") + store.set(TAURI_MIGRATED_KEY, true) + return + } + + for (const filename of readdirSync(dir)) { + if (!filename.endsWith(".dat")) continue + migrateFile(join(dir, filename), filename) + } + + log.log("tauri migration: complete") + store.set(TAURI_MIGRATED_KEY, true) +} diff --git a/packages/desktop-electron/src/main/server.ts b/packages/desktop-electron/src/main/server.ts new file mode 100644 index 00000000000..92018e72e75 --- /dev/null +++ b/packages/desktop-electron/src/main/server.ts @@ -0,0 +1,129 @@ +import { dialog } from "electron" + +import { getConfig, serve, type CommandChild, type Config } from "./cli" +import { DEFAULT_SERVER_URL_KEY, WSL_ENABLED_KEY } from "./constants" +import { store } from "./store" + +export type WslConfig = { enabled: boolean } + +export type HealthCheck = { wait: Promise } + +export function getDefaultServerUrl(): string | null { + const value = store.get(DEFAULT_SERVER_URL_KEY) + return typeof value === "string" ? value : null +} + +export function setDefaultServerUrl(url: string | null) { + if (url) { + store.set(DEFAULT_SERVER_URL_KEY, url) + return + } + + store.delete(DEFAULT_SERVER_URL_KEY) +} + +export function getWslConfig(): WslConfig { + const value = store.get(WSL_ENABLED_KEY) + return { enabled: typeof value === "boolean" ? value : false } +} + +export function setWslConfig(config: WslConfig) { + store.set(WSL_ENABLED_KEY, config.enabled) +} + +export async function getSavedServerUrl(): Promise { + const direct = getDefaultServerUrl() + if (direct) return direct + + const config = await getConfig().catch(() => null) + if (!config) return null + return getServerUrlFromConfig(config) +} + +export function spawnLocalServer(hostname: string, port: number, password: string) { + const { child, exit, events } = serve(hostname, port, password) + + const wait = (async () => { + const url = `http://${hostname}:${port}` + + const ready = async () => { + while (true) { + await new Promise((resolve) => setTimeout(resolve, 100)) + if (await checkHealth(url, password)) return + } + } + + const terminated = async () => { + const payload = await exit + throw new Error( + `Sidecar terminated before becoming healthy (code=${payload.code ?? "unknown"} signal=${ + payload.signal ?? "unknown" + })`, + ) + } + + await Promise.race([ready(), terminated()]) + })() + + return { child, health: { wait }, events } +} + +export async function checkHealth(url: string, password?: string | null): Promise { + let healthUrl: URL + try { + healthUrl = new URL("/global/health", url) + } catch { + return false + } + + const headers = new Headers() + if (password) { + const auth = Buffer.from(`opencode:${password}`).toString("base64") + headers.set("authorization", `Basic ${auth}`) + } + + try { + const res = await fetch(healthUrl, { + method: "GET", + headers, + signal: AbortSignal.timeout(3000), + }) + return res.ok + } catch { + return false + } +} + +export async function checkHealthOrAskRetry(url: string): Promise { + while (true) { + if (await checkHealth(url)) return true + + const result = await dialog.showMessageBox({ + type: "warning", + message: `Could not connect to configured server:\n${url}\n\nWould you like to retry or start a local server instead?`, + title: "Connection Failed", + buttons: ["Retry", "Start Local"], + defaultId: 0, + cancelId: 1, + }) + + if (result.response === 0) continue + return false + } +} + +export function normalizeHostnameForUrl(hostname: string) { + if (hostname === "0.0.0.0") return "127.0.0.1" + if (hostname === "::") return "[::1]" + if (hostname.includes(":") && !hostname.startsWith("[")) return `[${hostname}]` + return hostname +} + +export function getServerUrlFromConfig(config: Config) { + const server = config.server + if (!server?.port) return null + const host = server.hostname ? normalizeHostnameForUrl(server.hostname) : "127.0.0.1" + return `http://${host}:${server.port}` +} + +export type { CommandChild } diff --git a/packages/desktop-electron/src/main/store.ts b/packages/desktop-electron/src/main/store.ts new file mode 100644 index 00000000000..fa1c5682e25 --- /dev/null +++ b/packages/desktop-electron/src/main/store.ts @@ -0,0 +1,15 @@ +import Store from "electron-store" + +import { SETTINGS_STORE } from "./constants" + +const cache = new Map() + +export function getStore(name = SETTINGS_STORE) { + const cached = cache.get(name) + if (cached) return cached + const next = new Store({ name }) + cache.set(name, next) + return next +} + +export const store = getStore(SETTINGS_STORE) diff --git a/packages/desktop-electron/src/main/windows.ts b/packages/desktop-electron/src/main/windows.ts new file mode 100644 index 00000000000..9178457f8dc --- /dev/null +++ b/packages/desktop-electron/src/main/windows.ts @@ -0,0 +1,135 @@ +import windowState from "electron-window-state" +import { app, BrowserWindow, nativeImage } from "electron" +import { dirname, join } from "node:path" +import { fileURLToPath } from "node:url" + +type Globals = { + updaterEnabled: boolean + wsl: boolean + deepLinks?: string[] +} + +const root = dirname(fileURLToPath(import.meta.url)) + +function iconsDir() { + return app.isPackaged ? join(process.resourcesPath, "icons") : join(root, "../../resources/icons") +} + +function iconPath() { + const ext = process.platform === "win32" ? "ico" : "png" + return join(iconsDir(), `icon.${ext}`) +} + +export function setDockIcon() { + if (process.platform !== "darwin") return + app.dock?.setIcon(nativeImage.createFromPath(join(iconsDir(), "128x128@2x.png"))) +} + +export function createMainWindow(globals: Globals) { + const state = windowState({ + defaultWidth: 1280, + defaultHeight: 800, + }) + + const win = new BrowserWindow({ + x: state.x, + y: state.y, + width: state.width, + height: state.height, + show: true, + title: "OpenCode", + icon: iconPath(), + ...(process.platform === "darwin" + ? { + titleBarStyle: "hidden" as const, + trafficLightPosition: { x: 12, y: 14 }, + } + : {}), + ...(process.platform === "win32" + ? { + frame: false, + titleBarStyle: "hidden" as const, + titleBarOverlay: { + color: "transparent", + symbolColor: "#999", + height: 40, + }, + } + : {}), + webPreferences: { + preload: join(root, "../preload/index.mjs"), + sandbox: false, + }, + }) + + state.manage(win) + loadWindow(win, "index.html") + wireZoom(win) + injectGlobals(win, globals) + + return win +} + +export function createLoadingWindow(globals: Globals) { + const win = new BrowserWindow({ + width: 640, + height: 480, + resizable: false, + center: true, + show: true, + icon: iconPath(), + ...(process.platform === "darwin" ? { titleBarStyle: "hidden" as const } : {}), + ...(process.platform === "win32" + ? { + frame: false, + titleBarStyle: "hidden" as const, + titleBarOverlay: { + color: "transparent", + symbolColor: "#999", + height: 40, + }, + } + : {}), + webPreferences: { + preload: join(root, "../preload/index.mjs"), + sandbox: false, + }, + }) + + loadWindow(win, "loading.html") + injectGlobals(win, globals) + + return win +} + +function loadWindow(win: BrowserWindow, html: string) { + const devUrl = process.env.ELECTRON_RENDERER_URL + if (devUrl) { + const url = new URL(html, devUrl) + void win.loadURL(url.toString()) + return + } + + void win.loadFile(join(root, `../renderer/${html}`)) +} + +function injectGlobals(win: BrowserWindow, globals: Globals) { + win.webContents.on("dom-ready", () => { + const deepLinks = globals.deepLinks ?? [] + const data = { + updaterEnabled: globals.updaterEnabled, + wsl: globals.wsl, + deepLinks: Array.isArray(deepLinks) ? deepLinks.splice(0) : deepLinks, + } + void win.webContents.executeJavaScript( + `window.__OPENCODE__ = Object.assign(window.__OPENCODE__ ?? {}, ${JSON.stringify(data)})`, + ) + }) +} + +function wireZoom(win: BrowserWindow) { + win.webContents.setZoomFactor(1) + win.webContents.on("zoom-changed", () => { + win.webContents.setZoomFactor(1) + }) +} diff --git a/packages/desktop-electron/src/preload/index.ts b/packages/desktop-electron/src/preload/index.ts new file mode 100644 index 00000000000..a6520ab4242 --- /dev/null +++ b/packages/desktop-electron/src/preload/index.ts @@ -0,0 +1,66 @@ +import { contextBridge, ipcRenderer } from "electron" +import type { ElectronAPI, InitStep, SqliteMigrationProgress } from "./types" + +const api: ElectronAPI = { + killSidecar: () => ipcRenderer.invoke("kill-sidecar"), + installCli: () => ipcRenderer.invoke("install-cli"), + awaitInitialization: (onStep) => { + const handler = (_: unknown, step: InitStep) => onStep(step) + ipcRenderer.on("init-step", handler) + return ipcRenderer.invoke("await-initialization").finally(() => { + ipcRenderer.removeListener("init-step", handler) + }) + }, + getDefaultServerUrl: () => ipcRenderer.invoke("get-default-server-url"), + setDefaultServerUrl: (url) => ipcRenderer.invoke("set-default-server-url", url), + getWslConfig: () => ipcRenderer.invoke("get-wsl-config"), + setWslConfig: (config) => ipcRenderer.invoke("set-wsl-config", config), + getDisplayBackend: () => ipcRenderer.invoke("get-display-backend"), + setDisplayBackend: (backend) => ipcRenderer.invoke("set-display-backend", backend), + parseMarkdownCommand: (markdown) => ipcRenderer.invoke("parse-markdown", markdown), + checkAppExists: (appName) => ipcRenderer.invoke("check-app-exists", appName), + wslPath: (path, mode) => ipcRenderer.invoke("wsl-path", path, mode), + resolveAppPath: (appName) => ipcRenderer.invoke("resolve-app-path", appName), + storeGet: (name, key) => ipcRenderer.invoke("store-get", name, key), + storeSet: (name, key, value) => ipcRenderer.invoke("store-set", name, key, value), + storeDelete: (name, key) => ipcRenderer.invoke("store-delete", name, key), + storeClear: (name) => ipcRenderer.invoke("store-clear", name), + storeKeys: (name) => ipcRenderer.invoke("store-keys", name), + storeLength: (name) => ipcRenderer.invoke("store-length", name), + + onSqliteMigrationProgress: (cb) => { + const handler = (_: unknown, progress: SqliteMigrationProgress) => cb(progress) + ipcRenderer.on("sqlite-migration-progress", handler) + return () => ipcRenderer.removeListener("sqlite-migration-progress", handler) + }, + onMenuCommand: (cb) => { + const handler = (_: unknown, id: string) => cb(id) + ipcRenderer.on("menu-command", handler) + return () => ipcRenderer.removeListener("menu-command", handler) + }, + onDeepLink: (cb) => { + const handler = (_: unknown, urls: string[]) => cb(urls) + ipcRenderer.on("deep-link", handler) + return () => ipcRenderer.removeListener("deep-link", handler) + }, + + openDirectoryPicker: (opts) => ipcRenderer.invoke("open-directory-picker", opts), + openFilePicker: (opts) => ipcRenderer.invoke("open-file-picker", opts), + saveFilePicker: (opts) => ipcRenderer.invoke("save-file-picker", opts), + openLink: (url) => ipcRenderer.send("open-link", url), + openPath: (path, app) => ipcRenderer.invoke("open-path", path, app), + readClipboardImage: () => ipcRenderer.invoke("read-clipboard-image"), + showNotification: (title, body) => ipcRenderer.send("show-notification", title, body), + getWindowFocused: () => ipcRenderer.invoke("get-window-focused"), + setWindowFocus: () => ipcRenderer.invoke("set-window-focus"), + showWindow: () => ipcRenderer.invoke("show-window"), + relaunch: () => ipcRenderer.send("relaunch"), + getZoomFactor: () => ipcRenderer.invoke("get-zoom-factor"), + setZoomFactor: (factor) => ipcRenderer.invoke("set-zoom-factor", factor), + loadingWindowComplete: () => ipcRenderer.send("loading-window-complete"), + runUpdater: (alertOnFail) => ipcRenderer.invoke("run-updater", alertOnFail), + checkUpdate: () => ipcRenderer.invoke("check-update"), + installUpdate: () => ipcRenderer.invoke("install-update"), +} + +contextBridge.exposeInMainWorld("api", api) diff --git a/packages/desktop-electron/src/preload/types.ts b/packages/desktop-electron/src/preload/types.ts new file mode 100644 index 00000000000..af5410f5f55 --- /dev/null +++ b/packages/desktop-electron/src/preload/types.ts @@ -0,0 +1,64 @@ +export type InitStep = { phase: "server_waiting" } | { phase: "sqlite_waiting" } | { phase: "done" } + +export type ServerReadyData = { + url: string + password: string | null +} + +export type SqliteMigrationProgress = { type: "InProgress"; value: number } | { type: "Done" } + +export type WslConfig = { enabled: boolean } + +export type LinuxDisplayBackend = "wayland" | "auto" + +export type ElectronAPI = { + killSidecar: () => Promise + installCli: () => Promise + awaitInitialization: (onStep: (step: InitStep) => void) => Promise + getDefaultServerUrl: () => Promise + setDefaultServerUrl: (url: string | null) => Promise + getWslConfig: () => Promise + setWslConfig: (config: WslConfig) => Promise + getDisplayBackend: () => Promise + setDisplayBackend: (backend: LinuxDisplayBackend | null) => Promise + parseMarkdownCommand: (markdown: string) => Promise + checkAppExists: (appName: string) => Promise + wslPath: (path: string, mode: "windows" | "linux" | null) => Promise + resolveAppPath: (appName: string) => Promise + storeGet: (name: string, key: string) => Promise + storeSet: (name: string, key: string, value: string) => Promise + storeDelete: (name: string, key: string) => Promise + storeClear: (name: string) => Promise + storeKeys: (name: string) => Promise + storeLength: (name: string) => Promise + + onSqliteMigrationProgress: (cb: (progress: SqliteMigrationProgress) => void) => () => void + onMenuCommand: (cb: (id: string) => void) => () => void + onDeepLink: (cb: (urls: string[]) => void) => () => void + + openDirectoryPicker: (opts?: { + multiple?: boolean + title?: string + defaultPath?: string + }) => Promise + openFilePicker: (opts?: { + multiple?: boolean + title?: string + defaultPath?: string + }) => Promise + saveFilePicker: (opts?: { title?: string; defaultPath?: string }) => Promise + openLink: (url: string) => void + openPath: (path: string, app?: string) => Promise + readClipboardImage: () => Promise<{ buffer: ArrayBuffer; width: number; height: number } | null> + showNotification: (title: string, body?: string) => void + getWindowFocused: () => Promise + setWindowFocus: () => Promise + showWindow: () => Promise + relaunch: () => void + getZoomFactor: () => Promise + setZoomFactor: (factor: number) => Promise + loadingWindowComplete: () => void + runUpdater: (alertOnFail: boolean) => Promise + checkUpdate: () => Promise<{ updateAvailable: boolean; version?: string }> + installUpdate: () => Promise +} diff --git a/packages/desktop-electron/src/renderer/cli.ts b/packages/desktop-electron/src/renderer/cli.ts new file mode 100644 index 00000000000..11d3c1f1b08 --- /dev/null +++ b/packages/desktop-electron/src/renderer/cli.ts @@ -0,0 +1,12 @@ +import { initI18n, t } from "./i18n" + +export async function installCli(): Promise { + await initI18n() + + try { + const path = await window.api.installCli() + window.alert(t("desktop.cli.installed.message", { path })) + } catch (e) { + window.alert(t("desktop.cli.failed.message", { error: String(e) })) + } +} diff --git a/packages/desktop-electron/src/renderer/env.d.ts b/packages/desktop-electron/src/renderer/env.d.ts new file mode 100644 index 00000000000..d1590ff0486 --- /dev/null +++ b/packages/desktop-electron/src/renderer/env.d.ts @@ -0,0 +1,12 @@ +import type { ElectronAPI } from "../preload/types" + +declare global { + interface Window { + api: ElectronAPI + __OPENCODE__?: { + updaterEnabled?: boolean + wsl?: boolean + deepLinks?: string[] + } + } +} diff --git a/packages/desktop-electron/src/renderer/i18n/ar.ts b/packages/desktop-electron/src/renderer/i18n/ar.ts new file mode 100644 index 00000000000..fdbf0a80470 --- /dev/null +++ b/packages/desktop-electron/src/renderer/i18n/ar.ts @@ -0,0 +1,26 @@ +export const dict = { + "desktop.menu.checkForUpdates": "التحقق من وجود تحديثات...", + "desktop.menu.installCli": "تثبيت CLI...", + "desktop.menu.reloadWebview": "إعادة تحميل Webview", + "desktop.menu.restart": "إعادة تشغيل", + + "desktop.dialog.chooseFolder": "اختر مجلدًا", + "desktop.dialog.chooseFile": "اختر ملفًا", + "desktop.dialog.saveFile": "حفظ ملف", + + "desktop.updater.checkFailed.title": "فشل التحقق من التحديثات", + "desktop.updater.checkFailed.message": "فشل التحقق من وجود تحديثات", + "desktop.updater.none.title": "لا توجد تحديثات متاحة", + "desktop.updater.none.message": "أنت تستخدم بالفعل أحدث إصدار من OpenCode", + "desktop.updater.downloadFailed.title": "فشل التحديث", + "desktop.updater.downloadFailed.message": "فشل تنزيل التحديث", + "desktop.updater.downloaded.title": "تم تنزيل التحديث", + "desktop.updater.downloaded.prompt": "تم تنزيل إصدار {{version}} من OpenCode، هل ترغب في تثبيته وإعادة تشغيله؟", + "desktop.updater.installFailed.title": "فشل التحديث", + "desktop.updater.installFailed.message": "فشل تثبيت التحديث", + + "desktop.cli.installed.title": "تم تثبيت CLI", + "desktop.cli.installed.message": "تم تثبيت CLI في {{path}}\n\nأعد تشغيل الطرفية لاستخدام الأمر 'opencode'.", + "desktop.cli.failed.title": "فشل التثبيت", + "desktop.cli.failed.message": "فشل تثبيت CLI: {{error}}", +} diff --git a/packages/desktop-electron/src/renderer/i18n/br.ts b/packages/desktop-electron/src/renderer/i18n/br.ts new file mode 100644 index 00000000000..75fe2dc32bc --- /dev/null +++ b/packages/desktop-electron/src/renderer/i18n/br.ts @@ -0,0 +1,27 @@ +export const dict = { + "desktop.menu.checkForUpdates": "Verificar atualizações...", + "desktop.menu.installCli": "Instalar CLI...", + "desktop.menu.reloadWebview": "Recarregar Webview", + "desktop.menu.restart": "Reiniciar", + + "desktop.dialog.chooseFolder": "Escolher uma pasta", + "desktop.dialog.chooseFile": "Escolher um arquivo", + "desktop.dialog.saveFile": "Salvar arquivo", + + "desktop.updater.checkFailed.title": "Falha ao verificar atualizações", + "desktop.updater.checkFailed.message": "Falha ao verificar atualizações", + "desktop.updater.none.title": "Nenhuma atualização disponível", + "desktop.updater.none.message": "Você já está usando a versão mais recente do OpenCode", + "desktop.updater.downloadFailed.title": "Falha na atualização", + "desktop.updater.downloadFailed.message": "Falha ao baixar a atualização", + "desktop.updater.downloaded.title": "Atualização baixada", + "desktop.updater.downloaded.prompt": + "A versão {{version}} do OpenCode foi baixada. Você gostaria de instalá-la e reiniciar?", + "desktop.updater.installFailed.title": "Falha na atualização", + "desktop.updater.installFailed.message": "Falha ao instalar a atualização", + + "desktop.cli.installed.title": "CLI instalada", + "desktop.cli.installed.message": "CLI instalada em {{path}}\n\nReinicie seu terminal para usar o comando 'opencode'.", + "desktop.cli.failed.title": "Falha na instalação", + "desktop.cli.failed.message": "Falha ao instalar a CLI: {{error}}", +} diff --git a/packages/desktop-electron/src/renderer/i18n/bs.ts b/packages/desktop-electron/src/renderer/i18n/bs.ts new file mode 100644 index 00000000000..58c266f5305 --- /dev/null +++ b/packages/desktop-electron/src/renderer/i18n/bs.ts @@ -0,0 +1,28 @@ +export const dict = { + "desktop.menu.checkForUpdates": "Provjeri ažuriranja...", + "desktop.menu.installCli": "Instaliraj CLI...", + "desktop.menu.reloadWebview": "Ponovo učitavanje webview-a", + "desktop.menu.restart": "Restartuj", + + "desktop.dialog.chooseFolder": "Odaberi folder", + "desktop.dialog.chooseFile": "Odaberi datoteku", + "desktop.dialog.saveFile": "Sačuvaj datoteku", + + "desktop.updater.checkFailed.title": "Provjera ažuriranja nije uspjela", + "desktop.updater.checkFailed.message": "Nije moguće provjeriti ažuriranja", + "desktop.updater.none.title": "Nema dostupnog ažuriranja", + "desktop.updater.none.message": "Već koristiš najnoviju verziju OpenCode-a", + "desktop.updater.downloadFailed.title": "Ažuriranje nije uspjelo", + "desktop.updater.downloadFailed.message": "Neuspjelo preuzimanje ažuriranja", + "desktop.updater.downloaded.title": "Ažuriranje preuzeto", + "desktop.updater.downloaded.prompt": + "Verzija {{version}} OpenCode-a je preuzeta. Želiš li da je instaliraš i ponovo pokreneš aplikaciju?", + "desktop.updater.installFailed.title": "Ažuriranje nije uspjelo", + "desktop.updater.installFailed.message": "Neuspjela instalacija ažuriranja", + + "desktop.cli.installed.title": "CLI instaliran", + "desktop.cli.installed.message": + "CLI je instaliran u {{path}}\n\nRestartuj terminal da bi koristio komandu 'opencode'.", + "desktop.cli.failed.title": "Instalacija nije uspjela", + "desktop.cli.failed.message": "Neuspjela instalacija CLI-a: {{error}}", +} diff --git a/packages/desktop-electron/src/renderer/i18n/da.ts b/packages/desktop-electron/src/renderer/i18n/da.ts new file mode 100644 index 00000000000..2109495f761 --- /dev/null +++ b/packages/desktop-electron/src/renderer/i18n/da.ts @@ -0,0 +1,28 @@ +export const dict = { + "desktop.menu.checkForUpdates": "Tjek for opdateringer...", + "desktop.menu.installCli": "Installer CLI...", + "desktop.menu.reloadWebview": "Genindlæs Webview", + "desktop.menu.restart": "Genstart", + + "desktop.dialog.chooseFolder": "Vælg en mappe", + "desktop.dialog.chooseFile": "Vælg en fil", + "desktop.dialog.saveFile": "Gem fil", + + "desktop.updater.checkFailed.title": "Opdateringstjek mislykkedes", + "desktop.updater.checkFailed.message": "Kunne ikke tjekke for opdateringer", + "desktop.updater.none.title": "Ingen opdatering tilgængelig", + "desktop.updater.none.message": "Du bruger allerede den nyeste version af OpenCode", + "desktop.updater.downloadFailed.title": "Opdatering mislykkedes", + "desktop.updater.downloadFailed.message": "Kunne ikke downloade opdateringen", + "desktop.updater.downloaded.title": "Opdatering downloadet", + "desktop.updater.downloaded.prompt": + "Version {{version}} af OpenCode er blevet downloadet. Vil du installere den og genstarte?", + "desktop.updater.installFailed.title": "Opdatering mislykkedes", + "desktop.updater.installFailed.message": "Kunne ikke installere opdateringen", + + "desktop.cli.installed.title": "CLI installeret", + "desktop.cli.installed.message": + "CLI installeret i {{path}}\n\nGenstart din terminal for at bruge 'opencode'-kommandoen.", + "desktop.cli.failed.title": "Installation mislykkedes", + "desktop.cli.failed.message": "Kunne ikke installere CLI: {{error}}", +} diff --git a/packages/desktop-electron/src/renderer/i18n/de.ts b/packages/desktop-electron/src/renderer/i18n/de.ts new file mode 100644 index 00000000000..38ad8096e31 --- /dev/null +++ b/packages/desktop-electron/src/renderer/i18n/de.ts @@ -0,0 +1,28 @@ +export const dict = { + "desktop.menu.checkForUpdates": "Nach Updates suchen...", + "desktop.menu.installCli": "CLI installieren...", + "desktop.menu.reloadWebview": "Webview neu laden", + "desktop.menu.restart": "Neustart", + + "desktop.dialog.chooseFolder": "Ordner auswählen", + "desktop.dialog.chooseFile": "Datei auswählen", + "desktop.dialog.saveFile": "Datei speichern", + + "desktop.updater.checkFailed.title": "Updateprüfung fehlgeschlagen", + "desktop.updater.checkFailed.message": "Updates konnten nicht geprüft werden", + "desktop.updater.none.title": "Kein Update verfügbar", + "desktop.updater.none.message": "Sie verwenden bereits die neueste Version von OpenCode", + "desktop.updater.downloadFailed.title": "Update fehlgeschlagen", + "desktop.updater.downloadFailed.message": "Update konnte nicht heruntergeladen werden", + "desktop.updater.downloaded.title": "Update heruntergeladen", + "desktop.updater.downloaded.prompt": + "Version {{version}} von OpenCode wurde heruntergeladen. Möchten Sie sie installieren und neu starten?", + "desktop.updater.installFailed.title": "Update fehlgeschlagen", + "desktop.updater.installFailed.message": "Update konnte nicht installiert werden", + + "desktop.cli.installed.title": "CLI installiert", + "desktop.cli.installed.message": + "CLI wurde in {{path}} installiert\n\nStarten Sie Ihr Terminal neu, um den Befehl 'opencode' zu verwenden.", + "desktop.cli.failed.title": "Installation fehlgeschlagen", + "desktop.cli.failed.message": "CLI konnte nicht installiert werden: {{error}}", +} diff --git a/packages/desktop-electron/src/renderer/i18n/en.ts b/packages/desktop-electron/src/renderer/i18n/en.ts new file mode 100644 index 00000000000..4c30380d562 --- /dev/null +++ b/packages/desktop-electron/src/renderer/i18n/en.ts @@ -0,0 +1,27 @@ +export const dict = { + "desktop.menu.checkForUpdates": "Check for Updates...", + "desktop.menu.installCli": "Install CLI...", + "desktop.menu.reloadWebview": "Reload Webview", + "desktop.menu.restart": "Restart", + + "desktop.dialog.chooseFolder": "Choose a folder", + "desktop.dialog.chooseFile": "Choose a file", + "desktop.dialog.saveFile": "Save file", + + "desktop.updater.checkFailed.title": "Update Check Failed", + "desktop.updater.checkFailed.message": "Failed to check for updates", + "desktop.updater.none.title": "No Update Available", + "desktop.updater.none.message": "You are already using the latest version of OpenCode", + "desktop.updater.downloadFailed.title": "Update Failed", + "desktop.updater.downloadFailed.message": "Failed to download update", + "desktop.updater.downloaded.title": "Update Downloaded", + "desktop.updater.downloaded.prompt": + "Version {{version}} of OpenCode has been downloaded, would you like to install it and relaunch?", + "desktop.updater.installFailed.title": "Update Failed", + "desktop.updater.installFailed.message": "Failed to install update", + + "desktop.cli.installed.title": "CLI Installed", + "desktop.cli.installed.message": "CLI installed to {{path}}\n\nRestart your terminal to use the 'opencode' command.", + "desktop.cli.failed.title": "Installation Failed", + "desktop.cli.failed.message": "Failed to install CLI: {{error}}", +} diff --git a/packages/desktop-electron/src/renderer/i18n/es.ts b/packages/desktop-electron/src/renderer/i18n/es.ts new file mode 100644 index 00000000000..80504a8f248 --- /dev/null +++ b/packages/desktop-electron/src/renderer/i18n/es.ts @@ -0,0 +1,27 @@ +export const dict = { + "desktop.menu.checkForUpdates": "Buscar actualizaciones...", + "desktop.menu.installCli": "Instalar CLI...", + "desktop.menu.reloadWebview": "Recargar Webview", + "desktop.menu.restart": "Reiniciar", + + "desktop.dialog.chooseFolder": "Elegir una carpeta", + "desktop.dialog.chooseFile": "Elegir un archivo", + "desktop.dialog.saveFile": "Guardar archivo", + + "desktop.updater.checkFailed.title": "Comprobación de actualizaciones fallida", + "desktop.updater.checkFailed.message": "No se pudieron buscar actualizaciones", + "desktop.updater.none.title": "No hay actualizaciones disponibles", + "desktop.updater.none.message": "Ya estás usando la versión más reciente de OpenCode", + "desktop.updater.downloadFailed.title": "Actualización fallida", + "desktop.updater.downloadFailed.message": "No se pudo descargar la actualización", + "desktop.updater.downloaded.title": "Actualización descargada", + "desktop.updater.downloaded.prompt": + "Se ha descargado la versión {{version}} de OpenCode. ¿Quieres instalarla y reiniciar?", + "desktop.updater.installFailed.title": "Actualización fallida", + "desktop.updater.installFailed.message": "No se pudo instalar la actualización", + + "desktop.cli.installed.title": "CLI instalada", + "desktop.cli.installed.message": "CLI instalada en {{path}}\n\nReinicia tu terminal para usar el comando 'opencode'.", + "desktop.cli.failed.title": "Instalación fallida", + "desktop.cli.failed.message": "No se pudo instalar la CLI: {{error}}", +} diff --git a/packages/desktop-electron/src/renderer/i18n/fr.ts b/packages/desktop-electron/src/renderer/i18n/fr.ts new file mode 100644 index 00000000000..4f0bb2b16c6 --- /dev/null +++ b/packages/desktop-electron/src/renderer/i18n/fr.ts @@ -0,0 +1,28 @@ +export const dict = { + "desktop.menu.checkForUpdates": "Vérifier les mises à jour...", + "desktop.menu.installCli": "Installer la CLI...", + "desktop.menu.reloadWebview": "Recharger la Webview", + "desktop.menu.restart": "Redémarrer", + + "desktop.dialog.chooseFolder": "Choisir un dossier", + "desktop.dialog.chooseFile": "Choisir un fichier", + "desktop.dialog.saveFile": "Enregistrer le fichier", + + "desktop.updater.checkFailed.title": "Échec de la vérification des mises à jour", + "desktop.updater.checkFailed.message": "Impossible de vérifier les mises à jour", + "desktop.updater.none.title": "Aucune mise à jour disponible", + "desktop.updater.none.message": "Vous utilisez déjà la dernière version d'OpenCode", + "desktop.updater.downloadFailed.title": "Échec de la mise à jour", + "desktop.updater.downloadFailed.message": "Impossible de télécharger la mise à jour", + "desktop.updater.downloaded.title": "Mise à jour téléchargée", + "desktop.updater.downloaded.prompt": + "La version {{version}} d'OpenCode a été téléchargée. Voulez-vous l'installer et redémarrer ?", + "desktop.updater.installFailed.title": "Échec de la mise à jour", + "desktop.updater.installFailed.message": "Impossible d'installer la mise à jour", + + "desktop.cli.installed.title": "CLI installée", + "desktop.cli.installed.message": + "CLI installée dans {{path}}\n\nRedémarrez votre terminal pour utiliser la commande 'opencode'.", + "desktop.cli.failed.title": "Échec de l'installation", + "desktop.cli.failed.message": "Impossible d'installer la CLI : {{error}}", +} diff --git a/packages/desktop-electron/src/renderer/i18n/index.ts b/packages/desktop-electron/src/renderer/i18n/index.ts new file mode 100644 index 00000000000..81158ad244a --- /dev/null +++ b/packages/desktop-electron/src/renderer/i18n/index.ts @@ -0,0 +1,187 @@ +import * as i18n from "@solid-primitives/i18n" + +import { dict as desktopEn } from "./en" +import { dict as desktopZh } from "./zh" +import { dict as desktopZht } from "./zht" +import { dict as desktopKo } from "./ko" +import { dict as desktopDe } from "./de" +import { dict as desktopEs } from "./es" +import { dict as desktopFr } from "./fr" +import { dict as desktopDa } from "./da" +import { dict as desktopJa } from "./ja" +import { dict as desktopPl } from "./pl" +import { dict as desktopRu } from "./ru" +import { dict as desktopAr } from "./ar" +import { dict as desktopNo } from "./no" +import { dict as desktopBr } from "./br" +import { dict as desktopBs } from "./bs" + +import { dict as appEn } from "../../../../app/src/i18n/en" +import { dict as appZh } from "../../../../app/src/i18n/zh" +import { dict as appZht } from "../../../../app/src/i18n/zht" +import { dict as appKo } from "../../../../app/src/i18n/ko" +import { dict as appDe } from "../../../../app/src/i18n/de" +import { dict as appEs } from "../../../../app/src/i18n/es" +import { dict as appFr } from "../../../../app/src/i18n/fr" +import { dict as appDa } from "../../../../app/src/i18n/da" +import { dict as appJa } from "../../../../app/src/i18n/ja" +import { dict as appPl } from "../../../../app/src/i18n/pl" +import { dict as appRu } from "../../../../app/src/i18n/ru" +import { dict as appAr } from "../../../../app/src/i18n/ar" +import { dict as appNo } from "../../../../app/src/i18n/no" +import { dict as appBr } from "../../../../app/src/i18n/br" +import { dict as appBs } from "../../../../app/src/i18n/bs" + +export type Locale = + | "en" + | "zh" + | "zht" + | "ko" + | "de" + | "es" + | "fr" + | "da" + | "ja" + | "pl" + | "ru" + | "ar" + | "no" + | "br" + | "bs" + +type RawDictionary = typeof appEn & typeof desktopEn +type Dictionary = i18n.Flatten + +const LOCALES: readonly Locale[] = [ + "en", + "zh", + "zht", + "ko", + "de", + "es", + "fr", + "da", + "ja", + "pl", + "ru", + "bs", + "ar", + "no", + "br", +] + +function detectLocale(): Locale { + if (typeof navigator !== "object") return "en" + + const languages = navigator.languages?.length ? navigator.languages : [navigator.language] + for (const language of languages) { + if (!language) continue + if (language.toLowerCase().startsWith("zh")) { + if (language.toLowerCase().includes("hant")) return "zht" + return "zh" + } + if (language.toLowerCase().startsWith("ko")) return "ko" + if (language.toLowerCase().startsWith("de")) return "de" + if (language.toLowerCase().startsWith("es")) return "es" + if (language.toLowerCase().startsWith("fr")) return "fr" + if (language.toLowerCase().startsWith("da")) return "da" + if (language.toLowerCase().startsWith("ja")) return "ja" + if (language.toLowerCase().startsWith("pl")) return "pl" + if (language.toLowerCase().startsWith("ru")) return "ru" + if (language.toLowerCase().startsWith("ar")) return "ar" + if ( + language.toLowerCase().startsWith("no") || + language.toLowerCase().startsWith("nb") || + language.toLowerCase().startsWith("nn") + ) + return "no" + if (language.toLowerCase().startsWith("pt")) return "br" + if (language.toLowerCase().startsWith("bs")) return "bs" + } + + return "en" +} + +function parseLocale(value: unknown): Locale | null { + if (!value) return null + if (typeof value !== "string") return null + if ((LOCALES as readonly string[]).includes(value)) return value as Locale + return null +} + +function parseRecord(value: unknown) { + if (!value || typeof value !== "object") return null + if (Array.isArray(value)) return null + return value as Record +} + +function parseStored(value: unknown) { + if (typeof value !== "string") return value + try { + return JSON.parse(value) as unknown + } catch { + return value + } +} + +function pickLocale(value: unknown): Locale | null { + const direct = parseLocale(value) + if (direct) return direct + + const record = parseRecord(value) + if (!record) return null + + return parseLocale(record.locale) +} + +const base = i18n.flatten({ ...appEn, ...desktopEn }) + +function build(locale: Locale): Dictionary { + if (locale === "en") return base + if (locale === "zh") return { ...base, ...i18n.flatten(appZh), ...i18n.flatten(desktopZh) } + if (locale === "zht") return { ...base, ...i18n.flatten(appZht), ...i18n.flatten(desktopZht) } + if (locale === "de") return { ...base, ...i18n.flatten(appDe), ...i18n.flatten(desktopDe) } + if (locale === "es") return { ...base, ...i18n.flatten(appEs), ...i18n.flatten(desktopEs) } + if (locale === "fr") return { ...base, ...i18n.flatten(appFr), ...i18n.flatten(desktopFr) } + if (locale === "da") return { ...base, ...i18n.flatten(appDa), ...i18n.flatten(desktopDa) } + if (locale === "ja") return { ...base, ...i18n.flatten(appJa), ...i18n.flatten(desktopJa) } + if (locale === "pl") return { ...base, ...i18n.flatten(appPl), ...i18n.flatten(desktopPl) } + if (locale === "ru") return { ...base, ...i18n.flatten(appRu), ...i18n.flatten(desktopRu) } + if (locale === "ar") return { ...base, ...i18n.flatten(appAr), ...i18n.flatten(desktopAr) } + if (locale === "no") return { ...base, ...i18n.flatten(appNo), ...i18n.flatten(desktopNo) } + if (locale === "br") return { ...base, ...i18n.flatten(appBr), ...i18n.flatten(desktopBr) } + if (locale === "bs") return { ...base, ...i18n.flatten(appBs), ...i18n.flatten(desktopBs) } + return { ...base, ...i18n.flatten(appKo), ...i18n.flatten(desktopKo) } +} + +const state = { + locale: detectLocale(), + dict: base as Dictionary, + init: undefined as Promise | undefined, +} + +state.dict = build(state.locale) + +const translate = i18n.translator(() => state.dict, i18n.resolveTemplate) + +export function t(key: keyof Dictionary, params?: Record) { + return translate(key, params) +} + +export function initI18n(): Promise { + const cached = state.init + if (cached) return cached + + const promise = (async () => { + const raw = await window.api.storeGet("opencode.global.dat", "language").catch(() => null) + const value = parseStored(raw) + const next = pickLocale(value) ?? state.locale + + state.locale = next + state.dict = build(next) + return next + })().catch(() => state.locale) + + state.init = promise + return promise +} diff --git a/packages/desktop-electron/src/renderer/i18n/ja.ts b/packages/desktop-electron/src/renderer/i18n/ja.ts new file mode 100644 index 00000000000..fc485c6f401 --- /dev/null +++ b/packages/desktop-electron/src/renderer/i18n/ja.ts @@ -0,0 +1,28 @@ +export const dict = { + "desktop.menu.checkForUpdates": "アップデートを確認...", + "desktop.menu.installCli": "CLI をインストール...", + "desktop.menu.reloadWebview": "Webview を再読み込み", + "desktop.menu.restart": "再起動", + + "desktop.dialog.chooseFolder": "フォルダーを選択", + "desktop.dialog.chooseFile": "ファイルを選択", + "desktop.dialog.saveFile": "ファイルを保存", + + "desktop.updater.checkFailed.title": "アップデートの確認に失敗しました", + "desktop.updater.checkFailed.message": "アップデートを確認できませんでした", + "desktop.updater.none.title": "利用可能なアップデートはありません", + "desktop.updater.none.message": "すでに最新バージョンの OpenCode を使用しています", + "desktop.updater.downloadFailed.title": "アップデートに失敗しました", + "desktop.updater.downloadFailed.message": "アップデートをダウンロードできませんでした", + "desktop.updater.downloaded.title": "アップデートをダウンロードしました", + "desktop.updater.downloaded.prompt": + "OpenCode のバージョン {{version}} がダウンロードされました。インストールして再起動しますか?", + "desktop.updater.installFailed.title": "アップデートに失敗しました", + "desktop.updater.installFailed.message": "アップデートをインストールできませんでした", + + "desktop.cli.installed.title": "CLI をインストールしました", + "desktop.cli.installed.message": + "CLI を {{path}} にインストールしました\n\nターミナルを再起動して 'opencode' コマンドを使用してください。", + "desktop.cli.failed.title": "インストールに失敗しました", + "desktop.cli.failed.message": "CLI のインストールに失敗しました: {{error}}", +} diff --git a/packages/desktop-electron/src/renderer/i18n/ko.ts b/packages/desktop-electron/src/renderer/i18n/ko.ts new file mode 100644 index 00000000000..be27cec86ac --- /dev/null +++ b/packages/desktop-electron/src/renderer/i18n/ko.ts @@ -0,0 +1,27 @@ +export const dict = { + "desktop.menu.checkForUpdates": "업데이트 확인...", + "desktop.menu.installCli": "CLI 설치...", + "desktop.menu.reloadWebview": "Webview 새로고침", + "desktop.menu.restart": "다시 시작", + + "desktop.dialog.chooseFolder": "폴더 선택", + "desktop.dialog.chooseFile": "파일 선택", + "desktop.dialog.saveFile": "파일 저장", + + "desktop.updater.checkFailed.title": "업데이트 확인 실패", + "desktop.updater.checkFailed.message": "업데이트를 확인하지 못했습니다", + "desktop.updater.none.title": "사용 가능한 업데이트 없음", + "desktop.updater.none.message": "이미 최신 버전의 OpenCode를 사용하고 있습니다", + "desktop.updater.downloadFailed.title": "업데이트 실패", + "desktop.updater.downloadFailed.message": "업데이트를 다운로드하지 못했습니다", + "desktop.updater.downloaded.title": "업데이트 다운로드 완료", + "desktop.updater.downloaded.prompt": "OpenCode {{version}} 버전을 다운로드했습니다. 설치하고 다시 실행할까요?", + "desktop.updater.installFailed.title": "업데이트 실패", + "desktop.updater.installFailed.message": "업데이트를 설치하지 못했습니다", + + "desktop.cli.installed.title": "CLI 설치됨", + "desktop.cli.installed.message": + "CLI가 {{path}}에 설치되었습니다\n\n터미널을 다시 시작하여 'opencode' 명령을 사용하세요.", + "desktop.cli.failed.title": "설치 실패", + "desktop.cli.failed.message": "CLI 설치 실패: {{error}}", +} diff --git a/packages/desktop-electron/src/renderer/i18n/no.ts b/packages/desktop-electron/src/renderer/i18n/no.ts new file mode 100644 index 00000000000..e39bd7f3b44 --- /dev/null +++ b/packages/desktop-electron/src/renderer/i18n/no.ts @@ -0,0 +1,28 @@ +export const dict = { + "desktop.menu.checkForUpdates": "Se etter oppdateringer...", + "desktop.menu.installCli": "Installer CLI...", + "desktop.menu.reloadWebview": "Last inn Webview på nytt", + "desktop.menu.restart": "Start på nytt", + + "desktop.dialog.chooseFolder": "Velg en mappe", + "desktop.dialog.chooseFile": "Velg en fil", + "desktop.dialog.saveFile": "Lagre fil", + + "desktop.updater.checkFailed.title": "Oppdateringssjekk mislyktes", + "desktop.updater.checkFailed.message": "Kunne ikke se etter oppdateringer", + "desktop.updater.none.title": "Ingen oppdatering tilgjengelig", + "desktop.updater.none.message": "Du bruker allerede den nyeste versjonen av OpenCode", + "desktop.updater.downloadFailed.title": "Oppdatering mislyktes", + "desktop.updater.downloadFailed.message": "Kunne ikke laste ned oppdateringen", + "desktop.updater.downloaded.title": "Oppdatering lastet ned", + "desktop.updater.downloaded.prompt": + "Versjon {{version}} av OpenCode er lastet ned. Vil du installere den og starte på nytt?", + "desktop.updater.installFailed.title": "Oppdatering mislyktes", + "desktop.updater.installFailed.message": "Kunne ikke installere oppdateringen", + + "desktop.cli.installed.title": "CLI installert", + "desktop.cli.installed.message": + "CLI installert til {{path}}\n\nStart terminalen på nytt for å bruke 'opencode'-kommandoen.", + "desktop.cli.failed.title": "Installasjon mislyktes", + "desktop.cli.failed.message": "Kunne ikke installere CLI: {{error}}", +} diff --git a/packages/desktop-electron/src/renderer/i18n/pl.ts b/packages/desktop-electron/src/renderer/i18n/pl.ts new file mode 100644 index 00000000000..d3ad7ce64f8 --- /dev/null +++ b/packages/desktop-electron/src/renderer/i18n/pl.ts @@ -0,0 +1,28 @@ +export const dict = { + "desktop.menu.checkForUpdates": "Sprawdź aktualizacje...", + "desktop.menu.installCli": "Zainstaluj CLI...", + "desktop.menu.reloadWebview": "Przeładuj Webview", + "desktop.menu.restart": "Restartuj", + + "desktop.dialog.chooseFolder": "Wybierz folder", + "desktop.dialog.chooseFile": "Wybierz plik", + "desktop.dialog.saveFile": "Zapisz plik", + + "desktop.updater.checkFailed.title": "Nie udało się sprawdzić aktualizacji", + "desktop.updater.checkFailed.message": "Nie udało się sprawdzić aktualizacji", + "desktop.updater.none.title": "Brak dostępnych aktualizacji", + "desktop.updater.none.message": "Korzystasz już z najnowszej wersji OpenCode", + "desktop.updater.downloadFailed.title": "Aktualizacja nie powiodła się", + "desktop.updater.downloadFailed.message": "Nie udało się pobrać aktualizacji", + "desktop.updater.downloaded.title": "Aktualizacja pobrana", + "desktop.updater.downloaded.prompt": + "Pobrano wersję {{version}} OpenCode. Czy chcesz ją zainstalować i uruchomić ponownie?", + "desktop.updater.installFailed.title": "Aktualizacja nie powiodła się", + "desktop.updater.installFailed.message": "Nie udało się zainstalować aktualizacji", + + "desktop.cli.installed.title": "CLI zainstalowane", + "desktop.cli.installed.message": + "CLI zainstalowane w {{path}}\n\nUruchom ponownie terminal, aby użyć polecenia 'opencode'.", + "desktop.cli.failed.title": "Instalacja nie powiodła się", + "desktop.cli.failed.message": "Nie udało się zainstalować CLI: {{error}}", +} diff --git a/packages/desktop-electron/src/renderer/i18n/ru.ts b/packages/desktop-electron/src/renderer/i18n/ru.ts new file mode 100644 index 00000000000..8e09cc45b49 --- /dev/null +++ b/packages/desktop-electron/src/renderer/i18n/ru.ts @@ -0,0 +1,27 @@ +export const dict = { + "desktop.menu.checkForUpdates": "Проверить обновления...", + "desktop.menu.installCli": "Установить CLI...", + "desktop.menu.reloadWebview": "Перезагрузить Webview", + "desktop.menu.restart": "Перезапустить", + + "desktop.dialog.chooseFolder": "Выберите папку", + "desktop.dialog.chooseFile": "Выберите файл", + "desktop.dialog.saveFile": "Сохранить файл", + + "desktop.updater.checkFailed.title": "Не удалось проверить обновления", + "desktop.updater.checkFailed.message": "Не удалось проверить обновления", + "desktop.updater.none.title": "Обновлений нет", + "desktop.updater.none.message": "Вы уже используете последнюю версию OpenCode", + "desktop.updater.downloadFailed.title": "Обновление не удалось", + "desktop.updater.downloadFailed.message": "Не удалось скачать обновление", + "desktop.updater.downloaded.title": "Обновление загружено", + "desktop.updater.downloaded.prompt": "Версия OpenCode {{version}} загружена. Хотите установить и перезапустить?", + "desktop.updater.installFailed.title": "Обновление не удалось", + "desktop.updater.installFailed.message": "Не удалось установить обновление", + + "desktop.cli.installed.title": "CLI установлен", + "desktop.cli.installed.message": + "CLI установлен в {{path}}\n\nПерезапустите терминал, чтобы использовать команду 'opencode'.", + "desktop.cli.failed.title": "Ошибка установки", + "desktop.cli.failed.message": "Не удалось установить CLI: {{error}}", +} diff --git a/packages/desktop-electron/src/renderer/i18n/zh.ts b/packages/desktop-electron/src/renderer/i18n/zh.ts new file mode 100644 index 00000000000..aeb3a54e03e --- /dev/null +++ b/packages/desktop-electron/src/renderer/i18n/zh.ts @@ -0,0 +1,26 @@ +export const dict = { + "desktop.menu.checkForUpdates": "检查更新...", + "desktop.menu.installCli": "安装 CLI...", + "desktop.menu.reloadWebview": "重新加载 Webview", + "desktop.menu.restart": "重启", + + "desktop.dialog.chooseFolder": "选择文件夹", + "desktop.dialog.chooseFile": "选择文件", + "desktop.dialog.saveFile": "保存文件", + + "desktop.updater.checkFailed.title": "检查更新失败", + "desktop.updater.checkFailed.message": "无法检查更新", + "desktop.updater.none.title": "没有可用更新", + "desktop.updater.none.message": "你已经在使用最新版本的 OpenCode", + "desktop.updater.downloadFailed.title": "更新失败", + "desktop.updater.downloadFailed.message": "无法下载更新", + "desktop.updater.downloaded.title": "更新已下载", + "desktop.updater.downloaded.prompt": "已下载 OpenCode {{version}} 版本,是否安装并重启?", + "desktop.updater.installFailed.title": "更新失败", + "desktop.updater.installFailed.message": "无法安装更新", + + "desktop.cli.installed.title": "CLI 已安装", + "desktop.cli.installed.message": "CLI 已安装到 {{path}}\n\n重启终端以使用 'opencode' 命令。", + "desktop.cli.failed.title": "安装失败", + "desktop.cli.failed.message": "无法安装 CLI: {{error}}", +} diff --git a/packages/desktop-electron/src/renderer/i18n/zht.ts b/packages/desktop-electron/src/renderer/i18n/zht.ts new file mode 100644 index 00000000000..7fd677aca44 --- /dev/null +++ b/packages/desktop-electron/src/renderer/i18n/zht.ts @@ -0,0 +1,26 @@ +export const dict = { + "desktop.menu.checkForUpdates": "檢查更新...", + "desktop.menu.installCli": "安裝 CLI...", + "desktop.menu.reloadWebview": "重新載入 Webview", + "desktop.menu.restart": "重新啟動", + + "desktop.dialog.chooseFolder": "選擇資料夾", + "desktop.dialog.chooseFile": "選擇檔案", + "desktop.dialog.saveFile": "儲存檔案", + + "desktop.updater.checkFailed.title": "檢查更新失敗", + "desktop.updater.checkFailed.message": "無法檢查更新", + "desktop.updater.none.title": "沒有可用更新", + "desktop.updater.none.message": "你已在使用最新版的 OpenCode", + "desktop.updater.downloadFailed.title": "更新失敗", + "desktop.updater.downloadFailed.message": "無法下載更新", + "desktop.updater.downloaded.title": "更新已下載", + "desktop.updater.downloaded.prompt": "已下載 OpenCode {{version}} 版本,是否安裝並重新啟動?", + "desktop.updater.installFailed.title": "更新失敗", + "desktop.updater.installFailed.message": "無法安裝更新", + + "desktop.cli.installed.title": "CLI 已安裝", + "desktop.cli.installed.message": "CLI 已安裝到 {{path}}\n\n重新啟動終端機以使用 'opencode' 命令。", + "desktop.cli.failed.title": "安裝失敗", + "desktop.cli.failed.message": "無法安裝 CLI: {{error}}", +} diff --git a/packages/desktop-electron/src/renderer/index.html b/packages/desktop-electron/src/renderer/index.html new file mode 100644 index 00000000000..17564081969 --- /dev/null +++ b/packages/desktop-electron/src/renderer/index.html @@ -0,0 +1,23 @@ + + + + + + OpenCode + + + + + + + + + + + + + +
+ + + diff --git a/packages/desktop-electron/src/renderer/index.tsx b/packages/desktop-electron/src/renderer/index.tsx new file mode 100644 index 00000000000..b5193d626bd --- /dev/null +++ b/packages/desktop-electron/src/renderer/index.tsx @@ -0,0 +1,312 @@ +// @refresh reload + +import { + AppBaseProviders, + AppInterface, + handleNotificationClick, + type Platform, + PlatformProvider, + ServerConnection, + useCommand, +} from "@opencode-ai/app" +import { Splash } from "@opencode-ai/ui/logo" +import type { AsyncStorage } from "@solid-primitives/storage" +import { type Accessor, createResource, type JSX, onCleanup, onMount, Show } from "solid-js" +import { render } from "solid-js/web" +import { MemoryRouter } from "@solidjs/router" +import pkg from "../../package.json" +import { initI18n, t } from "./i18n" +import { UPDATER_ENABLED } from "./updater" +import { webviewZoom } from "./webview-zoom" +import "./styles.css" +import type { ServerReadyData } from "../preload/types" + +const root = document.getElementById("root") +if (import.meta.env.DEV && !(root instanceof HTMLElement)) { + throw new Error(t("error.dev.rootNotFound")) +} + +void initI18n() + +const deepLinkEvent = "opencode:deep-link" + +const emitDeepLinks = (urls: string[]) => { + if (urls.length === 0) return + window.__OPENCODE__ ??= {} + const pending = window.__OPENCODE__.deepLinks ?? [] + window.__OPENCODE__.deepLinks = [...pending, ...urls] + window.dispatchEvent(new CustomEvent(deepLinkEvent, { detail: { urls } })) +} + +const listenForDeepLinks = () => { + const startUrls = window.__OPENCODE__?.deepLinks ?? [] + if (startUrls.length) emitDeepLinks(startUrls) + return window.api.onDeepLink((urls) => emitDeepLinks(urls)) +} + +const createPlatform = (): Platform => { + const os = (() => { + const ua = navigator.userAgent + if (ua.includes("Mac")) return "macos" + if (ua.includes("Windows")) return "windows" + if (ua.includes("Linux")) return "linux" + return undefined + })() + + const wslHome = async () => { + if (os !== "windows" || !window.__OPENCODE__?.wsl) return undefined + return window.api.wslPath("~", "windows").catch(() => undefined) + } + + const handleWslPicker = async (result: T | null): Promise => { + if (!result || !window.__OPENCODE__?.wsl) return result + if (Array.isArray(result)) { + return Promise.all(result.map((path) => window.api.wslPath(path, "linux").catch(() => path))) as any + } + return window.api.wslPath(result, "linux").catch(() => result) as any + } + + const storage = (() => { + const cache = new Map() + + const createStorage = (name: string) => { + const api: AsyncStorage = { + getItem: (key: string) => window.api.storeGet(name, key), + setItem: (key: string, value: string) => window.api.storeSet(name, key, value), + removeItem: (key: string) => window.api.storeDelete(name, key), + clear: () => window.api.storeClear(name), + key: async (index: number) => (await window.api.storeKeys(name))[index], + getLength: () => window.api.storeLength(name), + get length() { + return api.getLength() + }, + } + return api + } + + return (name = "default.dat") => { + const cached = cache.get(name) + if (cached) return cached + const api = createStorage(name) + cache.set(name, api) + return api + } + })() + + return { + platform: "desktop", + os, + version: pkg.version, + + async openDirectoryPickerDialog(opts) { + const defaultPath = await wslHome() + const result = await window.api.openDirectoryPicker({ + multiple: opts?.multiple ?? false, + title: opts?.title ?? t("desktop.dialog.chooseFolder"), + defaultPath, + }) + return await handleWslPicker(result) + }, + + async openFilePickerDialog(opts) { + const result = await window.api.openFilePicker({ + multiple: opts?.multiple ?? false, + title: opts?.title ?? t("desktop.dialog.chooseFile"), + }) + return handleWslPicker(result) + }, + + async saveFilePickerDialog(opts) { + const result = await window.api.saveFilePicker({ + title: opts?.title ?? t("desktop.dialog.saveFile"), + defaultPath: opts?.defaultPath, + }) + return handleWslPicker(result) + }, + + openLink(url: string) { + window.api.openLink(url) + }, + async openPath(path: string, app?: string) { + if (os === "windows") { + const resolvedApp = app ? await window.api.resolveAppPath(app).catch(() => null) : null + const resolvedPath = await (async () => { + if (window.__OPENCODE__?.wsl) { + const converted = await window.api.wslPath(path, "windows").catch(() => null) + if (converted) return converted + } + return path + })() + return window.api.openPath(resolvedPath, resolvedApp ?? undefined) + } + return window.api.openPath(path, app) + }, + + back() { + window.history.back() + }, + + forward() { + window.history.forward() + }, + + storage, + + checkUpdate: async () => { + if (!UPDATER_ENABLED) return { updateAvailable: false } + return window.api.checkUpdate() + }, + + update: async () => { + if (!UPDATER_ENABLED) return + await window.api.installUpdate() + }, + + restart: async () => { + await window.api.killSidecar().catch(() => undefined) + window.api.relaunch() + }, + + notify: async (title, description, href) => { + const focused = await window.api.getWindowFocused().catch(() => document.hasFocus()) + if (focused) return + + const notification = new Notification(title, { + body: description ?? "", + icon: "https://opencode.ai/favicon-96x96-v3.png", + }) + notification.onclick = () => { + void window.api.showWindow() + void window.api.setWindowFocus() + handleNotificationClick(href) + notification.close() + } + }, + + fetch: (input, init) => { + if (input instanceof Request) return fetch(input) + return fetch(input, init) + }, + + getWslEnabled: async () => { + const next = await window.api.getWslConfig().catch(() => null) + if (next) return next.enabled + return window.__OPENCODE__!.wsl ?? false + }, + + setWslEnabled: async (enabled) => { + await window.api.setWslConfig({ enabled }) + }, + + getDefaultServerUrl: async () => { + return window.api.getDefaultServerUrl().catch(() => null) + }, + + setDefaultServerUrl: async (url: string | null) => { + await window.api.setDefaultServerUrl(url) + }, + + getDisplayBackend: async () => { + return window.api.getDisplayBackend().catch(() => null) + }, + + setDisplayBackend: async (backend) => { + await window.api.setDisplayBackend(backend) + }, + + parseMarkdown: (markdown: string) => window.api.parseMarkdownCommand(markdown), + + webviewZoom, + + checkAppExists: async (appName: string) => { + return window.api.checkAppExists(appName) + }, + + async readClipboardImage() { + const image = await window.api.readClipboardImage().catch(() => null) + if (!image) return null + const blob = new Blob([image.buffer], { type: "image/png" }) + return new File([blob], `pasted-image-${Date.now()}.png`, { type: "image/png" }) + }, + } +} + +let menuTrigger = null as null | ((id: string) => void) +window.api.onMenuCommand((id) => { + menuTrigger?.(id) +}) +listenForDeepLinks() + +render(() => { + const platform = createPlatform() + + function handleClick(e: MouseEvent) { + const link = (e.target as HTMLElement).closest("a.external-link") as HTMLAnchorElement | null + if (link?.href) { + e.preventDefault() + platform.openLink(link.href) + } + } + + onMount(() => { + document.addEventListener("click", handleClick) + onCleanup(() => { + document.removeEventListener("click", handleClick) + }) + }) + + return ( + + + + {(data) => { + const server: ServerConnection.Sidecar = { + displayName: "Local Server", + type: "sidecar", + variant: "base", + http: { + url: data().url, + username: "opencode", + password: data().password ?? undefined, + }, + } + + function Inner() { + const cmd = useCommand() + + menuTrigger = (id) => cmd.trigger(id) + + return null + } + + return ( + + + + ) + }} + + + + ) +}, root!) + +// Gate component that waits for the server to be ready +function ServerGate(props: { children: (data: Accessor) => JSX.Element }) { + const [serverData] = createResource(() => window.api.awaitInitialization(() => undefined)) + console.log({ serverData }) + if (serverData.state === "errored") throw serverData.error + + return ( + + +
+ } + > + {(data) => props.children(data)} + + ) +} diff --git a/packages/desktop-electron/src/renderer/loading.html b/packages/desktop-electron/src/renderer/loading.html new file mode 100644 index 00000000000..8def243b494 --- /dev/null +++ b/packages/desktop-electron/src/renderer/loading.html @@ -0,0 +1,23 @@ + + + + + + OpenCode + + + + + + + + + + + + + +
+ + + diff --git a/packages/desktop-electron/src/renderer/loading.tsx b/packages/desktop-electron/src/renderer/loading.tsx new file mode 100644 index 00000000000..16595035290 --- /dev/null +++ b/packages/desktop-electron/src/renderer/loading.tsx @@ -0,0 +1,80 @@ +import { render } from "solid-js/web" +import { MetaProvider } from "@solidjs/meta" +import "@opencode-ai/app/index.css" +import { Font } from "@opencode-ai/ui/font" +import { Splash } from "@opencode-ai/ui/logo" +import { Progress } from "@opencode-ai/ui/progress" +import "./styles.css" +import { createEffect, createMemo, createSignal, onCleanup, onMount } from "solid-js" +import type { InitStep, SqliteMigrationProgress } from "../preload/types" + +const root = document.getElementById("root")! +const lines = ["Just a moment...", "Migrating your database", "This may take a couple of minutes"] +const delays = [3000, 9000] + +render(() => { + const [step, setStep] = createSignal(null) + const [line, setLine] = createSignal(0) + const [percent, setPercent] = createSignal(0) + + const phase = createMemo(() => step()?.phase) + + const value = createMemo(() => { + if (phase() === "done") return 100 + return Math.max(25, Math.min(100, percent())) + }) + + window.api.awaitInitialization((next) => setStep(next as InitStep)).catch(() => undefined) + + onMount(() => { + setLine(0) + setPercent(0) + + const timers = delays.map((ms, i) => setTimeout(() => setLine(i + 1), ms)) + + const listener = window.api.onSqliteMigrationProgress((progress: SqliteMigrationProgress) => { + if (progress.type === "InProgress") setPercent(Math.max(0, Math.min(100, progress.value))) + if (progress.type === "Done") setPercent(100) + }) + + onCleanup(() => { + listener() + timers.forEach(clearTimeout) + }) + }) + + createEffect(() => { + if (phase() !== "done") return + + const timer = setTimeout(() => window.api.loadingWindowComplete(), 1000) + onCleanup(() => clearTimeout(timer)) + }) + + const status = createMemo(() => { + if (phase() === "done") return "All done" + if (phase() === "sqlite_waiting") return lines[line()] + return "Just a moment..." + }) + + return ( + +
+ +
+ +
+ + {status()} + + `${Math.round(value)}%`} + /> +
+
+
+
+ ) +}, root) diff --git a/packages/desktop-electron/src/renderer/styles.css b/packages/desktop-electron/src/renderer/styles.css new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/desktop-electron/src/renderer/updater.ts b/packages/desktop-electron/src/renderer/updater.ts new file mode 100644 index 00000000000..fe9e601db8e --- /dev/null +++ b/packages/desktop-electron/src/renderer/updater.ts @@ -0,0 +1,14 @@ +import { initI18n, t } from "./i18n" + +export const UPDATER_ENABLED = window.__OPENCODE__?.updaterEnabled ?? false + +export async function runUpdater({ alertOnFail }: { alertOnFail: boolean }) { + await initI18n() + try { + await window.api.runUpdater(alertOnFail) + } catch { + if (alertOnFail) { + window.alert(t("desktop.updater.checkFailed.message")) + } + } +} diff --git a/packages/desktop-electron/src/renderer/webview-zoom.ts b/packages/desktop-electron/src/renderer/webview-zoom.ts new file mode 100644 index 00000000000..9c0a3a3a35f --- /dev/null +++ b/packages/desktop-electron/src/renderer/webview-zoom.ts @@ -0,0 +1,38 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import { createSignal } from "solid-js" + +const OS_NAME = (() => { + if (navigator.userAgent.includes("Mac")) return "macos" + if (navigator.userAgent.includes("Windows")) return "windows" + if (navigator.userAgent.includes("Linux")) return "linux" + return "unknown" +})() + +const [webviewZoom, setWebviewZoom] = createSignal(1) + +const MAX_ZOOM_LEVEL = 10 +const MIN_ZOOM_LEVEL = 0.2 + +const clamp = (value: number) => Math.min(Math.max(value, MIN_ZOOM_LEVEL), MAX_ZOOM_LEVEL) + +const applyZoom = (next: number) => { + setWebviewZoom(next) + void window.api.setZoomFactor(next) +} + +window.addEventListener("keydown", (event) => { + if (!(OS_NAME === "macos" ? event.metaKey : event.ctrlKey)) return + + let newZoom = webviewZoom() + + if (event.key === "-") newZoom -= 0.2 + if (event.key === "=" || event.key === "+") newZoom += 0.2 + if (event.key === "0") newZoom = 1 + + applyZoom(clamp(newZoom)) +}) + +export { webviewZoom } diff --git a/packages/desktop-electron/sst-env.d.ts b/packages/desktop-electron/sst-env.d.ts new file mode 100644 index 00000000000..64441936d7a --- /dev/null +++ b/packages/desktop-electron/sst-env.d.ts @@ -0,0 +1,10 @@ +/* This file is auto-generated by SST. Do not edit. */ +/* tslint:disable */ +/* eslint-disable */ +/* deno-fmt-ignore-file */ +/* biome-ignore-all lint: auto-generated */ + +/// + +import "sst" +export {} \ No newline at end of file diff --git a/packages/desktop-electron/tsconfig.json b/packages/desktop-electron/tsconfig.json new file mode 100644 index 00000000000..160f6c3fd20 --- /dev/null +++ b/packages/desktop-electron/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "allowJs": true, + "resolveJsonModule": true, + "strict": true, + "isolatedModules": true, + "noEmit": true, + "emitDeclarationOnly": false, + "outDir": "node_modules/.ts-dist", + "types": ["vite/client", "node", "electron"] + }, + "references": [{ "path": "../app" }], + "include": ["src", "package.json"] +} diff --git a/packages/desktop/package.json b/packages/desktop/package.json index 4fe999e700a..d6a95c2ab63 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/desktop", "private": true, - "version": "1.2.15", + "version": "1.2.16", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/desktop/scripts/finalize-latest-json.ts b/packages/desktop/scripts/finalize-latest-json.ts index a6fe02a37aa..5445e0640d0 100644 --- a/packages/desktop/scripts/finalize-latest-json.ts +++ b/packages/desktop/scripts/finalize-latest-json.ts @@ -20,6 +20,9 @@ if (!repo) throw new Error("GH_REPO is required") const releaseId = process.env.OPENCODE_RELEASE if (!releaseId) throw new Error("OPENCODE_RELEASE is required") +const version = process.env.OPENCODE_VERSION +if (!releaseId) throw new Error("OPENCODE_VERSION is required") + const token = process.env.GH_TOKEN ?? process.env.GITHUB_TOKEN if (!token) throw new Error("GH_TOKEN or GITHUB_TOKEN is required") @@ -39,7 +42,6 @@ if (!releaseRes.ok) { type Asset = { name: string url: string - browser_download_url: string } type Release = { @@ -89,7 +91,7 @@ const entries: Record = {} const add = (key: string, asset: Asset, signature: string) => { if (entries[key]) return entries[key] = { - url: asset.browser_download_url, + url: `https://github.com/${repo}/releases/download/v${version}/${asset.name}`, signature, } } diff --git a/packages/enterprise/package.json b/packages/enterprise/package.json index cc46f7530fb..3a24ce16451 100644 --- a/packages/enterprise/package.json +++ b/packages/enterprise/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/enterprise", - "version": "1.2.15", + "version": "1.2.16", "private": true, "type": "module", "license": "MIT", diff --git a/packages/enterprise/src/routes/share/[shareID].tsx b/packages/enterprise/src/routes/share/[shareID].tsx index ada543b7dc0..007b4c268df 100644 --- a/packages/enterprise/src/routes/share/[shareID].tsx +++ b/packages/enterprise/src/routes/share/[shareID].tsx @@ -23,7 +23,6 @@ import { MessageNav } from "@opencode-ai/ui/message-nav" import { preloadMultiFileDiff, PreloadMultiFileDiffResult } from "@pierre/diffs/ssr" import { FileSSR } from "@opencode-ai/ui/file-ssr" import { clientOnly } from "@solidjs/start" -import { type IconName } from "@opencode-ai/ui/icons/provider" import { Meta, Title } from "@solidjs/meta" import { Base64 } from "js-base64" @@ -268,10 +267,9 @@ export default function () {
- + + +
{model()?.name ?? modelID()}
diff --git a/packages/enterprise/sst-env.d.ts b/packages/enterprise/sst-env.d.ts index edff904e015..23ae6e44bfb 100644 --- a/packages/enterprise/sst-env.d.ts +++ b/packages/enterprise/sst-env.d.ts @@ -119,10 +119,6 @@ declare module "sst" { "type": "sst.cloudflare.StaticSite" "url": string } - "ZEN_BLACK_LIMITS": { - "type": "sst.sst.Secret" - "value": string - } "ZEN_BLACK_PRICE": { "plan100": string "plan20": string @@ -130,7 +126,7 @@ declare module "sst" { "product": string "type": "sst.sst.Linkable" } - "ZEN_LITE_LIMITS": { + "ZEN_LIMITS": { "type": "sst.sst.Secret" "value": string } diff --git a/packages/extensions/zed/extension.toml b/packages/extensions/zed/extension.toml index e9f246af890..035b60ba7db 100644 --- a/packages/extensions/zed/extension.toml +++ b/packages/extensions/zed/extension.toml @@ -1,7 +1,7 @@ id = "opencode" name = "OpenCode" description = "The open source coding agent." -version = "1.2.15" +version = "1.2.16" schema_version = 1 authors = ["Anomaly"] repository = "https://github.com/anomalyco/opencode" @@ -11,26 +11,26 @@ name = "OpenCode" icon = "./icons/opencode.svg" [agent_servers.opencode.targets.darwin-aarch64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.15/opencode-darwin-arm64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.16/opencode-darwin-arm64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.darwin-x86_64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.15/opencode-darwin-x64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.16/opencode-darwin-x64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-aarch64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.15/opencode-linux-arm64.tar.gz" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.16/opencode-linux-arm64.tar.gz" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-x86_64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.15/opencode-linux-x64.tar.gz" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.16/opencode-linux-x64.tar.gz" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.windows-x86_64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.15/opencode-windows-x64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.16/opencode-windows-x64.zip" cmd = "./opencode.exe" args = ["acp"] diff --git a/packages/function/package.json b/packages/function/package.json index 63e50b99211..223531cf0e3 100644 --- a/packages/function/package.json +++ b/packages/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/function", - "version": "1.2.15", + "version": "1.2.16", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/function/sst-env.d.ts b/packages/function/sst-env.d.ts index edff904e015..23ae6e44bfb 100644 --- a/packages/function/sst-env.d.ts +++ b/packages/function/sst-env.d.ts @@ -119,10 +119,6 @@ declare module "sst" { "type": "sst.cloudflare.StaticSite" "url": string } - "ZEN_BLACK_LIMITS": { - "type": "sst.sst.Secret" - "value": string - } "ZEN_BLACK_PRICE": { "plan100": string "plan20": string @@ -130,7 +126,7 @@ declare module "sst" { "product": string "type": "sst.sst.Linkable" } - "ZEN_LITE_LIMITS": { + "ZEN_LIMITS": { "type": "sst.sst.Secret" "value": string } diff --git a/packages/opencode/migration/20260227213759_add_session_workspace_id/migration.sql b/packages/opencode/migration/20260227213759_add_session_workspace_id/migration.sql new file mode 100644 index 00000000000..f5488af2180 --- /dev/null +++ b/packages/opencode/migration/20260227213759_add_session_workspace_id/migration.sql @@ -0,0 +1,2 @@ +ALTER TABLE `session` ADD `workspace_id` text;--> statement-breakpoint +CREATE INDEX `session_workspace_idx` ON `session` (`workspace_id`); \ No newline at end of file diff --git a/packages/opencode/migration/20260227213759_add_session_workspace_id/snapshot.json b/packages/opencode/migration/20260227213759_add_session_workspace_id/snapshot.json new file mode 100644 index 00000000000..8cd94d00527 --- /dev/null +++ b/packages/opencode/migration/20260227213759_add_session_workspace_id/snapshot.json @@ -0,0 +1,983 @@ +{ + "version": "7", + "dialect": "sqlite", + "id": "572fb732-56f4-4b1e-b981-77152c9980dd", + "prevIds": ["1f1dbf2d-bf66-4b25-8af4-4ba7633b7e40"], + "ddl": [ + { + "name": "workspace", + "entityType": "tables" + }, + { + "name": "control_account", + "entityType": "tables" + }, + { + "name": "project", + "entityType": "tables" + }, + { + "name": "message", + "entityType": "tables" + }, + { + "name": "part", + "entityType": "tables" + }, + { + "name": "permission", + "entityType": "tables" + }, + { + "name": "session", + "entityType": "tables" + }, + { + "name": "todo", + "entityType": "tables" + }, + { + "name": "session_share", + "entityType": "tables" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "branch", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "project_id", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "config", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "email", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "url", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "access_token", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "refresh_token", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "token_expiry", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "active", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "worktree", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "vcs", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "name", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "icon_url", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "icon_color", + "entityType": "columns", + "table": "project" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "project" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "project" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_initialized", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "sandboxes", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "commands", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "message" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "session_id", + "entityType": "columns", + "table": "message" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "message" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "message" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "data", + "entityType": "columns", + "table": "message" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "part" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "message_id", + "entityType": "columns", + "table": "part" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "session_id", + "entityType": "columns", + "table": "part" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "part" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "part" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "data", + "entityType": "columns", + "table": "part" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "project_id", + "entityType": "columns", + "table": "permission" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "permission" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "permission" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "data", + "entityType": "columns", + "table": "permission" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "project_id", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "parent_id", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "slug", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "directory", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "title", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "version", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "share_url", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "summary_additions", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "summary_deletions", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "summary_files", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "summary_diffs", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "revert", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "permission", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_compacting", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_archived", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "session_id", + "entityType": "columns", + "table": "todo" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "content", + "entityType": "columns", + "table": "todo" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "status", + "entityType": "columns", + "table": "todo" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "priority", + "entityType": "columns", + "table": "todo" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "position", + "entityType": "columns", + "table": "todo" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "todo" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "todo" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "session_id", + "entityType": "columns", + "table": "session_share" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "session_share" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "secret", + "entityType": "columns", + "table": "session_share" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "url", + "entityType": "columns", + "table": "session_share" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "session_share" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "session_share" + }, + { + "columns": ["project_id"], + "tableTo": "project", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_workspace_project_id_project_id_fk", + "entityType": "fks", + "table": "workspace" + }, + { + "columns": ["session_id"], + "tableTo": "session", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_message_session_id_session_id_fk", + "entityType": "fks", + "table": "message" + }, + { + "columns": ["message_id"], + "tableTo": "message", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_part_message_id_message_id_fk", + "entityType": "fks", + "table": "part" + }, + { + "columns": ["project_id"], + "tableTo": "project", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_permission_project_id_project_id_fk", + "entityType": "fks", + "table": "permission" + }, + { + "columns": ["project_id"], + "tableTo": "project", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_session_project_id_project_id_fk", + "entityType": "fks", + "table": "session" + }, + { + "columns": ["session_id"], + "tableTo": "session", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_todo_session_id_session_id_fk", + "entityType": "fks", + "table": "todo" + }, + { + "columns": ["session_id"], + "tableTo": "session", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_session_share_session_id_session_id_fk", + "entityType": "fks", + "table": "session_share" + }, + { + "columns": ["email", "url"], + "nameExplicit": false, + "name": "control_account_pk", + "entityType": "pks", + "table": "control_account" + }, + { + "columns": ["session_id", "position"], + "nameExplicit": false, + "name": "todo_pk", + "entityType": "pks", + "table": "todo" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "workspace_pk", + "table": "workspace", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "project_pk", + "table": "project", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "message_pk", + "table": "message", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "part_pk", + "table": "part", + "entityType": "pks" + }, + { + "columns": ["project_id"], + "nameExplicit": false, + "name": "permission_pk", + "table": "permission", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "session_pk", + "table": "session", + "entityType": "pks" + }, + { + "columns": ["session_id"], + "nameExplicit": false, + "name": "session_share_pk", + "table": "session_share", + "entityType": "pks" + }, + { + "columns": [ + { + "value": "session_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "message_session_idx", + "entityType": "indexes", + "table": "message" + }, + { + "columns": [ + { + "value": "message_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "part_message_idx", + "entityType": "indexes", + "table": "part" + }, + { + "columns": [ + { + "value": "session_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "part_session_idx", + "entityType": "indexes", + "table": "part" + }, + { + "columns": [ + { + "value": "project_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "session_project_idx", + "entityType": "indexes", + "table": "session" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "session_workspace_idx", + "entityType": "indexes", + "table": "session" + }, + { + "columns": [ + { + "value": "parent_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "session_parent_idx", + "entityType": "indexes", + "table": "session" + }, + { + "columns": [ + { + "value": "session_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "todo_session_idx", + "entityType": "indexes", + "table": "todo" + } + ], + "renames": [] +} diff --git a/packages/opencode/migration/20260303231226_add_workspace_fields/migration.sql b/packages/opencode/migration/20260303231226_add_workspace_fields/migration.sql new file mode 100644 index 00000000000..185de59133b --- /dev/null +++ b/packages/opencode/migration/20260303231226_add_workspace_fields/migration.sql @@ -0,0 +1,5 @@ +ALTER TABLE `workspace` ADD `type` text NOT NULL;--> statement-breakpoint +ALTER TABLE `workspace` ADD `name` text;--> statement-breakpoint +ALTER TABLE `workspace` ADD `directory` text;--> statement-breakpoint +ALTER TABLE `workspace` ADD `extra` text;--> statement-breakpoint +ALTER TABLE `workspace` DROP COLUMN `config`; \ No newline at end of file diff --git a/packages/opencode/migration/20260303231226_add_workspace_fields/snapshot.json b/packages/opencode/migration/20260303231226_add_workspace_fields/snapshot.json new file mode 100644 index 00000000000..4fe320a2cc3 --- /dev/null +++ b/packages/opencode/migration/20260303231226_add_workspace_fields/snapshot.json @@ -0,0 +1,1013 @@ +{ + "version": "7", + "dialect": "sqlite", + "id": "4ec9de62-88a7-4bec-91cc-0a759e84db21", + "prevIds": ["572fb732-56f4-4b1e-b981-77152c9980dd"], + "ddl": [ + { + "name": "workspace", + "entityType": "tables" + }, + { + "name": "control_account", + "entityType": "tables" + }, + { + "name": "project", + "entityType": "tables" + }, + { + "name": "message", + "entityType": "tables" + }, + { + "name": "part", + "entityType": "tables" + }, + { + "name": "permission", + "entityType": "tables" + }, + { + "name": "session", + "entityType": "tables" + }, + { + "name": "todo", + "entityType": "tables" + }, + { + "name": "session_share", + "entityType": "tables" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "type", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "branch", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "name", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "directory", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "extra", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "project_id", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "email", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "url", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "access_token", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "refresh_token", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "token_expiry", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "active", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "worktree", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "vcs", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "name", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "icon_url", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "icon_color", + "entityType": "columns", + "table": "project" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "project" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "project" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_initialized", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "sandboxes", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "commands", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "message" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "session_id", + "entityType": "columns", + "table": "message" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "message" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "message" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "data", + "entityType": "columns", + "table": "message" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "part" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "message_id", + "entityType": "columns", + "table": "part" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "session_id", + "entityType": "columns", + "table": "part" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "part" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "part" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "data", + "entityType": "columns", + "table": "part" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "project_id", + "entityType": "columns", + "table": "permission" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "permission" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "permission" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "data", + "entityType": "columns", + "table": "permission" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "project_id", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "parent_id", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "slug", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "directory", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "title", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "version", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "share_url", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "summary_additions", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "summary_deletions", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "summary_files", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "summary_diffs", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "revert", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "permission", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_compacting", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_archived", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "session_id", + "entityType": "columns", + "table": "todo" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "content", + "entityType": "columns", + "table": "todo" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "status", + "entityType": "columns", + "table": "todo" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "priority", + "entityType": "columns", + "table": "todo" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "position", + "entityType": "columns", + "table": "todo" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "todo" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "todo" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "session_id", + "entityType": "columns", + "table": "session_share" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "session_share" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "secret", + "entityType": "columns", + "table": "session_share" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "url", + "entityType": "columns", + "table": "session_share" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "session_share" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "session_share" + }, + { + "columns": ["project_id"], + "tableTo": "project", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_workspace_project_id_project_id_fk", + "entityType": "fks", + "table": "workspace" + }, + { + "columns": ["session_id"], + "tableTo": "session", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_message_session_id_session_id_fk", + "entityType": "fks", + "table": "message" + }, + { + "columns": ["message_id"], + "tableTo": "message", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_part_message_id_message_id_fk", + "entityType": "fks", + "table": "part" + }, + { + "columns": ["project_id"], + "tableTo": "project", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_permission_project_id_project_id_fk", + "entityType": "fks", + "table": "permission" + }, + { + "columns": ["project_id"], + "tableTo": "project", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_session_project_id_project_id_fk", + "entityType": "fks", + "table": "session" + }, + { + "columns": ["session_id"], + "tableTo": "session", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_todo_session_id_session_id_fk", + "entityType": "fks", + "table": "todo" + }, + { + "columns": ["session_id"], + "tableTo": "session", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_session_share_session_id_session_id_fk", + "entityType": "fks", + "table": "session_share" + }, + { + "columns": ["email", "url"], + "nameExplicit": false, + "name": "control_account_pk", + "entityType": "pks", + "table": "control_account" + }, + { + "columns": ["session_id", "position"], + "nameExplicit": false, + "name": "todo_pk", + "entityType": "pks", + "table": "todo" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "workspace_pk", + "table": "workspace", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "project_pk", + "table": "project", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "message_pk", + "table": "message", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "part_pk", + "table": "part", + "entityType": "pks" + }, + { + "columns": ["project_id"], + "nameExplicit": false, + "name": "permission_pk", + "table": "permission", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "session_pk", + "table": "session", + "entityType": "pks" + }, + { + "columns": ["session_id"], + "nameExplicit": false, + "name": "session_share_pk", + "table": "session_share", + "entityType": "pks" + }, + { + "columns": [ + { + "value": "session_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "message_session_idx", + "entityType": "indexes", + "table": "message" + }, + { + "columns": [ + { + "value": "message_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "part_message_idx", + "entityType": "indexes", + "table": "part" + }, + { + "columns": [ + { + "value": "session_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "part_session_idx", + "entityType": "indexes", + "table": "part" + }, + { + "columns": [ + { + "value": "project_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "session_project_idx", + "entityType": "indexes", + "table": "session" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "session_workspace_idx", + "entityType": "indexes", + "table": "session" + }, + { + "columns": [ + { + "value": "parent_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "session_parent_idx", + "entityType": "indexes", + "table": "session" + }, + { + "columns": [ + { + "value": "session_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "todo_session_idx", + "entityType": "indexes", + "table": "todo" + } + ], + "renames": [] +} diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 9252468153b..e2bb6b81843 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.2.15", + "version": "1.2.16", "name": "opencode", "type": "module", "license": "MIT", @@ -89,8 +89,8 @@ "@opencode-ai/sdk": "workspace:*", "@opencode-ai/util": "workspace:*", "@openrouter/ai-sdk-provider": "1.5.4", - "@opentui/core": "0.1.81", - "@opentui/solid": "0.1.81", + "@opentui/core": "0.1.86", + "@opentui/solid": "0.1.86", "@parcel/watcher": "2.5.1", "@pierre/diffs": "catalog:", "@solid-primitives/event-bus": "1.1.2", diff --git a/packages/opencode/src/auth/index.ts b/packages/opencode/src/auth/index.ts index 776cc99b444..302a74f88c6 100644 --- a/packages/opencode/src/auth/index.ts +++ b/packages/opencode/src/auth/index.ts @@ -46,6 +46,7 @@ export namespace Auth { const data = await Filesystem.readJson>(filepath).catch(() => ({})) return Object.entries(data).reduce( (acc, [key, value]) => { + if (key.startsWith("$")) return acc const parsed = Info.safeParse(value) if (!parsed.success) return acc acc[key] = parsed.data @@ -56,13 +57,18 @@ export namespace Auth { } export async function set(key: string, info: Info) { + const normalized = key.replace(/\/+$/, "") const data = await all() - await Filesystem.writeJson(filepath, { ...data, [key]: info }, 0o600) + if (normalized !== key) delete data[key] + delete data[normalized + "/"] + await Filesystem.writeJson(filepath, { ...data, [normalized]: info }, 0o600) } export async function remove(key: string) { + const normalized = key.replace(/\/+$/, "") const data = await all() delete data[key] + delete data[normalized] await Filesystem.writeJson(filepath, data, 0o600) } } diff --git a/packages/opencode/src/cli/cmd/auth.ts b/packages/opencode/src/cli/cmd/auth.ts index 95635916413..f134aba3a7f 100644 --- a/packages/opencode/src/cli/cmd/auth.ts +++ b/packages/opencode/src/cli/cmd/auth.ts @@ -263,7 +263,8 @@ export const AuthLoginCommand = cmd({ UI.empty() prompts.intro("Add credential") if (args.url) { - const wellknown = await fetch(`${args.url}/.well-known/opencode`).then((x) => x.json() as any) + const url = args.url.replace(/\/+$/, "") + const wellknown = await fetch(`${url}/.well-known/opencode`).then((x) => x.json() as any) prompts.log.info(`Running \`${wellknown.auth.command.join(" ")}\``) const proc = Process.spawn(wellknown.auth.command, { stdout: "pipe", @@ -279,12 +280,12 @@ export const AuthLoginCommand = cmd({ prompts.outro("Done") return } - await Auth.set(args.url, { + await Auth.set(url, { type: "wellknown", key: wellknown.auth.env, token: token.trim(), }) - prompts.log.success("Logged into " + args.url) + prompts.log.success("Logged into " + url) prompts.outro("Done") return } @@ -337,7 +338,7 @@ export const AuthLoginCommand = cmd({ value: x.id, hint: { opencode: "recommended", - anthropic: "Claude Max or API key", + anthropic: "Claude Pro/Max or API key", openai: "ChatGPT Plus/Pro or API key", }[x.id], })), diff --git a/packages/opencode/src/cli/cmd/import.ts b/packages/opencode/src/cli/cmd/import.ts index 4d65060f18f..58c1928256a 100644 --- a/packages/opencode/src/cli/cmd/import.ts +++ b/packages/opencode/src/cli/cmd/import.ts @@ -131,7 +131,14 @@ export const ImportCommand = cmd({ return } - Database.use((db) => db.insert(SessionTable).values(Session.toRow(exportData.info)).onConflictDoNothing().run()) + const row = { ...Session.toRow(exportData.info), project_id: Instance.project.id } + Database.use((db) => + db + .insert(SessionTable) + .values(row) + .onConflictDoUpdate({ target: SessionTable.id, set: { project_id: row.project_id } }) + .run(), + ) for (const msg of exportData.messages) { Database.use((db) => diff --git a/packages/opencode/src/cli/cmd/run.ts b/packages/opencode/src/cli/cmd/run.ts index f3781f1abd8..61bc609bb7c 100644 --- a/packages/opencode/src/cli/cmd/run.ts +++ b/packages/opencode/src/cli/cmd/run.ts @@ -555,6 +555,45 @@ export const RunCommand = cmd({ // Validate agent if specified const agent = await (async () => { if (!args.agent) return undefined + + // When attaching, validate against the running server instead of local Instance state. + if (args.attach) { + const modes = await sdk.app + .agents(undefined, { throwOnError: true }) + .then((x) => x.data ?? []) + .catch(() => undefined) + + if (!modes) { + UI.println( + UI.Style.TEXT_WARNING_BOLD + "!", + UI.Style.TEXT_NORMAL, + `failed to list agents from ${args.attach}. Falling back to default agent`, + ) + return undefined + } + + const agent = modes.find((a) => a.name === args.agent) + if (!agent) { + UI.println( + UI.Style.TEXT_WARNING_BOLD + "!", + UI.Style.TEXT_NORMAL, + `agent "${args.agent}" not found. Falling back to default agent`, + ) + return undefined + } + + if (agent.mode === "subagent") { + UI.println( + UI.Style.TEXT_WARNING_BOLD + "!", + UI.Style.TEXT_NORMAL, + `agent "${args.agent}" is a subagent, not a primary agent. Falling back to default agent`, + ) + return undefined + } + + return args.agent + } + const entry = await Agent.get(args.agent) if (!entry) { UI.println( diff --git a/packages/opencode/src/cli/cmd/serve.ts b/packages/opencode/src/cli/cmd/serve.ts index 8f4bb014469..ab51fe8c3e3 100644 --- a/packages/opencode/src/cli/cmd/serve.ts +++ b/packages/opencode/src/cli/cmd/serve.ts @@ -18,14 +18,7 @@ export const ServeCommand = cmd({ const server = Server.listen(opts) console.log(`opencode server listening on http://${server.hostname}:${server.port}`) - let workspaceSync: Array> = [] - // Only available in development right now - if (Installation.isLocal()) { - workspaceSync = Project.list().map((project) => Workspace.startSyncing(project)) - } - await new Promise(() => {}) await server.stop() - await Promise.all(workspaceSync.map((item) => item.stop())) }, }) diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx index 7bf189f0902..eb2b54d852e 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx @@ -36,7 +36,7 @@ export function createDialogProviderOptions() { value: provider.id, description: { opencode: "(Recommended)", - anthropic: "(Claude Max or API key)", + anthropic: "(Claude Pro/Max or API key)", openai: "(ChatGPT Plus/Pro or API key)", "opencode-go": "Low cost subscription for everyone", }[provider.id], diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx index 31401836766..40e8fbbed4f 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -7,6 +7,7 @@ import { For, Match, on, + onMount, Show, Switch, useContext, @@ -153,7 +154,7 @@ export function Session() { const [timestamps, setTimestamps] = kv.signal<"hide" | "show">("timestamps", "hide") const [showDetails, setShowDetails] = kv.signal("tool_details_visibility", true) const [showAssistantMetadata, setShowAssistantMetadata] = kv.signal("assistant_metadata_visibility", true) - const [showScrollbar, setShowScrollbar] = kv.signal("scrollbar_visible", false) + const [showScrollbar, setShowScrollbar] = kv.signal("scrollbar_visible", true) const [showHeader, setShowHeader] = kv.signal("header_visible", true) const [diffWrapMode] = kv.signal<"word" | "none">("diff_wrap_mode", "word") const [animationsEnabled, setAnimationsEnabled] = kv.signal("animations_enabled", true) @@ -240,7 +241,6 @@ export function Session() { const logo = UI.logo(" ").split(/\r?\n/) return exit.message.set( [ - ``, `${logo[0] ?? ""}`, `${logo[1] ?? ""}`, `${logo[2] ?? ""}`, @@ -924,6 +924,7 @@ export function Session() { keybind: "session_parent", category: "Session", hidden: true, + enabled: !!session()?.parentID, onSelect: childSessionHandler((dialog) => { const parentID = session()?.parentID if (parentID) { @@ -941,6 +942,7 @@ export function Session() { keybind: "session_child_cycle", category: "Session", hidden: true, + enabled: !!session()?.parentID, onSelect: childSessionHandler((dialog) => { moveChild(1) dialog.clear() @@ -952,6 +954,7 @@ export function Session() { keybind: "session_child_cycle_reverse", category: "Session", hidden: true, + enabled: !!session()?.parentID, onSelect: childSessionHandler((dialog) => { moveChild(-1) dialog.clear() @@ -1323,6 +1326,8 @@ function AssistantMessage(props: { message: AssistantMessage; parts: Part[]; las return props.message.time.completed - user.time.created }) + const keybind = useKeybind() + return ( <> @@ -1340,6 +1345,14 @@ function AssistantMessage(props: { message: AssistantMessage; parts: Part[]; las ) }} + x.type === "tool" && x.tool === "task")}> + + + {keybind.print("session_child_first")} + view subagents + + + - - ~ {props.pending}} when={props.complete}> - {props.icon} {props.children} - - + + + + + + + ~ {props.pending}} when={props.complete}> + {props.icon} {props.children} + + + + {error()} @@ -1836,6 +1857,7 @@ function Glob(props: ToolProps) { function Read(props: ToolProps) { const { theme } = useTheme() + const isRunning = createMemo(() => props.part.state.status === "running") const loaded = createMemo(() => { if (props.part.state.status !== "completed") return [] if (props.part.state.time.compacted) return [] @@ -1845,15 +1867,19 @@ function Read(props: ToolProps) { }) return ( <> - + Read {normalizePath(props.input.filePath!)} {input(props.input, ["filePath"])} {(filepath) => ( - - - ↳ Loaded {normalizePath(filepath)} - + + ⤷ Loaded {normalizePath(filepath)} )} @@ -1921,62 +1947,59 @@ function Task(props: ToolProps) { const local = useLocal() const sync = useSync() + onMount(() => { + if (props.metadata.sessionId && !sync.data.message[props.metadata.sessionId]?.length) + sync.session.sync(props.metadata.sessionId) + }) + + const messages = createMemo(() => sync.data.message[props.metadata.sessionId ?? ""] ?? []) + const tools = createMemo(() => { - const sessionID = props.metadata.sessionId - const msgs = sync.data.message[sessionID ?? ""] ?? [] - return msgs.flatMap((msg) => + return messages().flatMap((msg) => (sync.data.part[msg.id] ?? []) .filter((part): part is ToolPart => part.type === "tool") .map((part) => ({ tool: part.tool, state: part.state })), ) }) - const current = createMemo(() => tools().findLast((x) => x.state.status !== "pending")) + const current = createMemo(() => tools().findLast((x) => (x.state as any).title)) const isRunning = createMemo(() => props.part.state.status === "running") + const duration = createMemo(() => { + const first = messages().find((x) => x.role === "user")?.time.created + const assistant = messages().findLast((x) => x.role === "assistant")?.time.completed + if (!first || !assistant) return 0 + return assistant - first + }) + + const content = createMemo(() => { + if (!props.input.description) return "" + let content = [`Task ${props.input.description}`] + + if (isRunning() && tools().length > 0) { + // content[0] += ` · ${tools().length} toolcalls` + if (current()) content.push(`↳ ${Locale.titlecase(current()!.tool)} ${(current()!.state as any).title}`) + else content.push(`↳ ${tools().length} toolcalls`) + } + + if (props.part.state.status === "completed") { + content.push(`└ ${tools().length} toolcalls · ${Locale.duration(duration())}`) + } + + return content.join("\n") + }) + return ( - - - navigate({ type: "session", sessionID: props.metadata.sessionId! }) - : undefined - } - part={props.part} - spinner={isRunning()} - > - - - {props.input.description} ({tools().length} toolcalls) - - - {(item) => { - const title = item().state.status === "completed" ? (item().state as any).title : "" - return ( - - └ {Locale.titlecase(item().tool)} {title} - - ) - }} - - - - - {keybind.print("session_child_first")} - view subagents - - - - - - - {props.input.subagent_type} Task {props.input.description} - - - + + {content()} + ) } @@ -2195,10 +2218,16 @@ function Diagnostics(props: { diagnostics?: Record[] function normalizePath(input?: string) { if (!input) return "" - if (path.isAbsolute(input)) { - return path.relative(process.cwd(), input) || "." - } - return input + + const cwd = process.cwd() + const absolute = path.isAbsolute(input) ? input : path.resolve(cwd, input) + const relative = path.relative(cwd, absolute) + + if (!relative) return "." + if (!relative.startsWith("..")) return relative + + // outside cwd - use absolute + return absolute } function input(input: Record, omit?: string[]): string { diff --git a/packages/opencode/src/cli/cmd/tui/thread.ts b/packages/opencode/src/cli/cmd/tui/thread.ts index 750347d9d63..f53cc392552 100644 --- a/packages/opencode/src/cli/cmd/tui/thread.ts +++ b/packages/opencode/src/cli/cmd/tui/thread.ts @@ -5,8 +5,8 @@ import { type rpc } from "./worker" import path from "path" import { fileURLToPath } from "url" import { UI } from "@/cli/ui" -import { iife } from "@/util/iife" import { Log } from "@/util/log" +import { withTimeout } from "@/util/timeout" import { withNetworkOptions, resolveNetworkOptions } from "@/cli/network" import { Filesystem } from "@/util/filesystem" import type { Event } from "@opencode-ai/sdk/v2" @@ -45,6 +45,20 @@ function createEventSource(client: RpcClient): EventSource { } } +async function target() { + if (typeof OPENCODE_WORKER_PATH !== "undefined") return OPENCODE_WORKER_PATH + const dist = new URL("./cli/cmd/tui/worker.js", import.meta.url) + if (await Filesystem.exists(fileURLToPath(dist))) return dist + return new URL("./worker.ts", import.meta.url) +} + +async function input(value?: string) { + const piped = process.stdin.isTTY ? undefined : await Bun.stdin.text() + if (!value) return piped + if (!piped) return value + return piped + "\n" + value +} + export const TuiThreadCommand = cmd({ command: "$0 [project]", describe: "start opencode tui", @@ -97,23 +111,17 @@ export const TuiThreadCommand = cmd({ } // Resolve relative paths against PWD to preserve behavior when using --cwd flag - const baseCwd = process.env.PWD ?? process.cwd() - const cwd = args.project ? path.resolve(baseCwd, args.project) : process.cwd() - const localWorker = new URL("./worker.ts", import.meta.url) - const distWorker = new URL("./cli/cmd/tui/worker.js", import.meta.url) - const workerPath = await iife(async () => { - if (typeof OPENCODE_WORKER_PATH !== "undefined") return OPENCODE_WORKER_PATH - if (await Filesystem.exists(fileURLToPath(distWorker))) return distWorker - return localWorker - }) + const root = process.env.PWD ?? process.cwd() + const cwd = args.project ? path.resolve(root, args.project) : process.cwd() + const file = await target() try { process.chdir(cwd) - } catch (e) { + } catch { UI.error("Failed to change directory to " + cwd) return } - const worker = new Worker(workerPath, { + const worker = new Worker(file, { env: Object.fromEntries( Object.entries(process.env).filter((entry): entry is [string, string] => entry[1] !== undefined), ), @@ -121,76 +129,88 @@ export const TuiThreadCommand = cmd({ worker.onerror = (e) => { Log.Default.error(e) } + const client = Rpc.client(worker) - process.on("uncaughtException", (e) => { - Log.Default.error(e) - }) - process.on("unhandledRejection", (e) => { + const error = (e: unknown) => { Log.Default.error(e) - }) - process.on("SIGUSR2", async () => { - await client.call("reload", undefined) - }) + } + const reload = () => { + client.call("reload", undefined).catch((err) => { + Log.Default.warn("worker reload failed", { + error: err instanceof Error ? err.message : String(err), + }) + }) + } + process.on("uncaughtException", error) + process.on("unhandledRejection", error) + process.on("SIGUSR2", reload) - const prompt = await iife(async () => { - const piped = !process.stdin.isTTY ? await Bun.stdin.text() : undefined - if (!args.prompt) return piped - return piped ? piped + "\n" + args.prompt : args.prompt - }) + let stopped = false + const stop = async () => { + if (stopped) return + stopped = true + process.off("uncaughtException", error) + process.off("unhandledRejection", error) + process.off("SIGUSR2", reload) + await withTimeout(client.call("shutdown", undefined), 5000).catch((error) => { + Log.Default.warn("worker shutdown failed", { + error: error instanceof Error ? error.message : String(error), + }) + }) + worker.terminate() + } + + const prompt = await input(args.prompt) const config = await Instance.provide({ directory: cwd, fn: () => TuiConfig.get(), }) - // Check if server should be started (port or hostname explicitly set in CLI or config) - const networkOpts = await resolveNetworkOptions(args) - const shouldStartServer = + const network = await resolveNetworkOptions(args) + const external = process.argv.includes("--port") || process.argv.includes("--hostname") || process.argv.includes("--mdns") || - networkOpts.mdns || - networkOpts.port !== 0 || - networkOpts.hostname !== "127.0.0.1" - - let url: string - let customFetch: typeof fetch | undefined - let events: EventSource | undefined - - if (shouldStartServer) { - // Start HTTP server for external access - const server = await client.call("server", networkOpts) - url = server.url - } else { - // Use direct RPC communication (no HTTP) - url = "http://opencode.internal" - customFetch = createWorkerFetch(client) - events = createEventSource(client) - } + network.mdns || + network.port !== 0 || + network.hostname !== "127.0.0.1" - const tuiPromise = tui({ - url, - config, - directory: cwd, - fetch: customFetch, - events, - args: { - continue: args.continue, - sessionID: args.session, - agent: args.agent, - model: args.model, - prompt, - fork: args.fork, - }, - onExit: async () => { - await client.call("shutdown", undefined) - }, - }) + const transport = external + ? { + url: (await client.call("server", network)).url, + fetch: undefined, + events: undefined, + } + : { + url: "http://opencode.internal", + fetch: createWorkerFetch(client), + events: createEventSource(client), + } setTimeout(() => { client.call("checkUpgrade", { directory: cwd }).catch(() => {}) - }, 1000) + }, 1000).unref?.() - await tuiPromise + try { + await tui({ + url: transport.url, + config, + directory: cwd, + fetch: transport.fetch, + events: transport.events, + args: { + continue: args.continue, + sessionID: args.session, + agent: args.agent, + model: args.model, + prompt, + fork: args.fork, + }, + onExit: stop, + }) + } finally { + await stop() + } } finally { unguard?.() } diff --git a/packages/opencode/src/cli/cmd/tui/worker.ts b/packages/opencode/src/cli/cmd/tui/worker.ts index bb5495c4811..e63f10ba80c 100644 --- a/packages/opencode/src/cli/cmd/tui/worker.ts +++ b/packages/opencode/src/cli/cmd/tui/worker.ts @@ -137,12 +137,7 @@ export const rpc = { async shutdown() { Log.Default.info("worker shutting down") if (eventStream.abort) eventStream.abort.abort() - await Promise.race([ - Instance.disposeAll(), - new Promise((resolve) => { - setTimeout(resolve, 5000) - }), - ]) + await Instance.disposeAll() if (server) server.stop(true) }, } diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 141f6156985..28c5b239a41 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -86,11 +86,12 @@ export namespace Config { let result: Info = {} for (const [key, value] of Object.entries(auth)) { if (value.type === "wellknown") { + const url = key.replace(/\/+$/, "") process.env[value.key] = value.token - log.debug("fetching remote config", { url: `${key}/.well-known/opencode` }) - const response = await fetch(`${key}/.well-known/opencode`) + log.debug("fetching remote config", { url: `${url}/.well-known/opencode` }) + const response = await fetch(`${url}/.well-known/opencode`) if (!response.ok) { - throw new Error(`failed to fetch remote config from ${key}: ${response.status}`) + throw new Error(`failed to fetch remote config from ${url}: ${response.status}`) } const wellknown = (await response.json()) as any const remoteConfig = wellknown.config ?? {} @@ -99,11 +100,11 @@ export namespace Config { result = mergeConfigConcatArrays( result, await load(JSON.stringify(remoteConfig), { - dir: path.dirname(`${key}/.well-known/opencode`), - source: `${key}/.well-known/opencode`, + dir: path.dirname(`${url}/.well-known/opencode`), + source: `${url}/.well-known/opencode`, }), ) - log.debug("loaded remote config from well-known", { url: key }) + log.debug("loaded remote config from well-known", { url }) } } diff --git a/packages/opencode/src/control-plane/adaptors/index.ts b/packages/opencode/src/control-plane/adaptors/index.ts index 77e1f53c6cf..a43fce24865 100644 --- a/packages/opencode/src/control-plane/adaptors/index.ts +++ b/packages/opencode/src/control-plane/adaptors/index.ts @@ -1,10 +1,20 @@ -import { WorktreeAdaptor } from "./worktree" -import type { Config } from "../config" -import type { Adaptor } from "./types" - -export function getAdaptor(config: Config): Adaptor { - switch (config.type) { - case "worktree": - return WorktreeAdaptor - } +import { lazy } from "@/util/lazy" +import type { Adaptor } from "../types" + +const ADAPTORS: Record Promise> = { + worktree: lazy(async () => (await import("./worktree")).WorktreeAdaptor), +} + +export function getAdaptor(type: string): Promise { + return ADAPTORS[type]() +} + +export function installAdaptor(type: string, adaptor: Adaptor) { + // This is experimental: mostly used for testing right now, but we + // will likely allow this in the future. Need to figure out the + // TypeScript story + + // @ts-expect-error we force the builtin types right now, but we + // will implement a way to extend the types for custom adaptors + ADAPTORS[type] = () => adaptor } diff --git a/packages/opencode/src/control-plane/adaptors/types.ts b/packages/opencode/src/control-plane/adaptors/types.ts deleted file mode 100644 index 47a0405a5d3..00000000000 --- a/packages/opencode/src/control-plane/adaptors/types.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { Config } from "../config" - -export type Adaptor = { - create(from: T, branch?: string | null): Promise<{ config: T; init: () => Promise }> - remove(from: T): Promise - request(from: T, method: string, url: string, data?: BodyInit, signal?: AbortSignal): Promise -} diff --git a/packages/opencode/src/control-plane/adaptors/worktree.ts b/packages/opencode/src/control-plane/adaptors/worktree.ts index e355bb77036..f8489095011 100644 --- a/packages/opencode/src/control-plane/adaptors/worktree.ts +++ b/packages/opencode/src/control-plane/adaptors/worktree.ts @@ -1,26 +1,46 @@ +import z from "zod" import { Worktree } from "@/worktree" -import type { Config } from "../config" -import type { Adaptor } from "./types" +import { type Adaptor, WorkspaceInfo } from "../types" -type WorktreeConfig = Extract +const Config = WorkspaceInfo.extend({ + name: WorkspaceInfo.shape.name.unwrap(), + branch: WorkspaceInfo.shape.branch.unwrap(), + directory: WorkspaceInfo.shape.directory.unwrap(), +}) -export const WorktreeAdaptor: Adaptor = { - async create(_from: WorktreeConfig, _branch: string) { - const next = await Worktree.create(undefined) +type Config = z.infer + +export const WorktreeAdaptor: Adaptor = { + async configure(info) { + const worktree = await Worktree.makeWorktreeInfo(info.name ?? undefined) return { - config: { - type: "worktree", - directory: next.directory, - }, - // Hack for now: `Worktree.create` puts all its async code in a - // `setTimeout` so it doesn't use this, but we should change that - init: async () => {}, + ...info, + name: worktree.name, + branch: worktree.branch, + directory: worktree.directory, } }, - async remove(config: WorktreeConfig) { + async create(info) { + const config = Config.parse(info) + const bootstrap = await Worktree.createFromInfo({ + name: config.name, + directory: config.directory, + branch: config.branch, + }) + return bootstrap() + }, + async remove(info) { + const config = Config.parse(info) await Worktree.remove({ directory: config.directory }) }, - async request(_from: WorktreeConfig, _method: string, _url: string, _data?: BodyInit, _signal?: AbortSignal) { - throw new Error("worktree does not support request") + async fetch(info, input: RequestInfo | URL, init?: RequestInit) { + const config = Config.parse(info) + const { WorkspaceServer } = await import("../workspace-server/server") + const url = input instanceof Request || input instanceof URL ? input : new URL(input, "http://opencode.internal") + const headers = new Headers(init?.headers ?? (input instanceof Request ? input.headers : undefined)) + headers.set("x-opencode-directory", config.directory) + + const request = new Request(url, { ...init, headers }) + return WorkspaceServer.App().fetch(request) }, } diff --git a/packages/opencode/src/control-plane/config.ts b/packages/opencode/src/control-plane/config.ts deleted file mode 100644 index 73dbc4bdbda..00000000000 --- a/packages/opencode/src/control-plane/config.ts +++ /dev/null @@ -1,10 +0,0 @@ -import z from "zod" - -export const Config = z.discriminatedUnion("type", [ - z.object({ - directory: z.string(), - type: z.literal("worktree"), - }), -]) - -export type Config = z.infer diff --git a/packages/opencode/src/control-plane/session-proxy-middleware.ts b/packages/opencode/src/control-plane/session-proxy-middleware.ts deleted file mode 100644 index df2591017a8..00000000000 --- a/packages/opencode/src/control-plane/session-proxy-middleware.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { Instance } from "@/project/instance" -import type { MiddlewareHandler } from "hono" -import { Installation } from "../installation" -import { getAdaptor } from "./adaptors" -import { Workspace } from "./workspace" - -// This middleware forwards all non-GET requests if the workspace is a -// remote. The remote workspace needs to handle session mutations -async function proxySessionRequest(req: Request) { - if (req.method === "GET") return - if (!Instance.directory.startsWith("wrk_")) return - - const workspace = await Workspace.get(Instance.directory) - if (!workspace) { - return new Response(`Workspace not found: ${Instance.directory}`, { - status: 500, - headers: { - "content-type": "text/plain; charset=utf-8", - }, - }) - } - if (workspace.config.type === "worktree") return - - const url = new URL(req.url) - const body = req.method === "HEAD" ? undefined : await req.arrayBuffer() - return getAdaptor(workspace.config).request( - workspace.config, - req.method, - `${url.pathname}${url.search}`, - body, - req.signal, - ) -} - -export const SessionProxyMiddleware: MiddlewareHandler = async (c, next) => { - // Only available in development for now - if (!Installation.isLocal()) { - return next() - } - - const response = await proxySessionRequest(c.req.raw) - if (response) { - return response - } - return next() -} diff --git a/packages/opencode/src/control-plane/types.ts b/packages/opencode/src/control-plane/types.ts new file mode 100644 index 00000000000..3d27757fd1b --- /dev/null +++ b/packages/opencode/src/control-plane/types.ts @@ -0,0 +1,20 @@ +import z from "zod" +import { Identifier } from "@/id/id" + +export const WorkspaceInfo = z.object({ + id: Identifier.schema("workspace"), + type: z.string(), + branch: z.string().nullable(), + name: z.string().nullable(), + directory: z.string().nullable(), + extra: z.unknown().nullable(), + projectID: z.string(), +}) +export type WorkspaceInfo = z.infer + +export type Adaptor = { + configure(input: WorkspaceInfo): WorkspaceInfo | Promise + create(input: WorkspaceInfo, from?: WorkspaceInfo): Promise + remove(config: WorkspaceInfo): Promise + fetch(config: WorkspaceInfo, input: RequestInfo | URL, init?: RequestInit): Promise +} diff --git a/packages/opencode/src/control-plane/workspace-context.ts b/packages/opencode/src/control-plane/workspace-context.ts new file mode 100644 index 00000000000..f7297b3f4b4 --- /dev/null +++ b/packages/opencode/src/control-plane/workspace-context.ts @@ -0,0 +1,23 @@ +import { Context } from "../util/context" + +interface Context { + workspaceID?: string +} + +const context = Context.create("workspace") + +export const WorkspaceContext = { + async provide(input: { workspaceID?: string; fn: () => R }): Promise { + return context.provide({ workspaceID: input.workspaceID }, async () => { + return input.fn() + }) + }, + + get workspaceID() { + try { + return context.use().workspaceID + } catch (e) { + return undefined + } + }, +} diff --git a/packages/opencode/src/control-plane/workspace-router-middleware.ts b/packages/opencode/src/control-plane/workspace-router-middleware.ts new file mode 100644 index 00000000000..b48f2fd2b7d --- /dev/null +++ b/packages/opencode/src/control-plane/workspace-router-middleware.ts @@ -0,0 +1,50 @@ +import { Instance } from "@/project/instance" +import type { MiddlewareHandler } from "hono" +import { Installation } from "../installation" +import { getAdaptor } from "./adaptors" +import { Workspace } from "./workspace" +import { WorkspaceContext } from "./workspace-context" + +// This middleware forwards all non-GET requests if the workspace is a +// remote. The remote workspace needs to handle session mutations +async function routeRequest(req: Request) { + // Right now, we need to forward all requests to the workspace + // because we don't have syncing. In the future all GET requests + // which don't mutate anything will be handled locally + // + // if (req.method === "GET") return + + if (!WorkspaceContext.workspaceID) return + + const workspace = await Workspace.get(WorkspaceContext.workspaceID) + if (!workspace) { + return new Response(`Workspace not found: ${WorkspaceContext.workspaceID}`, { + status: 500, + headers: { + "content-type": "text/plain; charset=utf-8", + }, + }) + } + + const adaptor = await getAdaptor(workspace.type) + + return adaptor.fetch(workspace, `${new URL(req.url).pathname}${new URL(req.url).search}`, { + method: req.method, + body: req.method === "GET" || req.method === "HEAD" ? undefined : await req.arrayBuffer(), + signal: req.signal, + headers: req.headers, + }) +} + +export const WorkspaceRouterMiddleware: MiddlewareHandler = async (c, next) => { + // Only available in development for now + if (!Installation.isLocal()) { + return next() + } + + const response = await routeRequest(c.req.raw) + if (response) { + return response + } + return next() +} diff --git a/packages/opencode/src/control-plane/workspace-server/server.ts b/packages/opencode/src/control-plane/workspace-server/server.ts index 716989942f4..fd7fd930867 100644 --- a/packages/opencode/src/control-plane/workspace-server/server.ts +++ b/packages/opencode/src/control-plane/workspace-server/server.ts @@ -1,17 +1,57 @@ import { Hono } from "hono" +import { Instance } from "../../project/instance" +import { InstanceBootstrap } from "../../project/bootstrap" import { SessionRoutes } from "../../server/routes/session" import { WorkspaceServerRoutes } from "./routes" +import { WorkspaceContext } from "../workspace-context" export namespace WorkspaceServer { export function App() { const session = new Hono() - .use("*", async (c, next) => { - if (c.req.method === "GET") return c.notFound() + .use(async (c, next) => { + // Right now, we need handle all requests because we don't + // have syncing. In the future all GET requests will handled + // by the control plane + // + // if (c.req.method === "GET") return c.notFound() await next() }) .route("/", SessionRoutes()) - return new Hono().route("/session", session).route("/", WorkspaceServerRoutes()) + return new Hono() + .use(async (c, next) => { + const workspaceID = c.req.query("workspace") || c.req.header("x-opencode-workspace") + const raw = c.req.query("directory") || c.req.header("x-opencode-directory") + if (workspaceID == null) { + throw new Error("workspaceID parameter is required") + } + if (raw == null) { + throw new Error("directory parameter is required") + } + + const directory = (() => { + try { + return decodeURIComponent(raw) + } catch { + return raw + } + })() + + return WorkspaceContext.provide({ + workspaceID, + async fn() { + return Instance.provide({ + directory, + init: InstanceBootstrap, + async fn() { + return next() + }, + }) + }, + }) + }) + .route("/session", session) + .route("/", WorkspaceServerRoutes()) } export function Listen(opts: { hostname: string; port: number }) { diff --git a/packages/opencode/src/control-plane/workspace.sql.ts b/packages/opencode/src/control-plane/workspace.sql.ts index 1a201198204..1ba1605f8ed 100644 --- a/packages/opencode/src/control-plane/workspace.sql.ts +++ b/packages/opencode/src/control-plane/workspace.sql.ts @@ -1,12 +1,14 @@ import { sqliteTable, text } from "drizzle-orm/sqlite-core" import { ProjectTable } from "@/project/project.sql" -import type { Config } from "./config" export const WorkspaceTable = sqliteTable("workspace", { id: text().primaryKey(), + type: text().notNull(), branch: text(), + name: text(), + directory: text(), + extra: text({ mode: "json" }), project_id: text() .notNull() .references(() => ProjectTable.id, { onDelete: "cascade" }), - config: text({ mode: "json" }).notNull().$type(), }) diff --git a/packages/opencode/src/control-plane/workspace.ts b/packages/opencode/src/control-plane/workspace.ts index 5ce373b127a..8c76fbdab99 100644 --- a/packages/opencode/src/control-plane/workspace.ts +++ b/packages/opencode/src/control-plane/workspace.ts @@ -7,8 +7,8 @@ import { BusEvent } from "@/bus/bus-event" import { GlobalBus } from "@/bus/global" import { Log } from "@/util/log" import { WorkspaceTable } from "./workspace.sql" -import { Config } from "./config" import { getAdaptor } from "./adaptors" +import { WorkspaceInfo } from "./types" import { parseSSE } from "./sse" export namespace Workspace { @@ -27,72 +27,64 @@ export namespace Workspace { ), } - export const Info = z - .object({ - id: Identifier.schema("workspace"), - branch: z.string().nullable(), - projectID: z.string(), - config: Config, - }) - .meta({ - ref: "Workspace", - }) + export const Info = WorkspaceInfo.meta({ + ref: "Workspace", + }) export type Info = z.infer function fromRow(row: typeof WorkspaceTable.$inferSelect): Info { return { id: row.id, + type: row.type, branch: row.branch, + name: row.name, + directory: row.directory, + extra: row.extra, projectID: row.project_id, - config: row.config, } } - export const create = fn( - z.object({ - id: Identifier.schema("workspace").optional(), - projectID: Info.shape.projectID, - branch: Info.shape.branch, - config: Info.shape.config, - }), - async (input) => { - const id = Identifier.ascending("workspace", input.id) - - const { config, init } = await getAdaptor(input.config).create(input.config, input.branch) - - const info: Info = { - id, - projectID: input.projectID, - branch: input.branch, - config, - } + const CreateInput = z.object({ + id: Identifier.schema("workspace").optional(), + type: Info.shape.type, + branch: Info.shape.branch, + projectID: Info.shape.projectID, + extra: Info.shape.extra, + }) - setTimeout(async () => { - await init() - - Database.use((db) => { - db.insert(WorkspaceTable) - .values({ - id: info.id, - branch: info.branch, - project_id: info.projectID, - config: info.config, - }) - .run() - }) + export const create = fn(CreateInput, async (input) => { + const id = Identifier.ascending("workspace", input.id) + const adaptor = await getAdaptor(input.type) - GlobalBus.emit("event", { - directory: id, - payload: { - type: Event.Ready.type, - properties: {}, - }, + const config = await adaptor.configure({ ...input, id, name: null, directory: null }) + + const info: Info = { + id, + type: config.type, + branch: config.branch ?? null, + name: config.name ?? null, + directory: config.directory ?? null, + extra: config.extra ?? null, + projectID: input.projectID, + } + + Database.use((db) => { + db.insert(WorkspaceTable) + .values({ + id: info.id, + type: info.type, + branch: info.branch, + name: info.name, + directory: info.directory, + extra: info.extra, + project_id: info.projectID, }) - }, 0) + .run() + }) - return info - }, - ) + await adaptor.create(config) + return info + }) export function list(project: Project.Info) { const rows = Database.use((db) => @@ -111,7 +103,8 @@ export namespace Workspace { const row = Database.use((db) => db.select().from(WorkspaceTable).where(eq(WorkspaceTable.id, id)).get()) if (row) { const info = fromRow(row) - await getAdaptor(info.config).remove(info.config) + const adaptor = await getAdaptor(row.type) + adaptor.remove(info) Database.use((db) => db.delete(WorkspaceTable).where(eq(WorkspaceTable.id, id)).run()) return info } @@ -120,9 +113,8 @@ export namespace Workspace { async function workspaceEventLoop(space: Info, stop: AbortSignal) { while (!stop.aborted) { - const res = await getAdaptor(space.config) - .request(space.config, "GET", "/event", undefined, stop) - .catch(() => undefined) + const adaptor = await getAdaptor(space.type) + const res = await adaptor.fetch(space, "/event", { method: "GET", signal: stop }).catch(() => undefined) if (!res || !res.ok || !res.body) { await Bun.sleep(1000) continue @@ -140,7 +132,7 @@ export namespace Workspace { export function startSyncing(project: Project.Info) { const stop = new AbortController() - const spaces = list(project).filter((space) => space.config.type !== "worktree") + const spaces = list(project).filter((space) => space.type !== "worktree") spaces.forEach((space) => { void workspaceEventLoop(space, stop.signal).catch((error) => { diff --git a/packages/opencode/src/flag/flag.ts b/packages/opencode/src/flag/flag.ts index e02f191c709..22eba6320e9 100644 --- a/packages/opencode/src/flag/flag.ts +++ b/packages/opencode/src/flag/flag.ts @@ -3,6 +3,11 @@ function truthy(key: string) { return value === "true" || value === "1" } +function falsy(key: string) { + const value = process.env[key]?.toLowerCase() + return value === "false" || value === "0" +} + export namespace Flag { export const OPENCODE_AUTO_SHARE = truthy("OPENCODE_AUTO_SHARE") export const OPENCODE_GIT_BASH_PATH = process.env["OPENCODE_GIT_BASH_PATH"] @@ -52,7 +57,7 @@ export namespace Flag { export const OPENCODE_EXPERIMENTAL_LSP_TOOL = OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_LSP_TOOL") export const OPENCODE_DISABLE_FILETIME_CHECK = truthy("OPENCODE_DISABLE_FILETIME_CHECK") export const OPENCODE_EXPERIMENTAL_PLAN_MODE = OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_PLAN_MODE") - export const OPENCODE_EXPERIMENTAL_MARKDOWN = truthy("OPENCODE_EXPERIMENTAL_MARKDOWN") + export const OPENCODE_EXPERIMENTAL_MARKDOWN = !falsy("OPENCODE_EXPERIMENTAL_MARKDOWN") export const OPENCODE_MODELS_URL = process.env["OPENCODE_MODELS_URL"] export const OPENCODE_MODELS_PATH = process.env["OPENCODE_MODELS_PATH"] diff --git a/packages/opencode/src/index.ts b/packages/opencode/src/index.ts index 9af79278c06..35b42dce77c 100644 --- a/packages/opencode/src/index.ts +++ b/packages/opencode/src/index.ts @@ -76,6 +76,7 @@ let cli = yargs(hideBin(process.argv)) process.env.AGENT = "1" process.env.OPENCODE = "1" + process.env.OPENCODE_PID = String(process.pid) Log.Default.info("opencode", { version: Installation.VERSION, diff --git a/packages/opencode/src/mcp/index.ts b/packages/opencode/src/mcp/index.ts index 3c29fe03d30..0dca27d6512 100644 --- a/packages/opencode/src/mcp/index.ts +++ b/packages/opencode/src/mcp/index.ts @@ -160,6 +160,28 @@ export namespace MCP { return typeof entry === "object" && entry !== null && "type" in entry } + async function descendants(pid: number): Promise { + if (process.platform === "win32") return [] + const pids: number[] = [] + const queue = [pid] + while (queue.length > 0) { + const current = queue.shift()! + const proc = Bun.spawn(["pgrep", "-P", String(current)], { stdout: "pipe", stderr: "pipe" }) + const [code, out] = await Promise.all([proc.exited, new Response(proc.stdout).text()]).catch( + () => [-1, ""] as const, + ) + if (code !== 0) continue + for (const tok of out.trim().split(/\s+/)) { + const cpid = parseInt(tok, 10) + if (!isNaN(cpid) && pids.indexOf(cpid) === -1) { + pids.push(cpid) + queue.push(cpid) + } + } + } + return pids + } + const state = Instance.state( async () => { const cfg = await Config.get() @@ -196,6 +218,21 @@ export namespace MCP { } }, async (state) => { + // The MCP SDK only signals the direct child process on close. + // Servers like chrome-devtools-mcp spawn grandchild processes + // (e.g. Chrome) that the SDK never reaches, leaving them orphaned. + // Kill the full descendant tree first so the server exits promptly + // and no processes are left behind. + for (const client of Object.values(state.clients)) { + const pid = (client.transport as any)?.pid + if (typeof pid !== "number") continue + for (const dpid of await descendants(pid)) { + try { + process.kill(dpid, "SIGTERM") + } catch {} + } + } + await Promise.all( Object.values(state.clients).map((client) => client.close().catch((error) => { diff --git a/packages/opencode/src/provider/error.ts b/packages/opencode/src/provider/error.ts index 0db03576e92..945d29f97f7 100644 --- a/packages/opencode/src/provider/error.ts +++ b/packages/opencode/src/provider/error.ts @@ -19,6 +19,7 @@ export namespace ProviderError { /context window exceeds limit/i, // MiniMax /exceeded model token limit/i, // Kimi For Coding, Moonshot /context[_ ]length[_ ]exceeded/i, // Generic fallback + /request entity too large/i, // HTTP 413 ] function isOpenAiErrorRetryable(e: APICallError) { @@ -76,6 +77,18 @@ export namespace ProviderError { } } catch {} + // If responseBody is HTML (e.g. from a gateway or proxy error page), + // provide a human-readable message instead of dumping raw markup + if (/^\s*` to re-authenticate." + } + if (e.statusCode === 403) { + return "Forbidden: request was blocked by a gateway or proxy. You may not have permission to access this resource — check your account and provider settings." + } + return msg + } + return `${msg}: ${e.responseBody}` }).trim() } @@ -165,7 +178,7 @@ export namespace ProviderError { export function parseAPICallError(input: { providerID: string; error: APICallError }): ParsedAPICallError { const m = message(input.providerID, input.error) - if (isOverflow(m)) { + if (isOverflow(m) || input.error.statusCode === 413) { return { type: "context_overflow", message: m, diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 022ec316795..81703836524 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -555,7 +555,28 @@ export namespace Provider { const { createAiGateway } = await import("ai-gateway-provider") const { createUnified } = await import("ai-gateway-provider/providers/unified") - const aigateway = createAiGateway({ accountId, gateway, apiKey: apiToken }) + const metadata = iife(() => { + if (input.options?.metadata) return input.options.metadata + try { + return JSON.parse(input.options?.headers?.["cf-aig-metadata"]) + } catch { + return undefined + } + }) + const opts = { + metadata, + cacheTtl: input.options?.cacheTtl, + cacheKey: input.options?.cacheKey, + skipCache: input.options?.skipCache, + collectLog: input.options?.collectLog, + } + + const aigateway = createAiGateway({ + accountId, + gateway, + apiKey: apiToken, + ...(Object.values(opts).some((v) => v !== undefined) ? { options: opts } : {}), + }) const unified = createUnified() return { diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index b659799c1b6..6980be05188 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -897,6 +897,31 @@ export namespace ProviderTransform { // Convert integer enums to string enums for Google/Gemini if (model.providerID === "google" || model.api.id.includes("gemini")) { + const isPlainObject = (node: unknown): node is Record => + typeof node === "object" && node !== null && !Array.isArray(node) + const hasCombiner = (node: unknown) => + isPlainObject(node) && (Array.isArray(node.anyOf) || Array.isArray(node.oneOf) || Array.isArray(node.allOf)) + const hasSchemaIntent = (node: unknown) => { + if (!isPlainObject(node)) return false + if (hasCombiner(node)) return true + return [ + "type", + "properties", + "items", + "prefixItems", + "enum", + "const", + "$ref", + "additionalProperties", + "patternProperties", + "required", + "not", + "if", + "then", + "else", + ].some((key) => key in node) + } + const sanitizeGemini = (obj: any): any => { if (obj === null || typeof obj !== "object") { return obj @@ -927,19 +952,18 @@ export namespace ProviderTransform { result.required = result.required.filter((field: any) => field in result.properties) } - if (result.type === "array") { + if (result.type === "array" && !hasCombiner(result)) { if (result.items == null) { result.items = {} } - // Ensure items has at least a type if it's an empty object - // This handles nested arrays like { type: "array", items: { type: "array", items: {} } } - if (typeof result.items === "object" && !Array.isArray(result.items) && !result.items.type) { + // Ensure items has a type only when it's still schema-empty. + if (isPlainObject(result.items) && !hasSchemaIntent(result.items)) { result.items.type = "string" } } // Remove properties/required from non-object types (Gemini rejects these) - if (result.type && result.type !== "object") { + if (result.type && result.type !== "object" && !hasCombiner(result)) { delete result.properties delete result.required } diff --git a/packages/opencode/src/server/routes/experimental.ts b/packages/opencode/src/server/routes/experimental.ts index 892bca48595..98c7ece1052 100644 --- a/packages/opencode/src/server/routes/experimental.ts +++ b/packages/opencode/src/server/routes/experimental.ts @@ -88,6 +88,7 @@ export const ExperimentalRoutes = lazy(() => ) }, ) + .route("/workspace", WorkspaceRoutes()) .post( "/worktree", describeRoute({ @@ -113,7 +114,6 @@ export const ExperimentalRoutes = lazy(() => return c.json(worktree) }, ) - .route("/workspace", WorkspaceRoutes()) .get( "/worktree", describeRoute({ diff --git a/packages/opencode/src/server/routes/session.ts b/packages/opencode/src/server/routes/session.ts index a391979520d..12938aeaba0 100644 --- a/packages/opencode/src/server/routes/session.ts +++ b/packages/opencode/src/server/routes/session.ts @@ -16,13 +16,11 @@ import { Log } from "../../util/log" import { PermissionNext } from "@/permission/next" import { errors } from "../error" import { lazy } from "../../util/lazy" -import { SessionProxyMiddleware } from "../../control-plane/session-proxy-middleware" const log = Log.create({ service: "server" }) export const SessionRoutes = lazy(() => new Hono() - .use(SessionProxyMiddleware) .get( "/", describeRoute({ diff --git a/packages/opencode/src/server/routes/workspace.ts b/packages/opencode/src/server/routes/workspace.ts index 0c64c9cd493..cd2d844aedf 100644 --- a/packages/opencode/src/server/routes/workspace.ts +++ b/packages/opencode/src/server/routes/workspace.ts @@ -9,7 +9,7 @@ import { lazy } from "../../util/lazy" export const WorkspaceRoutes = lazy(() => new Hono() .post( - "/:id", + "/", describeRoute({ summary: "Create workspace", description: "Create a workspace for the current project.", @@ -26,27 +26,17 @@ export const WorkspaceRoutes = lazy(() => ...errors(400), }, }), - validator( - "param", - z.object({ - id: Workspace.Info.shape.id, - }), - ), validator( "json", - z.object({ - branch: Workspace.Info.shape.branch, - config: Workspace.Info.shape.config, + Workspace.create.schema.omit({ + projectID: true, }), ), async (c) => { - const { id } = c.req.valid("param") const body = c.req.valid("json") const workspace = await Workspace.create({ - id, projectID: Instance.project.id, - branch: body.branch, - config: body.config, + ...body, }) return c.json(workspace) }, diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index 9fba9c1fe1a..6ea66be9858 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -21,6 +21,8 @@ import { Auth } from "../auth" import { Flag } from "../flag/flag" import { Command } from "../command" import { Global } from "../global" +import { WorkspaceContext } from "../control-plane/workspace-context" +import { WorkspaceRouterMiddleware } from "../control-plane/workspace-router-middleware" import { ProjectRoutes } from "./routes/project" import { SessionRoutes } from "./routes/session" import { PtyRoutes } from "./routes/pty" @@ -194,6 +196,7 @@ export namespace Server { ) .use(async (c, next) => { if (c.req.path === "/log") return next() + const workspaceID = c.req.query("workspace") || c.req.header("x-opencode-workspace") const raw = c.req.query("directory") || c.req.header("x-opencode-directory") || process.cwd() const directory = (() => { try { @@ -202,14 +205,21 @@ export namespace Server { return raw } })() - return Instance.provide({ - directory, - init: InstanceBootstrap, + + return WorkspaceContext.provide({ + workspaceID, async fn() { - return next() + return Instance.provide({ + directory, + init: InstanceBootstrap, + async fn() { + return next() + }, + }) }, }) }) + .use(WorkspaceRouterMiddleware) .get( "/doc", openAPIRouteHandler(app, { @@ -223,7 +233,15 @@ export namespace Server { }, }), ) - .use(validator("query", z.object({ directory: z.string().optional() }))) + .use( + validator( + "query", + z.object({ + directory: z.string().optional(), + workspace: z.string().optional(), + }), + ), + ) .route("/project", ProjectRoutes()) .route("/pty", PtyRoutes()) .route("/config", ConfigRoutes()) diff --git a/packages/opencode/src/session/compaction.ts b/packages/opencode/src/session/compaction.ts index 9245426057c..79884d641ea 100644 --- a/packages/opencode/src/session/compaction.ts +++ b/packages/opencode/src/session/compaction.ts @@ -104,8 +104,30 @@ export namespace SessionCompaction { sessionID: string abort: AbortSignal auto: boolean + overflow?: boolean }) { const userMessage = input.messages.findLast((m) => m.info.id === input.parentID)!.info as MessageV2.User + + let messages = input.messages + let replay: MessageV2.WithParts | undefined + if (input.overflow) { + const idx = input.messages.findIndex((m) => m.info.id === input.parentID) + for (let i = idx - 1; i >= 0; i--) { + const msg = input.messages[i] + if (msg.info.role === "user" && !msg.parts.some((p) => p.type === "compaction")) { + replay = msg + messages = input.messages.slice(0, i) + break + } + } + const hasContent = + replay && messages.some((m) => m.info.role === "user" && !m.parts.some((p) => p.type === "compaction")) + if (!hasContent) { + replay = undefined + messages = input.messages + } + } + const agent = await Agent.get("compaction") const model = agent.model ? await Provider.getModel(agent.model.providerID, agent.model.modelID) @@ -185,7 +207,7 @@ When constructing the summary, try to stick to this template: tools: {}, system: [], messages: [ - ...MessageV2.toModelMessages(input.messages, model), + ...MessageV2.toModelMessages(messages, model, { stripMedia: true }), { role: "user", content: [ @@ -199,29 +221,72 @@ When constructing the summary, try to stick to this template: model, }) + if (result === "compact") { + processor.message.error = new MessageV2.ContextOverflowError({ + message: replay + ? "Conversation history too large to compact - exceeds model context limit" + : "Session too large to compact - context exceeds model limit even after stripping media", + }).toObject() + processor.message.finish = "error" + await Session.updateMessage(processor.message) + return "stop" + } + if (result === "continue" && input.auto) { - const continueMsg = await Session.updateMessage({ - id: Identifier.ascending("message"), - role: "user", - sessionID: input.sessionID, - time: { - created: Date.now(), - }, - agent: userMessage.agent, - model: userMessage.model, - }) - await Session.updatePart({ - id: Identifier.ascending("part"), - messageID: continueMsg.id, - sessionID: input.sessionID, - type: "text", - synthetic: true, - text: "Continue if you have next steps, or stop and ask for clarification if you are unsure how to proceed.", - time: { - start: Date.now(), - end: Date.now(), - }, - }) + if (replay) { + const original = replay.info as MessageV2.User + const replayMsg = await Session.updateMessage({ + id: Identifier.ascending("message"), + role: "user", + sessionID: input.sessionID, + time: { created: Date.now() }, + agent: original.agent, + model: original.model, + format: original.format, + tools: original.tools, + system: original.system, + variant: original.variant, + }) + for (const part of replay.parts) { + if (part.type === "compaction") continue + const replayPart = + part.type === "file" && MessageV2.isMedia(part.mime) + ? { type: "text" as const, text: `[Attached ${part.mime}: ${part.filename ?? "file"}]` } + : part + await Session.updatePart({ + ...replayPart, + id: Identifier.ascending("part"), + messageID: replayMsg.id, + sessionID: input.sessionID, + }) + } + } else { + const continueMsg = await Session.updateMessage({ + id: Identifier.ascending("message"), + role: "user", + sessionID: input.sessionID, + time: { created: Date.now() }, + agent: userMessage.agent, + model: userMessage.model, + }) + const text = + (input.overflow + ? "The previous request exceeded the provider's size limit due to large media attachments. The conversation was compacted and media files were removed from context. If the user was asking about attached images or files, explain that the attachments were too large to process and suggest they try again with smaller or fewer files.\n\n" + : "") + + "Continue if you have next steps, or stop and ask for clarification if you are unsure how to proceed." + await Session.updatePart({ + id: Identifier.ascending("part"), + messageID: continueMsg.id, + sessionID: input.sessionID, + type: "text", + synthetic: true, + text, + time: { + start: Date.now(), + end: Date.now(), + }, + }) + } } if (processor.message.error) return "stop" Bus.publish(Event.Compacted, { sessionID: input.sessionID }) @@ -237,6 +302,7 @@ When constructing the summary, try to stick to this template: modelID: z.string(), }), auto: z.boolean(), + overflow: z.boolean().optional(), }), async (input) => { const msg = await Session.updateMessage({ @@ -255,6 +321,7 @@ When constructing the summary, try to stick to this template: sessionID: msg.sessionID, type: "compaction", auto: input.auto, + overflow: input.overflow, }) }, ) diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts index 22de477f8d1..b117632051f 100644 --- a/packages/opencode/src/session/index.ts +++ b/packages/opencode/src/session/index.ts @@ -22,6 +22,7 @@ import { SessionPrompt } from "./prompt" import { fn } from "@/util/fn" import { Command } from "../command" import { Snapshot } from "@/snapshot" +import { WorkspaceContext } from "../control-plane/workspace-context" import type { Provider } from "@/provider/provider" import { PermissionNext } from "@/permission/next" @@ -63,6 +64,7 @@ export namespace Session { id: row.id, slug: row.slug, projectID: row.project_id, + workspaceID: row.workspace_id ?? undefined, directory: row.directory, parentID: row.parent_id ?? undefined, title: row.title, @@ -84,6 +86,7 @@ export namespace Session { return { id: info.id, project_id: info.projectID, + workspace_id: info.workspaceID, parent_id: info.parentID, slug: info.slug, directory: info.directory, @@ -118,6 +121,7 @@ export namespace Session { id: Identifier.schema("session"), slug: z.string(), projectID: z.string(), + workspaceID: z.string().optional(), directory: z.string(), parentID: Identifier.schema("session").optional(), summary: z @@ -297,6 +301,7 @@ export namespace Session { version: Installation.VERSION, projectID: Instance.project.id, directory: input.directory, + workspaceID: WorkspaceContext.workspaceID, parentID: input.parentID, title: input.title ?? createDefaultTitle(!!input.parentID), permission: input.permission, @@ -527,6 +532,7 @@ export namespace Session { export function* list(input?: { directory?: string + workspaceID?: string roots?: boolean start?: number search?: string @@ -535,6 +541,9 @@ export namespace Session { const project = Instance.project const conditions = [eq(SessionTable.project_id, project.id)] + if (WorkspaceContext.workspaceID) { + conditions.push(eq(SessionTable.workspace_id, WorkspaceContext.workspaceID)) + } if (input?.directory) { conditions.push(eq(SessionTable.directory, input.directory)) } @@ -752,7 +761,7 @@ export namespace Session { .run() Database.effect(() => Bus.publish(MessageV2.Event.PartUpdated, { - part, + part: structuredClone(part), }), ) }) diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index 178751a2227..5b4e7bdbc04 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -17,6 +17,10 @@ import { type SystemError } from "bun" import type { Provider } from "@/provider/provider" export namespace MessageV2 { + export function isMedia(mime: string) { + return mime.startsWith("image/") || mime === "application/pdf" + } + export const OutputLengthError = NamedError.create("MessageOutputLengthError", z.object({})) export const AbortedError = NamedError.create("MessageAbortedError", z.object({ message: z.string() })) export const StructuredOutputError = NamedError.create( @@ -196,6 +200,7 @@ export namespace MessageV2 { export const CompactionPart = PartBase.extend({ type: z.literal("compaction"), auto: z.boolean(), + overflow: z.boolean().optional(), }).meta({ ref: "CompactionPart", }) @@ -488,7 +493,11 @@ export namespace MessageV2 { }) export type WithParts = z.infer - export function toModelMessages(input: WithParts[], model: Provider.Model): ModelMessage[] { + export function toModelMessages( + input: WithParts[], + model: Provider.Model, + options?: { stripMedia?: boolean }, + ): ModelMessage[] { const result: UIMessage[] = [] const toolNames = new Set() // Track media from tool results that need to be injected as user messages @@ -562,13 +571,21 @@ export namespace MessageV2 { text: part.text, }) // text/plain and directory files are converted into text parts, ignore them - if (part.type === "file" && part.mime !== "text/plain" && part.mime !== "application/x-directory") - userMessage.parts.push({ - type: "file", - url: part.url, - mediaType: part.mime, - filename: part.filename, - }) + if (part.type === "file" && part.mime !== "text/plain" && part.mime !== "application/x-directory") { + if (options?.stripMedia && isMedia(part.mime)) { + userMessage.parts.push({ + type: "text", + text: `[Attached ${part.mime}: ${part.filename ?? "file"}]`, + }) + } else { + userMessage.parts.push({ + type: "file", + url: part.url, + mediaType: part.mime, + filename: part.filename, + }) + } + } if (part.type === "compaction") { userMessage.parts.push({ @@ -618,14 +635,12 @@ export namespace MessageV2 { toolNames.add(part.tool) if (part.state.status === "completed") { const outputText = part.state.time.compacted ? "[Old tool result content cleared]" : part.state.output - const attachments = part.state.time.compacted ? [] : (part.state.attachments ?? []) + const attachments = part.state.time.compacted || options?.stripMedia ? [] : (part.state.attachments ?? []) // For providers that don't support media in tool results, extract media files // (images, PDFs) to be sent as a separate user message - const isMediaAttachment = (a: { mime: string }) => - a.mime.startsWith("image/") || a.mime === "application/pdf" - const mediaAttachments = attachments.filter(isMediaAttachment) - const nonMediaAttachments = attachments.filter((a) => !isMediaAttachment(a)) + const mediaAttachments = attachments.filter((a) => isMedia(a.mime)) + const nonMediaAttachments = attachments.filter((a) => !isMedia(a.mime)) if (!supportsMediaInToolResults && mediaAttachments.length > 0) { media.push(...mediaAttachments) } @@ -802,7 +817,8 @@ export namespace MessageV2 { msg.parts.some((part) => part.type === "compaction") ) break - if (msg.info.role === "assistant" && msg.info.summary && msg.info.finish) completed.add(msg.info.parentID) + if (msg.info.role === "assistant" && msg.info.summary && msg.info.finish && !msg.info.error) + completed.add(msg.info.parentID) } result.reverse() return result diff --git a/packages/opencode/src/session/processor.ts b/packages/opencode/src/session/processor.ts index e7532d20073..67edc0ecfe3 100644 --- a/packages/opencode/src/session/processor.ts +++ b/packages/opencode/src/session/processor.ts @@ -279,7 +279,10 @@ export namespace SessionProcessor { sessionID: input.sessionID, messageID: input.assistantMessage.parentID, }) - if (await SessionCompaction.isOverflow({ tokens: usage.tokens, model: input.model })) { + if ( + !input.assistantMessage.summary && + (await SessionCompaction.isOverflow({ tokens: usage.tokens, model: input.model })) + ) { needsCompaction = true } break @@ -354,27 +357,32 @@ export namespace SessionProcessor { }) const error = MessageV2.fromError(e, { providerID: input.model.providerID }) if (MessageV2.ContextOverflowError.isInstance(error)) { - // TODO: Handle context overflow error - } - const retry = SessionRetry.retryable(error) - if (retry !== undefined) { - attempt++ - const delay = SessionRetry.delay(attempt, error.name === "APIError" ? error : undefined) - SessionStatus.set(input.sessionID, { - type: "retry", - attempt, - message: retry, - next: Date.now() + delay, + needsCompaction = true + Bus.publish(Session.Event.Error, { + sessionID: input.sessionID, + error, }) - await SessionRetry.sleep(delay, input.abort).catch(() => {}) - continue + } else { + const retry = SessionRetry.retryable(error) + if (retry !== undefined) { + attempt++ + const delay = SessionRetry.delay(attempt, error.name === "APIError" ? error : undefined) + SessionStatus.set(input.sessionID, { + type: "retry", + attempt, + message: retry, + next: Date.now() + delay, + }) + await SessionRetry.sleep(delay, input.abort).catch(() => {}) + continue + } + input.assistantMessage.error = error + Bus.publish(Session.Event.Error, { + sessionID: input.assistantMessage.sessionID, + error: input.assistantMessage.error, + }) + SessionStatus.set(input.sessionID, { type: "idle" }) } - input.assistantMessage.error = error - Bus.publish(Session.Event.Error, { - sessionID: input.assistantMessage.sessionID, - error: input.assistantMessage.error, - }) - SessionStatus.set(input.sessionID, { type: "idle" }) } if (snapshot) { const patch = await Snapshot.patch(snapshot) diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 75bd3c9dfac..9dbb434f298 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -2,6 +2,7 @@ import path from "path" import os from "os" import fs from "fs/promises" import z from "zod" +import { zodToJsonSchema } from "zod-to-json-schema" import { Filesystem } from "../util/filesystem" import { Identifier } from "../id/id" import { MessageV2 } from "./message-v2" @@ -533,6 +534,7 @@ export namespace SessionPrompt { abort, sessionID, auto: task.auto, + overflow: task.overflow, }) if (result === "stop") break continue @@ -707,6 +709,7 @@ export namespace SessionPrompt { agent: lastUser.agent, model: lastUser.model, auto: true, + overflow: !processor.message.finish, }) } continue @@ -782,7 +785,7 @@ export namespace SessionPrompt { { modelID: input.model.api.id, providerID: input.model.providerID }, input.agent, )) { - const schema = ProviderTransform.schema(input.model, z.toJSONSchema(item.parameters)) + const schema = ProviderTransform.schema(input.model, zodToJsonSchema(item.parameters)) tools[item.id] = tool({ id: item.id as any, description: item.description, diff --git a/packages/opencode/src/session/session.sql.ts b/packages/opencode/src/session/session.sql.ts index 9c5c72c4c57..0630760f3bc 100644 --- a/packages/opencode/src/session/session.sql.ts +++ b/packages/opencode/src/session/session.sql.ts @@ -15,6 +15,7 @@ export const SessionTable = sqliteTable( project_id: text() .notNull() .references(() => ProjectTable.id, { onDelete: "cascade" }), + workspace_id: text(), parent_id: text(), slug: text().notNull(), directory: text().notNull(), @@ -31,7 +32,11 @@ export const SessionTable = sqliteTable( time_compacting: integer(), time_archived: integer(), }, - (table) => [index("session_project_idx").on(table.project_id), index("session_parent_idx").on(table.parent_id)], + (table) => [ + index("session_project_idx").on(table.project_id), + index("session_workspace_idx").on(table.workspace_id), + index("session_parent_idx").on(table.parent_id), + ], ) export const MessageTable = sqliteTable( diff --git a/packages/opencode/src/tool/bash.txt b/packages/opencode/src/tool/bash.txt index 47e9378e755..baafb00810a 100644 --- a/packages/opencode/src/tool/bash.txt +++ b/packages/opencode/src/tool/bash.txt @@ -24,7 +24,7 @@ Usage notes: - The command argument is required. - You can specify an optional timeout in milliseconds. If not specified, commands will time out after 120000ms (2 minutes). - It is very helpful if you write a clear, concise description of what this command does in 5-10 words. - - If the output exceeds ${maxLines} lines or ${maxBytes} bytes, it will be truncated and the full output will be written to a file. You can use Read with offset/limit to read specific sections or Grep to search the full content. Because of this, you do NOT need to use `head`, `tail`, or other truncation commands to limit output - just run the command directly. + - If the output exceeds ${maxLines} lines or ${maxBytes} bytes, it will be truncated and the full output will be written to a file. You can use Read with offset/limit to read specific sections or Grep to search the full content. Do NOT use `head`, `tail`, or other truncation commands to limit output; the full output will already be captured to a file for more precise searching. - Avoid using Bash with the `find`, `grep`, `cat`, `head`, `tail`, `sed`, `awk`, or `echo` commands, unless explicitly instructed or when these commands are truly necessary for the task. Instead, always prefer using the dedicated tools for these commands: - File search: Use Glob (NOT find or ls) diff --git a/packages/opencode/src/worktree/index.ts b/packages/opencode/src/worktree/index.ts index d85a0843fba..2267322494b 100644 --- a/packages/opencode/src/worktree/index.ts +++ b/packages/opencode/src/worktree/index.ts @@ -331,7 +331,7 @@ export namespace Worktree { }, 0) } - export const create = fn(CreateInput.optional(), async (input) => { + export async function makeWorktreeInfo(name?: string): Promise { if (Instance.project.vcs !== "git") { throw new NotGitError({ message: "Worktrees are only supported for git projects" }) } @@ -339,9 +339,11 @@ export namespace Worktree { const root = path.join(Global.Path.data, "worktree", Instance.project.id) await fs.mkdir(root, { recursive: true }) - const base = input?.name ? slug(input.name) : "" - const info = await candidate(root, base || undefined) + const base = name ? slug(name) : "" + return candidate(root, base || undefined) + } + export async function createFromInfo(info: Info, startCommand?: string) { const created = await $`git worktree add --no-checkout -b ${info.branch} ${info.directory}` .quiet() .nothrow() @@ -353,8 +355,9 @@ export namespace Worktree { await Project.addSandbox(Instance.project.id, info.directory).catch(() => undefined) const projectID = Instance.project.id - const extra = input?.startCommand?.trim() - setTimeout(() => { + const extra = startCommand?.trim() + + return () => { const start = async () => { const populated = await $`git reset --hard`.quiet().nothrow().cwd(info.directory) if (populated.exitCode !== 0) { @@ -411,8 +414,17 @@ export namespace Worktree { void start().catch((error) => { log.error("worktree start task failed", { directory: info.directory, error }) }) - }, 0) + } + } + export const create = fn(CreateInput.optional(), async (input) => { + const info = await makeWorktreeInfo(input?.name) + const bootstrap = await createFromInfo(info, input?.startCommand) + // This is needed due to how worktrees currently work in the + // desktop app + setTimeout(() => { + bootstrap() + }, 0) return info }) diff --git a/packages/opencode/test/auth/auth.test.ts b/packages/opencode/test/auth/auth.test.ts new file mode 100644 index 00000000000..a569c71139e --- /dev/null +++ b/packages/opencode/test/auth/auth.test.ts @@ -0,0 +1,58 @@ +import { test, expect } from "bun:test" +import { Auth } from "../../src/auth" + +test("set normalizes trailing slashes in keys", async () => { + await Auth.set("https://example.com/", { + type: "wellknown", + key: "TOKEN", + token: "abc", + }) + const data = await Auth.all() + expect(data["https://example.com"]).toBeDefined() + expect(data["https://example.com/"]).toBeUndefined() +}) + +test("set cleans up pre-existing trailing-slash entry", async () => { + // Simulate a pre-fix entry with trailing slash + await Auth.set("https://example.com/", { + type: "wellknown", + key: "TOKEN", + token: "old", + }) + // Re-login with normalized key (as the CLI does post-fix) + await Auth.set("https://example.com", { + type: "wellknown", + key: "TOKEN", + token: "new", + }) + const data = await Auth.all() + const keys = Object.keys(data).filter((k) => k.includes("example.com")) + expect(keys).toEqual(["https://example.com"]) + const entry = data["https://example.com"]! + expect(entry.type).toBe("wellknown") + if (entry.type === "wellknown") expect(entry.token).toBe("new") +}) + +test("remove deletes both trailing-slash and normalized keys", async () => { + await Auth.set("https://example.com", { + type: "wellknown", + key: "TOKEN", + token: "abc", + }) + await Auth.remove("https://example.com/") + const data = await Auth.all() + expect(data["https://example.com"]).toBeUndefined() + expect(data["https://example.com/"]).toBeUndefined() +}) + +test("set and remove are no-ops on keys without trailing slashes", async () => { + await Auth.set("anthropic", { + type: "api", + key: "sk-test", + }) + const data = await Auth.all() + expect(data["anthropic"]).toBeDefined() + await Auth.remove("anthropic") + const after = await Auth.all() + expect(after["anthropic"]).toBeUndefined() +}) diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts index f245dc3493d..40ab97449fb 100644 --- a/packages/opencode/test/config/config.test.ts +++ b/packages/opencode/test/config/config.test.ts @@ -1535,6 +1535,71 @@ test("project config overrides remote well-known config", async () => { } }) +test("wellknown URL with trailing slash is normalized", async () => { + const originalFetch = globalThis.fetch + let fetchedUrl: string | undefined + const mockFetch = mock((url: string | URL | Request) => { + const urlStr = url.toString() + if (urlStr.includes(".well-known/opencode")) { + fetchedUrl = urlStr + return Promise.resolve( + new Response( + JSON.stringify({ + config: { + mcp: { + slack: { + type: "remote", + url: "https://slack.example.com/mcp", + enabled: true, + }, + }, + }, + }), + { status: 200 }, + ), + ) + } + return originalFetch(url) + }) + globalThis.fetch = mockFetch as unknown as typeof fetch + + const originalAuthAll = Auth.all + Auth.all = mock(() => + Promise.resolve({ + "https://example.com/": { + type: "wellknown" as const, + key: "TEST_TOKEN", + token: "test-token", + }, + }), + ) + + try { + await using tmp = await tmpdir({ + git: true, + init: async (dir) => { + await Filesystem.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + }), + ) + }, + }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + await Config.get() + // Trailing slash should be stripped — no double slash in the fetch URL + expect(fetchedUrl).toBe("https://example.com/.well-known/opencode") + }, + }) + } finally { + globalThis.fetch = originalFetch + Auth.all = originalAuthAll + } +}) + describe("getPluginName", () => { test("extracts name from file:// URL", () => { expect(Config.getPluginName("file:///path/to/plugin/foo.js")).toBe("foo") diff --git a/packages/opencode/test/control-plane/session-proxy-middleware.test.ts b/packages/opencode/test/control-plane/session-proxy-middleware.test.ts index 596e4761ea9..369b9152ae7 100644 --- a/packages/opencode/test/control-plane/session-proxy-middleware.test.ts +++ b/packages/opencode/test/control-plane/session-proxy-middleware.test.ts @@ -5,8 +5,11 @@ import { tmpdir } from "../fixture/fixture" import { Project } from "../../src/project/project" import { WorkspaceTable } from "../../src/control-plane/workspace.sql" import { Instance } from "../../src/project/instance" +import { WorkspaceContext } from "../../src/control-plane/workspace-context" import { Database } from "../../src/storage/db" import { resetDatabase } from "../fixture/db" +import * as adaptors from "../../src/control-plane/adaptors" +import type { Adaptor } from "../../src/control-plane/types" afterEach(async () => { mock.restore() @@ -18,18 +21,35 @@ type State = { calls: Array<{ method: string; url: string; body?: string }> } -const remote = { type: "testing", name: "remote-a" } as unknown as typeof WorkspaceTable.$inferInsert.config +const remote = { type: "testing", name: "remote-a" } as unknown as typeof WorkspaceTable.$inferInsert async function setup(state: State) { - mock.module("../../src/control-plane/adaptors", () => ({ - getAdaptor: () => ({ - request: async (_config: unknown, method: string, url: string, data?: BodyInit) => { - const body = data ? await new Response(data).text() : undefined - state.calls.push({ method, url, body }) - return new Response("proxied", { status: 202 }) - }, - }), - })) + const TestAdaptor: Adaptor = { + configure(config) { + return config + }, + async create() { + throw new Error("not used") + }, + async remove() {}, + + async fetch(_config: unknown, input: RequestInfo | URL, init?: RequestInit) { + const url = + input instanceof Request || input instanceof URL + ? input.toString() + : new URL(input, "http://workspace.test").toString() + const request = new Request(url, init) + const body = request.method === "GET" || request.method === "HEAD" ? undefined : await request.text() + state.calls.push({ + method: request.method, + url: `${new URL(request.url).pathname}${new URL(request.url).search}`, + body, + }) + return new Response("proxied", { status: 202 }) + }, + } + + adaptors.installAdaptor("testing", TestAdaptor) await using tmp = await tmpdir({ git: true }) const { project } = await Project.fromDirectory(tmp.path) @@ -45,20 +65,23 @@ async function setup(state: State) { id: id1, branch: "main", project_id: project.id, - config: remote, + type: remote.type, + name: remote.name, }, { id: id2, branch: "main", project_id: project.id, - config: { type: "worktree", directory: tmp.path }, + type: "worktree", + directory: tmp.path, + name: "local", }, ]) .run(), ) - const { SessionProxyMiddleware } = await import("../../src/control-plane/session-proxy-middleware") - const app = new Hono().use(SessionProxyMiddleware) + const { WorkspaceRouterMiddleware } = await import("../../src/control-plane/workspace-router-middleware") + const app = new Hono().use(WorkspaceRouterMiddleware) return { id1, @@ -66,15 +89,19 @@ async function setup(state: State) { app, async request(input: RequestInfo | URL, init?: RequestInit) { return Instance.provide({ - directory: state.workspace === "first" ? id1 : id2, - fn: async () => app.request(input, init), + directory: tmp.path, + fn: async () => + WorkspaceContext.provide({ + workspaceID: state.workspace === "first" ? id1 : id2, + fn: () => app.request(input, init), + }), }) }, } } describe("control-plane/session-proxy-middleware", () => { - test("forwards non-GET session requests for remote workspaces", async () => { + test("forwards non-GET session requests for workspaces", async () => { const state: State = { workspace: "first", calls: [], @@ -102,46 +129,21 @@ describe("control-plane/session-proxy-middleware", () => { ]) }) - test("does not forward GET requests", async () => { - const state: State = { - workspace: "first", - calls: [], - } + // It will behave this way when we have syncing + // + // test("does not forward GET requests", async () => { + // const state: State = { + // workspace: "first", + // calls: [], + // } - const ctx = await setup(state) + // const ctx = await setup(state) - ctx.app.get("/session/foo", (c) => c.text("local", 200)) - const response = await ctx.request("http://workspace.test/session/foo?x=1") + // ctx.app.get("/session/foo", (c) => c.text("local", 200)) + // const response = await ctx.request("http://workspace.test/session/foo?x=1") - expect(response.status).toBe(200) - expect(await response.text()).toBe("local") - expect(state.calls).toEqual([]) - }) - - test("does not forward GET or POST requests for worktree workspaces", async () => { - const state: State = { - workspace: "second", - calls: [], - } - - const ctx = await setup(state) - - ctx.app.get("/session/foo", (c) => c.text("local-get", 200)) - ctx.app.post("/session/foo", (c) => c.text("local-post", 200)) - - const getResponse = await ctx.request("http://workspace.test/session/foo?x=1") - const postResponse = await ctx.request("http://workspace.test/session/foo?x=1", { - method: "POST", - body: JSON.stringify({ hello: "world" }), - headers: { - "content-type": "application/json", - }, - }) - - expect(getResponse.status).toBe(200) - expect(await getResponse.text()).toBe("local-get") - expect(postResponse.status).toBe(200) - expect(await postResponse.text()).toBe("local-post") - expect(state.calls).toEqual([]) - }) + // expect(response.status).toBe(200) + // expect(await response.text()).toBe("local") + // expect(state.calls).toEqual([]) + // }) }) diff --git a/packages/opencode/test/control-plane/workspace-server-sse.test.ts b/packages/opencode/test/control-plane/workspace-server-sse.test.ts index 91504af0fa0..7e7cddb1404 100644 --- a/packages/opencode/test/control-plane/workspace-server-sse.test.ts +++ b/packages/opencode/test/control-plane/workspace-server-sse.test.ts @@ -4,6 +4,7 @@ import { WorkspaceServer } from "../../src/control-plane/workspace-server/server import { parseSSE } from "../../src/control-plane/sse" import { GlobalBus } from "../../src/bus/global" import { resetDatabase } from "../fixture/db" +import { tmpdir } from "../fixture/fixture" afterEach(async () => { await resetDatabase() @@ -13,13 +14,17 @@ Log.init({ print: false }) describe("control-plane/workspace-server SSE", () => { test("streams GlobalBus events and parseSSE reads them", async () => { + await using tmp = await tmpdir({ git: true }) const app = WorkspaceServer.App() const stop = new AbortController() const seen: unknown[] = [] - try { const response = await app.request("/event", { signal: stop.signal, + headers: { + "x-opencode-workspace": "wrk_test_workspace", + "x-opencode-directory": tmp.path, + }, }) expect(response.status).toBe(200) diff --git a/packages/opencode/test/control-plane/workspace-sync.test.ts b/packages/opencode/test/control-plane/workspace-sync.test.ts index 2769c8a3b5a..899118920fb 100644 --- a/packages/opencode/test/control-plane/workspace-sync.test.ts +++ b/packages/opencode/test/control-plane/workspace-sync.test.ts @@ -7,6 +7,8 @@ import { Database } from "../../src/storage/db" import { WorkspaceTable } from "../../src/control-plane/workspace.sql" import { GlobalBus } from "../../src/bus/global" import { resetDatabase } from "../fixture/db" +import * as adaptors from "../../src/control-plane/adaptors" +import type { Adaptor } from "../../src/control-plane/types" afterEach(async () => { mock.restore() @@ -15,35 +17,34 @@ afterEach(async () => { Log.init({ print: false }) -const seen: string[] = [] -const remote = { type: "testing", name: "remote-a" } as unknown as typeof WorkspaceTable.$inferInsert.config +const remote = { type: "testing", name: "remote-a" } as unknown as typeof WorkspaceTable.$inferInsert -mock.module("../../src/control-plane/adaptors", () => ({ - getAdaptor: (config: { type: string }) => { - seen.push(config.type) - return { - async create() { - throw new Error("not used") +const TestAdaptor: Adaptor = { + configure(config) { + return config + }, + async create() { + throw new Error("not used") + }, + async remove() {}, + async fetch(_config: unknown, _input: RequestInfo | URL, _init?: RequestInit) { + const body = new ReadableStream({ + start(controller) { + const encoder = new TextEncoder() + controller.enqueue(encoder.encode('data: {"type":"remote.ready","properties":{}}\n\n')) + controller.close() }, - async remove() {}, - async request() { - const body = new ReadableStream({ - start(controller) { - const encoder = new TextEncoder() - controller.enqueue(encoder.encode('data: {"type":"remote.ready","properties":{}}\n\n')) - controller.close() - }, - }) - return new Response(body, { - status: 200, - headers: { - "content-type": "text/event-stream", - }, - }) + }) + return new Response(body, { + status: 200, + headers: { + "content-type": "text/event-stream", }, - } + }) }, -})) +} + +adaptors.installAdaptor("testing", TestAdaptor) describe("control-plane/workspace.startSyncing", () => { test("syncs only remote workspaces and emits remote SSE events", async () => { @@ -62,13 +63,16 @@ describe("control-plane/workspace.startSyncing", () => { id: id1, branch: "main", project_id: project.id, - config: remote, + type: remote.type, + name: remote.name, }, { id: id2, branch: "main", project_id: project.id, - config: { type: "worktree", directory: tmp.path }, + type: "worktree", + directory: tmp.path, + name: "local", }, ]) .run(), @@ -91,7 +95,5 @@ describe("control-plane/workspace.startSyncing", () => { ]) await sync.stop() - expect(seen).toContain("testing") - expect(seen).not.toContain("worktree") }) }) diff --git a/packages/opencode/test/provider/provider.test.ts b/packages/opencode/test/provider/provider.test.ts index 0a5aa415131..11c943db6f8 100644 --- a/packages/opencode/test/provider/provider.test.ts +++ b/packages/opencode/test/provider/provider.test.ts @@ -2218,3 +2218,64 @@ test("Google Vertex: supports OpenAI compatible models", async () => { }, }) }) + +test("cloudflare-ai-gateway loads with env variables", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + }), + ) + }, + }) + await Instance.provide({ + directory: tmp.path, + init: async () => { + Env.set("CLOUDFLARE_ACCOUNT_ID", "test-account") + Env.set("CLOUDFLARE_GATEWAY_ID", "test-gateway") + Env.set("CLOUDFLARE_API_TOKEN", "test-token") + }, + fn: async () => { + const providers = await Provider.list() + expect(providers["cloudflare-ai-gateway"]).toBeDefined() + }, + }) +}) + +test("cloudflare-ai-gateway forwards config metadata options", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + provider: { + "cloudflare-ai-gateway": { + options: { + metadata: { invoked_by: "test", project: "opencode" }, + }, + }, + }, + }), + ) + }, + }) + await Instance.provide({ + directory: tmp.path, + init: async () => { + Env.set("CLOUDFLARE_ACCOUNT_ID", "test-account") + Env.set("CLOUDFLARE_GATEWAY_ID", "test-gateway") + Env.set("CLOUDFLARE_API_TOKEN", "test-token") + }, + fn: async () => { + const providers = await Provider.list() + expect(providers["cloudflare-ai-gateway"]).toBeDefined() + expect(providers["cloudflare-ai-gateway"].options.metadata).toEqual({ + invoked_by: "test", + project: "opencode", + }) + }, + }) +}) diff --git a/packages/opencode/test/provider/transform.test.ts b/packages/opencode/test/provider/transform.test.ts index 189bdfd32b4..2329846351c 100644 --- a/packages/opencode/test/provider/transform.test.ts +++ b/packages/opencode/test/provider/transform.test.ts @@ -510,6 +510,106 @@ describe("ProviderTransform.schema - gemini nested array items", () => { }) }) +describe("ProviderTransform.schema - gemini combiner nodes", () => { + const geminiModel = { + providerID: "google", + api: { + id: "gemini-3-pro", + }, + } as any + + const walk = (node: any, cb: (node: any, path: (string | number)[]) => void, path: (string | number)[] = []) => { + if (node === null || typeof node !== "object") { + return + } + if (Array.isArray(node)) { + node.forEach((item, i) => walk(item, cb, [...path, i])) + return + } + cb(node, path) + Object.entries(node).forEach(([key, value]) => walk(value, cb, [...path, key])) + } + + test("keeps edits.items.anyOf without adding type", () => { + const schema = { + type: "object", + properties: { + edits: { + type: "array", + items: { + anyOf: [ + { + type: "object", + properties: { + old_string: { type: "string" }, + new_string: { type: "string" }, + }, + required: ["old_string", "new_string"], + }, + { + type: "object", + properties: { + old_string: { type: "string" }, + new_string: { type: "string" }, + replace_all: { type: "boolean" }, + }, + required: ["old_string", "new_string"], + }, + ], + }, + }, + }, + required: ["edits"], + } as any + + const result = ProviderTransform.schema(geminiModel, schema) as any + + expect(Array.isArray(result.properties.edits.items.anyOf)).toBe(true) + expect(result.properties.edits.items.type).toBeUndefined() + }) + + test("does not add sibling keys to combiner nodes during sanitize", () => { + const schema = { + type: "object", + properties: { + edits: { + type: "array", + items: { + anyOf: [{ type: "string" }, { type: "number" }], + }, + }, + value: { + oneOf: [{ type: "string" }, { type: "boolean" }], + }, + meta: { + allOf: [ + { + type: "object", + properties: { a: { type: "string" } }, + }, + { + type: "object", + properties: { b: { type: "string" } }, + }, + ], + }, + }, + } as any + const input = JSON.parse(JSON.stringify(schema)) + const result = ProviderTransform.schema(geminiModel, schema) as any + + walk(result, (node, path) => { + const hasCombiner = Array.isArray(node.anyOf) || Array.isArray(node.oneOf) || Array.isArray(node.allOf) + if (!hasCombiner) { + return + } + const before = path.reduce((acc: any, key) => acc?.[key], input) + const added = Object.keys(node).filter((key) => !(key in before)) + expect(added).toEqual([]) + }) + }) +}) + describe("ProviderTransform.schema - gemini non-object properties removal", () => { const geminiModel = { providerID: "google", diff --git a/packages/opencode/test/session/session.test.ts b/packages/opencode/test/session/session.test.ts index 219cef12713..aa9ca05d047 100644 --- a/packages/opencode/test/session/session.test.ts +++ b/packages/opencode/test/session/session.test.ts @@ -4,6 +4,8 @@ import { Session } from "../../src/session" import { Bus } from "../../src/bus" import { Log } from "../../src/util/log" import { Instance } from "../../src/project/instance" +import { MessageV2 } from "../../src/session/message-v2" +import { Identifier } from "../../src/id/id" const projectRoot = path.join(__dirname, "../..") Log.init({ print: false }) @@ -69,3 +71,72 @@ describe("session.started event", () => { }) }) }) + +describe("step-finish token propagation via Bus event", () => { + test( + "non-zero tokens propagate through PartUpdated event", + async () => { + await Instance.provide({ + directory: projectRoot, + fn: async () => { + const session = await Session.create({}) + + const messageID = Identifier.ascending("message") + await Session.updateMessage({ + id: messageID, + sessionID: session.id, + role: "user", + time: { created: Date.now() }, + agent: "user", + model: { providerID: "test", modelID: "test" }, + tools: {}, + mode: "", + } as unknown as MessageV2.Info) + + let received: MessageV2.Part | undefined + const unsub = Bus.subscribe(MessageV2.Event.PartUpdated, (event) => { + received = event.properties.part + }) + + const tokens = { + total: 1500, + input: 500, + output: 800, + reasoning: 200, + cache: { read: 100, write: 50 }, + } + + const partInput = { + id: Identifier.ascending("part"), + messageID, + sessionID: session.id, + type: "step-finish" as const, + reason: "stop", + cost: 0.005, + tokens, + } + + await Session.updatePart(partInput) + + await new Promise((resolve) => setTimeout(resolve, 100)) + + expect(received).toBeDefined() + expect(received!.type).toBe("step-finish") + const finish = received as MessageV2.StepFinishPart + expect(finish.tokens.input).toBe(500) + expect(finish.tokens.output).toBe(800) + expect(finish.tokens.reasoning).toBe(200) + expect(finish.tokens.total).toBe(1500) + expect(finish.tokens.cache.read).toBe(100) + expect(finish.tokens.cache.write).toBe(50) + expect(finish.cost).toBe(0.005) + expect(received).not.toBe(partInput) + + unsub() + await Session.remove(session.id) + }, + }) + }, + { timeout: 30000 }, + ) +}) diff --git a/packages/plugin/package.json b/packages/plugin/package.json index e476c41e2fb..2158c949f52 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/plugin", - "version": "1.2.15", + "version": "1.2.16", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index ffbdf219824..dc48510e612 100644 --- a/packages/sdk/js/package.json +++ b/packages/sdk/js/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/sdk", - "version": "1.2.15", + "version": "1.2.16", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts index ec8ee46857d..1c1b31e46f0 100644 --- a/packages/sdk/js/src/v2/gen/sdk.gen.ts +++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts @@ -373,10 +373,21 @@ export class Project extends HeyApiClient { public list( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).get({ url: "/project", ...options, @@ -392,10 +403,21 @@ export class Project extends HeyApiClient { public current( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).get({ url: "/project/current", ...options, @@ -412,6 +434,7 @@ export class Project extends HeyApiClient { parameters: { projectID: string directory?: string + workspace?: string name?: string icon?: { url?: string @@ -434,6 +457,7 @@ export class Project extends HeyApiClient { args: [ { in: "path", key: "projectID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "body", key: "name" }, { in: "body", key: "icon" }, { in: "body", key: "commands" }, @@ -463,10 +487,21 @@ export class Pty extends HeyApiClient { public list( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).get({ url: "/pty", ...options, @@ -482,6 +517,7 @@ export class Pty extends HeyApiClient { public create( parameters?: { directory?: string + workspace?: string command?: string args?: Array cwd?: string @@ -498,6 +534,7 @@ export class Pty extends HeyApiClient { { args: [ { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "body", key: "command" }, { in: "body", key: "args" }, { in: "body", key: "cwd" }, @@ -528,6 +565,7 @@ export class Pty extends HeyApiClient { parameters: { ptyID: string directory?: string + workspace?: string }, options?: Options, ) { @@ -538,6 +576,7 @@ export class Pty extends HeyApiClient { args: [ { in: "path", key: "ptyID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], @@ -558,6 +597,7 @@ export class Pty extends HeyApiClient { parameters: { ptyID: string directory?: string + workspace?: string }, options?: Options, ) { @@ -568,6 +608,7 @@ export class Pty extends HeyApiClient { args: [ { in: "path", key: "ptyID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], @@ -588,6 +629,7 @@ export class Pty extends HeyApiClient { parameters: { ptyID: string directory?: string + workspace?: string title?: string size?: { rows: number @@ -603,6 +645,7 @@ export class Pty extends HeyApiClient { args: [ { in: "path", key: "ptyID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "body", key: "title" }, { in: "body", key: "size" }, ], @@ -630,6 +673,7 @@ export class Pty extends HeyApiClient { parameters: { ptyID: string directory?: string + workspace?: string }, options?: Options, ) { @@ -640,6 +684,7 @@ export class Pty extends HeyApiClient { args: [ { in: "path", key: "ptyID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], @@ -661,10 +706,21 @@ export class Config2 extends HeyApiClient { public get( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).get({ url: "/config", ...options, @@ -680,6 +736,7 @@ export class Config2 extends HeyApiClient { public update( parameters?: { directory?: string + workspace?: string config?: Config3 }, options?: Options, @@ -690,6 +747,7 @@ export class Config2 extends HeyApiClient { { args: [ { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { key: "config", map: "body" }, ], }, @@ -715,10 +773,21 @@ export class Config2 extends HeyApiClient { public providers( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).get({ url: "/config/providers", ...options, @@ -736,10 +805,21 @@ export class Tool extends HeyApiClient { public ids( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).get({ url: "/experimental/tool/ids", ...options, @@ -755,6 +835,7 @@ export class Tool extends HeyApiClient { public list( parameters: { directory?: string + workspace?: string provider: string model: string }, @@ -766,6 +847,7 @@ export class Tool extends HeyApiClient { { args: [ { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "query", key: "provider" }, { in: "query", key: "model" }, ], @@ -780,16 +862,16 @@ export class Tool extends HeyApiClient { } } -export class Worktree extends HeyApiClient { +export class Workspace extends HeyApiClient { /** - * Remove worktree + * List workspaces * - * Remove a git worktree and delete its branch. + * List all workspaces. */ - public remove( + public list( parameters?: { directory?: string - worktreeRemoveInput?: WorktreeRemoveInput + workspace?: string }, options?: Options, ) { @@ -799,51 +881,75 @@ export class Worktree extends HeyApiClient { { args: [ { in: "query", key: "directory" }, - { key: "worktreeRemoveInput", map: "body" }, + { in: "query", key: "workspace" }, ], }, ], ) - return (options?.client ?? this.client).delete({ - url: "/experimental/worktree", + return (options?.client ?? this.client).get({ + url: "/experimental/workspace", ...options, ...params, - headers: { - "Content-Type": "application/json", - ...options?.headers, - ...params.headers, - }, }) } /** - * List worktrees + * Create workspace * - * List all sandbox worktrees for the current project. + * Create a workspace for the current project. */ - public list( + public create( parameters?: { directory?: string + workspace?: string + id?: string + type?: string + branch?: string | null + extra?: unknown | null }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) - return (options?.client ?? this.client).get({ - url: "/experimental/worktree", + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + { in: "body", key: "id" }, + { in: "body", key: "type" }, + { in: "body", key: "branch" }, + { in: "body", key: "extra" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post< + ExperimentalWorkspaceCreateResponses, + ExperimentalWorkspaceCreateErrors, + ThrowOnError + >({ + url: "/experimental/workspace", ...options, ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, }) } /** - * Create worktree + * Remove workspace * - * Create a new git worktree for the current project and run any configured startup scripts. + * Remove an existing workspace. */ - public create( - parameters?: { + public remove( + parameters: { + id: string directory?: string - worktreeCreateInput?: WorktreeCreateInput + workspace?: string }, options?: Options, ) { @@ -852,33 +958,41 @@ export class Worktree extends HeyApiClient { [ { args: [ + { in: "path", key: "id" }, { in: "query", key: "directory" }, - { key: "worktreeCreateInput", map: "body" }, + { in: "query", key: "workspace" }, ], }, ], ) - return (options?.client ?? this.client).post({ - url: "/experimental/worktree", + return (options?.client ?? this.client).delete< + ExperimentalWorkspaceRemoveResponses, + ExperimentalWorkspaceRemoveErrors, + ThrowOnError + >({ + url: "/experimental/workspace/{id}", ...options, ...params, - headers: { - "Content-Type": "application/json", - ...options?.headers, - ...params.headers, - }, }) } +} +export class Session extends HeyApiClient { /** - * Reset worktree + * List sessions * - * Reset a worktree branch to the primary default branch. + * Get a list of all OpenCode sessions across projects, sorted by most recently updated. Archived sessions are excluded by default. */ - public reset( + public list( parameters?: { directory?: string - worktreeResetInput?: WorktreeResetInput + workspace?: string + roots?: boolean + start?: number + cursor?: number + search?: string + limit?: number + archived?: boolean }, options?: Options, ) { @@ -888,34 +1002,35 @@ export class Worktree extends HeyApiClient { { args: [ { in: "query", key: "directory" }, - { key: "worktreeResetInput", map: "body" }, + { in: "query", key: "workspace" }, + { in: "query", key: "roots" }, + { in: "query", key: "start" }, + { in: "query", key: "cursor" }, + { in: "query", key: "search" }, + { in: "query", key: "limit" }, + { in: "query", key: "archived" }, ], }, ], ) - return (options?.client ?? this.client).post({ - url: "/experimental/worktree/reset", + return (options?.client ?? this.client).get({ + url: "/experimental/session", ...options, ...params, - headers: { - "Content-Type": "application/json", - ...options?.headers, - ...params.headers, - }, }) } } -export class Workspace extends HeyApiClient { +export class Resource extends HeyApiClient { /** - * Remove workspace + * Get MCP resources * - * Remove an existing workspace. + * Get all available MCP resources from connected servers. Optionally filter by name. */ - public remove( - parameters: { - id: string + public list( + parameters?: { directory?: string + workspace?: string }, options?: Options, ) { @@ -924,37 +1039,48 @@ export class Workspace extends HeyApiClient { [ { args: [ - { in: "path", key: "id" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], ) - return (options?.client ?? this.client).delete< - ExperimentalWorkspaceRemoveResponses, - ExperimentalWorkspaceRemoveErrors, - ThrowOnError - >({ - url: "/experimental/workspace/{id}", + return (options?.client ?? this.client).get({ + url: "/experimental/resource", ...options, ...params, }) } +} + +export class Experimental extends HeyApiClient { + private _workspace?: Workspace + get workspace(): Workspace { + return (this._workspace ??= new Workspace({ client: this.client })) + } + private _session?: Session + get session(): Session { + return (this._session ??= new Session({ client: this.client })) + } + + private _resource?: Resource + get resource(): Resource { + return (this._resource ??= new Resource({ client: this.client })) + } +} + +export class Worktree extends HeyApiClient { /** - * Create workspace + * Remove worktree * - * Create a workspace for the current project. + * Remove a git worktree and delete its branch. */ - public create( - parameters: { - id: string + public remove( + parameters?: { directory?: string - branch?: string | null - config?: { - directory: string - type: "worktree" - } + workspace?: string + worktreeRemoveInput?: WorktreeRemoveInput }, options?: Options, ) { @@ -963,20 +1089,15 @@ export class Workspace extends HeyApiClient { [ { args: [ - { in: "path", key: "id" }, { in: "query", key: "directory" }, - { in: "body", key: "branch" }, - { in: "body", key: "config" }, + { in: "query", key: "workspace" }, + { key: "worktreeRemoveInput", map: "body" }, ], }, ], ) - return (options?.client ?? this.client).post< - ExperimentalWorkspaceCreateResponses, - ExperimentalWorkspaceCreateErrors, - ThrowOnError - >({ - url: "/experimental/workspace/{id}", + return (options?.client ?? this.client).delete({ + url: "/experimental/worktree", ...options, ...params, headers: { @@ -988,40 +1109,45 @@ export class Workspace extends HeyApiClient { } /** - * List workspaces + * List worktrees * - * List all workspaces. + * List all sandbox worktrees for the current project. */ public list( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) - return (options?.client ?? this.client).get({ - url: "/experimental/workspace", + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) + return (options?.client ?? this.client).get({ + url: "/experimental/worktree", ...options, ...params, }) } -} -export class Session extends HeyApiClient { /** - * List sessions + * Create worktree * - * Get a list of all OpenCode sessions across projects, sorted by most recently updated. Archived sessions are excluded by default. + * Create a new git worktree for the current project and run any configured startup scripts. */ - public list( + public create( parameters?: { directory?: string - roots?: boolean - start?: number - cursor?: number - search?: string - limit?: number - archived?: boolean + workspace?: string + worktreeCreateInput?: WorktreeCreateInput }, options?: Options, ) { @@ -1031,59 +1157,59 @@ export class Session extends HeyApiClient { { args: [ { in: "query", key: "directory" }, - { in: "query", key: "roots" }, - { in: "query", key: "start" }, - { in: "query", key: "cursor" }, - { in: "query", key: "search" }, - { in: "query", key: "limit" }, - { in: "query", key: "archived" }, + { in: "query", key: "workspace" }, + { key: "worktreeCreateInput", map: "body" }, ], }, ], ) - return (options?.client ?? this.client).get({ - url: "/experimental/session", + return (options?.client ?? this.client).post({ + url: "/experimental/worktree", ...options, ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, }) } -} -export class Resource extends HeyApiClient { /** - * Get MCP resources + * Reset worktree * - * Get all available MCP resources from connected servers. Optionally filter by name. + * Reset a worktree branch to the primary default branch. */ - public list( + public reset( parameters?: { directory?: string + workspace?: string + worktreeResetInput?: WorktreeResetInput }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) - return (options?.client ?? this.client).get({ - url: "/experimental/resource", - ...options, - ...params, - }) - } -} - -export class Experimental extends HeyApiClient { - private _workspace?: Workspace - get workspace(): Workspace { - return (this._workspace ??= new Workspace({ client: this.client })) - } - - private _session?: Session - get session(): Session { - return (this._session ??= new Session({ client: this.client })) - } - - private _resource?: Resource - get resource(): Resource { - return (this._resource ??= new Resource({ client: this.client })) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + { key: "worktreeResetInput", map: "body" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post({ + url: "/experimental/worktree/reset", + ...options, + ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, + }) } } @@ -1096,6 +1222,7 @@ export class Session2 extends HeyApiClient { public list( parameters?: { directory?: string + workspace?: string roots?: boolean start?: number search?: string @@ -1109,6 +1236,7 @@ export class Session2 extends HeyApiClient { { args: [ { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "query", key: "roots" }, { in: "query", key: "start" }, { in: "query", key: "search" }, @@ -1132,6 +1260,7 @@ export class Session2 extends HeyApiClient { public create( parameters?: { directory?: string + workspace?: string parentID?: string title?: string permission?: PermissionRuleset @@ -1144,6 +1273,7 @@ export class Session2 extends HeyApiClient { { args: [ { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "body", key: "parentID" }, { in: "body", key: "title" }, { in: "body", key: "permission" }, @@ -1171,10 +1301,21 @@ export class Session2 extends HeyApiClient { public status( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).get({ url: "/session/status", ...options, @@ -1191,6 +1332,7 @@ export class Session2 extends HeyApiClient { parameters: { sessionID: string directory?: string + workspace?: string }, options?: Options, ) { @@ -1201,6 +1343,7 @@ export class Session2 extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], @@ -1221,6 +1364,7 @@ export class Session2 extends HeyApiClient { parameters: { sessionID: string directory?: string + workspace?: string }, options?: Options, ) { @@ -1231,6 +1375,7 @@ export class Session2 extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], @@ -1251,6 +1396,7 @@ export class Session2 extends HeyApiClient { parameters: { sessionID: string directory?: string + workspace?: string title?: string time?: { archived?: number @@ -1265,6 +1411,7 @@ export class Session2 extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "body", key: "title" }, { in: "body", key: "time" }, ], @@ -1292,6 +1439,7 @@ export class Session2 extends HeyApiClient { parameters: { sessionID: string directory?: string + workspace?: string }, options?: Options, ) { @@ -1302,6 +1450,7 @@ export class Session2 extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], @@ -1322,6 +1471,7 @@ export class Session2 extends HeyApiClient { parameters: { sessionID: string directory?: string + workspace?: string }, options?: Options, ) { @@ -1332,6 +1482,7 @@ export class Session2 extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], @@ -1352,6 +1503,7 @@ export class Session2 extends HeyApiClient { parameters: { sessionID: string directory?: string + workspace?: string modelID?: string providerID?: string messageID?: string @@ -1365,6 +1517,7 @@ export class Session2 extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "body", key: "modelID" }, { in: "body", key: "providerID" }, { in: "body", key: "messageID" }, @@ -1393,6 +1546,7 @@ export class Session2 extends HeyApiClient { parameters: { sessionID: string directory?: string + workspace?: string messageID?: string }, options?: Options, @@ -1404,6 +1558,7 @@ export class Session2 extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "body", key: "messageID" }, ], }, @@ -1430,6 +1585,7 @@ export class Session2 extends HeyApiClient { parameters: { sessionID: string directory?: string + workspace?: string }, options?: Options, ) { @@ -1440,6 +1596,7 @@ export class Session2 extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], @@ -1460,6 +1617,7 @@ export class Session2 extends HeyApiClient { parameters: { sessionID: string directory?: string + workspace?: string }, options?: Options, ) { @@ -1470,6 +1628,7 @@ export class Session2 extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], @@ -1490,6 +1649,7 @@ export class Session2 extends HeyApiClient { parameters: { sessionID: string directory?: string + workspace?: string }, options?: Options, ) { @@ -1500,6 +1660,7 @@ export class Session2 extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], @@ -1520,6 +1681,7 @@ export class Session2 extends HeyApiClient { parameters: { sessionID: string directory?: string + workspace?: string messageID?: string }, options?: Options, @@ -1531,6 +1693,7 @@ export class Session2 extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "query", key: "messageID" }, ], }, @@ -1552,6 +1715,7 @@ export class Session2 extends HeyApiClient { parameters: { sessionID: string directory?: string + workspace?: string providerID?: string modelID?: string auto?: boolean @@ -1565,6 +1729,7 @@ export class Session2 extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "body", key: "providerID" }, { in: "body", key: "modelID" }, { in: "body", key: "auto" }, @@ -1593,6 +1758,7 @@ export class Session2 extends HeyApiClient { parameters: { sessionID: string directory?: string + workspace?: string limit?: number }, options?: Options, @@ -1604,6 +1770,7 @@ export class Session2 extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "query", key: "limit" }, ], }, @@ -1625,6 +1792,7 @@ export class Session2 extends HeyApiClient { parameters: { sessionID: string directory?: string + workspace?: string messageID?: string model?: { providerID: string @@ -1649,6 +1817,7 @@ export class Session2 extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "body", key: "messageID" }, { in: "body", key: "model" }, { in: "body", key: "agent" }, @@ -1684,6 +1853,7 @@ export class Session2 extends HeyApiClient { sessionID: string messageID: string directory?: string + workspace?: string }, options?: Options, ) { @@ -1695,6 +1865,7 @@ export class Session2 extends HeyApiClient { { in: "path", key: "sessionID" }, { in: "path", key: "messageID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], @@ -1720,6 +1891,7 @@ export class Session2 extends HeyApiClient { sessionID: string messageID: string directory?: string + workspace?: string }, options?: Options, ) { @@ -1731,6 +1903,7 @@ export class Session2 extends HeyApiClient { { in: "path", key: "sessionID" }, { in: "path", key: "messageID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], @@ -1751,6 +1924,7 @@ export class Session2 extends HeyApiClient { parameters: { sessionID: string directory?: string + workspace?: string messageID?: string model?: { providerID: string @@ -1775,6 +1949,7 @@ export class Session2 extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "body", key: "messageID" }, { in: "body", key: "model" }, { in: "body", key: "agent" }, @@ -1809,6 +1984,7 @@ export class Session2 extends HeyApiClient { parameters: { sessionID: string directory?: string + workspace?: string messageID?: string agent?: string model?: string @@ -1833,6 +2009,7 @@ export class Session2 extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "body", key: "messageID" }, { in: "body", key: "agent" }, { in: "body", key: "model" }, @@ -1865,6 +2042,7 @@ export class Session2 extends HeyApiClient { parameters: { sessionID: string directory?: string + workspace?: string agent?: string model?: { providerID: string @@ -1881,6 +2059,7 @@ export class Session2 extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "body", key: "agent" }, { in: "body", key: "model" }, { in: "body", key: "command" }, @@ -1909,6 +2088,7 @@ export class Session2 extends HeyApiClient { parameters: { sessionID: string directory?: string + workspace?: string messageID?: string partID?: string }, @@ -1921,6 +2101,7 @@ export class Session2 extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "body", key: "messageID" }, { in: "body", key: "partID" }, ], @@ -1948,6 +2129,7 @@ export class Session2 extends HeyApiClient { parameters: { sessionID: string directory?: string + workspace?: string }, options?: Options, ) { @@ -1958,6 +2140,7 @@ export class Session2 extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], @@ -1980,6 +2163,7 @@ export class Part extends HeyApiClient { messageID: string partID: string directory?: string + workspace?: string }, options?: Options, ) { @@ -1992,6 +2176,7 @@ export class Part extends HeyApiClient { { in: "path", key: "messageID" }, { in: "path", key: "partID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], @@ -2012,6 +2197,7 @@ export class Part extends HeyApiClient { messageID: string partID: string directory?: string + workspace?: string part?: Part2 }, options?: Options, @@ -2025,6 +2211,7 @@ export class Part extends HeyApiClient { { in: "path", key: "messageID" }, { in: "path", key: "partID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { key: "part", map: "body" }, ], }, @@ -2056,6 +2243,7 @@ export class Permission extends HeyApiClient { sessionID: string permissionID: string directory?: string + workspace?: string response?: "once" | "always" | "reject" }, options?: Options, @@ -2068,6 +2256,7 @@ export class Permission extends HeyApiClient { { in: "path", key: "sessionID" }, { in: "path", key: "permissionID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "body", key: "response" }, ], }, @@ -2094,6 +2283,7 @@ export class Permission extends HeyApiClient { parameters: { requestID: string directory?: string + workspace?: string reply?: "once" | "always" | "reject" message?: string }, @@ -2106,6 +2296,7 @@ export class Permission extends HeyApiClient { args: [ { in: "path", key: "requestID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "body", key: "reply" }, { in: "body", key: "message" }, ], @@ -2132,10 +2323,21 @@ export class Permission extends HeyApiClient { public list( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).get({ url: "/permission", ...options, @@ -2153,10 +2355,21 @@ export class Question extends HeyApiClient { public list( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).get({ url: "/question", ...options, @@ -2173,6 +2386,7 @@ export class Question extends HeyApiClient { parameters: { requestID: string directory?: string + workspace?: string answers?: Array }, options?: Options, @@ -2184,6 +2398,7 @@ export class Question extends HeyApiClient { args: [ { in: "path", key: "requestID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "body", key: "answers" }, ], }, @@ -2210,6 +2425,7 @@ export class Question extends HeyApiClient { parameters: { requestID: string directory?: string + workspace?: string }, options?: Options, ) { @@ -2220,6 +2436,7 @@ export class Question extends HeyApiClient { args: [ { in: "path", key: "requestID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], @@ -2242,6 +2459,7 @@ export class Oauth extends HeyApiClient { parameters: { providerID: string directory?: string + workspace?: string method?: number }, options?: Options, @@ -2253,6 +2471,7 @@ export class Oauth extends HeyApiClient { args: [ { in: "path", key: "providerID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "body", key: "method" }, ], }, @@ -2283,6 +2502,7 @@ export class Oauth extends HeyApiClient { parameters: { providerID: string directory?: string + workspace?: string method?: number code?: string }, @@ -2295,6 +2515,7 @@ export class Oauth extends HeyApiClient { args: [ { in: "path", key: "providerID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "body", key: "method" }, { in: "body", key: "code" }, ], @@ -2327,10 +2548,21 @@ export class Provider extends HeyApiClient { public list( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).get({ url: "/provider", ...options, @@ -2346,10 +2578,21 @@ export class Provider extends HeyApiClient { public auth( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).get({ url: "/provider/auth", ...options, @@ -2372,6 +2615,7 @@ export class Find extends HeyApiClient { public text( parameters: { directory?: string + workspace?: string pattern: string }, options?: Options, @@ -2382,6 +2626,7 @@ export class Find extends HeyApiClient { { args: [ { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "query", key: "pattern" }, ], }, @@ -2402,6 +2647,7 @@ export class Find extends HeyApiClient { public files( parameters: { directory?: string + workspace?: string query: string dirs?: "true" | "false" type?: "file" | "directory" @@ -2415,6 +2661,7 @@ export class Find extends HeyApiClient { { args: [ { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "query", key: "query" }, { in: "query", key: "dirs" }, { in: "query", key: "type" }, @@ -2438,6 +2685,7 @@ export class Find extends HeyApiClient { public symbols( parameters: { directory?: string + workspace?: string query: string }, options?: Options, @@ -2448,6 +2696,7 @@ export class Find extends HeyApiClient { { args: [ { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "query", key: "query" }, ], }, @@ -2470,6 +2719,7 @@ export class File extends HeyApiClient { public list( parameters: { directory?: string + workspace?: string path: string }, options?: Options, @@ -2480,6 +2730,7 @@ export class File extends HeyApiClient { { args: [ { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "query", key: "path" }, ], }, @@ -2500,6 +2751,7 @@ export class File extends HeyApiClient { public read( parameters: { directory?: string + workspace?: string path: string }, options?: Options, @@ -2510,6 +2762,7 @@ export class File extends HeyApiClient { { args: [ { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "query", key: "path" }, ], }, @@ -2530,10 +2783,21 @@ export class File extends HeyApiClient { public status( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).get({ url: "/file/status", ...options, @@ -2552,6 +2816,7 @@ export class Auth2 extends HeyApiClient { parameters: { name: string directory?: string + workspace?: string }, options?: Options, ) { @@ -2562,6 +2827,7 @@ export class Auth2 extends HeyApiClient { args: [ { in: "path", key: "name" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], @@ -2582,6 +2848,7 @@ export class Auth2 extends HeyApiClient { parameters: { name: string directory?: string + workspace?: string }, options?: Options, ) { @@ -2592,6 +2859,7 @@ export class Auth2 extends HeyApiClient { args: [ { in: "path", key: "name" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], @@ -2612,6 +2880,7 @@ export class Auth2 extends HeyApiClient { parameters: { name: string directory?: string + workspace?: string code?: string }, options?: Options, @@ -2623,6 +2892,7 @@ export class Auth2 extends HeyApiClient { args: [ { in: "path", key: "name" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "body", key: "code" }, ], }, @@ -2649,6 +2919,7 @@ export class Auth2 extends HeyApiClient { parameters: { name: string directory?: string + workspace?: string }, options?: Options, ) { @@ -2659,6 +2930,7 @@ export class Auth2 extends HeyApiClient { args: [ { in: "path", key: "name" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], @@ -2682,10 +2954,21 @@ export class Mcp extends HeyApiClient { public status( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).get({ url: "/mcp", ...options, @@ -2701,6 +2984,7 @@ export class Mcp extends HeyApiClient { public add( parameters?: { directory?: string + workspace?: string name?: string config?: McpLocalConfig | McpRemoteConfig }, @@ -2712,6 +2996,7 @@ export class Mcp extends HeyApiClient { { args: [ { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "body", key: "name" }, { in: "body", key: "config" }, ], @@ -2737,6 +3022,7 @@ export class Mcp extends HeyApiClient { parameters: { name: string directory?: string + workspace?: string }, options?: Options, ) { @@ -2747,6 +3033,7 @@ export class Mcp extends HeyApiClient { args: [ { in: "path", key: "name" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], @@ -2765,6 +3052,7 @@ export class Mcp extends HeyApiClient { parameters: { name: string directory?: string + workspace?: string }, options?: Options, ) { @@ -2775,6 +3063,7 @@ export class Mcp extends HeyApiClient { args: [ { in: "path", key: "name" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], @@ -2801,10 +3090,21 @@ export class Control extends HeyApiClient { public next( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).get({ url: "/tui/control/next", ...options, @@ -2820,6 +3120,7 @@ export class Control extends HeyApiClient { public response( parameters?: { directory?: string + workspace?: string body?: unknown }, options?: Options, @@ -2830,6 +3131,7 @@ export class Control extends HeyApiClient { { args: [ { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { key: "body", map: "body" }, ], }, @@ -2857,6 +3159,7 @@ export class Tui extends HeyApiClient { public appendPrompt( parameters?: { directory?: string + workspace?: string text?: string }, options?: Options, @@ -2867,6 +3170,7 @@ export class Tui extends HeyApiClient { { args: [ { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "body", key: "text" }, ], }, @@ -2892,10 +3196,21 @@ export class Tui extends HeyApiClient { public openHelp( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).post({ url: "/tui/open-help", ...options, @@ -2911,10 +3226,21 @@ export class Tui extends HeyApiClient { public openSessions( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).post({ url: "/tui/open-sessions", ...options, @@ -2930,10 +3256,21 @@ export class Tui extends HeyApiClient { public openThemes( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).post({ url: "/tui/open-themes", ...options, @@ -2949,10 +3286,21 @@ export class Tui extends HeyApiClient { public openModels( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).post({ url: "/tui/open-models", ...options, @@ -2968,10 +3316,21 @@ export class Tui extends HeyApiClient { public submitPrompt( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).post({ url: "/tui/submit-prompt", ...options, @@ -2987,10 +3346,21 @@ export class Tui extends HeyApiClient { public clearPrompt( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).post({ url: "/tui/clear-prompt", ...options, @@ -3006,6 +3376,7 @@ export class Tui extends HeyApiClient { public executeCommand( parameters?: { directory?: string + workspace?: string command?: string }, options?: Options, @@ -3016,6 +3387,7 @@ export class Tui extends HeyApiClient { { args: [ { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "body", key: "command" }, ], }, @@ -3041,6 +3413,7 @@ export class Tui extends HeyApiClient { public showToast( parameters?: { directory?: string + workspace?: string title?: string message?: string variant?: "info" | "success" | "warning" | "error" @@ -3054,6 +3427,7 @@ export class Tui extends HeyApiClient { { args: [ { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "body", key: "title" }, { in: "body", key: "message" }, { in: "body", key: "variant" }, @@ -3082,6 +3456,7 @@ export class Tui extends HeyApiClient { public publish( parameters?: { directory?: string + workspace?: string body?: EventTuiPromptAppend | EventTuiCommandExecute | EventTuiToastShow | EventTuiSessionSelect }, options?: Options, @@ -3092,6 +3467,7 @@ export class Tui extends HeyApiClient { { args: [ { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { key: "body", map: "body" }, ], }, @@ -3117,6 +3493,7 @@ export class Tui extends HeyApiClient { public selectSession( parameters?: { directory?: string + workspace?: string sessionID?: string }, options?: Options, @@ -3127,6 +3504,7 @@ export class Tui extends HeyApiClient { { args: [ { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "body", key: "sessionID" }, ], }, @@ -3159,10 +3537,21 @@ export class Instance extends HeyApiClient { public dispose( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).post({ url: "/instance/dispose", ...options, @@ -3180,10 +3569,21 @@ export class Path extends HeyApiClient { public get( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).get({ url: "/path", ...options, @@ -3201,10 +3601,21 @@ export class Vcs extends HeyApiClient { public get( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).get({ url: "/vcs", ...options, @@ -3222,10 +3633,21 @@ export class Command extends HeyApiClient { public list( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).get({ url: "/command", ...options, @@ -3243,6 +3665,7 @@ export class App extends HeyApiClient { public log( parameters?: { directory?: string + workspace?: string service?: string level?: "debug" | "info" | "error" | "warn" message?: string @@ -3258,6 +3681,7 @@ export class App extends HeyApiClient { { args: [ { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "body", key: "service" }, { in: "body", key: "level" }, { in: "body", key: "message" }, @@ -3286,10 +3710,21 @@ export class App extends HeyApiClient { public agents( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).get({ url: "/agent", ...options, @@ -3305,10 +3740,21 @@ export class App extends HeyApiClient { public skills( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).get({ url: "/skill", ...options, @@ -3326,10 +3772,21 @@ export class Lsp extends HeyApiClient { public status( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).get({ url: "/lsp", ...options, @@ -3347,10 +3804,21 @@ export class Formatter extends HeyApiClient { public status( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).get({ url: "/formatter", ...options, @@ -3368,10 +3836,21 @@ export class Event extends HeyApiClient { public subscribe( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).sse.get({ url: "/event", ...options, @@ -3418,16 +3897,16 @@ export class OpencodeClient extends HeyApiClient { return (this._tool ??= new Tool({ client: this.client })) } - private _worktree?: Worktree - get worktree(): Worktree { - return (this._worktree ??= new Worktree({ client: this.client })) - } - private _experimental?: Experimental get experimental(): Experimental { return (this._experimental ??= new Experimental({ client: this.client })) } + private _worktree?: Worktree + get worktree(): Worktree { + return (this._worktree ??= new Worktree({ client: this.client })) + } + private _session?: Session2 get session(): Session2 { return (this._session ??= new Session2({ client: this.client })) diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 385de2cc85e..afb2224a751 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -505,6 +505,7 @@ export type CompactionPart = { messageID: string type: "compaction" auto: boolean + overflow?: boolean } export type Part = @@ -808,6 +809,7 @@ export type Session = { id: string slug: string projectID: string + workspaceID?: string directory: string parentID?: string summary?: { @@ -887,21 +889,6 @@ export type EventVcsBranchUpdated = { } } -export type EventWorktreeReady = { - type: "worktree.ready" - properties: { - name: string - branch: string - } -} - -export type EventWorktreeFailed = { - type: "worktree.failed" - properties: { - message: string - } -} - export type EventWorkspaceReady = { type: "workspace.ready" properties: { @@ -955,6 +942,21 @@ export type EventPtyDeleted = { } } +export type EventWorktreeReady = { + type: "worktree.ready" + properties: { + name: string + branch: string + } +} + +export type EventWorktreeFailed = { + type: "worktree.failed" + properties: { + message: string + } +} + export type Event = | EventInstallationUpdated | EventInstallationUpdateAvailable @@ -993,14 +995,14 @@ export type Event = | EventSessionDiff | EventSessionError | EventVcsBranchUpdated - | EventWorktreeReady - | EventWorktreeFailed | EventWorkspaceReady | EventWorkspaceFailed | EventPtyCreated | EventPtyUpdated | EventPtyExited | EventPtyDeleted + | EventWorktreeReady + | EventWorktreeFailed export type GlobalEvent = { directory: string @@ -1629,6 +1631,16 @@ export type ToolListItem = { export type ToolList = Array +export type Workspace = { + id: string + type: string + branch: string | null + name: string | null + directory: string | null + extra: unknown | null + projectID: string +} + export type Worktree = { name: string branch: string @@ -1643,16 +1655,6 @@ export type WorktreeCreateInput = { startCommand?: string } -export type Workspace = { - id: string - branch: string | null - projectID: string - config: { - directory: string - type: "worktree" - } -} - export type WorktreeRemoveInput = { directory: string } @@ -1671,6 +1673,7 @@ export type GlobalSession = { id: string slug: string projectID: string + workspaceID?: string directory: string parentID?: string summary?: { @@ -2051,6 +2054,7 @@ export type ProjectListData = { path?: never query?: { directory?: string + workspace?: string } url: "/project" } @@ -2069,6 +2073,7 @@ export type ProjectCurrentData = { path?: never query?: { directory?: string + workspace?: string } url: "/project/current" } @@ -2102,6 +2107,7 @@ export type ProjectUpdateData = { } query?: { directory?: string + workspace?: string } url: "/project/{projectID}" } @@ -2133,6 +2139,7 @@ export type PtyListData = { path?: never query?: { directory?: string + workspace?: string } url: "/pty" } @@ -2159,6 +2166,7 @@ export type PtyCreateData = { path?: never query?: { directory?: string + workspace?: string } url: "/pty" } @@ -2188,6 +2196,7 @@ export type PtyRemoveData = { } query?: { directory?: string + workspace?: string } url: "/pty/{ptyID}" } @@ -2217,6 +2226,7 @@ export type PtyGetData = { } query?: { directory?: string + workspace?: string } url: "/pty/{ptyID}" } @@ -2252,6 +2262,7 @@ export type PtyUpdateData = { } query?: { directory?: string + workspace?: string } url: "/pty/{ptyID}" } @@ -2281,6 +2292,7 @@ export type PtyConnectData = { } query?: { directory?: string + workspace?: string } url: "/pty/{ptyID}/connect" } @@ -2308,6 +2320,7 @@ export type ConfigGetData = { path?: never query?: { directory?: string + workspace?: string } url: "/config" } @@ -2326,6 +2339,7 @@ export type ConfigUpdateData = { path?: never query?: { directory?: string + workspace?: string } url: "/config" } @@ -2353,6 +2367,7 @@ export type ConfigProvidersData = { path?: never query?: { directory?: string + workspace?: string } url: "/config/providers" } @@ -2376,6 +2391,7 @@ export type ToolIdsData = { path?: never query?: { directory?: string + workspace?: string } url: "/experimental/tool/ids" } @@ -2403,6 +2419,7 @@ export type ToolListData = { path?: never query: { directory?: string + workspace?: string provider: string model: string } @@ -2427,77 +2444,60 @@ export type ToolListResponses = { export type ToolListResponse = ToolListResponses[keyof ToolListResponses] -export type WorktreeRemoveData = { - body?: WorktreeRemoveInput - path?: never - query?: { - directory?: string - } - url: "/experimental/worktree" -} - -export type WorktreeRemoveErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type WorktreeRemoveError = WorktreeRemoveErrors[keyof WorktreeRemoveErrors] - -export type WorktreeRemoveResponses = { - /** - * Worktree removed - */ - 200: boolean -} - -export type WorktreeRemoveResponse = WorktreeRemoveResponses[keyof WorktreeRemoveResponses] - -export type WorktreeListData = { +export type ExperimentalWorkspaceListData = { body?: never path?: never query?: { directory?: string + workspace?: string } - url: "/experimental/worktree" + url: "/experimental/workspace" } -export type WorktreeListResponses = { +export type ExperimentalWorkspaceListResponses = { /** - * List of worktree directories + * Workspaces */ - 200: Array + 200: Array } -export type WorktreeListResponse = WorktreeListResponses[keyof WorktreeListResponses] +export type ExperimentalWorkspaceListResponse = + ExperimentalWorkspaceListResponses[keyof ExperimentalWorkspaceListResponses] -export type WorktreeCreateData = { - body?: WorktreeCreateInput +export type ExperimentalWorkspaceCreateData = { + body?: { + id?: string + type: string + branch: string | null + extra: unknown | null + } path?: never query?: { directory?: string + workspace?: string } - url: "/experimental/worktree" + url: "/experimental/workspace" } -export type WorktreeCreateErrors = { +export type ExperimentalWorkspaceCreateErrors = { /** * Bad request */ 400: BadRequestError } -export type WorktreeCreateError = WorktreeCreateErrors[keyof WorktreeCreateErrors] +export type ExperimentalWorkspaceCreateError = + ExperimentalWorkspaceCreateErrors[keyof ExperimentalWorkspaceCreateErrors] -export type WorktreeCreateResponses = { +export type ExperimentalWorkspaceCreateResponses = { /** - * Worktree created + * Workspace created */ - 200: Worktree + 200: Workspace } -export type WorktreeCreateResponse = WorktreeCreateResponses[keyof WorktreeCreateResponses] +export type ExperimentalWorkspaceCreateResponse = + ExperimentalWorkspaceCreateResponses[keyof ExperimentalWorkspaceCreateResponses] export type ExperimentalWorkspaceRemoveData = { body?: never @@ -2506,6 +2506,7 @@ export type ExperimentalWorkspaceRemoveData = { } query?: { directory?: string + workspace?: string } url: "/experimental/workspace/{id}" } @@ -2530,67 +2531,87 @@ export type ExperimentalWorkspaceRemoveResponses = { export type ExperimentalWorkspaceRemoveResponse = ExperimentalWorkspaceRemoveResponses[keyof ExperimentalWorkspaceRemoveResponses] -export type ExperimentalWorkspaceCreateData = { - body?: { - branch: string | null - config: { - directory: string - type: "worktree" - } - } - path: { - id: string - } +export type WorktreeRemoveData = { + body?: WorktreeRemoveInput + path?: never query?: { directory?: string + workspace?: string } - url: "/experimental/workspace/{id}" + url: "/experimental/worktree" } -export type ExperimentalWorkspaceCreateErrors = { +export type WorktreeRemoveErrors = { /** * Bad request */ 400: BadRequestError } -export type ExperimentalWorkspaceCreateError = - ExperimentalWorkspaceCreateErrors[keyof ExperimentalWorkspaceCreateErrors] +export type WorktreeRemoveError = WorktreeRemoveErrors[keyof WorktreeRemoveErrors] -export type ExperimentalWorkspaceCreateResponses = { +export type WorktreeRemoveResponses = { /** - * Workspace created + * Worktree removed */ - 200: Workspace + 200: boolean } -export type ExperimentalWorkspaceCreateResponse = - ExperimentalWorkspaceCreateResponses[keyof ExperimentalWorkspaceCreateResponses] +export type WorktreeRemoveResponse = WorktreeRemoveResponses[keyof WorktreeRemoveResponses] -export type ExperimentalWorkspaceListData = { +export type WorktreeListData = { body?: never path?: never query?: { directory?: string + workspace?: string } - url: "/experimental/workspace" + url: "/experimental/worktree" } -export type ExperimentalWorkspaceListResponses = { +export type WorktreeListResponses = { /** - * Workspaces + * List of worktree directories */ - 200: Array + 200: Array } -export type ExperimentalWorkspaceListResponse = - ExperimentalWorkspaceListResponses[keyof ExperimentalWorkspaceListResponses] +export type WorktreeListResponse = WorktreeListResponses[keyof WorktreeListResponses] + +export type WorktreeCreateData = { + body?: WorktreeCreateInput + path?: never + query?: { + directory?: string + workspace?: string + } + url: "/experimental/worktree" +} + +export type WorktreeCreateErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type WorktreeCreateError = WorktreeCreateErrors[keyof WorktreeCreateErrors] + +export type WorktreeCreateResponses = { + /** + * Worktree created + */ + 200: Worktree +} + +export type WorktreeCreateResponse = WorktreeCreateResponses[keyof WorktreeCreateResponses] export type WorktreeResetData = { body?: WorktreeResetInput path?: never query?: { directory?: string + workspace?: string } url: "/experimental/worktree/reset" } @@ -2621,6 +2642,7 @@ export type ExperimentalSessionListData = { * Filter sessions by project directory */ directory?: string + workspace?: string /** * Only return root sessions (no parentID) */ @@ -2663,6 +2685,7 @@ export type ExperimentalResourceListData = { path?: never query?: { directory?: string + workspace?: string } url: "/experimental/resource" } @@ -2687,6 +2710,7 @@ export type SessionListData = { * Filter sessions by project directory */ directory?: string + workspace?: string /** * Only return root sessions (no parentID) */ @@ -2725,6 +2749,7 @@ export type SessionCreateData = { path?: never query?: { directory?: string + workspace?: string } url: "/session" } @@ -2752,6 +2777,7 @@ export type SessionStatusData = { path?: never query?: { directory?: string + workspace?: string } url: "/session/status" } @@ -2783,6 +2809,7 @@ export type SessionDeleteData = { } query?: { directory?: string + workspace?: string } url: "/session/{sessionID}" } @@ -2816,6 +2843,7 @@ export type SessionGetData = { } query?: { directory?: string + workspace?: string } url: "/session/{sessionID}" } @@ -2854,6 +2882,7 @@ export type SessionUpdateData = { } query?: { directory?: string + workspace?: string } url: "/session/{sessionID}" } @@ -2887,6 +2916,7 @@ export type SessionChildrenData = { } query?: { directory?: string + workspace?: string } url: "/session/{sessionID}/children" } @@ -2923,6 +2953,7 @@ export type SessionTodoData = { } query?: { directory?: string + workspace?: string } url: "/session/{sessionID}/todo" } @@ -2963,6 +2994,7 @@ export type SessionInitData = { } query?: { directory?: string + workspace?: string } url: "/session/{sessionID}/init" } @@ -2998,6 +3030,7 @@ export type SessionForkData = { } query?: { directory?: string + workspace?: string } url: "/session/{sessionID}/fork" } @@ -3018,6 +3051,7 @@ export type SessionAbortData = { } query?: { directory?: string + workspace?: string } url: "/session/{sessionID}/abort" } @@ -3051,6 +3085,7 @@ export type SessionUnshareData = { } query?: { directory?: string + workspace?: string } url: "/session/{sessionID}/share" } @@ -3084,6 +3119,7 @@ export type SessionShareData = { } query?: { directory?: string + workspace?: string } url: "/session/{sessionID}/share" } @@ -3117,6 +3153,7 @@ export type SessionDiffData = { } query?: { directory?: string + workspace?: string messageID?: string } url: "/session/{sessionID}/diff" @@ -3145,6 +3182,7 @@ export type SessionSummarizeData = { } query?: { directory?: string + workspace?: string } url: "/session/{sessionID}/summarize" } @@ -3181,6 +3219,7 @@ export type SessionMessagesData = { } query?: { directory?: string + workspace?: string limit?: number } url: "/session/{sessionID}/message" @@ -3239,6 +3278,7 @@ export type SessionPromptData = { } query?: { directory?: string + workspace?: string } url: "/session/{sessionID}/message" } @@ -3282,6 +3322,7 @@ export type SessionDeleteMessageData = { } query?: { directory?: string + workspace?: string } url: "/session/{sessionID}/message/{messageID}" } @@ -3322,6 +3363,7 @@ export type SessionMessageData = { } query?: { directory?: string + workspace?: string } url: "/session/{sessionID}/message/{messageID}" } @@ -3369,6 +3411,7 @@ export type PartDeleteData = { } query?: { directory?: string + workspace?: string } url: "/session/{sessionID}/message/{messageID}/part/{partID}" } @@ -3413,6 +3456,7 @@ export type PartUpdateData = { } query?: { directory?: string + workspace?: string } url: "/session/{sessionID}/message/{messageID}/part/{partID}" } @@ -3467,6 +3511,7 @@ export type SessionPromptAsyncData = { } query?: { directory?: string + workspace?: string } url: "/session/{sessionID}/prompt_async" } @@ -3518,6 +3563,7 @@ export type SessionCommandData = { } query?: { directory?: string + workspace?: string } url: "/session/{sessionID}/command" } @@ -3564,6 +3610,7 @@ export type SessionShellData = { } query?: { directory?: string + workspace?: string } url: "/session/{sessionID}/shell" } @@ -3600,6 +3647,7 @@ export type SessionRevertData = { } query?: { directory?: string + workspace?: string } url: "/session/{sessionID}/revert" } @@ -3633,6 +3681,7 @@ export type SessionUnrevertData = { } query?: { directory?: string + workspace?: string } url: "/session/{sessionID}/unrevert" } @@ -3669,6 +3718,7 @@ export type PermissionRespondData = { } query?: { directory?: string + workspace?: string } url: "/session/{sessionID}/permissions/{permissionID}" } @@ -3705,6 +3755,7 @@ export type PermissionReplyData = { } query?: { directory?: string + workspace?: string } url: "/permission/{requestID}/reply" } @@ -3736,6 +3787,7 @@ export type PermissionListData = { path?: never query?: { directory?: string + workspace?: string } url: "/permission" } @@ -3754,6 +3806,7 @@ export type QuestionListData = { path?: never query?: { directory?: string + workspace?: string } url: "/question" } @@ -3779,6 +3832,7 @@ export type QuestionReplyData = { } query?: { directory?: string + workspace?: string } url: "/question/{requestID}/reply" } @@ -3812,6 +3866,7 @@ export type QuestionRejectData = { } query?: { directory?: string + workspace?: string } url: "/question/{requestID}/reject" } @@ -3843,6 +3898,7 @@ export type ProviderListData = { path?: never query?: { directory?: string + workspace?: string } url: "/provider" } @@ -3928,6 +3984,7 @@ export type ProviderAuthData = { path?: never query?: { directory?: string + workspace?: string } url: "/provider/auth" } @@ -3958,6 +4015,7 @@ export type ProviderOauthAuthorizeData = { } query?: { directory?: string + workspace?: string } url: "/provider/{providerID}/oauth/authorize" } @@ -3999,6 +4057,7 @@ export type ProviderOauthCallbackData = { } query?: { directory?: string + workspace?: string } url: "/provider/{providerID}/oauth/callback" } @@ -4026,6 +4085,7 @@ export type FindTextData = { path?: never query: { directory?: string + workspace?: string pattern: string } url: "/find" @@ -4061,6 +4121,7 @@ export type FindFilesData = { path?: never query: { directory?: string + workspace?: string query: string dirs?: "true" | "false" type?: "file" | "directory" @@ -4083,6 +4144,7 @@ export type FindSymbolsData = { path?: never query: { directory?: string + workspace?: string query: string } url: "/find/symbol" @@ -4102,6 +4164,7 @@ export type FileListData = { path?: never query: { directory?: string + workspace?: string path: string } url: "/file" @@ -4121,6 +4184,7 @@ export type FileReadData = { path?: never query: { directory?: string + workspace?: string path: string } url: "/file/content" @@ -4140,6 +4204,7 @@ export type FileStatusData = { path?: never query?: { directory?: string + workspace?: string } url: "/file/status" } @@ -4158,6 +4223,7 @@ export type McpStatusData = { path?: never query?: { directory?: string + workspace?: string } url: "/mcp" } @@ -4181,6 +4247,7 @@ export type McpAddData = { path?: never query?: { directory?: string + workspace?: string } url: "/mcp" } @@ -4212,6 +4279,7 @@ export type McpAuthRemoveData = { } query?: { directory?: string + workspace?: string } url: "/mcp/{name}/auth" } @@ -4243,6 +4311,7 @@ export type McpAuthStartData = { } query?: { directory?: string + workspace?: string } url: "/mcp/{name}/auth" } @@ -4286,6 +4355,7 @@ export type McpAuthCallbackData = { } query?: { directory?: string + workspace?: string } url: "/mcp/{name}/auth/callback" } @@ -4319,6 +4389,7 @@ export type McpAuthAuthenticateData = { } query?: { directory?: string + workspace?: string } url: "/mcp/{name}/auth/authenticate" } @@ -4352,6 +4423,7 @@ export type McpConnectData = { } query?: { directory?: string + workspace?: string } url: "/mcp/{name}/connect" } @@ -4372,6 +4444,7 @@ export type McpDisconnectData = { } query?: { directory?: string + workspace?: string } url: "/mcp/{name}/disconnect" } @@ -4392,6 +4465,7 @@ export type TuiAppendPromptData = { path?: never query?: { directory?: string + workspace?: string } url: "/tui/append-prompt" } @@ -4419,6 +4493,7 @@ export type TuiOpenHelpData = { path?: never query?: { directory?: string + workspace?: string } url: "/tui/open-help" } @@ -4437,6 +4512,7 @@ export type TuiOpenSessionsData = { path?: never query?: { directory?: string + workspace?: string } url: "/tui/open-sessions" } @@ -4455,6 +4531,7 @@ export type TuiOpenThemesData = { path?: never query?: { directory?: string + workspace?: string } url: "/tui/open-themes" } @@ -4473,6 +4550,7 @@ export type TuiOpenModelsData = { path?: never query?: { directory?: string + workspace?: string } url: "/tui/open-models" } @@ -4491,6 +4569,7 @@ export type TuiSubmitPromptData = { path?: never query?: { directory?: string + workspace?: string } url: "/tui/submit-prompt" } @@ -4509,6 +4588,7 @@ export type TuiClearPromptData = { path?: never query?: { directory?: string + workspace?: string } url: "/tui/clear-prompt" } @@ -4529,6 +4609,7 @@ export type TuiExecuteCommandData = { path?: never query?: { directory?: string + workspace?: string } url: "/tui/execute-command" } @@ -4564,6 +4645,7 @@ export type TuiShowToastData = { path?: never query?: { directory?: string + workspace?: string } url: "/tui/show-toast" } @@ -4582,6 +4664,7 @@ export type TuiPublishData = { path?: never query?: { directory?: string + workspace?: string } url: "/tui/publish" } @@ -4614,6 +4697,7 @@ export type TuiSelectSessionData = { path?: never query?: { directory?: string + workspace?: string } url: "/tui/select-session" } @@ -4645,6 +4729,7 @@ export type TuiControlNextData = { path?: never query?: { directory?: string + workspace?: string } url: "/tui/control/next" } @@ -4666,6 +4751,7 @@ export type TuiControlResponseData = { path?: never query?: { directory?: string + workspace?: string } url: "/tui/control/response" } @@ -4684,6 +4770,7 @@ export type InstanceDisposeData = { path?: never query?: { directory?: string + workspace?: string } url: "/instance/dispose" } @@ -4702,6 +4789,7 @@ export type PathGetData = { path?: never query?: { directory?: string + workspace?: string } url: "/path" } @@ -4720,6 +4808,7 @@ export type VcsGetData = { path?: never query?: { directory?: string + workspace?: string } url: "/vcs" } @@ -4738,6 +4827,7 @@ export type CommandListData = { path?: never query?: { directory?: string + workspace?: string } url: "/command" } @@ -4775,6 +4865,7 @@ export type AppLogData = { path?: never query?: { directory?: string + workspace?: string } url: "/log" } @@ -4802,6 +4893,7 @@ export type AppAgentsData = { path?: never query?: { directory?: string + workspace?: string } url: "/agent" } @@ -4820,6 +4912,7 @@ export type AppSkillsData = { path?: never query?: { directory?: string + workspace?: string } url: "/skill" } @@ -4843,6 +4936,7 @@ export type LspStatusData = { path?: never query?: { directory?: string + workspace?: string } url: "/lsp" } @@ -4861,6 +4955,7 @@ export type FormatterStatusData = { path?: never query?: { directory?: string + workspace?: string } url: "/formatter" } @@ -4879,6 +4974,7 @@ export type EventSubscribeData = { path?: never query?: { directory?: string + workspace?: string } url: "/event" } diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 8632baf2d32..7db79bcbe24 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -265,6 +265,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "List all projects", @@ -302,6 +309,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Get current project", @@ -337,6 +351,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "projectID", @@ -435,6 +456,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "List PTY sessions", @@ -470,6 +498,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Create PTY session", @@ -550,6 +585,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "ptyID", @@ -600,6 +642,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "ptyID", @@ -676,6 +725,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "ptyID", @@ -728,6 +784,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "ptyID", @@ -779,6 +842,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Get configuration", @@ -811,6 +881,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Update configuration", @@ -864,6 +941,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "List config providers", @@ -916,6 +1000,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "List tool IDs", @@ -961,6 +1052,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "query", "name": "provider", @@ -1010,9 +1108,9 @@ ] } }, - "/experimental/worktree": { + "/experimental/workspace": { "post": { - "operationId": "worktree.create", + "operationId": "experimental.workspace.create", "parameters": [ { "in": "query", @@ -1020,17 +1118,24 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], - "summary": "Create worktree", - "description": "Create a new git worktree for the current project and run any configured startup scripts.", + "summary": "Create workspace", + "description": "Create a workspace for the current project.", "responses": { "200": { - "description": "Worktree created", + "description": "Workspace created", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Worktree" + "$ref": "#/components/schemas/Workspace" } } } @@ -1050,7 +1155,35 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/WorktreeCreateInput" + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^wrk.*" + }, + "type": { + "type": "string" + }, + "branch": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "extra": { + "anyOf": [ + {}, + { + "type": "null" + } + ] + } + }, + "required": ["type", "branch", "extra"] } } } @@ -1058,12 +1191,12 @@ "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.worktree.create({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.experimental.workspace.create({\n ...\n})" } ] }, "get": { - "operationId": "worktree.list", + "operationId": "experimental.workspace.list", "parameters": [ { "in": "query", @@ -1071,19 +1204,26 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], - "summary": "List worktrees", - "description": "List all sandbox worktrees for the current project.", + "summary": "List workspaces", + "description": "List all workspaces.", "responses": { "200": { - "description": "List of worktree directories", + "description": "Workspaces", "content": { "application/json": { "schema": { "type": "array", "items": { - "type": "string" + "$ref": "#/components/schemas/Workspace" } } } @@ -1093,12 +1233,14 @@ "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.worktree.list({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.experimental.workspace.list({\n ...\n})" } ] - }, + } + }, + "/experimental/workspace/{id}": { "delete": { - "operationId": "worktree.remove", + "operationId": "experimental.workspace.remove", "parameters": [ { "in": "query", @@ -1106,17 +1248,33 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "id", + "schema": { + "type": "string", + "pattern": "^wrk.*" + }, + "required": true } ], - "summary": "Remove worktree", - "description": "Remove a git worktree and delete its branch.", + "summary": "Remove workspace", + "description": "Remove an existing workspace.", "responses": { "200": { - "description": "Worktree removed", + "description": "Workspace removed", "content": { "application/json": { "schema": { - "type": "boolean" + "$ref": "#/components/schemas/Workspace" } } } @@ -1132,26 +1290,17 @@ } } }, - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/WorktreeRemoveInput" - } - } - } - }, "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.worktree.remove({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.experimental.workspace.remove({\n ...\n})" } ] } }, - "/experimental/workspace/{id}": { + "/experimental/worktree": { "post": { - "operationId": "experimental.workspace.create", + "operationId": "worktree.create", "parameters": [ { "in": "query", @@ -1161,24 +1310,22 @@ } }, { - "in": "path", - "name": "id", + "in": "query", + "name": "workspace", "schema": { - "type": "string", - "pattern": "^wrk.*" - }, - "required": true + "type": "string" + } } ], - "summary": "Create workspace", - "description": "Create a workspace for the current project.", + "summary": "Create worktree", + "description": "Create a new git worktree for the current project and run any configured startup scripts.", "responses": { "200": { - "description": "Workspace created", + "description": "Worktree created", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Workspace" + "$ref": "#/components/schemas/Worktree" } } } @@ -1198,37 +1345,7 @@ "content": { "application/json": { "schema": { - "type": "object", - "properties": { - "branch": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - }, - "config": { - "anyOf": [ - { - "type": "object", - "properties": { - "directory": { - "type": "string" - }, - "type": { - "type": "string", - "const": "worktree" - } - }, - "required": ["directory", "type"] - } - ] - } - }, - "required": ["branch", "config"] + "$ref": "#/components/schemas/WorktreeCreateInput" } } } @@ -1236,12 +1353,12 @@ "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.experimental.workspace.create({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.worktree.create({\n ...\n})" } ] }, - "delete": { - "operationId": "experimental.workspace.remove", + "get": { + "operationId": "worktree.list", "parameters": [ { "in": "query", @@ -1251,34 +1368,25 @@ } }, { - "in": "path", - "name": "id", + "in": "query", + "name": "workspace", "schema": { - "type": "string", - "pattern": "^wrk.*" - }, - "required": true + "type": "string" + } } ], - "summary": "Remove workspace", - "description": "Remove an existing workspace.", + "summary": "List worktrees", + "description": "List all sandbox worktrees for the current project.", "responses": { "200": { - "description": "Workspace removed", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Workspace" - } - } - } - }, - "400": { - "description": "Bad request", + "description": "List of worktree directories", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/BadRequestError" + "type": "array", + "items": { + "type": "string" + } } } } @@ -1287,14 +1395,12 @@ "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.experimental.workspace.remove({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.worktree.list({\n ...\n})" } ] - } - }, - "/experimental/workspace": { - "get": { - "operationId": "experimental.workspace.list", + }, + "delete": { + "operationId": "worktree.remove", "parameters": [ { "in": "query", @@ -1302,29 +1408,52 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], - "summary": "List workspaces", - "description": "List all workspaces.", + "summary": "Remove worktree", + "description": "Remove a git worktree and delete its branch.", "responses": { "200": { - "description": "Workspaces", + "description": "Worktree removed", "content": { "application/json": { "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Workspace" - } + "type": "boolean" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" } } } } }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorktreeRemoveInput" + } + } + } + }, "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.experimental.workspace.list({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.worktree.remove({\n ...\n})" } ] } @@ -1339,6 +1468,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Reset worktree", @@ -1394,6 +1530,13 @@ }, "description": "Filter sessions by project directory" }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "query", "name": "roots", @@ -1478,6 +1621,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Get MCP resources", @@ -1520,6 +1670,13 @@ }, "description": "Filter sessions by project directory" }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "query", "name": "roots", @@ -1586,6 +1743,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Create session", @@ -1651,6 +1815,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Get session status", @@ -1702,6 +1873,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "sessionID", @@ -1764,6 +1942,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "sessionID", @@ -1825,6 +2010,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "sessionID", @@ -1909,6 +2101,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "sessionID", @@ -1976,6 +2175,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "sessionID", @@ -2042,6 +2248,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "sessionID", @@ -2127,6 +2340,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "sessionID", @@ -2185,6 +2405,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "sessionID", @@ -2247,6 +2474,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "sessionID", @@ -2307,6 +2541,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "sessionID", @@ -2370,6 +2611,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "sessionID", @@ -2424,6 +2672,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "sessionID", @@ -2509,6 +2764,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "sessionID", @@ -2592,6 +2854,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "sessionID", @@ -2739,6 +3008,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "sessionID", @@ -2821,6 +3097,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "sessionID", @@ -2888,7 +3171,14 @@ "parameters": [ { "in": "query", - "name": "directory", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "workspace", "schema": { "type": "string" } @@ -2971,6 +3261,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "sessionID", @@ -3060,6 +3357,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "sessionID", @@ -3188,6 +3492,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "sessionID", @@ -3326,6 +3637,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "sessionID", @@ -3419,6 +3737,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "sessionID", @@ -3501,6 +3826,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "sessionID", @@ -3563,6 +3895,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "sessionID", @@ -3650,6 +3989,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "requestID", @@ -3730,6 +4076,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "List pending permissions", @@ -3767,6 +4120,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "List pending questions", @@ -3805,6 +4165,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "requestID", @@ -3886,6 +4253,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "requestID", @@ -3947,6 +4321,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "List providers", @@ -4209,6 +4590,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Get provider auth methods", @@ -4253,6 +4641,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "providerID", @@ -4322,6 +4717,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "providerID", @@ -4395,6 +4797,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "query", "name": "pattern", @@ -4491,6 +4900,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "query", "name": "query", @@ -4561,6 +4977,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "query", "name": "query", @@ -4606,6 +5029,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "query", "name": "path", @@ -4651,6 +5081,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "query", "name": "path", @@ -4692,6 +5129,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Get file status", @@ -4729,6 +5173,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Get MCP status", @@ -4767,6 +5218,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Add MCP server", @@ -4843,6 +5301,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "schema": { "type": "string" @@ -4910,6 +5375,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "schema": { "type": "string" @@ -4969,6 +5441,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "schema": { "type": "string" @@ -5047,6 +5526,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "schema": { "type": "string" @@ -5109,6 +5595,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "name", @@ -5150,6 +5643,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "name", @@ -5190,6 +5690,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Append TUI prompt", @@ -5249,6 +5756,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Open help dialog", @@ -5283,6 +5797,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Open sessions dialog", @@ -5317,6 +5838,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Open themes dialog", @@ -5351,6 +5879,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Open models dialog", @@ -5385,6 +5920,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Submit TUI prompt", @@ -5419,6 +5961,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Clear TUI prompt", @@ -5453,6 +6002,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Execute TUI command", @@ -5512,6 +6068,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Show TUI toast", @@ -5573,6 +6136,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Publish TUI event", @@ -5639,6 +6209,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Select session", @@ -5706,7 +6283,14 @@ "parameters": [ { "in": "query", - "name": "directory", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "workspace", "schema": { "type": "string" } @@ -5751,6 +6335,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Submit TUI response", @@ -5792,6 +6383,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Dispose instance", @@ -5826,6 +6424,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Get paths", @@ -5860,6 +6465,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Get VCS info", @@ -5894,6 +6506,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "List commands", @@ -5931,6 +6550,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Write log", @@ -6008,6 +6634,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "List agents", @@ -6045,6 +6678,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "List skills", @@ -6097,6 +6737,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Get LSP status", @@ -6134,6 +6781,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Get formatter status", @@ -6171,6 +6825,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Subscribe to events", @@ -7618,6 +8279,9 @@ }, "auto": { "type": "boolean" + }, + "overflow": { + "type": "boolean" } }, "required": ["id", "sessionID", "messageID", "type", "auto"] @@ -8374,6 +9038,9 @@ "projectID": { "type": "string" }, + "workspaceID": { + "type": "string" + }, "directory": { "type": "string" }, @@ -8602,47 +9269,6 @@ }, "required": ["type", "properties"] }, - "Event.worktree.ready": { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "worktree.ready" - }, - "properties": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "branch": { - "type": "string" - } - }, - "required": ["name", "branch"] - } - }, - "required": ["type", "properties"] - }, - "Event.worktree.failed": { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "worktree.failed" - }, - "properties": { - "type": "object", - "properties": { - "message": { - "type": "string" - } - }, - "required": ["message"] - } - }, - "required": ["type", "properties"] - }, "Event.workspace.ready": { "type": "object", "properties": { @@ -8794,6 +9420,47 @@ }, "required": ["type", "properties"] }, + "Event.worktree.ready": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "worktree.ready" + }, + "properties": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "branch": { + "type": "string" + } + }, + "required": ["name", "branch"] + } + }, + "required": ["type", "properties"] + }, + "Event.worktree.failed": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "worktree.failed" + }, + "properties": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": ["message"] + } + }, + "required": ["type", "properties"] + }, "Event": { "anyOf": [ { @@ -8907,12 +9574,6 @@ { "$ref": "#/components/schemas/Event.vcs.branch.updated" }, - { - "$ref": "#/components/schemas/Event.worktree.ready" - }, - { - "$ref": "#/components/schemas/Event.worktree.failed" - }, { "$ref": "#/components/schemas/Event.workspace.ready" }, @@ -8930,6 +9591,12 @@ }, { "$ref": "#/components/schemas/Event.pty.deleted" + }, + { + "$ref": "#/components/schemas/Event.worktree.ready" + }, + { + "$ref": "#/components/schemas/Event.worktree.failed" } ] }, @@ -10323,6 +10990,60 @@ "$ref": "#/components/schemas/ToolListItem" } }, + "Workspace": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^wrk.*" + }, + "type": { + "type": "string" + }, + "branch": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "directory": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "extra": { + "anyOf": [ + {}, + { + "type": "null" + } + ] + }, + "projectID": { + "type": "string" + } + }, + "required": ["id", "type", "branch", "name", "directory", "extra", "projectID"] + }, "Worktree": { "type": "object", "properties": { @@ -10350,46 +11071,6 @@ } } }, - "Workspace": { - "type": "object", - "properties": { - "id": { - "type": "string", - "pattern": "^wrk.*" - }, - "branch": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - }, - "projectID": { - "type": "string" - }, - "config": { - "anyOf": [ - { - "type": "object", - "properties": { - "directory": { - "type": "string" - }, - "type": { - "type": "string", - "const": "worktree" - } - }, - "required": ["directory", "type"] - } - ] - } - }, - "required": ["id", "branch", "projectID", "config"] - }, "WorktreeRemoveInput": { "type": "object", "properties": { @@ -10436,6 +11117,9 @@ "projectID": { "type": "string" }, + "workspaceID": { + "type": "string" + }, "directory": { "type": "string" }, diff --git a/packages/slack/package.json b/packages/slack/package.json index 72ffe20d5e3..f78d5c4c71f 100644 --- a/packages/slack/package.json +++ b/packages/slack/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/slack", - "version": "1.2.15", + "version": "1.2.16", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/storybook/.storybook/main.ts b/packages/storybook/.storybook/main.ts index 6c850858a55..a6423530f6a 100644 --- a/packages/storybook/.storybook/main.ts +++ b/packages/storybook/.storybook/main.ts @@ -1,9 +1,12 @@ import { defineMain } from "storybook-solidjs-vite" import path from "node:path" import { fileURLToPath } from "node:url" +import tailwindcss from "@tailwindcss/vite" const here = path.dirname(fileURLToPath(import.meta.url)) const ui = path.resolve(here, "../../ui") +const app = path.resolve(here, "../../app/src") +const mocks = path.resolve(here, "./mocks") export default defineMain({ framework: { @@ -21,15 +24,41 @@ export default defineMain({ async viteFinal(config) { const { mergeConfig, searchForWorkspaceRoot } = await import("vite") return mergeConfig(config, { + plugins: [tailwindcss()], resolve: { dedupe: ["solid-js", "solid-js/web", "@solidjs/meta"], + alias: [ + { find: "@solidjs/router", replacement: path.resolve(mocks, "solid-router.tsx") }, + { find: /^@\/context\/local$/, replacement: path.resolve(mocks, "app/context/local.ts") }, + { find: /^@\/context\/file$/, replacement: path.resolve(mocks, "app/context/file.ts") }, + { find: /^@\/context\/prompt$/, replacement: path.resolve(mocks, "app/context/prompt.ts") }, + { find: /^@\/context\/layout$/, replacement: path.resolve(mocks, "app/context/layout.ts") }, + { find: /^@\/context\/sdk$/, replacement: path.resolve(mocks, "app/context/sdk.ts") }, + { find: /^@\/context\/sync$/, replacement: path.resolve(mocks, "app/context/sync.ts") }, + { find: /^@\/context\/comments$/, replacement: path.resolve(mocks, "app/context/comments.ts") }, + { find: /^@\/context\/command$/, replacement: path.resolve(mocks, "app/context/command.ts") }, + { find: /^@\/context\/permission$/, replacement: path.resolve(mocks, "app/context/permission.ts") }, + { find: /^@\/context\/language$/, replacement: path.resolve(mocks, "app/context/language.ts") }, + { find: /^@\/context\/platform$/, replacement: path.resolve(mocks, "app/context/platform.ts") }, + { find: /^@\/context\/global-sync$/, replacement: path.resolve(mocks, "app/context/global-sync.ts") }, + { find: /^@\/hooks\/use-providers$/, replacement: path.resolve(mocks, "app/hooks/use-providers.ts") }, + { + find: /^@\/components\/dialog-select-model$/, + replacement: path.resolve(mocks, "app/components/dialog-select-model.tsx"), + }, + { + find: /^@\/components\/dialog-select-model-unpaid$/, + replacement: path.resolve(mocks, "app/components/dialog-select-model-unpaid.tsx"), + }, + { find: "@", replacement: app }, + ], }, worker: { format: "es", }, server: { fs: { - allow: [searchForWorkspaceRoot(process.cwd()), ui], + allow: [searchForWorkspaceRoot(process.cwd()), ui, app, mocks], }, }, }) diff --git a/packages/storybook/.storybook/mocks/app/components/dialog-select-model-unpaid.tsx b/packages/storybook/.storybook/mocks/app/components/dialog-select-model-unpaid.tsx new file mode 100644 index 00000000000..8496c59a714 --- /dev/null +++ b/packages/storybook/.storybook/mocks/app/components/dialog-select-model-unpaid.tsx @@ -0,0 +1,3 @@ +export function DialogSelectModelUnpaid() { + return
Select model
+} diff --git a/packages/storybook/.storybook/mocks/app/components/dialog-select-model.tsx b/packages/storybook/.storybook/mocks/app/components/dialog-select-model.tsx new file mode 100644 index 00000000000..584924f02ff --- /dev/null +++ b/packages/storybook/.storybook/mocks/app/components/dialog-select-model.tsx @@ -0,0 +1,7 @@ +import { splitProps } from "solid-js" + +export function ModelSelectorPopover(props: { triggerAs: any; triggerProps?: Record; children: any }) { + const [local] = splitProps(props, ["triggerAs", "triggerProps", "children"]) + const Trigger = local.triggerAs + return {local.children} +} diff --git a/packages/storybook/.storybook/mocks/app/context/command.ts b/packages/storybook/.storybook/mocks/app/context/command.ts new file mode 100644 index 00000000000..1aa0423d3cc --- /dev/null +++ b/packages/storybook/.storybook/mocks/app/context/command.ts @@ -0,0 +1,22 @@ +const keybinds: Record = { + "file.attach": "mod+u", + "prompt.mode.shell": "mod+shift+x", + "prompt.mode.normal": "mod+shift+e", + "permissions.autoaccept": "mod+shift+a", + "agent.cycle": "mod+.", + "model.choose": "mod+m", + "model.variant.cycle": "mod+shift+m", +} + +export function useCommand() { + return { + options: [], + register() { + return () => undefined + }, + trigger() {}, + keybind(id: string) { + return keybinds[id] + }, + } +} diff --git a/packages/storybook/.storybook/mocks/app/context/comments.ts b/packages/storybook/.storybook/mocks/app/context/comments.ts new file mode 100644 index 00000000000..6c01d203b81 --- /dev/null +++ b/packages/storybook/.storybook/mocks/app/context/comments.ts @@ -0,0 +1,34 @@ +import { createSignal } from "solid-js" + +type Comment = { + id: string + file: string + selection: { start: number; end: number } + comment: string + time: number +} + +const [list, setList] = createSignal([]) +const [focus, setFocus] = createSignal<{ file: string; id: string } | null>(null) +const [active, setActive] = createSignal<{ file: string; id: string } | null>(null) + +export function useComments() { + return { + all: list, + replace(next: Comment[]) { + setList(next) + }, + remove(file: string, id: string) { + setList((current) => current.filter((item) => !(item.file === file && item.id === id))) + }, + clear() { + setList([]) + setFocus(null) + setActive(null) + }, + focus, + setFocus, + active, + setActive, + } +} diff --git a/packages/storybook/.storybook/mocks/app/context/file.ts b/packages/storybook/.storybook/mocks/app/context/file.ts new file mode 100644 index 00000000000..db2158a5cd5 --- /dev/null +++ b/packages/storybook/.storybook/mocks/app/context/file.ts @@ -0,0 +1,47 @@ +export type FileSelection = { + startLine: number + startChar: number + endLine: number + endChar: number +} + +export type SelectedLineRange = { + start: number + end: number +} + +export function selectionFromLines(selection?: SelectedLineRange): FileSelection | undefined { + if (!selection) return undefined + return { + startLine: selection.start, + startChar: 0, + endLine: selection.end, + endChar: 0, + } +} + +const pool = [ + "src/session/timeline.tsx", + "src/session/composer.tsx", + "src/components/prompt-input.tsx", + "src/components/session-todo-dock.tsx", + "README.md", +] + +export function useFile() { + return { + tab(path: string) { + return `file:${path}` + }, + pathFromTab(tab: string) { + if (!tab.startsWith("file:")) return "" + return tab.slice(5) + }, + load: async () => undefined, + async searchFilesAndDirectories(query: string) { + const text = query.trim().toLowerCase() + if (!text) return pool + return pool.filter((path) => path.toLowerCase().includes(text)) + }, + } +} diff --git a/packages/storybook/.storybook/mocks/app/context/global-sync.ts b/packages/storybook/.storybook/mocks/app/context/global-sync.ts new file mode 100644 index 00000000000..2eb134d37cd --- /dev/null +++ b/packages/storybook/.storybook/mocks/app/context/global-sync.ts @@ -0,0 +1,42 @@ +import { createStore } from "solid-js/store" + +const provider = { + all: [ + { + id: "anthropic", + models: { + "claude-3-7-sonnet": { + id: "claude-3-7-sonnet", + name: "Claude 3.7 Sonnet", + cost: { input: 1, output: 1 }, + }, + }, + }, + ], + connected: ["anthropic"], + default: { anthropic: "claude-3-7-sonnet" }, +} + +const [store, setStore] = createStore({ + todo: {} as Record, + provider, + session: [] as any[], + config: { permission: {} }, +}) + +export function useGlobalSync() { + return { + data: { + provider, + session_todo: store.todo, + }, + child() { + return [store, setStore] as const + }, + todo: { + set(sessionID: string, todos: any[]) { + setStore("todo", sessionID, todos) + }, + }, + } +} diff --git a/packages/storybook/.storybook/mocks/app/context/language.ts b/packages/storybook/.storybook/mocks/app/context/language.ts new file mode 100644 index 00000000000..87446554228 --- /dev/null +++ b/packages/storybook/.storybook/mocks/app/context/language.ts @@ -0,0 +1,74 @@ +const dict: Record = { + "session.todo.title": "Todos", + "session.todo.collapse": "Collapse todos", + "session.todo.expand": "Expand todos", + "prompt.loading": "Loading prompt...", + "prompt.placeholder.normal": "Ask anything...", + "prompt.placeholder.simple": "Ask anything...", + "prompt.placeholder.shell": "Run a shell command...", + "prompt.placeholder.summarizeComment": "Summarize this comment", + "prompt.placeholder.summarizeComments": "Summarize these comments", + "prompt.action.attachFile": "Attach file", + "prompt.action.send": "Send", + "prompt.action.stop": "Stop", + "prompt.attachment.remove": "Remove attachment", + "prompt.dropzone.label": "Drop image to attach", + "prompt.dropzone.file.label": "Drop file to attach", + "prompt.mode.shell": "Shell", + "prompt.mode.normal": "Prompt", + "dialog.model.select.title": "Select model", + "common.default": "Default", + "common.key.esc": "Esc", + "command.category.file": "File", + "command.category.session": "Session", + "command.agent.cycle": "Cycle agent", + "command.model.choose": "Choose model", + "command.model.variant.cycle": "Cycle model variant", + "command.prompt.mode.shell": "Switch to shell mode", + "command.prompt.mode.normal": "Switch to prompt mode", + "command.permissions.autoaccept.enable": "Enable auto-accept", + "command.permissions.autoaccept.disable": "Disable auto-accept", + "prompt.example.1": "Refactor this function and keep behavior the same", + "prompt.example.2": "Find the root cause of this error", + "prompt.example.3": "Write tests for this module", + "prompt.example.4": "Explain this diff", + "prompt.example.5": "Optimize this query", + "prompt.example.6": "Clean up this component", + "prompt.example.7": "Summarize the recent changes", + "prompt.example.8": "Add accessibility checks", + "prompt.example.9": "Review this API design", + "prompt.example.10": "Generate migration notes", + "prompt.example.11": "Patch this bug", + "prompt.example.12": "Make this animation smoother", + "prompt.example.13": "Improve error handling", + "prompt.example.14": "Document this feature", + "prompt.example.15": "Refine these styles", + "prompt.example.16": "Check edge cases", + "prompt.example.17": "Help me write a commit message", + "prompt.example.18": "Reduce re-renders in this component", + "prompt.example.19": "Verify keyboard navigation", + "prompt.example.20": "Make this copy clearer", + "prompt.example.21": "Add telemetry for this flow", + "prompt.example.22": "Compare these two implementations", + "prompt.example.23": "Create a minimal reproduction", + "prompt.example.24": "Suggest naming improvements", + "prompt.example.25": "What should we test next?", +} + +function render(template: string, params?: Record) { + if (!params) return template + return template.replace(/\{\{([^}]+)\}\}/g, (_, key: string) => { + const value = params[key.trim()] + if (value === undefined || value === null) return "" + return String(value) + }) +} + +export function useLanguage() { + return { + locale: () => "en" as const, + t(key: string, params?: Record) { + return render(dict[key] ?? key, params) + }, + } +} diff --git a/packages/storybook/.storybook/mocks/app/context/layout.ts b/packages/storybook/.storybook/mocks/app/context/layout.ts new file mode 100644 index 00000000000..0c5d6e97c90 --- /dev/null +++ b/packages/storybook/.storybook/mocks/app/context/layout.ts @@ -0,0 +1,41 @@ +import { createSignal } from "solid-js" + +const [all, setAll] = createSignal([]) +const [active, setActive] = createSignal(undefined) +const [reviewOpen, setReviewOpen] = createSignal(false) + +const tabs = { + all, + active, + open(tab: string) { + setAll((current) => (current.includes(tab) ? current : [...current, tab])) + }, + setActive(tab: string) { + if (!all().includes(tab)) { + tabs.open(tab) + } + setActive(tab) + }, +} + +const view = { + reviewPanel: { + opened: reviewOpen, + open() { + setReviewOpen(true) + }, + }, +} + +export function useLayout() { + return { + tabs: () => tabs, + view: () => view, + fileTree: { + setTab() {}, + }, + handoff: { + setTabs() {}, + }, + } +} diff --git a/packages/storybook/.storybook/mocks/app/context/local.ts b/packages/storybook/.storybook/mocks/app/context/local.ts new file mode 100644 index 00000000000..d1f02e5bb32 --- /dev/null +++ b/packages/storybook/.storybook/mocks/app/context/local.ts @@ -0,0 +1,41 @@ +import { createSignal } from "solid-js" + +const model = { + id: "claude-3-7-sonnet", + name: "Claude 3.7 Sonnet", + provider: { id: "anthropic" }, + variants: { fast: {}, thinking: {} }, +} + +const agents = [{ name: "build" }, { name: "review" }, { name: "plan" }] + +const [agent, setAgent] = createSignal(agents[0].name) +const [variant, setVariant] = createSignal(undefined) + +export function useLocal() { + return { + slug: () => "c3Rvcnk=", + agent: { + list: () => agents, + current: () => agents.find((item) => item.name === agent()) ?? agents[0], + set(value?: string) { + if (!value) { + setAgent(agents[0].name) + return + } + const hit = agents.find((item) => item.name === value) + setAgent(hit?.name ?? agents[0].name) + }, + }, + model: { + current: () => model, + variant: { + list: () => Object.keys(model.variants), + current: () => variant(), + set(next?: string) { + setVariant(next) + }, + }, + }, + } +} diff --git a/packages/storybook/.storybook/mocks/app/context/permission.ts b/packages/storybook/.storybook/mocks/app/context/permission.ts new file mode 100644 index 00000000000..b6fb37d96b7 --- /dev/null +++ b/packages/storybook/.storybook/mocks/app/context/permission.ts @@ -0,0 +1,24 @@ +const accepted = new Set() + +function key(sessionID: string, directory?: string) { + return `${directory ?? ""}:${sessionID}` +} + +export function usePermission() { + return { + autoResponds() { + return false + }, + isAutoAccepting(sessionID: string, directory?: string) { + return accepted.has(key(sessionID, directory)) + }, + toggleAutoAccept(sessionID: string, directory?: string) { + const next = key(sessionID, directory) + if (accepted.has(next)) { + accepted.delete(next) + return + } + accepted.add(next) + }, + } +} diff --git a/packages/storybook/.storybook/mocks/app/context/platform.ts b/packages/storybook/.storybook/mocks/app/context/platform.ts new file mode 100644 index 00000000000..74233cd1e5c --- /dev/null +++ b/packages/storybook/.storybook/mocks/app/context/platform.ts @@ -0,0 +1,16 @@ +import type { Platform } from "../../../../../app/src/context/platform" + +const value: Platform = { + platform: "web", + openLink() {}, + restart: async () => {}, + back() {}, + forward() {}, + notify: async () => {}, + fetch: globalThis.fetch.bind(globalThis), + parseMarkdown: async (markdown: string) => markdown, +} + +export function usePlatform() { + return value +} diff --git a/packages/storybook/.storybook/mocks/app/context/prompt.ts b/packages/storybook/.storybook/mocks/app/context/prompt.ts new file mode 100644 index 00000000000..e5e0e5d3354 --- /dev/null +++ b/packages/storybook/.storybook/mocks/app/context/prompt.ts @@ -0,0 +1,117 @@ +import { createSignal } from "solid-js" + +interface PartBase { + content: string + start: number + end: number +} + +export interface TextPart extends PartBase { + type: "text" +} + +export interface FileAttachmentPart extends PartBase { + type: "file" + path: string +} + +export interface AgentPart extends PartBase { + type: "agent" + name: string +} + +export interface ImageAttachmentPart { + type: "image" + id: string + filename: string + mime: string + dataUrl: string +} + +export type ContentPart = TextPart | FileAttachmentPart | AgentPart | ImageAttachmentPart +export type Prompt = ContentPart[] + +type ContextItem = { + key: string + type: "file" + path: string + selection?: { startLine: number; startChar: number; endLine: number; endChar: number } + comment?: string + commentID?: string + commentOrigin?: "review" | "file" + preview?: string +} + +export const DEFAULT_PROMPT: Prompt = [{ type: "text", content: "", start: 0, end: 0 }] + +function clonePart(part: ContentPart): ContentPart { + if (part.type === "image") return { ...part } + if (part.type === "agent") return { ...part } + if (part.type === "file") return { ...part } + return { ...part } +} + +function clonePrompt(prompt: Prompt) { + return prompt.map(clonePart) +} + +export function isPromptEqual(a: Prompt, b: Prompt) { + if (a.length !== b.length) return false + return a.every((part, i) => JSON.stringify(part) === JSON.stringify(b[i])) +} + +let index = 0 +const [prompt, setPrompt] = createSignal(clonePrompt(DEFAULT_PROMPT)) +const [cursor, setCursor] = createSignal(0) +const [items, setItems] = createSignal([]) + +const withKey = (item: Omit & { key?: string }): ContextItem => ({ + ...item, + key: item.key ?? `ctx:${++index}`, +}) + +export function usePrompt() { + return { + ready: () => true, + current: prompt, + cursor, + dirty: () => !isPromptEqual(prompt(), DEFAULT_PROMPT), + set(next: Prompt, cursorPosition?: number) { + setPrompt(clonePrompt(next)) + if (cursorPosition !== undefined) setCursor(cursorPosition) + }, + reset() { + setPrompt(clonePrompt(DEFAULT_PROMPT)) + setCursor(0) + setItems((current) => current.filter((item) => !!item.comment?.trim())) + }, + context: { + items, + add(item: Omit & { key?: string }) { + const next = withKey(item) + if (items().some((current) => current.key === next.key)) return + setItems((current) => [...current, next]) + }, + remove(key: string) { + setItems((current) => current.filter((item) => item.key !== key)) + }, + removeComment(path: string, commentID: string) { + setItems((current) => + current.filter((item) => !(item.type === "file" && item.path === path && item.commentID === commentID)), + ) + }, + updateComment(path: string, commentID: string, next: Partial) { + setItems((current) => + current.map((item) => { + if (item.type !== "file" || item.path !== path || item.commentID !== commentID) return item + return withKey({ ...item, ...next }) + }), + ) + }, + replaceComments(next: Array & { key?: string }>) { + const nonComment = items().filter((item) => !item.comment?.trim()) + setItems([...nonComment, ...next.map(withKey)]) + }, + }, + } +} diff --git a/packages/storybook/.storybook/mocks/app/context/sdk.ts b/packages/storybook/.storybook/mocks/app/context/sdk.ts new file mode 100644 index 00000000000..c37d682496d --- /dev/null +++ b/packages/storybook/.storybook/mocks/app/context/sdk.ts @@ -0,0 +1,25 @@ +const make = (directory: string) => ({ + session: { + create: async () => ({ data: { id: "story-session" } }), + prompt: async () => ({ data: undefined }), + shell: async () => ({ data: undefined }), + command: async () => ({ data: undefined }), + abort: async () => ({ data: undefined }), + }, + worktree: { + create: async () => ({ data: { directory: `${directory}/worktree-1` } }), + }, +}) + +const root = "/tmp/story" + +export function useSDK() { + return { + directory: root, + url: "http://localhost:4096", + client: make(root), + createClient(input: { directory: string }) { + return make(input.directory) + }, + } +} diff --git a/packages/storybook/.storybook/mocks/app/context/sync.ts b/packages/storybook/.storybook/mocks/app/context/sync.ts new file mode 100644 index 00000000000..bfc49dc83a9 --- /dev/null +++ b/packages/storybook/.storybook/mocks/app/context/sync.ts @@ -0,0 +1,32 @@ +import { createStore } from "solid-js/store" + +const [data, setData] = createStore({ + session: [] as Array<{ id: string; parentID?: string }>, + permission: {} as Record>, + question: {} as Record>, + session_diff: {} as Record>, + message: { + "story-session": [] as Array<{ id: string; role: string }>, + } as Record>, + session_status: {} as Record, + agent: [{ name: "build", mode: "task", hidden: false }], + command: [{ name: "fix", description: "Run fix command", source: "project" }], +}) + +export function useSync() { + return { + data, + set(...input: unknown[]) { + ;(setData as (...args: unknown[]) => void)(...input) + }, + session: { + get(id: string) { + return { id } + }, + optimistic: { + add() {}, + remove() {}, + }, + }, + } +} diff --git a/packages/storybook/.storybook/mocks/app/hooks/use-providers.ts b/packages/storybook/.storybook/mocks/app/hooks/use-providers.ts new file mode 100644 index 00000000000..04bd2b485c1 --- /dev/null +++ b/packages/storybook/.storybook/mocks/app/hooks/use-providers.ts @@ -0,0 +1,23 @@ +const model_id = "claude-3-7-sonnet" + +const provider = { + id: "anthropic", + models: { + [model_id]: { + id: model_id, + name: "Claude 3.7 Sonnet", + cost: { input: 1, output: 1 }, + variants: { fast: {}, thinking: {} }, + }, + }, +} + +export function useProviders() { + return { + all: () => [provider], + default: () => ({ anthropic: model_id }), + connected: () => [provider], + paid: () => [provider], + popular: () => [provider], + } +} diff --git a/packages/storybook/.storybook/mocks/solid-router.tsx b/packages/storybook/.storybook/mocks/solid-router.tsx new file mode 100644 index 00000000000..872c17ffe45 --- /dev/null +++ b/packages/storybook/.storybook/mocks/solid-router.tsx @@ -0,0 +1,20 @@ +import type { ParentProps } from "solid-js" + +export function useParams() { + return { + dir: "c3Rvcnk=", + id: "story-session", + } +} + +export function useNavigate() { + return () => undefined +} + +export function MemoryRouter(props: ParentProps) { + return props.children +} + +export function Route(props: ParentProps) { + return props.children +} diff --git a/packages/storybook/.storybook/preview.tsx b/packages/storybook/.storybook/preview.tsx index cb5ee4329bb..4e28e430391 100644 --- a/packages/storybook/.storybook/preview.tsx +++ b/packages/storybook/.storybook/preview.tsx @@ -1,4 +1,4 @@ -import "@opencode-ai/ui/styles" +import "@opencode-ai/ui/styles/tailwind" import { createEffect, onCleanup, onMount } from "solid-js" import addonA11y from "@storybook/addon-a11y" @@ -7,12 +7,8 @@ import { MetaProvider } from "@solidjs/meta" import { addons } from "storybook/preview-api" import { GLOBALS_UPDATED } from "storybook/internal/core-events" import { createJSXDecorator, definePreview } from "storybook-solidjs-vite" -import { Code } from "@opencode-ai/ui/code" -import { CodeComponentProvider } from "@opencode-ai/ui/context/code" import { DialogProvider } from "@opencode-ai/ui/context/dialog" -import { DiffComponentProvider } from "@opencode-ai/ui/context/diff" import { MarkedProvider } from "@opencode-ai/ui/context/marked" -import { Diff } from "@opencode-ai/ui/diff" import { ThemeProvider, useTheme, type ColorScheme } from "@opencode-ai/ui/theme" import { Font } from "@opencode-ai/ui/font" @@ -58,20 +54,16 @@ const frame = createJSXDecorator((Story, context) => { - - -
- -
-
-
+
+ +
diff --git a/packages/storybook/package.json b/packages/storybook/package.json index 2ab92bd5f81..b1dae1c9568 100644 --- a/packages/storybook/package.json +++ b/packages/storybook/package.json @@ -8,19 +8,20 @@ "build": "storybook build" }, "devDependencies": { + "@tailwindcss/vite": "catalog:", "@opencode-ai/ui": "workspace:*", "@solidjs/meta": "catalog:", - "@storybook/addon-a11y": "^10.2.10", - "@storybook/addon-docs": "^10.2.10", - "@storybook/addon-links": "^10.2.10", - "@storybook/addon-onboarding": "^10.2.10", - "@storybook/addon-vitest": "^10.2.10", + "@storybook/addon-a11y": "^10.2.13", + "@storybook/addon-docs": "^10.2.13", + "@storybook/addon-links": "^10.2.13", + "@storybook/addon-onboarding": "^10.2.13", + "@storybook/addon-vitest": "^10.2.13", "@tsconfig/node22": "catalog:", "@types/node": "catalog:", "@types/react": "18.0.25", "react": "18.2.0", "solid-js": "catalog:", - "storybook": "^10.2.10", + "storybook": "^10.2.13", "storybook-solidjs-vite": "^10.0.9", "typescript": "catalog:", "vite": "catalog:" diff --git a/packages/storybook/sst-env.d.ts b/packages/storybook/sst-env.d.ts new file mode 100644 index 00000000000..64441936d7a --- /dev/null +++ b/packages/storybook/sst-env.d.ts @@ -0,0 +1,10 @@ +/* This file is auto-generated by SST. Do not edit. */ +/* tslint:disable */ +/* eslint-disable */ +/* deno-fmt-ignore-file */ +/* biome-ignore-all lint: auto-generated */ + +/// + +import "sst" +export {} \ No newline at end of file diff --git a/packages/ui/package.json b/packages/ui/package.json index b2f9bb40111..42a1d40dd1c 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/ui", - "version": "1.2.15", + "version": "1.2.16", "type": "module", "license": "MIT", "exports": { @@ -34,6 +34,7 @@ "@types/bun": "catalog:", "@types/katex": "0.16.7", "@types/luxon": "catalog:", + "@typescript/native-preview": "catalog:", "tailwindcss": "catalog:", "typescript": "catalog:", "vite": "catalog:", @@ -50,7 +51,6 @@ "@solid-primitives/media": "2.3.3", "@solid-primitives/resize-observer": "2.1.3", "@solidjs/meta": "catalog:", - "@typescript/native-preview": "catalog:", "dompurify": "3.3.1", "fuzzysort": "catalog:", "katex": "0.16.27", @@ -59,6 +59,9 @@ "marked-katex-extension": "5.1.6", "marked-shiki": "catalog:", "morphdom": "2.7.8", + "motion": "12.34.5", + "motion-dom": "12.34.3", + "motion-utils": "12.29.2", "remeda": "catalog:", "shiki": "catalog:", "solid-js": "catalog:", diff --git a/packages/ui/src/components/animated-number.css b/packages/ui/src/components/animated-number.css new file mode 100644 index 00000000000..022b347e968 --- /dev/null +++ b/packages/ui/src/components/animated-number.css @@ -0,0 +1,75 @@ +[data-component="animated-number"] { + display: inline-flex; + align-items: baseline; + vertical-align: baseline; + line-height: inherit; + font-variant-numeric: tabular-nums; + + [data-slot="animated-number-value"] { + display: inline-flex; + flex-direction: row-reverse; + align-items: baseline; + justify-content: flex-end; + line-height: inherit; + width: var(--animated-number-width, 1ch); + overflow: hidden; + transition: width var(--tool-motion-spring-ms, 560ms) var(--tool-motion-ease, cubic-bezier(0.22, 1, 0.36, 1)); + } + + [data-slot="animated-number-digit"] { + display: inline-block; + width: 1ch; + height: 1em; + line-height: 1em; + overflow: hidden; + vertical-align: baseline; + -webkit-mask-image: linear-gradient( + to bottom, + transparent 0%, + #000 var(--tool-motion-mask, 18%), + #000 calc(100% - var(--tool-motion-mask, 18%)), + transparent 100% + ); + mask-image: linear-gradient( + to bottom, + transparent 0%, + #000 var(--tool-motion-mask, 18%), + #000 calc(100% - var(--tool-motion-mask, 18%)), + transparent 100% + ); + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + } + + [data-slot="animated-number-strip"] { + display: inline-flex; + flex-direction: column; + transform: translateY(calc(var(--animated-number-offset, 10) * -1em)); + transition-property: transform; + transition-duration: var(--animated-number-duration, 560ms); + transition-timing-function: var(--tool-motion-ease, cubic-bezier(0.22, 1, 0.36, 1)); + } + + [data-slot="animated-number-strip"][data-animating="false"] { + transition-duration: 0ms; + } + + [data-slot="animated-number-cell"] { + display: inline-flex; + align-items: center; + justify-content: center; + width: 1ch; + height: 1em; + line-height: 1em; + } +} + +@media (prefers-reduced-motion: reduce) { + [data-component="animated-number"] [data-slot="animated-number-value"] { + transition-duration: 0ms; + } + + [data-component="animated-number"] [data-slot="animated-number-strip"] { + transition-duration: 0ms; + } +} diff --git a/packages/ui/src/components/animated-number.tsx b/packages/ui/src/components/animated-number.tsx new file mode 100644 index 00000000000..b5fceba2563 --- /dev/null +++ b/packages/ui/src/components/animated-number.tsx @@ -0,0 +1,100 @@ +import { For, Index, createEffect, createMemo, createSignal, on } from "solid-js" + +const TRACK = Array.from({ length: 30 }, (_, index) => index % 10) +const DURATION = 600 + +function normalize(value: number) { + return ((value % 10) + 10) % 10 +} + +function spin(from: number, to: number, direction: 1 | -1) { + if (from === to) return 0 + if (direction > 0) return (to - from + 10) % 10 + return -((from - to + 10) % 10) +} + +function Digit(props: { value: number; direction: 1 | -1 }) { + const [step, setStep] = createSignal(props.value + 10) + const [animating, setAnimating] = createSignal(false) + let last = props.value + + createEffect( + on( + () => props.value, + (next) => { + const delta = spin(last, next, props.direction) + last = next + if (!delta) { + setAnimating(false) + setStep(next + 10) + return + } + + setAnimating(true) + setStep((value) => value + delta) + }, + { defer: true }, + ), + ) + + return ( + + { + setAnimating(false) + setStep((value) => normalize(value) + 10) + }} + style={{ + "--animated-number-offset": `${step()}`, + "--animated-number-duration": `var(--tool-motion-odometer-ms, ${DURATION}ms)`, + }} + > + {(value) => {value}} + + + ) +} + +export function AnimatedNumber(props: { value: number; class?: string }) { + const target = createMemo(() => { + if (!Number.isFinite(props.value)) return 0 + return Math.max(0, Math.round(props.value)) + }) + + const [value, setValue] = createSignal(target()) + const [direction, setDirection] = createSignal<1 | -1>(1) + + createEffect( + on( + target, + (next) => { + const current = value() + if (next === current) return + + setDirection(next > current ? 1 : -1) + setValue(next) + }, + { defer: true }, + ), + ) + + const label = createMemo(() => value().toString()) + const digits = createMemo(() => + Array.from(label(), (char) => { + const code = char.charCodeAt(0) - 48 + if (code < 0 || code > 9) return 0 + return code + }).reverse(), + ) + const width = createMemo(() => `${digits().length}ch`) + + return ( + + + {(digit) => } + + + ) +} diff --git a/packages/ui/src/components/basic-tool.css b/packages/ui/src/components/basic-tool.css index 1240ad7b995..02be54d738b 100644 --- a/packages/ui/src/components/basic-tool.css +++ b/packages/ui/src/components/basic-tool.css @@ -64,7 +64,7 @@ [data-slot="basic-tool-tool-info-main"] { display: flex; - align-items: center; + align-items: baseline; gap: 8px; min-width: 0; overflow: hidden; diff --git a/packages/ui/src/components/basic-tool.tsx b/packages/ui/src/components/basic-tool.tsx index 53bdc9ce1ec..fff6e92f17c 100644 --- a/packages/ui/src/components/basic-tool.tsx +++ b/packages/ui/src/components/basic-tool.tsx @@ -1,4 +1,5 @@ import { createEffect, createSignal, For, Match, on, onCleanup, Show, Switch, type JSX } from "solid-js" +import { animate, type AnimationPlaybackControls } from "motion" import { Collapsible } from "./collapsible" import type { IconProps } from "./icon" import { TextShimmer } from "./text-shimmer" @@ -29,9 +30,12 @@ export interface BasicToolProps { forceOpen?: boolean defer?: boolean locked?: boolean + animated?: boolean onSubtitleClick?: () => void } +const SPRING = { type: "spring" as const, visualDuration: 0.35, bounce: 0 } + export function BasicTool(props: BasicToolProps) { const [open, setOpen] = createSignal(props.defaultOpen ?? false) const [ready, setReady] = createSignal(open()) @@ -73,6 +77,38 @@ export function BasicTool(props: BasicToolProps) { ), ) + // Animated height for collapsible open/close + let contentRef: HTMLDivElement | undefined + let heightAnim: AnimationPlaybackControls | undefined + const initialOpen = open() + + createEffect( + on( + open, + (isOpen) => { + if (!props.animated || !contentRef) return + heightAnim?.stop() + if (isOpen) { + contentRef.style.overflow = "hidden" + heightAnim = animate(contentRef, { height: "auto" }, SPRING) + heightAnim.finished.then(() => { + if (!contentRef || !open()) return + contentRef.style.overflow = "visible" + contentRef.style.height = "auto" + }) + } else { + contentRef.style.overflow = "hidden" + heightAnim = animate(contentRef, { height: "0px" }, SPRING) + } + }, + { defer: true }, + ), + ) + + onCleanup(() => { + heightAnim?.stop() + }) + const handleOpenChange = (value: boolean) => { if (pending()) return if (props.locked && !value) return @@ -96,9 +132,7 @@ export function BasicTool(props: BasicToolProps) { [trigger().titleClass ?? ""]: !!trigger().titleClass, }} > - - - + @@ -147,7 +181,20 @@ export function BasicTool(props: BasicToolProps) {
- + +
+ {props.children} +
+
+ {props.children} diff --git a/packages/ui/src/components/checkbox.css b/packages/ui/src/components/checkbox.css index 82994bb88a9..30417996591 100644 --- a/packages/ui/src/components/checkbox.css +++ b/packages/ui/src/components/checkbox.css @@ -28,6 +28,10 @@ flex-shrink: 0; border-radius: var(--radius-sm); border: 1px solid var(--border-weak-base); + transition: + border-color 220ms var(--tool-motion-ease, cubic-bezier(0.22, 1, 0.36, 1)), + background-color 220ms var(--tool-motion-ease, cubic-bezier(0.22, 1, 0.36, 1)), + box-shadow 220ms var(--tool-motion-ease, cubic-bezier(0.22, 1, 0.36, 1)); /* background-color: var(--surface-weak); */ } @@ -39,6 +43,10 @@ height: 100%; color: var(--icon-base); opacity: 0; + transform: scale(0.9); + transition: + opacity 180ms var(--tool-motion-ease, cubic-bezier(0.22, 1, 0.36, 1)), + transform 220ms var(--tool-motion-ease, cubic-bezier(0.22, 1, 0.36, 1)); } /* [data-slot="checkbox-checkbox-content"] { */ @@ -100,6 +108,7 @@ &[data-checked] [data-slot="checkbox-checkbox-indicator"], &[data-indeterminate] [data-slot="checkbox-checkbox-indicator"] { opacity: 1; + transform: scale(1); } &[data-disabled] { diff --git a/packages/ui/src/components/code.stories.tsx b/packages/ui/src/components/code.stories.tsx deleted file mode 100644 index 992fa630242..00000000000 --- a/packages/ui/src/components/code.stories.tsx +++ /dev/null @@ -1,70 +0,0 @@ -// @ts-nocheck -import * as mod from "./code" -import { create } from "../storybook/scaffold" -import { code } from "../storybook/fixtures" - -const docs = `### Overview -Syntax-highlighted code viewer with selection support and large-file virtualization. - -Use alongside \`LineComment\` and \`Diff\` in review workflows. - -### API -- Required: \`file\` with file name + contents. -- Optional: \`language\`, \`annotations\`, \`selectedLines\`, \`commentedLines\`. -- Optional callbacks: \`onRendered\`, \`onLineSelectionEnd\`. - -### Variants and states -- Supports large-file virtualization automatically. - -### Behavior -- Re-renders when \`file\` or rendering options change. -- Optional line selection integrates with selection callbacks. - -### Accessibility -- TODO: confirm keyboard find and selection behavior. - -### Theming/tokens -- Uses \`data-component="code"\` and Pierre CSS variables from \`styleVariables\`. - -` - -const story = create({ - title: "UI/Code", - mod, - args: { - file: code, - language: "ts", - }, -}) - -export default { - title: "UI/Code", - id: "components-code", - component: story.meta.component, - tags: ["autodocs"], - parameters: { - docs: { - description: { - component: docs, - }, - }, - }, -} - -export const Basic = story.Basic - -export const SelectedLines = { - args: { - enableLineSelection: true, - selectedLines: { start: 2, end: 4 }, - }, -} - -export const CommentedLines = { - args: { - commentedLines: [ - { start: 1, end: 1 }, - { start: 5, end: 6 }, - ], - }, -} diff --git a/packages/ui/src/components/diff-ssr.stories.tsx b/packages/ui/src/components/diff-ssr.stories.tsx deleted file mode 100644 index d1adce28066..00000000000 --- a/packages/ui/src/components/diff-ssr.stories.tsx +++ /dev/null @@ -1,97 +0,0 @@ -// @ts-nocheck -import { preloadMultiFileDiff } from "@pierre/diffs/ssr" -import { createResource, Show } from "solid-js" -import * as mod from "./diff-ssr" -import { createDefaultOptions } from "../pierre" -import { WorkerPoolProvider } from "../context/worker-pool" -import { getWorkerPools } from "../pierre/worker" -import { diff } from "../storybook/fixtures" - -const docs = `### Overview -Server-rendered diff hydration component for preloaded Pierre diff output. - -Use alongside server routes that preload diffs. -Pair with \`DiffChanges\` for summaries. - -### API -- Required: \`before\`, \`after\`, and \`preloadedDiff\` from \`preloadMultiFileDiff\`. -- Optional: \`diffStyle\`, \`annotations\`, \`selectedLines\`, \`commentedLines\`. - -### Variants and states -- Unified/split styles (preloaded must match the style used during preload). - -### Behavior -- Hydrates pre-rendered diff HTML into a live diff instance. -- Requires a worker pool provider for syntax highlighting. - -### Accessibility -- TODO: confirm keyboard behavior from the Pierre diff engine. - -### Theming/tokens -- Uses \`data-component="diff"\` with Pierre CSS variables and theme tokens. - -` - -const load = async () => { - return preloadMultiFileDiff({ - oldFile: diff.before, - newFile: diff.after, - options: createDefaultOptions("unified"), - }) -} - -const loadSplit = async () => { - return preloadMultiFileDiff({ - oldFile: diff.before, - newFile: diff.after, - options: createDefaultOptions("split"), - }) -} - -export default { - title: "UI/DiffSSR", - id: "components-diff-ssr", - component: mod.Diff, - tags: ["autodocs"], - parameters: { - docs: { - description: { - component: docs, - }, - }, - }, -} - -export const Basic = { - render: () => { - const [data] = createResource(load) - return ( - - Loading pre-rendered diff...
}> - {(preloaded) => ( -
- -
- )} - - - ) - }, -} - -export const Split = { - render: () => { - const [data] = createResource(loadSplit) - return ( - - Loading pre-rendered diff...}> - {(preloaded) => ( -
- -
- )} -
-
- ) - }, -} diff --git a/packages/ui/src/components/diff.stories.tsx b/packages/ui/src/components/diff.stories.tsx deleted file mode 100644 index 03bf4a0e0f0..00000000000 --- a/packages/ui/src/components/diff.stories.tsx +++ /dev/null @@ -1,96 +0,0 @@ -// @ts-nocheck -import * as mod from "./diff" -import { create } from "../storybook/scaffold" -import { diff } from "../storybook/fixtures" - -const docs = `### Overview -Render a code diff with OpenCode styling using the Pierre diff engine. - -Pair with \`DiffChanges\` for summary counts. -Use \`LineComment\` or external UI for annotation workflows. - -### API -- Required: \`before\` and \`after\` file contents (name + contents). -- Optional: \`diffStyle\` ("unified" | "split"), \`annotations\`, \`selectedLines\`, \`commentedLines\`. -- Optional interaction: \`enableLineSelection\`, \`onLineSelectionEnd\`. -- Passes through Pierre FileDiff options (see component source). - -### Variants and states -- Unified and split diff styles. -- Optional line selection + commented line highlighting. - -### Behavior -- Re-renders when \`before\`/\`after\` or diff options change. -- Line selection uses mouse drag/selection when enabled. - -### Accessibility -- TODO: confirm keyboard behavior from the Pierre diff engine. -- Provide surrounding labels or headings when used as a standalone view. - -### Theming/tokens -- Uses \`data-component="diff"\` and Pierre CSS variables from \`styleVariables\`. -- Colors derive from theme tokens (diff add/delete, background, text). - -` - -const story = create({ - title: "UI/Diff", - mod, - args: { - before: diff.before, - after: diff.after, - diffStyle: "unified", - }, -}) - -export default { - title: "UI/Diff", - id: "components-diff", - component: story.meta.component, - tags: ["autodocs"], - parameters: { - docs: { - description: { - component: docs, - }, - }, - }, - argTypes: { - diffStyle: { - control: "select", - options: ["unified", "split"], - }, - enableLineSelection: { - control: "boolean", - }, - }, -} - -export const Unified = story.Basic - -export const Split = { - args: { - diffStyle: "split", - }, -} - -export const Selectable = { - args: { - enableLineSelection: true, - }, -} - -export const SelectedLines = { - args: { - selectedLines: { start: 2, end: 4 }, - }, -} - -export const CommentedLines = { - args: { - commentedLines: [ - { start: 1, end: 1 }, - { start: 4, end: 4 }, - ], - }, -} diff --git a/packages/ui/src/components/message-part.css b/packages/ui/src/components/message-part.css index 58227f62597..3eee45c75fc 100644 --- a/packages/ui/src/components/message-part.css +++ b/packages/ui/src/components/message-part.css @@ -46,12 +46,18 @@ overflow: hidden; background: var(--surface-weak); border: 1px solid var(--border-weak-base); - transition: border-color 0.15s ease; + transition: + border-color 0.15s ease, + opacity 0.3s ease; &:hover { border-color: var(--border-strong-base); } + &[data-queued] { + opacity: 0.6; + } + &[data-type="image"] { width: 48px; height: 48px; @@ -101,6 +107,11 @@ border: 1px solid var(--border-weak-base); padding: 8px 12px; border-radius: 6px; + transition: opacity 0.3s ease; + + &[data-queued] { + opacity: 0.6; + } [data-highlight="file"] { color: var(--syntax-property); @@ -113,6 +124,14 @@ max-width: 100%; } + [data-slot="user-message-queued-indicator"] { + margin-top: 6px; + margin-right: 2px; + font-size: var(--font-size-small); + color: var(--text-weak); + user-select: none; + } + [data-slot="user-message-copy-wrapper"] { min-height: 24px; margin-top: 4px; @@ -149,6 +168,7 @@ align-items: center; justify-content: flex-end; overflow: hidden; + gap: 6px; } [data-slot="user-message-meta-tail"] { @@ -225,6 +245,33 @@ } } +[data-component="compaction-part"] { + width: 100%; + display: flex; + flex-direction: column; + align-items: stretch; + + [data-slot="compaction-part-divider"] { + display: flex; + align-items: center; + gap: 12px; + padding: 10px 0; + width: 100%; + } + + [data-slot="compaction-part-line"] { + flex: 1 1 auto; + height: 1px; + background: var(--border-weak-base); + } + + [data-slot="compaction-part-label"] { + flex: 0 0 auto; + white-space: nowrap; + text-align: center; + } +} + [data-component="reasoning-part"] { width: 100%; color: var(--text-base); @@ -561,29 +608,8 @@ cursor: pointer; [data-slot="context-tool-group-title"] { - min-width: 0; - display: flex; - align-items: center; - gap: 8px; - font-family: var(--font-family-sans); - font-size: 14px; - font-weight: var(--font-weight-medium); - line-height: var(--line-height-large); - color: var(--text-strong); - } - - [data-slot="context-tool-group-label"] { - flex-shrink: 0; - } - - [data-slot="context-tool-group-summary"] { flex-shrink: 1; min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - font-weight: var(--font-weight-regular); - color: var(--text-base); } [data-slot="collapsible-arrow"] { diff --git a/packages/ui/src/components/message-part.tsx b/packages/ui/src/components/message-part.tsx index 6b6dfe2e50e..aecdbc8e41f 100644 --- a/packages/ui/src/components/message-part.tsx +++ b/packages/ui/src/components/message-part.tsx @@ -5,9 +5,11 @@ import { createSignal, For, Match, + onMount, Show, Switch, onCleanup, + Index, type JSX, } from "solid-js" import stripAnsi from "strip-ansi" @@ -47,6 +49,42 @@ import { checksum } from "@opencode-ai/util/encode" import { Tooltip } from "./tooltip" import { IconButton } from "./icon-button" import { TextShimmer } from "./text-shimmer" +import { AnimatedCountList } from "./tool-count-summary" +import { ToolStatusTitle } from "./tool-status-title" +import { animate } from "motion" + +function ShellSubmessage(props: { text: string; animate?: boolean }) { + let widthRef: HTMLSpanElement | undefined + let valueRef: HTMLSpanElement | undefined + + onMount(() => { + if (!props.animate) return + requestAnimationFrame(() => { + if (widthRef) { + animate(widthRef, { width: "auto" }, { type: "spring", visualDuration: 0.25, bounce: 0 }) + } + if (valueRef) { + animate(valueRef, { opacity: 1, filter: "blur(0px)" }, { duration: 0.32, ease: [0.16, 1, 0.3, 1] }) + } + }) + }) + + return ( + + + + + {props.text} + + + + + ) +} interface Diagnostic { range: { @@ -92,6 +130,7 @@ export interface MessageProps { parts: PartType[] showAssistantCopyPartID?: string | null interrupted?: boolean + queued?: boolean showReasoningSummaries?: boolean } @@ -271,6 +310,102 @@ function list(value: T[] | undefined | null, fallback: T[]) { return fallback } +function same(a: readonly T[] | undefined, b: readonly T[] | undefined) { + if (a === b) return true + if (!a || !b) return false + if (a.length !== b.length) return false + return a.every((x, i) => x === b[i]) +} + +type PartRef = { + messageID: string + partID: string +} + +type PartGroup = + | { + key: string + type: "part" + ref: PartRef + } + | { + key: string + type: "context" + refs: PartRef[] + } + +function sameRef(a: PartRef, b: PartRef) { + return a.messageID === b.messageID && a.partID === b.partID +} + +function sameGroup(a: PartGroup, b: PartGroup) { + if (a === b) return true + if (a.key !== b.key) return false + if (a.type !== b.type) return false + if (a.type === "part") { + if (b.type !== "part") return false + return sameRef(a.ref, b.ref) + } + if (b.type !== "context") return false + if (a.refs.length !== b.refs.length) return false + return a.refs.every((ref, i) => sameRef(ref, b.refs[i]!)) +} + +function sameGroups(a: readonly PartGroup[] | undefined, b: readonly PartGroup[] | undefined) { + if (a === b) return true + if (!a || !b) return false + if (a.length !== b.length) return false + return a.every((item, i) => sameGroup(item, b[i]!)) +} + +function groupParts(parts: { messageID: string; part: PartType }[]) { + const result: PartGroup[] = [] + let start = -1 + + const flush = (end: number) => { + if (start < 0) return + const first = parts[start] + const last = parts[end] + if (!first || !last) { + start = -1 + return + } + result.push({ + key: `context:${first.part.id}`, + type: "context", + refs: parts.slice(start, end + 1).map((item) => ({ + messageID: item.messageID, + partID: item.part.id, + })), + }) + start = -1 + } + + parts.forEach((item, index) => { + if (isContextGroupTool(item.part)) { + if (start < 0) start = index + return + } + + flush(index - 1) + result.push({ + key: `part:${item.messageID}:${item.part.id}`, + type: "part", + ref: { + messageID: item.messageID, + partID: item.part.id, + }, + }) + }) + + flush(parts.length - 1) + return result +} + +function partByID(parts: readonly PartType[], partID: string) { + return parts.find((part) => part.id === partID) +} + function renderable(part: PartType, showReasoningSummaries = true) { if (part.type === "tool") { if (HIDDEN_TOOLS.has(part.tool)) return false @@ -303,101 +438,88 @@ export function AssistantParts(props: { }) { const data = useData() const emptyParts: PartType[] = [] + const emptyTools: ToolPart[] = [] - const grouped = createMemo(() => { - const keys: string[] = [] - const items: Record< - string, - { type: "part"; part: PartType; message: AssistantMessage } | { type: "context"; parts: ToolPart[] } - > = {} - const push = ( - key: string, - item: { type: "part"; part: PartType; message: AssistantMessage } | { type: "context"; parts: ToolPart[] }, - ) => { - keys.push(key) - items[key] = item - } - - const parts = props.messages.flatMap((message) => - list(data.store.part?.[message.id], emptyParts) - .filter((part) => renderable(part, props.showReasoningSummaries ?? true)) - .map((part) => ({ message, part })), - ) - - let start = -1 - - const flush = (end: number) => { - if (start < 0) return - const first = parts[start] - const last = parts[end] - if (!first || !last) { - start = -1 - return - } - push(`context:${first.part.id}`, { - type: "context", - parts: parts - .slice(start, end + 1) - .map((x) => x.part) - .filter((part): part is ToolPart => isContextGroupTool(part)), - }) - start = -1 - } - - parts.forEach((item, index) => { - if (isContextGroupTool(item.part)) { - if (start < 0) start = index - return - } + const grouped = createMemo( + () => + groupParts( + props.messages.flatMap((message) => + list(data.store.part?.[message.id], emptyParts) + .filter((part) => renderable(part, props.showReasoningSummaries ?? true)) + .map((part) => ({ + messageID: message.id, + part, + })), + ), + ), + [] as PartGroup[], + { equals: sameGroups }, + ) - flush(index - 1) - push(`part:${item.message.id}:${item.part.id}`, { type: "part", part: item.part, message: item.message }) - }) + const last = createMemo(() => grouped().at(-1)?.key) - flush(parts.length - 1) + return ( + + {(entryAccessor) => { + const entryType = createMemo(() => entryAccessor().type) - return { keys, items } - }) + return ( + + + {(() => { + const parts = createMemo( + () => { + const entry = entryAccessor() as { type: "context"; refs: PartRef[] } + return entry.refs + .map((ref) => partByID(list(data.store.part?.[ref.messageID], emptyParts), ref.partID)) + .filter((part): part is ToolPart => !!part && isContextGroupTool(part)) + }, + emptyTools, + { equals: same }, + ) + const busy = createMemo(() => props.working && last() === entryAccessor().key) - const last = createMemo(() => grouped().keys.at(-1)) + return ( + 0}> + + + ) + })()} + + + {(() => { + const message = createMemo(() => { + const entry = entryAccessor() as { type: "part"; ref: PartRef } + return props.messages.find((item) => item.id === entry.ref.messageID) + }) + const part = createMemo(() => { + const entry = entryAccessor() as { type: "part"; ref: PartRef } + return partByID(list(data.store.part?.[entry.ref.messageID], emptyParts), entry.ref.partID) + }) - return ( - - {(key) => { - const item = createMemo(() => grouped().items[key]) - const ctx = createMemo(() => { - const value = item() - if (!value) return - if (value.type !== "context") return - return value - }) - const part = createMemo(() => { - const value = item() - if (!value) return - if (value.type !== "part") return - return value - }) - const tail = createMemo(() => last() === key) - return ( - <> - - {(entry) => } - - - {(entry) => ( - - )} - - + return ( + + {(msg) => ( + + {(p) => ( + + )} + + )} + + ) + })()} + + ) }} - + ) } @@ -468,23 +590,11 @@ function contextToolTrigger(part: ToolPart, i18n: ReturnType) { } } -function contextToolSummary(parts: ToolPart[], i18n: ReturnType) { +function contextToolSummary(parts: ToolPart[]) { const read = parts.filter((part) => part.tool === "read").length const search = parts.filter((part) => part.tool === "glob" || part.tool === "grep").length const list = parts.filter((part) => part.tool === "list").length - return [ - read - ? i18n.t(read === 1 ? "ui.messagePart.context.read.one" : "ui.messagePart.context.read.other", { count: read }) - : undefined, - search - ? i18n.t(search === 1 ? "ui.messagePart.context.search.one" : "ui.messagePart.context.search.other", { - count: search, - }) - : undefined, - list - ? i18n.t(list === 1 ? "ui.messagePart.context.list.one" : "ui.messagePart.context.list.other", { count: list }) - : undefined, - ].filter((value): value is string => !!value) + return { read, search, list } } export function registerPartComponent(type: string, component: PartComponent) { @@ -500,6 +610,7 @@ export function Message(props: MessageProps) { message={userMessage() as UserMessage} parts={props.parts} interrupted={props.interrupted} + queued={props.queued} /> )} @@ -523,81 +634,72 @@ export function AssistantMessageDisplay(props: { showAssistantCopyPartID?: string | null showReasoningSummaries?: boolean }) { - const grouped = createMemo(() => { - const keys: string[] = [] - const items: Record = {} - const push = (key: string, item: { type: "part"; part: PartType } | { type: "context"; parts: ToolPart[] }) => { - keys.push(key) - items[key] = item - } - - const parts = props.parts - let start = -1 - - const flush = (end: number) => { - if (start < 0) return - const first = parts[start] - const last = parts[end] - if (!first || !last) { - start = -1 - return - } - push(`context:${first.id}`, { - type: "context", - parts: parts.slice(start, end + 1).filter((part): part is ToolPart => isContextGroupTool(part)), - }) - start = -1 - } - - parts.forEach((part, index) => { - if (!renderable(part, props.showReasoningSummaries ?? true)) return - - if (isContextGroupTool(part)) { - if (start < 0) start = index - return - } + const emptyTools: ToolPart[] = [] + const grouped = createMemo( + () => + groupParts( + props.parts + .filter((part) => renderable(part, props.showReasoningSummaries ?? true)) + .map((part) => ({ + messageID: props.message.id, + part, + })), + ), + [] as PartGroup[], + { equals: sameGroups }, + ) - flush(index - 1) - push(`part:${part.id}`, { type: "part", part }) - }) + return ( + + {(entryAccessor) => { + const entryType = createMemo(() => entryAccessor().type) - flush(parts.length - 1) + return ( + + + {(() => { + const parts = createMemo( + () => { + const entry = entryAccessor() as { type: "context"; refs: PartRef[] } + return entry.refs + .map((ref) => partByID(props.parts, ref.partID)) + .filter((part): part is ToolPart => !!part && isContextGroupTool(part)) + }, + emptyTools, + { equals: same }, + ) - return { keys, items } - }) + return ( + 0}> + + + ) + })()} + + + {(() => { + const part = createMemo(() => { + const entry = entryAccessor() as { type: "part"; ref: PartRef } + return partByID(props.parts, entry.ref.partID) + }) - return ( - - {(key) => { - const item = createMemo(() => grouped().items[key]) - const ctx = createMemo(() => { - const value = item() - if (!value) return - if (value.type !== "context") return - return value - }) - const part = createMemo(() => { - const value = item() - if (!value) return - if (value.type !== "part") return - return value - }) - return ( - <> - {(entry) => } - - {(entry) => ( - - )} - - + return ( + + {(p) => ( + + )} + + ) + })()} + + ) }} - + ) } @@ -608,42 +710,64 @@ function ContextToolGroup(props: { parts: ToolPart[]; busy?: boolean }) { () => !!props.busy || props.parts.some((part) => part.state.status === "pending" || part.state.status === "running"), ) - const summary = createMemo(() => contextToolSummary(props.parts, i18n)) - const details = createMemo(() => summary().join(", ")) + const summary = createMemo(() => contextToolSummary(props.parts)) return (
- - {i18n.t("ui.sessionTurn.status.gatheredContext")} - - {details()} - - - } + - - - - - - {details()} - + + - + + + +
- - {(part) => { - const trigger = contextToolTrigger(part, i18n) - const running = part.state.status === "pending" || part.state.status === "running" + + {(partAccessor) => { + const trigger = createMemo(() => contextToolTrigger(partAccessor(), i18n)) + const running = createMemo( + () => partAccessor().state.status === "pending" || partAccessor().state.status === "running", + ) return (
@@ -652,15 +776,13 @@ function ContextToolGroup(props: { parts: ToolPart[]; busy?: boolean }) {
- - - + - - {trigger.subtitle} + + {trigger().subtitle} - - + + {(arg) => {arg}} @@ -672,14 +794,19 @@ function ContextToolGroup(props: { parts: ToolPart[]; busy?: boolean }) {
) }} - +
) } -export function UserMessageDisplay(props: { message: UserMessage; parts: PartType[]; interrupted?: boolean }) { +export function UserMessageDisplay(props: { + message: UserMessage + parts: PartType[] + interrupted?: boolean + queued?: boolean +}) { const data = useData() const dialog = useDialog() const i18n = useI18n() @@ -759,6 +886,7 @@ export function UserMessageDisplay(props: { message: UserMessage; parts: PartTyp
{ if (file.mime.startsWith("image/") && file.url) { openImagePreview(file.url, file.filename) @@ -787,9 +915,14 @@ export function UserMessageDisplay(props: { message: UserMessage; parts: PartTyp <>
-
+
+ +
+ +
+
@@ -965,30 +1098,30 @@ function ToolFileAccordion(props: { path: string; actions?: JSX.Element; childre PART_MAPPING["tool"] = function ToolPartDisplay(props) { const i18n = useI18n() - const part = props.part as ToolPart - if (part.tool === "todowrite" || part.tool === "todoread") return null + const part = () => props.part as ToolPart + if (part().tool === "todowrite" || part().tool === "todoread") return null const hideQuestion = createMemo( - () => part.tool === "question" && (part.state.status === "pending" || part.state.status === "running"), + () => part().tool === "question" && (part().state.status === "pending" || part().state.status === "running"), ) const emptyInput: Record = {} const emptyMetadata: Record = {} - const input = () => part.state?.input ?? emptyInput + const input = () => part().state?.input ?? emptyInput // @ts-expect-error - const partMetadata = () => part.state?.metadata ?? emptyMetadata + const partMetadata = () => part().state?.metadata ?? emptyMetadata - const render = ToolRegistry.render(part.tool) ?? GenericTool + const render = createMemo(() => ToolRegistry.render(part().tool) ?? GenericTool) return (
- + {(error) => { const cleaned = error().replace("Error: ", "") - if (part.tool === "question" && cleaned.includes("dismissed this question")) { + if (part().tool === "question" && cleaned.includes("dismissed this question")) { return (
@@ -1020,13 +1153,13 @@ PART_MAPPING["tool"] = function ToolPartDisplay(props) { @@ -1037,10 +1170,25 @@ PART_MAPPING["tool"] = function ToolPartDisplay(props) { ) } +PART_MAPPING["compaction"] = function CompactionPartDisplay() { + const i18n = useI18n() + return ( +
+
+ + + {i18n.t("ui.messagePart.compaction")} + + +
+
+ ) +} + PART_MAPPING["text"] = function TextPartDisplay(props) { const data = useData() const i18n = useI18n() - const part = props.part as TextPart + const part = () => props.part as TextPart const interrupted = createMemo( () => props.message.role === "assistant" && (props.message as AssistantMessage).error?.name === "MessageAbortedError", @@ -1083,18 +1231,18 @@ PART_MAPPING["text"] = function TextPartDisplay(props) { return items.filter((x) => !!x).join(" \u00B7 ") }) - const displayText = () => (part.text ?? "").trim() + const displayText = () => (part().text ?? "").trim() const throttledText = createThrottledValue(displayText) const isLastTextPart = createMemo(() => { const last = (data.store.part?.[props.message.id] ?? []) .filter((item): item is TextPart => item?.type === "text" && !!item.text?.trim()) .at(-1) - return last?.id === part.id + return last?.id === part().id }) const showCopy = createMemo(() => { if (props.message.role !== "assistant") return isLastTextPart() if (props.showAssistantCopyPartID === null) return false - if (typeof props.showAssistantCopyPartID === "string") return props.showAssistantCopyPartID === part.id + if (typeof props.showAssistantCopyPartID === "string") return props.showAssistantCopyPartID === part().id return isLastTextPart() }) const [copied, setCopied] = createSignal(false) @@ -1111,7 +1259,7 @@ PART_MAPPING["text"] = function TextPartDisplay(props) {
- +
@@ -1142,14 +1290,14 @@ PART_MAPPING["text"] = function TextPartDisplay(props) { } PART_MAPPING["reasoning"] = function ReasoningPartDisplay(props) { - const part = props.part as ReasoningPart - const text = () => part.text.trim() + const part = () => props.part as ReasoningPart + const text = () => part().text.trim() const throttledText = createThrottledValue(text) return (
- +
) @@ -1291,9 +1439,7 @@ ToolRegistry.register({
- - - + { - const sessionId = childSessionId() - const url = href() - if (!sessionId || !url) return - - e.stopPropagation() - - if (e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return - - const nav = data.navigateToSession - if (!nav || typeof window === "undefined") return - - e.preventDefault() - const before = window.location.pathname + window.location.search + window.location.hash - nav(sessionId) - setTimeout(() => { - const after = window.location.pathname + window.location.search + window.location.hash - if (after === before) window.location.assign(url) - }, 50) - } - const titleContent = () => const trigger = () => ( @@ -1385,7 +1510,7 @@ ToolRegistry.register({ data-slot="basic-tool-tool-subtitle" class="clickable subagent-link" href={url()} - onClick={handleLinkClick} + onClick={(e) => e.stopPropagation()} > {description()} @@ -1408,6 +1533,8 @@ ToolRegistry.register({ name: "bash", render(props) { const i18n = useI18n() + const pending = () => props.status === "pending" || props.status === "running" + const sawPending = pending() const text = createMemo(() => { const cmd = props.input.command ?? props.metadata.command ?? "" const out = stripAnsi(props.output || props.metadata.output || "") @@ -1427,10 +1554,18 @@ ToolRegistry.register({ +
+ + + + + + +
+
+ } >
@@ -1480,9 +1615,7 @@ ToolRegistry.register({
- - - + {filename()} @@ -1552,9 +1685,7 @@ ToolRegistry.register({
- - - + {filename()} @@ -1746,9 +1877,7 @@ ToolRegistry.register({
- - - + {getFilename(file().relativePath)} diff --git a/packages/ui/src/components/motion-spring.tsx b/packages/ui/src/components/motion-spring.tsx new file mode 100644 index 00000000000..a5104a1a3ef --- /dev/null +++ b/packages/ui/src/components/motion-spring.tsx @@ -0,0 +1,45 @@ +import { attachSpring, motionValue } from "motion" +import type { SpringOptions } from "motion" +import { createEffect, createSignal, onCleanup } from "solid-js" + +type Opt = Partial> +const eq = (a: Opt | undefined, b: Opt | undefined) => + a?.visualDuration === b?.visualDuration && + a?.bounce === b?.bounce && + a?.stiffness === b?.stiffness && + a?.damping === b?.damping && + a?.mass === b?.mass && + a?.velocity === b?.velocity + +export function useSpring(target: () => number, options?: Opt | (() => Opt)) { + const read = () => (typeof options === "function" ? options() : options) + const [value, setValue] = createSignal(target()) + const source = motionValue(value()) + const spring = motionValue(value()) + let config = read() + let stop = attachSpring(spring, source, config) + let off = spring.on("change", (next: number) => setValue(next)) + + createEffect(() => { + source.set(target()) + }) + + createEffect(() => { + if (!options) return + const next = read() + if (eq(config, next)) return + config = next + stop() + stop = attachSpring(spring, source, next) + setValue(spring.get()) + }) + + onCleanup(() => { + off() + stop() + spring.destroy() + source.destroy() + }) + + return value +} diff --git a/packages/ui/src/components/provider-icon.tsx b/packages/ui/src/components/provider-icon.tsx index d653765a554..edfdd035713 100644 --- a/packages/ui/src/components/provider-icon.tsx +++ b/packages/ui/src/components/provider-icon.tsx @@ -1,14 +1,15 @@ import type { Component, JSX } from "solid-js" -import { splitProps } from "solid-js" +import { createMemo, splitProps } from "solid-js" import sprite from "./provider-icons/sprite.svg" -import type { IconName } from "./provider-icons/types" +import { iconNames, type IconName } from "./provider-icons/types" export type ProviderIconProps = JSX.SVGElementTags["svg"] & { - id: IconName + id: string } export const ProviderIcon: Component = (props) => { const [local, rest] = splitProps(props, ["id", "class", "classList"]) + const resolved = createMemo(() => (iconNames.includes(local.id as IconName) ? local.id : "synthetic")) return ( = (props) => { [local.class ?? ""]: !!local.class, }} > - + ) } diff --git a/packages/ui/src/components/radio-group.css b/packages/ui/src/components/radio-group.css index 4faaa33f434..e9cc7118463 100644 --- a/packages/ui/src/components/radio-group.css +++ b/packages/ui/src/components/radio-group.css @@ -48,9 +48,9 @@ transition: opacity 200ms ease-out, box-shadow 100ms ease-in-out, - width 200ms ease-out, - height 200ms ease-out, - transform 200ms ease-out; + width 200ms cubic-bezier(0.22, 1.2, 0.36, 1), + height 200ms cubic-bezier(0.22, 1.2, 0.36, 1), + transform 300ms cubic-bezier(0.22, 1.2, 0.36, 1); will-change: transform; z-index: 0; } diff --git a/packages/ui/src/components/scroll-view.css b/packages/ui/src/components/scroll-view.css index f81ae297664..f6a49e241c6 100644 --- a/packages/ui/src/components/scroll-view.css +++ b/packages/ui/src/components/scroll-view.css @@ -19,7 +19,7 @@ position: absolute; right: 0; top: 0; - width: 16px; + width: 12px; transition: opacity 200ms ease; cursor: default; user-select: none; @@ -29,10 +29,11 @@ .scroll-view__thumb::after { content: ""; position: absolute; - right: 4px; + left: 50%; + transform: translateX(-50%); top: 0; bottom: 0; - width: 6px; + width: 4px; border-radius: 9999px; background-color: var(--border-weak-base); backdrop-filter: blur(4px); diff --git a/packages/ui/src/components/session-retry.tsx b/packages/ui/src/components/session-retry.tsx new file mode 100644 index 00000000000..ac0eb035d96 --- /dev/null +++ b/packages/ui/src/components/session-retry.tsx @@ -0,0 +1,74 @@ +import { createEffect, createMemo, createSignal, on, onCleanup, Show } from "solid-js" +import type { SessionStatus } from "@opencode-ai/sdk/v2/client" +import { useI18n } from "../context/i18n" +import { Card } from "./card" +import { Tooltip } from "./tooltip" +import { Spinner } from "./spinner" + +export function SessionRetry(props: { status: SessionStatus; show?: boolean }) { + const i18n = useI18n() + const retry = createMemo(() => { + if (props.status.type !== "retry") return + return props.status + }) + const [seconds, setSeconds] = createSignal(0) + createEffect( + on(retry, (current) => { + if (!current) return + const update = () => { + const next = retry()?.next + if (!next) return + setSeconds(Math.round((next - Date.now()) / 1000)) + } + update() + const timer = setInterval(update, 1000) + onCleanup(() => clearInterval(timer)) + }), + ) + const message = createMemo(() => { + const current = retry() + if (!current) return "" + if (current.message.includes("exceeded your current quota") && current.message.includes("gemini")) { + return i18n.t("ui.sessionTurn.retry.geminiHot") + } + if (current.message.length > 80) return current.message.slice(0, 80) + "..." + return current.message + }) + const truncated = createMemo(() => { + const current = retry() + if (!current) return false + return current.message.length > 80 + }) + const info = createMemo(() => { + const current = retry() + if (!current) return "" + const count = Math.max(0, seconds()) + const delay = count > 0 ? i18n.t("ui.sessionTurn.retry.inSeconds", { seconds: count }) : "" + const retrying = i18n.t("ui.sessionTurn.retry.retrying") + const line = [retrying, delay].filter(Boolean).join(" ") + if (!line) return i18n.t("ui.sessionTurn.retry.attempt", { attempt: current.attempt }) + return i18n.t("ui.sessionTurn.retry.attemptLine", { line, attempt: current.attempt }) + }) + + return ( + +
+ +
+ +
+ {message()}
}> + +
+ {message()} +
+
+ + {(line) =>
{line()}
}
+
+
+ +
+ + ) +} diff --git a/packages/ui/src/components/session-review.css b/packages/ui/src/components/session-review.css index 60da85e6f26..014a70e7406 100644 --- a/packages/ui/src/components/session-review.css +++ b/packages/ui/src/components/session-review.css @@ -3,11 +3,10 @@ flex-direction: column; gap: 0px; height: 100%; - overflow-y: auto; - scrollbar-width: none; - contain: strict; - &::-webkit-scrollbar { - display: none; + + [data-slot="session-review-scroll"] { + flex: 1 1 auto; + min-height: 0; } .scroll-view__viewport { @@ -17,13 +16,11 @@ [data-slot="session-review-container"] { flex: 1 1 auto; - padding-right: 4px; + padding-right: 0; } [data-slot="session-review-header"] { - position: sticky; - top: 0; - z-index: 20; + z-index: 120; background-color: var(--background-stronger); height: 40px; padding-bottom: 8px; @@ -63,7 +60,7 @@ } [data-component="sticky-accordion-header"] { - --sticky-accordion-top: 40px; + --sticky-accordion-top: 0px; } [data-slot="session-review-accordion-item"][data-selected] diff --git a/packages/ui/src/components/session-review.tsx b/packages/ui/src/components/session-review.tsx index 77bd9506dc6..c75baf921a0 100644 --- a/packages/ui/src/components/session-review.tsx +++ b/packages/ui/src/components/session-review.tsx @@ -554,20 +554,7 @@ export const SessionReview = (props: SessionReviewProps) => { } return ( - { - scroll = el - props.scrollRef?.(el) - }} - onScroll={props.onScroll as any} - onKeyDown={handleReviewKeyDown} - classList={{ - ...(props.classList ?? {}), - [props.classes?.root ?? ""]: !!props.classes?.root, - [props.class ?? ""]: !!props.class, - }} - > +
{props.title ?? i18n.t("ui.sessionReview.title")}
@@ -599,301 +586,319 @@ export const SessionReview = (props: SessionReviewProps) => { {props.actions}
- - (searchHits().length ? Math.min(searchActive(), searchHits().length - 1) : 0)} - count={() => searchHits().length} - setInput={(el) => { - searchInput = el - }} - onInput={(value) => { - setSearchQuery(value) - setSearchActive(0) - }} - onKeyDown={(event) => handleSearchInputKeyDown(event)} - onClose={closeSearch} - onPrev={() => navigateSearch(-1)} - onNext={() => navigateSearch(1)} - /> - -
- - - - {(file) => { - let wrapper: HTMLDivElement | undefined - - const diff = createMemo(() => diffs().get(file)) - const item = () => diff()! - - const expanded = createMemo(() => open().includes(file)) - const force = () => !!store.force[file] - - const comments = createMemo(() => (props.comments ?? []).filter((c) => c.file === file)) - const commentedLines = createMemo(() => comments().map((c) => c.selection)) - - const beforeText = () => (typeof item().before === "string" ? item().before : "") - const afterText = () => (typeof item().after === "string" ? item().after : "") - const changedLines = () => item().additions + item().deletions - const mediaKind = createMemo(() => mediaKindFromPath(file)) - - const tooLarge = createMemo(() => { - if (!expanded()) return false - if (force()) return false - if (mediaKind()) return false - return changedLines() > MAX_DIFF_CHANGED_LINES - }) - - const isAdded = () => item().status === "added" || (beforeText().length === 0 && afterText().length > 0) - const isDeleted = () => - item().status === "deleted" || (afterText().length === 0 && beforeText().length > 0) - - const selectedLines = createMemo(() => { - const current = selection() - if (!current || current.file !== file) return null - return current.range - }) - - const draftRange = createMemo(() => { - const current = commenting() - if (!current || current.file !== file) return null - return current.range - }) - - const commentsUi = createLineCommentController({ - comments, - label: i18n.t("ui.lineComment.submit"), - draftKey: () => file, - state: { - opened: () => { - const current = opened() + + { + scroll = el + props.scrollRef?.(el) + }} + onScroll={props.onScroll as any} + onKeyDown={handleReviewKeyDown} + classList={{ + [props.classes?.root ?? ""]: !!props.classes?.root, + }} + > + + (searchHits().length ? Math.min(searchActive(), searchHits().length - 1) : 0)} + count={() => searchHits().length} + setInput={(el) => { + searchInput = el + }} + onInput={(value) => { + setSearchQuery(value) + setSearchActive(0) + }} + onKeyDown={(event) => handleSearchInputKeyDown(event)} + onClose={closeSearch} + onPrev={() => navigateSearch(-1)} + onNext={() => navigateSearch(1)} + /> + + +
+ +
+ + + {(file) => { + let wrapper: HTMLDivElement | undefined + + const diff = createMemo(() => diffs().get(file)) + const item = () => diff()! + + const expanded = createMemo(() => open().includes(file)) + const force = () => !!store.force[file] + + const comments = createMemo(() => (props.comments ?? []).filter((c) => c.file === file)) + const commentedLines = createMemo(() => comments().map((c) => c.selection)) + + const beforeText = () => (typeof item().before === "string" ? item().before : "") + const afterText = () => (typeof item().after === "string" ? item().after : "") + const changedLines = () => item().additions + item().deletions + const mediaKind = createMemo(() => mediaKindFromPath(file)) + + const tooLarge = createMemo(() => { + if (!expanded()) return false + if (force()) return false + if (mediaKind()) return false + return changedLines() > MAX_DIFF_CHANGED_LINES + }) + + const isAdded = () => + item().status === "added" || (beforeText().length === 0 && afterText().length > 0) + const isDeleted = () => + item().status === "deleted" || (afterText().length === 0 && beforeText().length > 0) + + const selectedLines = createMemo(() => { + const current = selection() if (!current || current.file !== file) return null - return current.id - }, - setOpened: (id) => setOpened(id ? { file, id } : null), - selected: selectedLines, - setSelected: (range) => setSelection(range ? { file, range } : null), - commenting: draftRange, - setCommenting: (range) => setCommenting(range ? { file, range } : null), - }, - getSide: selectionSide, - clearSelectionOnSelectionEndNull: false, - onSubmit: ({ comment, selection }) => { - props.onLineComment?.({ - file, - selection, - comment, - preview: selectionPreview(item(), selection), + return current.range }) - }, - onUpdate: ({ id, comment, selection }) => { - props.onLineCommentUpdate?.({ - id, - file, - selection, - comment, - preview: selectionPreview(item(), selection), + + const draftRange = createMemo(() => { + const current = commenting() + if (!current || current.file !== file) return null + return current.range }) - }, - onDelete: (comment) => { - props.onLineCommentDelete?.({ - id: comment.id, - file, + + const commentsUi = createLineCommentController({ + comments, + label: i18n.t("ui.lineComment.submit"), + draftKey: () => file, + state: { + opened: () => { + const current = opened() + if (!current || current.file !== file) return null + return current.id + }, + setOpened: (id) => setOpened(id ? { file, id } : null), + selected: selectedLines, + setSelected: (range) => setSelection(range ? { file, range } : null), + commenting: draftRange, + setCommenting: (range) => setCommenting(range ? { file, range } : null), + }, + getSide: selectionSide, + clearSelectionOnSelectionEndNull: false, + onSubmit: ({ comment, selection }) => { + props.onLineComment?.({ + file, + selection, + comment, + preview: selectionPreview(item(), selection), + }) + }, + onUpdate: ({ id, comment, selection }) => { + props.onLineCommentUpdate?.({ + id, + file, + selection, + comment, + preview: selectionPreview(item(), selection), + }) + }, + onDelete: (comment) => { + props.onLineCommentDelete?.({ + id: comment.id, + file, + }) + }, + editSubmitLabel: props.lineCommentActions?.saveLabel, + renderCommentActions: props.lineCommentActions + ? (comment, controls) => ( + + ) + : undefined, }) - }, - editSubmitLabel: props.lineCommentActions?.saveLabel, - renderCommentActions: props.lineCommentActions - ? (comment, controls) => ( - - ) - : undefined, - }) - - onCleanup(() => { - anchors.delete(file) - readyFiles.delete(file) - searchHandles.delete(file) - if (highlightedFile === file) highlightedFile = undefined - }) - - const handleLineSelected = (range: SelectedLineRange | null) => { - if (!props.onLineComment) return - commentsUi.onLineSelected(range) - } - - const handleLineSelectionEnd = (range: SelectedLineRange | null) => { - if (!props.onLineComment) return - commentsUi.onLineSelectionEnd(range) - } - - return ( - - - -
-
- -
- - {`\u202A${getDirectory(file)}\u202C`} - - {getFilename(file)} - - - - - -
-
-
- - -
- - {i18n.t("ui.sessionReview.change.added")} - - -
-
- - - {i18n.t("ui.sessionReview.change.removed")} - - - - - {i18n.t("ui.sessionReview.change.modified")} - - - - - -
- - - -
-
-
-
- -
{ - wrapper = el - anchors.set(file, el) - }} + + onCleanup(() => { + anchors.delete(file) + readyFiles.delete(file) + searchHandles.delete(file) + if (highlightedFile === file) highlightedFile = undefined + }) + + const handleLineSelected = (range: SelectedLineRange | null) => { + if (!props.onLineComment) return + commentsUi.onLineSelected(range) + } + + const handleLineSelectionEnd = (range: SelectedLineRange | null) => { + if (!props.onLineComment) return + commentsUi.onLineSelectionEnd(range) + } + + return ( + - - - -
-
- {i18n.t("ui.sessionReview.largeDiff.title")} -
-
- {i18n.t("ui.sessionReview.largeDiff.meta", { - limit: MAX_DIFF_CHANGED_LINES.toLocaleString(), - current: changedLines().toLocaleString(), - })} -
-
- + + +
+
+ +
+ + {`\u202A${getDirectory(file)}\u202C`} + + {getFilename(file)} + + + + +
- - - { - readyFiles.add(file) - props.onDiffRendered?.() - }} - enableLineSelection={props.onLineComment != null} - enableHoverUtility={props.onLineComment != null} - onLineSelected={handleLineSelected} - onLineSelectionEnd={handleLineSelectionEnd} - onLineNumberSelectionEnd={commentsUi.onLineNumberSelectionEnd} - annotations={commentsUi.annotations()} - renderAnnotation={commentsUi.renderAnnotation} - renderHoverUtility={props.onLineComment ? commentsUi.renderHoverUtility : undefined} - selectedLines={selectedLines()} - commentedLines={commentedLines()} - search={{ - shortcuts: "disabled", - showBar: false, - disableVirtualization: searchExpanded(), - register: (handle: FileSearchHandle | null) => { - if (!handle) { - searchHandles.delete(file) - readyFiles.delete(file) - if (highlightedFile === file) highlightedFile = undefined - return - } - - searchHandles.set(file, handle) - }, - }} - before={{ - name: file, - contents: typeof item().before === "string" ? item().before : "", - }} - after={{ - name: file, - contents: typeof item().after === "string" ? item().after : "", - }} - media={{ - mode: "auto", - path: file, - before: item().before, - after: item().after, - readFile: props.readFile, - }} - /> - - - -
- - - ) - }} - - - -
- +
+ + +
+ + {i18n.t("ui.sessionReview.change.added")} + + +
+
+ + + {i18n.t("ui.sessionReview.change.removed")} + + + + + {i18n.t("ui.sessionReview.change.modified")} + + + + + +
+ + + +
+
+ + + +
{ + wrapper = el + anchors.set(file, el) + }} + > + + + +
+
+ {i18n.t("ui.sessionReview.largeDiff.title")} +
+
+ {i18n.t("ui.sessionReview.largeDiff.meta", { + limit: MAX_DIFF_CHANGED_LINES.toLocaleString(), + current: changedLines().toLocaleString(), + })} +
+
+ +
+
+
+ + { + readyFiles.add(file) + props.onDiffRendered?.() + }} + enableLineSelection={props.onLineComment != null} + enableHoverUtility={props.onLineComment != null} + onLineSelected={handleLineSelected} + onLineSelectionEnd={handleLineSelectionEnd} + onLineNumberSelectionEnd={commentsUi.onLineNumberSelectionEnd} + annotations={commentsUi.annotations()} + renderAnnotation={commentsUi.renderAnnotation} + renderHoverUtility={props.onLineComment ? commentsUi.renderHoverUtility : undefined} + selectedLines={selectedLines()} + commentedLines={commentedLines()} + search={{ + shortcuts: "disabled", + showBar: false, + disableVirtualization: searchExpanded(), + register: (handle: FileSearchHandle | null) => { + if (!handle) { + searchHandles.delete(file) + readyFiles.delete(file) + if (highlightedFile === file) highlightedFile = undefined + return + } + + searchHandles.set(file, handle) + }, + }} + before={{ + name: file, + contents: typeof item().before === "string" ? item().before : "", + }} + after={{ + name: file, + contents: typeof item().after === "string" ? item().after : "", + }} + media={{ + mode: "auto", + path: file, + before: item().before, + after: item().after, + readFile: props.readFile, + }} + /> + +
+
+
+
+
+ ) + }} + + +
+ +
+ +
) } diff --git a/packages/ui/src/components/session-turn.css b/packages/ui/src/components/session-turn.css index 9639e6635a7..15d7b503525 100644 --- a/packages/ui/src/components/session-turn.css +++ b/packages/ui/src/components/session-turn.css @@ -37,6 +37,12 @@ max-width: 100%; } + [data-slot="session-turn-compaction"] { + width: 100%; + min-width: 0; + align-self: stretch; + } + [data-slot="session-turn-thinking"] { display: flex; align-items: center; @@ -47,23 +53,20 @@ font-family: var(--font-family-sans); font-size: var(--font-size-base); font-weight: var(--font-weight-medium); - line-height: var(--line-height-large); + line-height: 20px; min-height: 20px; [data-component="spinner"] { width: 16px; height: 16px; } + } - [data-slot="session-turn-thinking-heading"] { - flex: 1 1 auto; - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - color: var(--text-weaker); - font-weight: var(--font-weight-regular); - } + [data-component="text-reveal"].session-turn-thinking-heading { + flex: 1 1 auto; + min-width: 0; + color: var(--text-weaker); + font-weight: var(--font-weight-regular); } .error-card { diff --git a/packages/ui/src/components/session-turn.tsx b/packages/ui/src/components/session-turn.tsx index 3116d4b65c0..a8a41b8ef41 100644 --- a/packages/ui/src/components/session-turn.tsx +++ b/packages/ui/src/components/session-turn.tsx @@ -1,4 +1,5 @@ import { AssistantMessage, type FileDiff, Message as MessageType, Part as PartType } from "@opencode-ai/sdk/v2/client" +import type { SessionStatus } from "@opencode-ai/sdk/v2" import { useData } from "../context" import { useFileComponent } from "../context/file" @@ -6,7 +7,7 @@ import { Binary } from "@opencode-ai/util/binary" import { getDirectory, getFilename } from "@opencode-ai/util/path" import { createEffect, createMemo, createSignal, For, on, ParentProps, Show } from "solid-js" import { Dynamic } from "solid-js/web" -import { AssistantParts, Message, PART_MAPPING } from "./message-part" +import { AssistantParts, Message, Part, PART_MAPPING } from "./message-part" import { Card } from "./card" import { Accordion } from "./accordion" import { StickyAccordionHeader } from "./sticky-accordion-header" @@ -14,6 +15,8 @@ import { Collapsible } from "./collapsible" import { DiffChanges } from "./diff-changes" import { Icon } from "./icon" import { TextShimmer } from "./text-shimmer" +import { SessionRetry } from "./session-retry" +import { TextReveal } from "./text-reveal" import { createAutoScroll } from "../hooks" import { useI18n } from "../context/i18n" @@ -138,10 +141,12 @@ export function SessionTurn( props: ParentProps<{ sessionID: string messageID: string - lastUserMessageID?: string showReasoningSummaries?: boolean shellToolDefaultOpen?: boolean editToolDefaultOpen?: boolean + active?: boolean + queued?: boolean + status?: SessionStatus onUserInteracted?: () => void classes?: { root?: string @@ -186,18 +191,41 @@ export function SessionTurn( return msg }) - const lastUserMessageID = createMemo(() => { - if (props.lastUserMessageID) return props.lastUserMessageID + const pending = createMemo(() => { + if (typeof props.active === "boolean" && typeof props.queued === "boolean") return + const messages = allMessages() ?? emptyMessages + return messages.findLast( + (item): item is AssistantMessage => item.role === "assistant" && typeof item.time.completed !== "number", + ) + }) + const pendingUser = createMemo(() => { + const item = pending() + if (!item?.parentID) return const messages = allMessages() ?? emptyMessages - for (let i = messages.length - 1; i >= 0; i--) { - const msg = messages[i] - if (msg?.role === "user") return msg.id - } - return undefined + const result = Binary.search(messages, item.parentID, (m) => m.id) + const msg = result.found ? messages[result.index] : messages.find((m) => m.id === item.parentID) + if (!msg || msg.role !== "user") return + return msg }) - const isLastUserMessage = createMemo(() => props.messageID === lastUserMessageID()) + const active = createMemo(() => { + if (typeof props.active === "boolean") return props.active + const msg = message() + const parent = pendingUser() + if (!msg || !parent) return false + return parent.id === msg.id + }) + + const queued = createMemo(() => { + if (typeof props.queued === "boolean") return props.queued + const id = message()?.id + if (!id) return false + if (!pendingUser()) return false + const item = pending() + if (!item) return false + return id > item.id + }) const parts = createMemo(() => { const msg = message() @@ -205,6 +233,8 @@ export function SessionTurn( return list(data.store.part?.[msg.id], emptyParts) }) + const compaction = createMemo(() => parts().find((part) => part.type === "compaction")) + const diffs = createMemo(() => { const files = message()?.summary?.diffs if (!files?.length) return emptyDiffs @@ -283,8 +313,12 @@ export function SessionTurn( return unwrap(String(msg)) }) - const status = createMemo(() => data.store.session_status[props.sessionID] ?? idle) - const working = createMemo(() => status().type !== "idle" && isLastUserMessage()) + const status = createMemo(() => { + if (props.status !== undefined) return props.status + if (typeof props.active === "boolean" && !props.active) return idle + return data.store.session_status[props.sessionID] ?? idle + }) + const working = createMemo(() => status().type !== "idle" && active()) const showReasoningSummaries = createMemo(() => props.showReasoningSummaries ?? true) const assistantCopyPartID = createMemo(() => { @@ -332,8 +366,9 @@ export function SessionTurn( ) const showThinking = createMemo(() => { if (!working() || !!error()) return false + if (queued()) return false + if (status().type === "retry") return false if (showReasoningSummaries()) return assistantVisible() === 0 - if (assistantTailVisible() === "text") return false return true }) @@ -361,8 +396,15 @@ export function SessionTurn( class={props.classes?.container} >
- +
+ + {(part) => ( +
+ +
+ )} +
0}>
- - {(text) => {text()}} + +
+ 0 && !working()}>
diff --git a/packages/ui/src/components/shell-submessage-motion.stories.tsx b/packages/ui/src/components/shell-submessage-motion.stories.tsx new file mode 100644 index 00000000000..1f53b6e4de3 --- /dev/null +++ b/packages/ui/src/components/shell-submessage-motion.stories.tsx @@ -0,0 +1,329 @@ +// @ts-nocheck +import { createEffect, createSignal, onCleanup } from "solid-js" +import { BasicTool } from "./basic-tool" +import { animate } from "motion" + +export default { + title: "UI/Shell Submessage Motion", + id: "components-shell-submessage-motion", + tags: ["autodocs"], + parameters: { + docs: { + description: { + component: `### Overview +Interactive playground for animating the Shell tool subtitle ("submessage") in the timeline trigger row. + +### Production component path +- Trigger layout: \`packages/ui/src/components/basic-tool.tsx\` +- Bash tool subtitle source: \`packages/ui/src/components/message-part.tsx\` (tool: \`bash\`, \`trigger.subtitle\`) + +### What this playground tunes +- Width reveal (spring-driven pixel width via \`useSpring\`) +- Opacity fade +- Blur settle`, + }, + }, + }, +} + +const btn = (accent?: boolean) => + ({ + padding: "6px 14px", + "border-radius": "6px", + border: "1px solid var(--color-divider, #333)", + background: accent ? "var(--color-accent, #58f)" : "var(--color-fill-element, #222)", + color: "var(--color-text, #eee)", + cursor: "pointer", + "font-size": "13px", + }) as const + +const sliderLabel = { + "font-size": "11px", + "font-family": "monospace", + color: "var(--color-text-weak, #666)", + "min-width": "84px", + "flex-shrink": "0", + "text-align": "right", +} + +const sliderValue = { + "font-family": "monospace", + "font-size": "11px", + color: "var(--color-text-weak, #aaa)", + "min-width": "76px", +} + +const shellCss = ` +[data-component="shell-submessage-scene"] [data-component="tool-trigger"] [data-slot="basic-tool-tool-info-main"] { + align-items: baseline; +} + +[data-component="shell-submessage"] { + min-width: 0; + max-width: 100%; + display: inline-flex; + align-items: baseline; + vertical-align: baseline; +} + +[data-component="shell-submessage"] [data-slot="shell-submessage-width"] { + min-width: 0; + max-width: 100%; + display: inline-flex; + align-items: baseline; + overflow: hidden; +} + +[data-component="shell-submessage"] [data-slot="shell-submessage-value"] { + display: inline-block; + vertical-align: baseline; + min-width: 0; + line-height: inherit; + white-space: nowrap; + opacity: 0; + filter: blur(var(--shell-sub-blur, 2px)); + transition-property: opacity, filter; + transition-duration: var(--shell-sub-fade-ms, 320ms); + transition-timing-function: var(--shell-sub-fade-ease, cubic-bezier(0.22, 1, 0.36, 1)); +} + +[data-component="shell-submessage"][data-visible] [data-slot="shell-submessage-value"] { + opacity: 1; + filter: blur(0px); +} +` + +const ease = { + smooth: "cubic-bezier(0.16, 1, 0.3, 1)", + snappy: "cubic-bezier(0.22, 1, 0.36, 1)", + standard: "cubic-bezier(0.2, 0.8, 0.2, 1)", + linear: "linear", +} + +function SpringSubmessage(props: { text: string; visible: boolean; visualDuration: number; bounce: number }) { + let ref: HTMLSpanElement | undefined + let widthRef: HTMLSpanElement | undefined + + createEffect(() => { + if (!widthRef) return + if (props.visible) { + requestAnimationFrame(() => { + ref?.setAttribute("data-visible", "") + animate( + widthRef!, + { width: "auto" }, + { type: "spring", visualDuration: props.visualDuration, bounce: props.bounce }, + ) + }) + } else { + ref?.removeAttribute("data-visible") + animate( + widthRef, + { width: "0px" }, + { type: "spring", visualDuration: props.visualDuration, bounce: props.bounce }, + ) + } + }) + + return ( + + + + {props.text || "\u00A0"} + + + + ) +} + +export const Playground = { + render: () => { + const [text, setText] = createSignal("Prints five topic blocks between timed commands") + const [show, setShow] = createSignal(true) + const [visualDuration, setVisualDuration] = createSignal(0.35) + const [bounce, setBounce] = createSignal(0) + const [fadeMs, setFadeMs] = createSignal(320) + const [blur, setBlur] = createSignal(2) + const [fadeEase, setFadeEase] = createSignal("snappy") + const [auto, setAuto] = createSignal(false) + let replayTimer + let autoTimer + + const replay = () => { + setShow(false) + if (replayTimer) clearTimeout(replayTimer) + replayTimer = setTimeout(() => { + setShow(true) + }, 50) + } + + const stopAuto = () => { + if (autoTimer) clearInterval(autoTimer) + autoTimer = undefined + setAuto(false) + } + + const toggleAuto = () => { + if (auto()) { + stopAuto() + return + } + setAuto(true) + autoTimer = setInterval(replay, 2200) + } + + onCleanup(() => { + if (replayTimer) clearTimeout(replayTimer) + if (autoTimer) clearInterval(autoTimer) + }) + + return ( +
+ + + +
+ Shell + +
+
+ } + > +
+ {"$ cat <<'TOPIC1'"} +
+ + +
+ + + +
+ +
+
+ subtitle + setText(e.currentTarget.value)} + style={{ + width: "420px", + "max-width": "100%", + padding: "6px 8px", + "border-radius": "6px", + border: "1px solid var(--color-divider, #333)", + background: "var(--color-fill-element, #222)", + color: "var(--color-text, #eee)", + }} + /> +
+ +
+ visualDuration + setVisualDuration(Number(e.currentTarget.value))} + /> + {visualDuration().toFixed(2)}s +
+ +
+ bounce + setBounce(Number(e.currentTarget.value))} + /> + {bounce().toFixed(2)} +
+ +
+ fade ease + +
+ +
+ fade + setFadeMs(Number(e.currentTarget.value))} + /> + {fadeMs()}ms +
+ +
+ blur + setBlur(Number(e.currentTarget.value))} + /> + {blur()}px +
+
+
+ ) + }, +} diff --git a/packages/ui/src/components/shell-submessage.css b/packages/ui/src/components/shell-submessage.css new file mode 100644 index 00000000000..f72ba3fc755 --- /dev/null +++ b/packages/ui/src/components/shell-submessage.css @@ -0,0 +1,23 @@ +[data-component="shell-submessage"] { + min-width: 0; + max-width: 100%; + display: inline-flex; + align-items: baseline; + vertical-align: baseline; +} + +[data-component="shell-submessage"] [data-slot="shell-submessage-width"] { + min-width: 0; + max-width: 100%; + display: inline-flex; + align-items: baseline; + overflow: hidden; +} + +[data-component="shell-submessage"] [data-slot="shell-submessage-value"] { + display: inline-block; + vertical-align: baseline; + min-width: 0; + line-height: inherit; + white-space: nowrap; +} diff --git a/packages/ui/src/components/text-reveal.css b/packages/ui/src/components/text-reveal.css new file mode 100644 index 00000000000..f799962f094 --- /dev/null +++ b/packages/ui/src/components/text-reveal.css @@ -0,0 +1,150 @@ +/* + * TextReveal — mask-position wipe animation + * + * Instead of sliding text through a fixed mask (odometer style), + * the mask itself sweeps across each span to reveal/hide text. + * + * Direction: top-to-bottom. New text drops in from above, old text exits downward. + * + * Entering: gradient reveals top-to-bottom (top of text appears first). + * gradient(to bottom, white 33%, transparent 33%+edge) + * pos 0 100% = transparent covers element = hidden + * pos 0 0% = white covers element = visible + * + * Leaving: gradient hides top-to-bottom (top of text disappears first). + * gradient(to top, white 33%, transparent 33%+edge) + * pos 0 100% = white covers element = visible + * pos 0 0% = transparent covers element = hidden + * + * Both transition from 0 100% (swap) → 0 0% (settled). + */ + +[data-component="text-reveal"] { + --_edge: var(--text-reveal-edge, 17%); + --_dur: var(--text-reveal-duration, 450ms); + --_spring: var(--text-reveal-spring, cubic-bezier(0.34, 1.08, 0.64, 1)); + --_spring-soft: var(--text-reveal-spring-soft, cubic-bezier(0.34, 1, 0.64, 1)); + --_travel: var(--text-reveal-travel, 0px); + + display: inline-flex; + align-items: center; + min-width: 0; + overflow: visible; + + [data-slot="text-reveal-track"] { + display: grid; + min-height: 20px; + line-height: 20px; + justify-items: start; + align-items: center; + overflow: visible; + transition: width var(--_dur) var(--_spring-soft); + } + + [data-slot="text-reveal-entering"], + [data-slot="text-reveal-leaving"] { + grid-area: 1 / 1; + line-height: 20px; + white-space: nowrap; + justify-self: start; + text-align: start; + mask-size: 100% 300%; + -webkit-mask-size: 100% 300%; + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + transition-duration: var(--_dur); + transition-timing-function: var(--_spring); + } + + /* ── entering: reveal top-to-bottom ── + * Gradient(to top): white at bottom, transparent at top of mask. + * Settled pos 0 100% = white covers element = visible + * Swap pos 0 0% = transparent covers = hidden + * Slides from above: translateY(-travel) → translateY(0) + */ + [data-slot="text-reveal-entering"] { + mask-image: linear-gradient(to top, white 33%, transparent calc(33% + var(--_edge))); + -webkit-mask-image: linear-gradient(to top, white 33%, transparent calc(33% + var(--_edge))); + mask-position: 0 100%; + -webkit-mask-position: 0 100%; + transition-property: + mask-position, + -webkit-mask-position, + transform; + transform: translateY(0); + } + + /* ── leaving: hide top-to-bottom + slide downward ── + * Gradient(to bottom): white at top, transparent at bottom of mask. + * Swap pos 0 0% = white covers element = visible + * Settled pos 0 100% = transparent covers = hidden + * Slides down: translateY(0) → translateY(travel) + */ + [data-slot="text-reveal-leaving"] { + mask-image: linear-gradient(to bottom, white 33%, transparent calc(33% + var(--_edge))); + -webkit-mask-image: linear-gradient(to bottom, white 33%, transparent calc(33% + var(--_edge))); + mask-position: 0 100%; + -webkit-mask-position: 0 100%; + transition-property: + mask-position, + -webkit-mask-position, + transform; + transform: translateY(var(--_travel)); + } + + /* ── swapping: instant reset ── + * Snap entering to hidden (above), leaving to visible (center). + */ + &[data-swapping="true"] [data-slot="text-reveal-entering"] { + mask-position: 0 0%; + -webkit-mask-position: 0 0%; + transform: translateY(calc(var(--_travel) * -1)); + transition-duration: 0ms !important; + } + + &[data-swapping="true"] [data-slot="text-reveal-leaving"] { + mask-position: 0 0%; + -webkit-mask-position: 0 0%; + transform: translateY(0); + transition-duration: 0ms !important; + } + + /* ── not ready: kill all transitions ── */ + &[data-ready="false"] [data-slot="text-reveal-track"] { + transition-duration: 0ms !important; + } + + &[data-ready="false"] [data-slot="text-reveal-entering"], + &[data-ready="false"] [data-slot="text-reveal-leaving"] { + transition-duration: 0ms !important; + } + + &[data-truncate="true"] { + width: 100%; + } + + &[data-truncate="true"] [data-slot="text-reveal-track"] { + width: 100%; + min-width: 0; + overflow: hidden; + } + + &[data-truncate="true"] [data-slot="text-reveal-entering"], + &[data-truncate="true"] [data-slot="text-reveal-leaving"] { + min-width: 0; + width: 100%; + overflow: hidden; + text-overflow: ellipsis; + } +} + +@media (prefers-reduced-motion: reduce) { + [data-component="text-reveal"] [data-slot="text-reveal-track"] { + transition-duration: 0ms !important; + } + + [data-component="text-reveal"] [data-slot="text-reveal-entering"], + [data-component="text-reveal"] [data-slot="text-reveal-leaving"] { + transition-duration: 0ms !important; + } +} diff --git a/packages/ui/src/components/text-reveal.stories.tsx b/packages/ui/src/components/text-reveal.stories.tsx new file mode 100644 index 00000000000..df514ca38d1 --- /dev/null +++ b/packages/ui/src/components/text-reveal.stories.tsx @@ -0,0 +1,310 @@ +// @ts-nocheck +import { createSignal, onCleanup } from "solid-js" +import { TextReveal } from "./text-reveal" + +export default { + title: "UI/TextReveal", + id: "components-text-reveal", + tags: ["autodocs"], + parameters: { + docs: { + description: { + component: `### Overview +Playground for the TextReveal text transition component. + +**Hybrid** — mask wipe + vertical slide: gradient sweeps AND text moves downward. + +**Wipe only** — pure mask wipe: gradient sweeps top-to-bottom, text stays in place.`, + }, + }, + }, +} + +const TEXTS = [ + "Refactor ToolStatusTitle DOM measurement", + "Remove inline measure nodes", + "Run typechecks and report changes", + "Verify reduced-motion behavior", + "Review diff for animation edge cases", + "Check keyboard semantics", + undefined, + "Planning key generation details", + "Analyzing error handling", + "Considering edge cases", +] + +const btn = (accent?: boolean) => + ({ + padding: "5px 12px", + "border-radius": "6px", + border: accent ? "1px solid var(--color-accent, #58f)" : "1px solid var(--color-divider, #333)", + background: accent ? "var(--color-accent, #58f)" : "var(--color-fill-element, #222)", + color: "var(--color-text, #eee)", + cursor: "pointer", + "font-size": "12px", + }) as const + +const sliderLabel = { + width: "90px", + "font-size": "12px", + color: "var(--color-text-secondary, #a3a3a3)", + "flex-shrink": "0", +} as const + +const cardStyle = { + padding: "20px 24px", + "border-radius": "10px", + border: "1px solid var(--color-divider, #333)", + background: "var(--color-fill-element, #1a1a1a)", + display: "grid", + gap: "12px", +} as const + +const cardLabel = { + "font-size": "11px", + "font-family": "monospace", + color: "var(--color-text-weak, #666)", +} as const + +const previewRow = { + display: "flex", + "align-items": "center", + gap: "8px", + "font-size": "14px", + "font-weight": "500", + "line-height": "20px", + color: "var(--text-weak, #aaa)", + "min-height": "20px", + overflow: "visible", +} as const + +const headingSlot = { + "min-width": "0", + overflow: "visible", + color: "var(--text-weaker, #888)", + "font-weight": "400", +} as const + +export const Playground = { + render: () => { + const [index, setIndex] = createSignal(0) + const [cycling, setCycling] = createSignal(false) + const [growOnly, setGrowOnly] = createSignal(true) + + const [duration, setDuration] = createSignal(600) + const [bounce, setBounce] = createSignal(1.0) + const [bounceSoft, setBounceSoft] = createSignal(1.0) + + const [hybridTravel, setHybridTravel] = createSignal(25) + const [hybridEdge, setHybridEdge] = createSignal(17) + + const [edge, setEdge] = createSignal(17) + const [revealTravel, setRevealTravel] = createSignal(0) + + let timer: number | undefined + const text = () => TEXTS[index()] + const next = () => setIndex((i) => (i + 1) % TEXTS.length) + const prev = () => setIndex((i) => (i - 1 + TEXTS.length) % TEXTS.length) + + const toggleCycle = () => { + if (cycling()) { + if (timer) clearTimeout(timer) + timer = undefined + setCycling(false) + return + } + setCycling(true) + const tick = () => { + next() + timer = window.setTimeout(tick, 700 + Math.floor(Math.random() * 600)) + } + timer = window.setTimeout(tick, 700 + Math.floor(Math.random() * 600)) + } + + onCleanup(() => { + if (timer) clearTimeout(timer) + }) + + const spring = () => `cubic-bezier(0.34, ${bounce()}, 0.64, 1)` + const springSoft = () => `cubic-bezier(0.34, ${bounceSoft()}, 0.64, 1)` + + return ( +
+
+
+ text-reveal (mask wipe + slide) +
+ Thinking + + + +
+
+ +
+ text-reveal (mask wipe only) +
+ Thinking + + + +
+
+
+ +
+ {TEXTS.map((t, i) => ( + + ))} +
+ +
+ + + + +
+ +
+
Hybrid (wipe + slide)
+ + + + + +
Shared
+ + + + + + + +
+ Wipe only +
+ + + + +
+ +
+ text: {text() ?? "(none)"} · growOnly: {growOnly() ? "on" : "off"} +
+
+ ) + }, +} diff --git a/packages/ui/src/components/text-reveal.tsx b/packages/ui/src/components/text-reveal.tsx new file mode 100644 index 00000000000..c4fe1302f0e --- /dev/null +++ b/packages/ui/src/components/text-reveal.tsx @@ -0,0 +1,135 @@ +import { createEffect, createSignal, on, onCleanup, onMount } from "solid-js" + +const px = (value: number | string | undefined, fallback: number) => { + if (typeof value === "number") return `${value}px` + if (typeof value === "string") return value + return `${fallback}px` +} + +const ms = (value: number | string | undefined, fallback: number) => { + if (typeof value === "number") return `${value}ms` + if (typeof value === "string") return value + return `${fallback}ms` +} + +const pct = (value: number | undefined, fallback: number) => { + const v = value ?? fallback + return `${v}%` +} + +export function TextReveal(props: { + text?: string + class?: string + duration?: number | string + /** Gradient edge softness as a percentage of the mask (0 = hard wipe, 17 = soft). */ + edge?: number + /** Optional small vertical travel for entering text (px). Default 0. */ + travel?: number | string + spring?: string + springSoft?: string + growOnly?: boolean + truncate?: boolean +}) { + const [cur, setCur] = createSignal(props.text) + const [old, setOld] = createSignal() + const [width, setWidth] = createSignal("auto") + const [ready, setReady] = createSignal(false) + const [swapping, setSwapping] = createSignal(false) + let inRef: HTMLSpanElement | undefined + let outRef: HTMLSpanElement | undefined + let rootRef: HTMLSpanElement | undefined + let frame: number | undefined + + const win = () => inRef?.scrollWidth ?? 0 + const wout = () => outRef?.scrollWidth ?? 0 + + const widen = (next: number) => { + if (next <= 0) return + if (props.growOnly ?? true) { + const prev = Number.parseFloat(width()) + if (Number.isFinite(prev) && next <= prev) return + } + setWidth(`${next}px`) + } + + createEffect( + on( + () => props.text, + (next, prev) => { + if (next === prev) return + if (typeof next === "string" && typeof prev === "string" && next.startsWith(prev)) { + setCur(next) + widen(win()) + return + } + setSwapping(true) + setOld(prev) + setCur(next) + + if (typeof requestAnimationFrame !== "function") { + widen(Math.max(win(), wout())) + rootRef?.offsetHeight + setSwapping(false) + return + } + if (frame !== undefined && typeof cancelAnimationFrame === "function") cancelAnimationFrame(frame) + frame = requestAnimationFrame(() => { + widen(Math.max(win(), wout())) + rootRef?.offsetHeight + setSwapping(false) + frame = undefined + }) + }, + ), + ) + + onMount(() => { + widen(win()) + const fonts = typeof document !== "undefined" ? document.fonts : undefined + if (typeof requestAnimationFrame !== "function") { + setReady(true) + return + } + if (!fonts) { + requestAnimationFrame(() => setReady(true)) + return + } + fonts.ready.finally(() => { + widen(win()) + requestAnimationFrame(() => setReady(true)) + }) + }) + + onCleanup(() => { + if (frame === undefined || typeof cancelAnimationFrame !== "function") return + cancelAnimationFrame(frame) + }) + + return ( + + + + {cur() ?? "\u00A0"} + + + {old() ?? "\u00A0"} + + + + ) +} diff --git a/packages/ui/src/components/text-shimmer.css b/packages/ui/src/components/text-shimmer.css index 929a2d85161..f042dd2d862 100644 --- a/packages/ui/src/components/text-shimmer.css +++ b/packages/ui/src/components/text-shimmer.css @@ -1,43 +1,119 @@ [data-component="text-shimmer"] { --text-shimmer-step: 45ms; --text-shimmer-duration: 1200ms; + --text-shimmer-swap: 220ms; + --text-shimmer-index: 0; + --text-shimmer-angle: 90deg; + --text-shimmer-spread: 5.2ch; + --text-shimmer-size: 360%; + --text-shimmer-base-color: var(--text-weak); + --text-shimmer-peak-color: var(--text-strong); + --text-shimmer-sweep: linear-gradient( + var(--text-shimmer-angle), + transparent calc(50% - var(--text-shimmer-spread)), + var(--text-shimmer-peak-color) 50%, + transparent calc(50% + var(--text-shimmer-spread)) + ); + --text-shimmer-base: linear-gradient(var(--text-shimmer-base-color), var(--text-shimmer-base-color)); + + display: inline-flex; + align-items: baseline; + font: inherit; + letter-spacing: inherit; + line-height: inherit; } [data-component="text-shimmer"] [data-slot="text-shimmer-char"] { + display: inline-grid; white-space: pre; + font: inherit; + letter-spacing: inherit; + line-height: inherit; +} + +[data-component="text-shimmer"] [data-slot="text-shimmer-char-base"], +[data-component="text-shimmer"] [data-slot="text-shimmer-char-shimmer"] { + grid-area: 1 / 1; + white-space: pre; + transition: opacity var(--text-shimmer-swap) ease-out; + font: inherit; + letter-spacing: inherit; + line-height: inherit; +} + +[data-component="text-shimmer"] [data-slot="text-shimmer-char-base"] { color: inherit; + opacity: 1; +} + +[data-component="text-shimmer"] [data-slot="text-shimmer-char-shimmer"] { + color: var(--text-weaker); + opacity: 0; } -[data-component="text-shimmer"][data-active="true"] [data-slot="text-shimmer-char"] { - animation-name: text-shimmer-char; +[data-component="text-shimmer"][data-active="true"] [data-slot="text-shimmer-char-shimmer"] { + opacity: 1; +} + +[data-component="text-shimmer"] [data-slot="text-shimmer-char-shimmer"][data-run="true"] { + animation-name: text-shimmer-sweep; animation-duration: var(--text-shimmer-duration); animation-iteration-count: infinite; - animation-timing-function: ease-in-out; - animation-delay: calc(var(--text-shimmer-step) * var(--text-shimmer-index)); + animation-timing-function: linear; + animation-fill-mode: both; + animation-delay: calc(var(--text-shimmer-step) * var(--text-shimmer-index) * -1); + will-change: background-position; } -@keyframes text-shimmer-char { - 0%, - 100% { - color: var(--text-weaker); +@keyframes text-shimmer-sweep { + 0% { + background-position: + 100% 0, + 0 0; } - 30% { - color: var(--text-weak); + 100% { + background-position: + 0% 0, + 0 0; } +} - 55% { - color: var(--text-base); +@supports ((-webkit-background-clip: text) or (background-clip: text)) { + [data-component="text-shimmer"] [data-slot="text-shimmer-char-shimmer"] { + color: transparent; + -webkit-text-fill-color: transparent; + background-image: var(--text-shimmer-sweep), var(--text-shimmer-base); + background-size: + var(--text-shimmer-size) 100%, + 100% 100%; + background-position: + 100% 0, + 0 0; + background-repeat: no-repeat; + -webkit-background-clip: text; + background-clip: text; } - 75% { - color: var(--text-strong); + [data-component="text-shimmer"][data-active="true"] [data-slot="text-shimmer-char-base"] { + opacity: 0; } } @media (prefers-reduced-motion: reduce) { - [data-component="text-shimmer"] [data-slot="text-shimmer-char"] { + [data-component="text-shimmer"] [data-slot="text-shimmer-char-base"], + [data-component="text-shimmer"] [data-slot="text-shimmer-char-shimmer"] { + transition-duration: 0ms; + } + + [data-component="text-shimmer"] [data-slot="text-shimmer-char-shimmer"] { animation: none !important; color: inherit; + -webkit-text-fill-color: currentColor; + background-image: none; + } + + [data-component="text-shimmer"] [data-slot="text-shimmer-char-base"] { + opacity: 1 !important; } } diff --git a/packages/ui/src/components/text-shimmer.stories.tsx b/packages/ui/src/components/text-shimmer.stories.tsx index 4b6de34c2e9..a88a7158b11 100644 --- a/packages/ui/src/components/text-shimmer.stories.tsx +++ b/packages/ui/src/components/text-shimmer.stories.tsx @@ -1,5 +1,6 @@ // @ts-nocheck import * as mod from "./text-shimmer" +import { useArgs } from "storybook/preview-api" import { create } from "../storybook/scaffold" const docs = `### Overview @@ -9,13 +10,14 @@ Use for pending states inside buttons or list rows. ### API - Required: \`text\` string. -- Optional: \`as\`, \`active\`, \`stepMs\`, \`durationMs\`. +- Optional: \`as\`, \`active\`, \`offset\`, \`class\`. ### Variants and states - Active/inactive state via \`active\`. ### Behavior -- Characters animate with staggered delays. +- Uses a moving gradient sweep clipped to text. +- \`offset\` lets multiple shimmers run out-of-phase. ### Accessibility - Uses \`aria-label\` with the full text. @@ -25,13 +27,27 @@ Use for pending states inside buttons or list rows. ` -const story = create({ title: "UI/TextShimmer", mod, args: { text: "Loading..." } }) +const defaults = { + text: "Loading...", + active: true, + class: "text-14-medium text-text-strong", + offset: 0, +} as const + +const story = create({ title: "UI/TextShimmer", mod, args: defaults }) export default { title: "UI/TextShimmer", id: "components-text-shimmer", component: story.meta.component, tags: ["autodocs"], + args: defaults, + argTypes: { + text: { control: "text" }, + class: { control: "text" }, + active: { control: "boolean" }, + offset: { control: { type: "range", min: 0, max: 80, step: 1 } }, + }, parameters: { docs: { description: { @@ -41,7 +57,32 @@ export default { }, } -export const Basic = story.Basic +export const Basic = { + args: defaults, + render: (args) => { + const [, updateArgs] = useArgs() + const reset = () => updateArgs(defaults) + return ( +
+ + +
+ ) + }, +} export const Inactive = { args: { @@ -49,11 +90,3 @@ export const Inactive = { active: false, }, } - -export const CustomTiming = { - args: { - text: "Custom timing", - stepMs: 80, - durationMs: 1800, - }, -} diff --git a/packages/ui/src/components/text-shimmer.tsx b/packages/ui/src/components/text-shimmer.tsx index 6ee4ef4020f..c4c20b8e768 100644 --- a/packages/ui/src/components/text-shimmer.tsx +++ b/packages/ui/src/components/text-shimmer.tsx @@ -1,4 +1,4 @@ -import { For, createMemo, type ValidComponent } from "solid-js" +import { createEffect, createMemo, createSignal, onCleanup, type ValidComponent } from "solid-js" import { Dynamic } from "solid-js/web" export const TextShimmer = (props: { @@ -6,31 +6,56 @@ export const TextShimmer = (props: { class?: string as?: T active?: boolean - stepMs?: number - durationMs?: number + offset?: number }) => { - const chars = createMemo(() => Array.from(props.text)) - const active = () => props.active ?? true + const active = createMemo(() => props.active ?? true) + const offset = createMemo(() => props.offset ?? 0) + const [run, setRun] = createSignal(active()) + const swap = 220 + let timer: ReturnType | undefined + + createEffect(() => { + if (timer) { + clearTimeout(timer) + timer = undefined + } + + if (active()) { + setRun(true) + return + } + + timer = setTimeout(() => { + timer = undefined + setRun(false) + }, swap) + }) + + onCleanup(() => { + if (!timer) return + clearTimeout(timer) + }) return ( - - {(char, index) => ( - - )} - + + + + ) } diff --git a/packages/ui/src/components/text-strikethrough.css b/packages/ui/src/components/text-strikethrough.css new file mode 100644 index 00000000000..1be80546838 --- /dev/null +++ b/packages/ui/src/components/text-strikethrough.css @@ -0,0 +1,27 @@ +/* + * TextStrikethrough — spring-animated strikethrough line + * + * Draws a line-through from left to right using clip-path on a + * transparent-text overlay that carries the text-decoration. + * Grid stacking (grid-area: 1/1) layers the overlay on the base text. + * + * Key trick: -webkit-text-fill-color hides the glyph paint while + * keeping `color` (and therefore `currentColor` / text-decoration-color) + * set to the real inherited text color. + */ + +[data-component="text-strikethrough"] { + display: grid; +} + +[data-slot="text-strikethrough-line"] { + -webkit-text-fill-color: transparent; + text-decoration-line: line-through; + pointer-events: none; +} + +@media (prefers-reduced-motion: reduce) { + [data-slot="text-strikethrough-line"] { + clip-path: none !important; + } +} diff --git a/packages/ui/src/components/text-strikethrough.stories.tsx b/packages/ui/src/components/text-strikethrough.stories.tsx new file mode 100644 index 00000000000..b07e7455348 --- /dev/null +++ b/packages/ui/src/components/text-strikethrough.stories.tsx @@ -0,0 +1,279 @@ +// @ts-nocheck +import { createEffect, createSignal, onCleanup, onMount } from "solid-js" +import { useSpring } from "./motion-spring" +import { TextStrikethrough } from "./text-strikethrough" + +const TEXT_SHORT = "Remove inline measure nodes" +const TEXT_MED = "Remove inline measure nodes and keep width morph behavior intact" +const TEXT_LONG = + "Refactor ToolStatusTitle DOM measurement to offscreen global measurer (unconstrained by timeline layout)" + +const btn = (active?: boolean) => + ({ + padding: "8px 18px", + "border-radius": "6px", + border: "1px solid var(--color-divider, #444)", + background: active ? "var(--color-accent, #58f)" : "var(--color-fill-element, #222)", + color: "var(--color-text, #eee)", + cursor: "pointer", + "font-size": "14px", + "font-weight": "500", + }) as const + +const heading = { + "font-size": "11px", + "font-weight": "600", + "text-transform": "uppercase" as const, + "letter-spacing": "0.05em", + color: "var(--text-weak, #888)", + "margin-bottom": "4px", +} + +const card = { + padding: "16px 20px", + "border-radius": "10px", + border: "1px solid var(--border-weak-base, #333)", + background: "var(--surface-base, #1a1a1a)", +} + +/* ─── Variant A: scaleX pseudo-line at 50% ─── */ +function VariantA(props: { active: boolean; text: string }) { + const progress = useSpring( + () => (props.active ? 1 : 0), + () => ({ visualDuration: 0.35, bounce: 0 }), + ) + return ( + + {props.text} + + + ) +} + +/* ─── Variant D: background-image line ─── */ +function VariantD(props: { active: boolean; text: string }) { + const progress = useSpring( + () => (props.active ? 1 : 0), + () => ({ visualDuration: 0.35, bounce: 0 }), + ) + return ( + + {props.text} + + ) +} + +/* ─── Variant E: grid stacking + clip-path (container %) ─── */ +function VariantE(props: { active: boolean; text: string }) { + const progress = useSpring( + () => (props.active ? 1 : 0), + () => ({ visualDuration: 0.35, bounce: 0 }), + ) + return ( + + {props.text} + + + ) +} + +/* ─── Variant F: grid stacking + clip-path mapped to text width ─── */ +function VariantF(props: { active: boolean; text: string }) { + const progress = useSpring( + () => (props.active ? 1 : 0), + () => ({ visualDuration: 0.35, bounce: 0 }), + ) + let baseRef: HTMLSpanElement | undefined + let containerRef: HTMLSpanElement | undefined + const [textWidth, setTextWidth] = createSignal(0) + const [containerWidth, setContainerWidth] = createSignal(0) + + const measure = () => { + if (baseRef) setTextWidth(baseRef.scrollWidth) + if (containerRef) setContainerWidth(containerRef.offsetWidth) + } + + onMount(measure) + createEffect(() => { + const el = containerRef + if (!el) return + const observer = new ResizeObserver(measure) + observer.observe(el) + onCleanup(() => observer.disconnect()) + }) + + const clipRight = () => { + const cw = containerWidth() + const tw = textWidth() + if (cw <= 0 || tw <= 0) return `${(1 - progress()) * 100}%` + const revealed = progress() * tw + const remaining = Math.max(0, cw - revealed) + return `${remaining}px` + } + + return ( + + + {props.text} + + + + ) +} + +export default { + title: "UI/Text Strikethrough", + id: "components-text-strikethrough", + tags: ["autodocs"], + parameters: { + docs: { + description: { + component: `### Animated Strikethrough Variants + +- **A** — scaleX line at 50% (single line only) +- **D** — background-image line (single line only) +- **E** — grid stacking + clip-path (container %) +- **F** — grid stacking + clip-path mapped to text width (the real component)`, + }, + }, + }, +} + +export const Playground = { + render: () => { + const [active, setActive] = createSignal(false) + const toggle = () => setActive((v) => !v) + + return ( +
+ + +
+
F — grid stacking + clip mapped to text width (THE COMPONENT)
+ +
+ +
+ +
+ +
+
F (inline) — same but just inline variants
+ +
+ +
+ +
+ +
+
E — grid stacking + clip-path (container %)
+ +
+ +
+ +
+ +
+
A — scaleX line at 50%
+ +
+ +
+ +
+
D — background-image line
+ +
+ +
+
+ ) + }, +} diff --git a/packages/ui/src/components/text-strikethrough.tsx b/packages/ui/src/components/text-strikethrough.tsx new file mode 100644 index 00000000000..211e7d44c09 --- /dev/null +++ b/packages/ui/src/components/text-strikethrough.tsx @@ -0,0 +1,85 @@ +import type { JSX } from "solid-js" +import { createEffect, createSignal, onCleanup, onMount } from "solid-js" +import { useSpring } from "./motion-spring" + +export function TextStrikethrough(props: { + /** Whether the strikethrough is active (line drawn across). */ + active: boolean + /** The text to display. Rendered twice internally (base + decoration overlay). */ + text: string + /** Spring visual duration in seconds. Default 0.35. */ + visualDuration?: number + class?: string + style?: JSX.CSSProperties +}) { + const progress = useSpring( + () => (props.active ? 1 : 0), + () => ({ visualDuration: props.visualDuration ?? 0.35, bounce: 0 }), + ) + + let baseRef: HTMLSpanElement | undefined + let containerRef: HTMLSpanElement | undefined + const [textWidth, setTextWidth] = createSignal(0) + const [containerWidth, setContainerWidth] = createSignal(0) + + const measure = () => { + if (baseRef) setTextWidth(baseRef.scrollWidth) + if (containerRef) setContainerWidth(containerRef.offsetWidth) + } + + onMount(measure) + + createEffect(() => { + const el = containerRef + if (!el) return + const observer = new ResizeObserver(measure) + observer.observe(el) + onCleanup(() => observer.disconnect()) + }) + + // Revealed pixels from left = progress * textWidth + const revealedPx = () => { + const tw = textWidth() + return tw > 0 ? progress() * tw : 0 + } + + // Overlay clip: hide everything to the right of revealed area + const overlayClip = () => { + const cw = containerWidth() + const tw = textWidth() + if (cw <= 0 || tw <= 0) return `inset(0 ${(1 - progress()) * 100}% 0 0)` + const remaining = Math.max(0, cw - revealedPx()) + return `inset(0 ${remaining}px 0 0)` + } + + // Base clip: hide everything to the left of revealed area (complementary) + const baseClip = () => { + const px = revealedPx() + if (px <= 0.5) return "none" + return `inset(0 0 0 ${px}px)` + } + + return ( + + + {props.text} + + + + ) +} diff --git a/packages/ui/src/components/thinking-heading.stories.tsx b/packages/ui/src/components/thinking-heading.stories.tsx new file mode 100644 index 00000000000..90eb7ee3190 --- /dev/null +++ b/packages/ui/src/components/thinking-heading.stories.tsx @@ -0,0 +1,837 @@ +// @ts-nocheck +import { createSignal, createEffect, on, onMount, onCleanup } from "solid-js" +import { TextShimmer } from "./text-shimmer" +import { TextReveal } from "./text-reveal" + +export default { + title: "UI/ThinkingHeading", + id: "components-thinking-heading", + tags: ["autodocs"], + parameters: { + docs: { + description: { + component: `### Overview +Playground for animating the secondary heading beside "Thinking". + +Uses TextReveal for the production heading animation with tunable +duration, travel, bounce, and fade controls.`, + }, + }, + }, +} + +const HEADINGS = [ + "Planning key generation details", + "Analyzing error handling", + undefined, + "Reviewing authentication flow", + "Considering edge cases", + "Evaluating performance", + "Structuring the response", + "Checking type safety", + "Designing the API surface", + "Mapping dependencies", + "Outlining test strategy", +] + +// --------------------------------------------------------------------------- +// CSS +// +// Custom properties driven by sliders: +// --h-duration transition duration (e.g. "600ms") +// --h-duration-raw unitless number for calc (e.g. "600") +// --h-blur blur radius (e.g. "4px") +// --h-travel vertical travel distance (e.g. "18px") +// --h-spring full cubic-bezier for movement (set from bounce slider) +// --h-spring-soft softer version for width transitions +// --h-mask-size fade depth at top/bottom of odometer mask +// --h-mask-pad base padding-block on odometer track +// --h-mask-height extra vertical mask area per side +// --h-mask-bg background color for fade overlays +// --------------------------------------------------------------------------- + +const STYLES = ` +/* ── shared base ────────────────────────────────────────────────── */ +[data-variant] { + display: inline-flex; + align-items: center; +} + +[data-variant] [data-slot="track"] { + display: grid; + overflow: visible; + min-height: 20px; + justify-items: start; + align-items: center; + transition: width var(--h-duration, 600ms) var(--h-spring-soft, cubic-bezier(0.34, 1.1, 0.64, 1)); +} + +[data-variant] [data-slot="entering"], +[data-variant] [data-slot="leaving"] { + grid-area: 1 / 1; + line-height: 20px; + white-space: nowrap; + justify-self: start; +} + +/* kill transitions before fonts are ready */ +[data-variant][data-ready="false"] [data-slot="track"], +[data-variant][data-ready="false"] [data-slot="entering"], +[data-variant][data-ready="false"] [data-slot="leaving"] { + transition-duration: 0ms !important; +} + + +/* ── 1. spring-up ───────────────────────────────────────────────── * + * New text rises from below, old text exits upward. */ + +[data-variant="spring-up"] [data-slot="entering"], +[data-variant="spring-up"] [data-slot="leaving"] { + transition-property: transform, opacity, filter; + transition-duration: + var(--h-duration, 600ms), + calc(var(--h-duration-raw, 600) * 0.6 * 1ms), + calc(var(--h-duration-raw, 600) * 0.5 * 1ms); + transition-timing-function: var(--h-spring), ease-out, ease-out; +} +[data-variant="spring-up"] [data-slot="entering"] { + transform: translateY(0); + opacity: 1; + filter: blur(0); +} +[data-variant="spring-up"] [data-slot="leaving"] { + transform: translateY(calc(var(--h-travel, 18px) * -1)); + opacity: 0; + filter: blur(var(--h-blur, 0px)); +} +[data-variant="spring-up"][data-swapping="true"] [data-slot="entering"] { + transform: translateY(var(--h-travel, 18px)); + opacity: 0; + filter: blur(var(--h-blur, 0px)); + transition-duration: 0ms !important; +} +[data-variant="spring-up"][data-swapping="true"] [data-slot="leaving"] { + transform: translateY(0); + opacity: 1; + filter: blur(0); + transition-duration: 0ms !important; +} + + +/* ── 2. spring-down ─────────────────────────────────────────────── * + * New text drops from above, old text exits downward. */ + +[data-variant="spring-down"] [data-slot="entering"], +[data-variant="spring-down"] [data-slot="leaving"] { + transition-property: transform, opacity, filter; + transition-duration: + var(--h-duration, 600ms), + calc(var(--h-duration-raw, 600) * 0.6 * 1ms), + calc(var(--h-duration-raw, 600) * 0.5 * 1ms); + transition-timing-function: var(--h-spring), ease-out, ease-out; +} +[data-variant="spring-down"] [data-slot="entering"] { + transform: translateY(0); + opacity: 1; + filter: blur(0); +} +[data-variant="spring-down"] [data-slot="leaving"] { + transform: translateY(var(--h-travel, 18px)); + opacity: 0; + filter: blur(var(--h-blur, 0px)); +} +[data-variant="spring-down"][data-swapping="true"] [data-slot="entering"] { + transform: translateY(calc(var(--h-travel, 18px) * -1)); + opacity: 0; + filter: blur(var(--h-blur, 0px)); + transition-duration: 0ms !important; +} +[data-variant="spring-down"][data-swapping="true"] [data-slot="leaving"] { + transform: translateY(0); + opacity: 1; + filter: blur(0); + transition-duration: 0ms !important; +} + + +/* ── 3. spring-pop ──────────────────────────────────────────────── * + * Scale + slight vertical shift + blur. Playful, bouncy. */ + +[data-variant="spring-pop"] [data-slot="entering"], +[data-variant="spring-pop"] [data-slot="leaving"] { + transition-property: transform, opacity, filter; + transition-duration: + var(--h-duration, 600ms), + calc(var(--h-duration-raw, 600) * 0.55 * 1ms), + calc(var(--h-duration-raw, 600) * 0.55 * 1ms); + transition-timing-function: var(--h-spring), ease-out, ease-out; + transform-origin: left center; +} +[data-variant="spring-pop"] [data-slot="entering"] { + transform: translateY(0) scale(1); + opacity: 1; + filter: blur(0); +} +[data-variant="spring-pop"] [data-slot="leaving"] { + transform: translateY(calc(var(--h-travel, 18px) * -0.35)) scale(0.92); + opacity: 0; + filter: blur(var(--h-blur, 3px)); +} +[data-variant="spring-pop"][data-swapping="true"] [data-slot="entering"] { + transform: translateY(calc(var(--h-travel, 18px) * 0.35)) scale(0.92); + opacity: 0; + filter: blur(var(--h-blur, 3px)); + transition-duration: 0ms !important; +} +[data-variant="spring-pop"][data-swapping="true"] [data-slot="leaving"] { + transform: translateY(0) scale(1); + opacity: 1; + filter: blur(0); + transition-duration: 0ms !important; +} + + +/* ── 4. spring-blur ─────────────────────────────────────────────── * + * Pure crossfade with heavy blur. No vertical movement. * + * Width still animates with spring. */ + +[data-variant="spring-blur"] [data-slot="entering"], +[data-variant="spring-blur"] [data-slot="leaving"] { + transition-property: opacity, filter; + transition-duration: + calc(var(--h-duration-raw, 600) * 0.75 * 1ms), + var(--h-duration, 600ms); + transition-timing-function: ease-out, var(--h-spring-soft); +} +[data-variant="spring-blur"] [data-slot="entering"] { + opacity: 1; + filter: blur(0); +} +[data-variant="spring-blur"] [data-slot="leaving"] { + opacity: 0; + filter: blur(calc(var(--h-blur, 4px) * 2)); +} +[data-variant="spring-blur"][data-swapping="true"] [data-slot="entering"] { + opacity: 0; + filter: blur(calc(var(--h-blur, 4px) * 2)); + transition-duration: 0ms !important; +} +[data-variant="spring-blur"][data-swapping="true"] [data-slot="leaving"] { + opacity: 1; + filter: blur(0); + transition-duration: 0ms !important; +} + + +/* ── 5. odometer ──────────────────────────────────────────────── * + * Both texts scroll vertically through a clipped track. * + * * + * overflow:hidden clips at the padding-box edge. * + * mask-image fades to transparent at that same edge. * + * Result: content is invisible at the clip boundary → no hard * + * edge ever visible. Padding + mask height extend the clip area * + * so text has room to travel through the gradient fade zone. * + * * + * Uses transparent→white which works in both alpha & luminance * + * mask modes (transparent=hidden, white=visible in both). */ + +[data-variant="odometer"] [data-slot="track"] { + --h-mask-stop: min(var(--h-mask-size, 20px), calc(50% - 0.5px)); + --h-odo-shift: calc( + 100% + var(--h-travel, 18px) + var(--h-mask-height, 0px) + max(calc(var(--h-mask-pad, 28px) - 28px), 0px) + ); + position: relative; + align-items: stretch; + overflow: hidden; + padding-block: calc(var(--h-mask-pad, 28px) + var(--h-mask-height, 0px)); + margin-block: calc((var(--h-mask-pad, 28px) + var(--h-mask-height, 0px)) * -1); + -webkit-mask-image: linear-gradient( + to bottom, + transparent 0px, + white var(--h-mask-stop), + white calc(100% - var(--h-mask-stop)), + transparent 100% + ); + mask-image: linear-gradient( + to bottom, + transparent 0px, + white var(--h-mask-stop), + white calc(100% - var(--h-mask-stop)), + transparent 100% + ); + transition: width var(--h-duration, 600ms) var(--h-spring-soft, cubic-bezier(0.34, 1.1, 0.64, 1)); +} + +/* on swap, jump width instantly to the max of both texts */ +[data-variant="odometer"][data-swapping="true"] [data-slot="track"] { + transition-duration: 0ms !important; +} + +[data-variant="odometer"] [data-slot="entering"], +[data-variant="odometer"] [data-slot="leaving"] { + transition-property: transform; + transition-duration: var(--h-duration, 600ms); + transition-timing-function: var(--h-spring); + opacity: 1; +} +/* settled: entering in view, leaving pushed below */ +[data-variant="odometer"] [data-slot="entering"] { + transform: translateY(0); +} +[data-variant="odometer"] [data-slot="leaving"] { + transform: translateY(var(--h-odo-shift)); +} +/* swapping: snap entering above, leaving in-place */ +[data-variant="odometer"][data-swapping="true"] [data-slot="entering"] { + transform: translateY(calc(var(--h-odo-shift) * -1)); + transition-duration: 0ms !important; +} +[data-variant="odometer"][data-swapping="true"] [data-slot="leaving"] { + transform: translateY(0); + transition-duration: 0ms !important; +} + +/* ── odometer + blur ──────────────────────────────────────────── * + * Optional: adds opacity + blur transitions on top of the * + * positional odometer movement. */ + +[data-variant="odometer"][data-odo-blur="true"] [data-slot="entering"], +[data-variant="odometer"][data-odo-blur="true"] [data-slot="leaving"] { + transition-property: transform, opacity, filter; + transition-duration: + var(--h-duration, 600ms), + calc(var(--h-duration-raw, 600) * 0.6 * 1ms), + calc(var(--h-duration-raw, 600) * 0.5 * 1ms); +} +[data-variant="odometer"][data-odo-blur="true"] [data-slot="entering"] { + opacity: 1; + filter: blur(0); +} +[data-variant="odometer"][data-odo-blur="true"] [data-slot="leaving"] { + opacity: 0; + filter: blur(var(--h-blur, 4px)); +} +[data-variant="odometer"][data-odo-blur="true"][data-swapping="true"] [data-slot="entering"] { + opacity: 0; + filter: blur(var(--h-blur, 4px)); +} +[data-variant="odometer"][data-odo-blur="true"][data-swapping="true"] [data-slot="leaving"] { + opacity: 1; + filter: blur(0); +} + +/* ── debug: show fade zones ───────────────────────────────────── */ +[data-variant="odometer"][data-debug="true"] [data-slot="track"] { + outline: 1px dashed rgba(255, 0, 0, 0.6); +} +[data-variant="odometer"][data-debug="true"] [data-slot="track"]::before, +[data-variant="odometer"][data-debug="true"] [data-slot="track"]::after { + content: ""; + position: absolute; + left: 0; + right: 0; + height: var(--h-mask-stop); + pointer-events: none; +} +[data-variant="odometer"][data-debug="true"] [data-slot="track"]::before { + top: 0; + background: linear-gradient(to bottom, rgba(255, 0, 0, 0.3), transparent); +} +[data-variant="odometer"][data-debug="true"] [data-slot="track"]::after { + bottom: 0; + background: linear-gradient(to top, rgba(255, 0, 0, 0.3), transparent); +} + + +/* ── slider styling ─────────────────────────────────────────────── */ +input[type="range"].heading-slider { + -webkit-appearance: none; + appearance: none; + width: 140px; + height: 4px; + border-radius: 2px; + background: var(--color-divider, #444); + outline: none; +} +input[type="range"].heading-slider::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 14px; + height: 14px; + border-radius: 50%; + background: var(--color-accent, #58f); + cursor: pointer; + border: none; +} +` + +// --------------------------------------------------------------------------- +// Animated heading component +// +// Width is measured via scrollWidth (NOT Range.getBoundingClientRect) because +// getBoundingClientRect includes CSS transforms — so scale(0.92) during the +// swap phase would measure 92% of the real width and permanently clip text. +// scrollWidth returns the layout/intrinsic width, unaffected by transforms. +// --------------------------------------------------------------------------- + +function AnimatedHeading(props) { + const [current, setCurrent] = createSignal(props.text) + const [leaving, setLeaving] = createSignal(undefined) + const [width, setWidth] = createSignal("auto") + const [ready, setReady] = createSignal(false) + const [swapping, setSwapping] = createSignal(false) + let enterRef + let leaveRef + let containerRef + let frame + + const measureEnter = () => enterRef?.scrollWidth ?? 0 + const measureLeave = () => leaveRef?.scrollWidth ?? 0 + const widen = (px) => { + if (px <= 0) return + const w = Number.parseFloat(width()) + if (Number.isFinite(w) && px <= w) return + setWidth(`${px}px`) + } + + const measure = () => { + if (!current()) { + setWidth("0px") + return + } + const px = measureEnter() + if (px > 0) setWidth(`${px}px`) + } + + createEffect( + on( + () => props.text, + (next, prev) => { + if (next === prev) return + setSwapping(true) + setLeaving(prev) + setCurrent(next) + + if (frame) cancelAnimationFrame(frame) + frame = requestAnimationFrame(() => { + // For odometer keep width as a grow-only max so heading never shrinks. + if (props.variant === "odometer") { + const enterW = measureEnter() + const leaveW = measureLeave() + widen(Math.max(enterW, leaveW)) + containerRef?.offsetHeight // reflow with max width + swap positions + setSwapping(false) + } else { + containerRef?.offsetHeight + setSwapping(false) + measure() + } + frame = undefined + }) + }, + ), + ) + + onMount(() => { + measure() + document.fonts?.ready.finally(() => { + measure() + requestAnimationFrame(() => setReady(true)) + }) + }) + + onCleanup(() => { + if (frame) cancelAnimationFrame(frame) + }) + + return ( + + + + {current() ?? "\u00A0"} + + + {leaving() ?? "\u00A0"} + + + + ) +} + +// --------------------------------------------------------------------------- +// Button / layout styles +// --------------------------------------------------------------------------- + +const btn = (accent) => ({ + padding: "6px 14px", + "border-radius": "6px", + border: "1px solid var(--color-divider, #333)", + background: accent ? "var(--color-danger-fill, #c33)" : "var(--color-fill-element, #222)", + color: "var(--color-text, #eee)", + cursor: "pointer", + "font-size": "13px", +}) + +const smallBtn = (active) => ({ + padding: "4px 12px", + "border-radius": "6px", + border: active ? "1px solid var(--color-accent, #58f)" : "1px solid var(--color-divider, #333)", + background: active ? "var(--color-accent, #58f)" : "var(--color-fill-element, #222)", + color: "var(--color-text, #eee)", + cursor: "pointer", + "font-size": "12px", +}) + +const sliderLabel = { + "font-size": "11px", + "font-family": "monospace", + color: "var(--color-text-weak, #666)", + "min-width": "70px", + "flex-shrink": "0", + "text-align": "right", +} + +const sliderValue = { + "font-family": "monospace", + "font-size": "11px", + color: "var(--color-text-weak, #aaa)", + "min-width": "60px", +} + +const cardLabel = { + "font-size": "11px", + "font-family": "monospace", + color: "var(--color-text-weak, #666)", +} + +const thinkingRow = { + display: "flex", + "align-items": "center", + gap: "8px", + "min-width": "0", + "font-size": "14px", + "font-weight": "500", + "line-height": "20px", + "min-height": "20px", + color: "var(--text-weak, #aaa)", +} + +const headingSlot = { + "min-width": "0", + overflow: "visible", + "white-space": "nowrap", + color: "var(--text-weaker, #888)", + "font-weight": "400", +} + +const cardStyle = { + padding: "16px 20px", + "border-radius": "10px", + border: "1px solid var(--color-divider, #333)", + background: "var(--h-mask-bg, #1a1a1a)", + display: "grid", + gap: "8px", +} + +// --------------------------------------------------------------------------- +// Variants +// --------------------------------------------------------------------------- + +const VARIANTS: { key: string; label: string }[] = [] + +// --------------------------------------------------------------------------- +// Story +// --------------------------------------------------------------------------- + +export const Playground = { + render: () => { + const [heading, setHeading] = createSignal(HEADINGS[0]) + const [headingIndex, setHeadingIndex] = createSignal(0) + const [active, setActive] = createSignal(true) + const [cycling, setCycling] = createSignal(false) + let cycleTimer + + // tunable params + const [duration, setDuration] = createSignal(550) + const [blur, setBlur] = createSignal(2) + const [travel, setTravel] = createSignal(4) + const [bounce, setBounce] = createSignal(1.35) + const [maskSize, setMaskSize] = createSignal(12) + const [maskPad, setMaskPad] = createSignal(9) + const [maskHeight, setMaskHeight] = createSignal(0) + const [debug, setDebug] = createSignal(false) + const [odoBlur, setOdoBlur] = createSignal(false) + + const nextHeading = () => { + setHeadingIndex((i) => { + const next = (i + 1) % HEADINGS.length + setHeading(HEADINGS[next]) + return next + }) + } + + const prevHeading = () => { + setHeadingIndex((i) => { + const prev = (i - 1 + HEADINGS.length) % HEADINGS.length + setHeading(HEADINGS[prev]) + return prev + }) + } + + const toggleCycling = () => { + if (cycling()) { + clearTimeout(cycleTimer) + cycleTimer = undefined + setCycling(false) + return + } + setCycling(true) + const tick = () => { + if (!cycling()) return + nextHeading() + cycleTimer = setTimeout(tick, 850 + Math.floor(Math.random() * 550)) + } + cycleTimer = setTimeout(tick, 850 + Math.floor(Math.random() * 550)) + } + + const clearHeading = () => { + setHeading(undefined) + if (cycling()) { + clearTimeout(cycleTimer) + cycleTimer = undefined + setCycling(false) + } + } + + onCleanup(() => { + if (cycleTimer) clearTimeout(cycleTimer) + }) + + const vars = () => ({ + "--h-duration": `${duration()}ms`, + "--h-duration-raw": `${duration()}`, + "--h-blur": `${blur()}px`, + "--h-travel": `${travel()}px`, + "--h-spring": `cubic-bezier(0.34, ${bounce()}, 0.64, 1)`, + "--h-spring-soft": `cubic-bezier(0.34, ${Math.max(bounce() * 0.7, 1)}, 0.64, 1)`, + "--h-mask-size": `${maskSize()}px`, + "--h-mask-pad": `${maskPad()}px`, + "--h-mask-height": `${maskHeight()}px`, + "--h-mask-bg": "#1a1a1a", + }) + + return ( +
+ + + {/* ── Variant cards ─────────────────────────────────── */} +
+
+ TextReveal (production) + + + + + + +
+ {VARIANTS.map((v) => ( +
+ {v.label} + + + + + + +
+ ))} +
+ + {/* ── Sliders ──────────────────────────────────────── */} +
+
+ duration + setDuration(Number(e.currentTarget.value))} + /> + {duration()}ms +
+ +
+ blur + setBlur(Number(e.currentTarget.value))} + /> + {blur()}px +
+ +
+ travel + setTravel(Number(e.currentTarget.value))} + /> + {travel()}px +
+ +
+ bounce + setBounce(Number(e.currentTarget.value))} + /> + + {bounce().toFixed(2)} {bounce() <= 1.05 ? "(none)" : bounce() >= 1.9 ? "(heavy)" : ""} + +
+ +
+ mask + setMaskSize(Number(e.currentTarget.value))} + /> + + {maskSize()}px {maskSize() === 0 ? "(hard)" : ""} + +
+ +
+ mask pad + setMaskPad(Number(e.currentTarget.value))} + /> + {maskPad()}px +
+ +
+ mask height + setMaskHeight(Number(e.currentTarget.value))} + /> + {maskHeight()}px +
+
+ + {/* ── Controls ─────────────────────────────────────── */} +
+
+ + + + + + + +
+ +
+ {HEADINGS.map((h, i) => ( + + ))} +
+ +
+ heading: {heading() ?? "(none)"} · sim: {cycling() ? "on" : "off"} · bounce: {bounce().toFixed(2)} · + odo-blur: {odoBlur() ? "on" : "off"} +
+
+
+ ) + }, +} diff --git a/packages/ui/src/components/todo-panel-motion.stories.tsx b/packages/ui/src/components/todo-panel-motion.stories.tsx new file mode 100644 index 00000000000..39d34215783 --- /dev/null +++ b/packages/ui/src/components/todo-panel-motion.stories.tsx @@ -0,0 +1,584 @@ +// @ts-nocheck +import { createEffect, createMemo, createSignal, onCleanup } from "solid-js" +import type { Todo } from "@opencode-ai/sdk/v2" +import { useGlobalSync } from "@/context/global-sync" +import { SessionComposerRegion, createSessionComposerState } from "@/pages/session/composer" + +export default { + title: "UI/Todo Panel Motion", + id: "components-todo-panel-motion", + tags: ["autodocs"], + parameters: { + docs: { + description: { + component: `### Overview +This playground renders the real session composer region from app code. + +### Source path +- \`packages/app/src/pages/session/composer/session-composer-region.tsx\` + +### Includes +- \`SessionTodoDock\` (real) +- \`PromptInput\` (real) + +No visual reimplementation layer is used for the dock/input stack.`, + }, + }, + }, +} + +const pool = [ + "Refactor ToolStatusTitle DOM measurement to offscreen global measurer (unconstrained by timeline layout)", + "Remove inline measure nodes/CSS hooks and keep width morph behavior intact", + "Run typechecks/tests and report what changed", + "Verify reduced-motion behavior in timeline", + "Review diff for animation edge cases", + "Document rollout notes in PR description", + "Check keyboard and screen reader semantics", + "Add storybook controls for iteration speed", +] + +const btn = (accent?: boolean) => + ({ + padding: "6px 14px", + "border-radius": "6px", + border: "1px solid var(--color-divider, #333)", + background: accent ? "var(--color-accent, #58f)" : "var(--color-fill-element, #222)", + color: "var(--color-text, #eee)", + cursor: "pointer", + "font-size": "13px", + }) as const + +const css = ` +[data-component="todo-stage"] { + display: grid; + gap: 20px; + padding: 20px; +} + +[data-component="todo-preview"] { + height: 560px; + min-height: 0; +} + +[data-component="todo-session-root"] { + position: relative; + width: 100%; + height: 100%; + overflow: hidden; + display: flex; + flex-direction: column; + background: var(--background-base); + border: 1px solid var(--border-weak-base); + border-radius: 12px; +} + +[data-component="todo-session-frame"] { + flex: 1 1 auto; + min-height: 0; + display: flex; + flex-direction: column; +} + +[data-component="todo-session-panel"] { + position: relative; + flex: 1 1 auto; + min-height: 0; + height: 100%; + display: flex; + flex-direction: column; + background: var(--background-stronger); +} + +[data-slot="todo-preview-content"] { + flex: 1 1 auto; + min-height: 0; + overflow: hidden; +} + +[data-slot="todo-preview-scroll"] { + height: 100%; + overflow: auto; + min-height: 0; + padding: 14px 16px; + display: flex; + flex-direction: column; + gap: 10px; +} + +[data-slot="todo-preview-spacer"] { + flex: 1 1 auto; + min-height: 0; +} + +[data-slot="todo-preview-msg"] { + border-radius: 8px; + border: 1px solid var(--border-weak-base); + background: var(--surface-base); + color: var(--text-weak); + padding: 8px 10px; + font-size: 13px; + line-height: 1.35; +} + +[data-slot="todo-preview-msg"][data-strong="true"] { + color: var(--text-strong); +} +` + +export const Playground = { + render: () => { + const global = useGlobalSync() + const [open, setOpen] = createSignal(true) + const [step, setStep] = createSignal(1) + const [dockOpenDuration, setDockOpenDuration] = createSignal(0.3) + const [dockOpenBounce, setDockOpenBounce] = createSignal(0) + const [dockCloseDuration, setDockCloseDuration] = createSignal(0.3) + const [dockCloseBounce, setDockCloseBounce] = createSignal(0) + const [drawerExpandDuration, setDrawerExpandDuration] = createSignal(0.3) + const [drawerExpandBounce, setDrawerExpandBounce] = createSignal(0) + const [drawerCollapseDuration, setDrawerCollapseDuration] = createSignal(0.3) + const [drawerCollapseBounce, setDrawerCollapseBounce] = createSignal(0) + const [subtitleDuration, setSubtitleDuration] = createSignal(600) + const [subtitleAuto, setSubtitleAuto] = createSignal(true) + const [subtitleTravel, setSubtitleTravel] = createSignal(25) + const [subtitleEdge, setSubtitleEdge] = createSignal(17) + const [countDuration, setCountDuration] = createSignal(600) + const [countMask, setCountMask] = createSignal(18) + const [countMaskHeight, setCountMaskHeight] = createSignal(0) + const [countWidthDuration, setCountWidthDuration] = createSignal(560) + const state = createSessionComposerState({ closeMs: () => Math.round(dockCloseDuration() * 1000) }) + let frame + let composerRef + let scrollRef + + const todos = createMemo(() => { + const done = Math.max(0, Math.min(3, step())) + return pool.slice(0, 3).map((content, i) => ({ + id: `todo-${i + 1}`, + content, + status: i < done ? "completed" : i === done && done < 3 ? "in_progress" : "pending", + })) + }) + + createEffect(() => { + global.todo.set("story-session", todos()) + }) + + const clear = () => { + if (frame) cancelAnimationFrame(frame) + frame = undefined + } + + const pin = () => { + if (!scrollRef) return + scrollRef.scrollTop = scrollRef.scrollHeight + } + + const collapsed = () => + !!composerRef?.querySelector('[data-action="session-todo-toggle-button"][data-collapsed="true"]') + + const setCollapsed = (value: boolean) => { + const button = composerRef?.querySelector('[data-action="session-todo-toggle-button"]') + if (!(button instanceof HTMLButtonElement)) return + if (collapsed() === value) return + button.click() + } + + const openDock = () => { + clear() + setOpen(true) + frame = requestAnimationFrame(() => { + pin() + frame = undefined + }) + } + + const closeDock = () => { + clear() + setOpen(false) + } + + const dockOpen = () => open() + + const toggleDock = () => { + if (dockOpen()) { + closeDock() + return + } + openDock() + } + + const toggleDrawer = () => { + if (!dockOpen()) { + openDock() + frame = requestAnimationFrame(() => { + pin() + setCollapsed(true) + frame = undefined + }) + return + } + setCollapsed(!collapsed()) + } + + const cycle = () => { + setStep((value) => (value + 1) % 4) + } + + onCleanup(clear) + + return ( +
+ + +
+
+
+
+
+
+
+
+ Thinking Checking type safety +
+
Shell Prints five topic blocks between timed commands
+
+
+ +
+ {}} + newSessionWorktree="" + onNewSessionWorktreeReset={() => {}} + onSubmit={() => {}} + onResponseSubmit={pin} + setPromptDockRef={() => {}} + dockOpenVisualDuration={dockOpenDuration()} + dockOpenBounce={dockOpenBounce()} + dockCloseVisualDuration={dockCloseDuration()} + dockCloseBounce={dockCloseBounce()} + drawerExpandVisualDuration={drawerExpandDuration()} + drawerExpandBounce={drawerExpandBounce()} + drawerCollapseVisualDuration={drawerCollapseDuration()} + drawerCollapseBounce={drawerCollapseBounce()} + subtitleDuration={subtitleDuration()} + subtitleTravel={subtitleAuto() ? undefined : subtitleTravel()} + subtitleEdge={subtitleAuto() ? undefined : subtitleEdge()} + countDuration={countDuration()} + countMask={countMask()} + countMaskHeight={countMaskHeight()} + countWidthDuration={countWidthDuration()} + /> +
+
+
+
+
+ +
+ + + + {[0, 1, 2, 3].map((value) => ( + + ))} +
+ +
+
Dock open
+ + + +
+ Dock close +
+ + + +
+ Drawer expand +
+ + + +
+ Drawer collapse +
+ + + +
+ Subtitle odometer +
+ + + + + +
+ Count odometer +
+ + + + +
+
+ ) + }, +} diff --git a/packages/ui/src/components/tool-count-label.css b/packages/ui/src/components/tool-count-label.css new file mode 100644 index 00000000000..11a33ff5d14 --- /dev/null +++ b/packages/ui/src/components/tool-count-label.css @@ -0,0 +1,57 @@ +[data-component="tool-count-label"] { + display: inline-flex; + align-items: baseline; + white-space: nowrap; + gap: 0; + + [data-slot="tool-count-label-before"] { + display: inline-block; + white-space: pre; + line-height: inherit; + } + + [data-slot="tool-count-label-word"] { + display: inline-flex; + align-items: baseline; + white-space: pre; + line-height: inherit; + } + + [data-slot="tool-count-label-stem"] { + display: inline-block; + white-space: pre; + } + + [data-slot="tool-count-label-suffix"] { + display: inline-grid; + grid-template-columns: 0fr; + opacity: 0; + filter: blur(calc(var(--tool-motion-blur, 2px) * 0.42)); + overflow: hidden; + transform: translateX(-0.04em); + transition-property: grid-template-columns, opacity, filter, transform; + transition-duration: 250ms, 250ms, 250ms, 250ms; + transition-timing-function: + var(--tool-motion-ease, cubic-bezier(0.22, 1, 0.36, 1)), ease-out, ease-out, + var(--tool-motion-ease, cubic-bezier(0.22, 1, 0.36, 1)); + } + + [data-slot="tool-count-label-suffix"][data-active="true"] { + grid-template-columns: 1fr; + opacity: 1; + filter: blur(0); + transform: translateX(0); + } + + [data-slot="tool-count-label-suffix-inner"] { + min-width: 0; + overflow: hidden; + white-space: pre; + } +} + +@media (prefers-reduced-motion: reduce) { + [data-component="tool-count-label"] [data-slot="tool-count-label-suffix"] { + transition-duration: 0ms; + } +} diff --git a/packages/ui/src/components/tool-count-label.tsx b/packages/ui/src/components/tool-count-label.tsx new file mode 100644 index 00000000000..67e861cdcb3 --- /dev/null +++ b/packages/ui/src/components/tool-count-label.tsx @@ -0,0 +1,58 @@ +import { createMemo } from "solid-js" +import { AnimatedNumber } from "./animated-number" + +function split(text: string) { + const match = /{{\s*count\s*}}/.exec(text) + if (!match) return { before: "", after: text } + if (match.index === undefined) return { before: "", after: text } + return { + before: text.slice(0, match.index), + after: text.slice(match.index + match[0].length), + } +} + +function common(one: string, other: string) { + const a = Array.from(one) + const b = Array.from(other) + let i = 0 + while (i < a.length && i < b.length && a[i] === b[i]) i++ + return { + stem: a.slice(0, i).join(""), + one: a.slice(i).join(""), + other: b.slice(i).join(""), + } +} + +export function AnimatedCountLabel(props: { count: number; one: string; other: string; class?: string }) { + const one = createMemo(() => split(props.one)) + const other = createMemo(() => split(props.other)) + const singular = createMemo(() => Math.round(props.count) === 1) + const active = createMemo(() => (singular() ? one() : other())) + const suffix = createMemo(() => common(one().after, other().after)) + const splitSuffix = createMemo( + () => + one().before === other().before && + (one().after.startsWith(other().after) || other().after.startsWith(one().after)), + ) + const before = createMemo(() => (splitSuffix() ? one().before : active().before)) + const stem = createMemo(() => (splitSuffix() ? suffix().stem : active().after)) + const tail = createMemo(() => { + if (!splitSuffix()) return "" + if (singular()) return suffix().one + return suffix().other + }) + const showTail = createMemo(() => splitSuffix() && tail().length > 0) + + return ( + + {before()} + + + {stem()} + + {tail()} + + + + ) +} diff --git a/packages/ui/src/components/tool-count-summary.css b/packages/ui/src/components/tool-count-summary.css new file mode 100644 index 00000000000..da8455267cc --- /dev/null +++ b/packages/ui/src/components/tool-count-summary.css @@ -0,0 +1,102 @@ +[data-component="tool-count-summary"] { + display: inline-flex; + align-items: baseline; + white-space: nowrap; + + [data-slot="tool-count-summary-empty"] { + display: inline-grid; + grid-template-columns: 1fr; + align-items: baseline; + opacity: 1; + filter: blur(0); + transform: translateY(0) scale(1); + overflow: hidden; + transform-origin: left center; + transition-property: grid-template-columns, opacity, filter, transform; + transition-duration: + var(--tool-motion-spring-ms, 480ms), var(--tool-motion-fade-ms, 240ms), var(--tool-motion-fade-ms, 280ms), + var(--tool-motion-spring-ms, 480ms); + transition-timing-function: + var(--tool-motion-ease, cubic-bezier(0.22, 1, 0.36, 1)), ease-out, ease-out, + var(--tool-motion-ease, cubic-bezier(0.22, 1, 0.36, 1)); + } + + [data-slot="tool-count-summary-empty"][data-active="false"] { + grid-template-columns: 0fr; + opacity: 0; + filter: blur(calc(var(--tool-motion-blur, 2px) * 0.72)); + transform: translateY(0.05em) scale(0.985); + } + + [data-slot="tool-count-summary-item"] { + display: inline-grid; + grid-template-columns: 0fr; + align-items: baseline; + opacity: 0; + filter: blur(var(--tool-motion-blur, 2px)); + transform: translateY(0.06em) scale(0.985); + overflow: hidden; + transform-origin: left center; + transition-property: grid-template-columns, opacity, filter, transform; + transition-duration: + var(--tool-motion-spring-ms, 480ms), var(--tool-motion-fade-ms, 280ms), var(--tool-motion-fade-ms, 320ms), + var(--tool-motion-spring-ms, 480ms); + transition-timing-function: + var(--tool-motion-ease, cubic-bezier(0.22, 1, 0.36, 1)), ease-out, ease-out, + var(--tool-motion-ease, cubic-bezier(0.22, 1, 0.36, 1)); + } + + [data-slot="tool-count-summary-item"][data-active="true"] { + grid-template-columns: 1fr; + opacity: 1; + filter: blur(0); + transform: translateY(0) scale(1); + } + + [data-slot="tool-count-summary-empty-inner"] { + min-width: 0; + overflow: hidden; + white-space: nowrap; + } + + [data-slot="tool-count-summary-item-inner"] { + display: inline-flex; + align-items: baseline; + min-width: 0; + overflow: hidden; + white-space: nowrap; + } + + [data-slot="tool-count-summary-prefix"] { + display: inline-flex; + align-items: baseline; + justify-content: flex-start; + max-width: 0; + margin-right: 0; + opacity: 0; + filter: blur(calc(var(--tool-motion-blur, 2px) * 0.55)); + overflow: hidden; + transform: translateX(-0.08em); + transition-property: opacity, filter, transform; + transition-duration: + calc(var(--tool-motion-fade-ms, 200ms) * 0.75), calc(var(--tool-motion-fade-ms, 220ms) * 0.75), + calc(var(--tool-motion-fade-ms, 220ms) * 0.6); + transition-timing-function: ease-out, ease-out, ease-out; + } + + [data-slot="tool-count-summary-prefix"][data-active="true"] { + max-width: 1ch; + margin-right: 0.45ch; + opacity: 1; + filter: blur(0); + transform: translateX(0); + } +} + +@media (prefers-reduced-motion: reduce) { + [data-component="tool-count-summary"] [data-slot="tool-count-summary-empty"], + [data-component="tool-count-summary"] [data-slot="tool-count-summary-item"], + [data-component="tool-count-summary"] [data-slot="tool-count-summary-prefix"] { + transition-duration: 0ms; + } +} diff --git a/packages/ui/src/components/tool-count-summary.stories.tsx b/packages/ui/src/components/tool-count-summary.stories.tsx new file mode 100644 index 00000000000..4be3a02bbec --- /dev/null +++ b/packages/ui/src/components/tool-count-summary.stories.tsx @@ -0,0 +1,230 @@ +// @ts-nocheck +import { createSignal, onCleanup } from "solid-js" +import { AnimatedCountList, type CountItem } from "./tool-count-summary" +import { ToolStatusTitle } from "./tool-status-title" + +export default { + title: "UI/AnimatedCountList", + id: "components-animated-count-list", + tags: ["autodocs"], + parameters: { + docs: { + description: { + component: `### Overview +Animated count list that smoothly transitions items in/out as counts change. + +Uses \`grid-template-columns: 0fr → 1fr\` for width animations and the odometer +digit roller for count transitions. Shown here with \`ToolStatusTitle\` exactly +as it appears in the context tool group on the session page.`, + }, + }, + }, +} + +const TEXT = { + active: "Exploring", + done: "Explored", + read: { one: "{{count}} read", other: "{{count}} reads" }, + search: { one: "{{count}} search", other: "{{count}} searches" }, + list: { one: "{{count}} list", other: "{{count}} lists" }, +} as const + +function rand(min: number, max: number) { + return Math.floor(Math.random() * (max - min + 1)) + min +} + +const btn = (accent?: boolean) => + ({ + padding: "6px 14px", + "border-radius": "6px", + border: "1px solid var(--color-divider, #333)", + background: accent ? "var(--color-danger-fill, #c33)" : "var(--color-fill-element, #222)", + color: "var(--color-text, #eee)", + cursor: "pointer", + "font-size": "13px", + }) as const + +const smallBtn = (active?: boolean) => + ({ + padding: "4px 12px", + "border-radius": "6px", + border: active ? "1px solid var(--color-accent, #58f)" : "1px solid var(--color-divider, #333)", + background: active ? "var(--color-accent, #58f)" : "var(--color-fill-element, #222)", + color: "var(--color-text, #eee)", + cursor: "pointer", + "font-size": "12px", + }) as const + +export const Playground = { + render: () => { + const [reads, setReads] = createSignal(0) + const [searches, setSearches] = createSignal(0) + const [lists, setLists] = createSignal(0) + const [active, setActive] = createSignal(false) + const [reducedMotion, setReducedMotion] = createSignal(false) + + let timeouts: ReturnType[] = [] + + const clearAll = () => { + for (const t of timeouts) clearTimeout(t) + timeouts = [] + } + + onCleanup(clearAll) + + const startSim = () => { + clearAll() + setReads(0) + setSearches(0) + setLists(0) + setActive(true) + const steps = rand(3, 10) + let elapsed = 0 + + for (let i = 0; i < steps; i++) { + const delay = rand(300, 800) + elapsed += delay + const t = setTimeout(() => { + const pick = rand(0, 2) + if (pick === 0) setReads((n) => n + 1) + else if (pick === 1) setSearches((n) => n + 1) + else setLists((n) => n + 1) + }, elapsed) + timeouts.push(t) + } + + const end = setTimeout(() => setActive(false), elapsed + 100) + timeouts.push(end) + } + + const stopSim = () => { + clearAll() + setActive(false) + } + + const reset = () => { + stopSim() + setReads(0) + setSearches(0) + setLists(0) + } + + const items = (): CountItem[] => [ + { key: "read", count: reads(), one: TEXT.read.one, other: TEXT.read.other }, + { key: "search", count: searches(), one: TEXT.search.one, other: TEXT.search.other }, + { key: "list", count: lists(), one: TEXT.list.one, other: TEXT.list.other }, + ] + + return ( +
+ {reducedMotion() && ( + + )} + + {/* Matches context-tool-group-trigger layout from message-part.tsx */} + + + + + + + + + +
+ + + +
+ +
+ + + +
+ +
+ motion: {reducedMotion() ? "reduced" : "normal"} · active: {active() ? "true" : "false"} · reads: {reads()} · + searches: {searches()} · lists: {lists()} +
+
+ ) + }, +} + +export const Empty = { + render: () => ( + + + + + ), +} + +export const Done = { + render: () => ( + + + + + + + ), +} diff --git a/packages/ui/src/components/tool-count-summary.tsx b/packages/ui/src/components/tool-count-summary.tsx new file mode 100644 index 00000000000..a5cb5b40d22 --- /dev/null +++ b/packages/ui/src/components/tool-count-summary.tsx @@ -0,0 +1,52 @@ +import { Index, createMemo } from "solid-js" +import { AnimatedCountLabel } from "./tool-count-label" + +export type CountItem = { + key: string + count: number + one: string + other: string +} + +export function AnimatedCountList(props: { items: CountItem[]; fallback?: string; class?: string }) { + const visible = createMemo(() => props.items.filter((item) => item.count > 0)) + const fallback = createMemo(() => props.fallback ?? "") + const showEmpty = createMemo(() => visible().length === 0 && fallback().length > 0) + + return ( + + + {fallback()} + + + + {(item, index) => { + const active = createMemo(() => item().count > 0) + const hasPrev = createMemo(() => { + for (let i = index - 1; i >= 0; i--) { + if (props.items[i].count > 0) return true + } + return false + }) + + return ( + <> + + , + + + + + + + + ) + }} + + + ) +} diff --git a/packages/ui/src/components/tool-status-title.css b/packages/ui/src/components/tool-status-title.css new file mode 100644 index 00000000000..d4415bd2daf --- /dev/null +++ b/packages/ui/src/components/tool-status-title.css @@ -0,0 +1,89 @@ +[data-component="tool-status-title"] { + display: inline-flex; + align-items: baseline; + white-space: nowrap; + text-align: start; + + [data-slot="tool-status-suffix"] { + display: inline-flex; + align-items: baseline; + white-space: nowrap; + } + + [data-slot="tool-status-prefix"] { + white-space: nowrap; + flex-shrink: 0; + } + + [data-slot="tool-status-swap"], + [data-slot="tool-status-tail"] { + display: inline-grid; + overflow: hidden; + justify-items: start; + transition: width var(--tool-motion-spring-ms, 480ms) var(--tool-motion-ease, cubic-bezier(0.22, 1, 0.36, 1)); + } + + [data-slot="tool-status-active"], + [data-slot="tool-status-done"] { + grid-area: 1 / 1; + white-space: nowrap; + justify-self: start; + text-align: start; + transition-property: opacity, filter, transform; + transition-duration: + var(--tool-motion-fade-ms, 240ms), calc(var(--tool-motion-fade-ms, 240ms) * 0.8), + calc(var(--tool-motion-fade-ms, 240ms) * 0.8); + transition-timing-function: ease-out, ease-out, ease-out; + } + + &[data-ready="false"] { + [data-slot="tool-status-swap"], + [data-slot="tool-status-tail"] { + transition-duration: 0ms; + } + + [data-slot="tool-status-active"], + [data-slot="tool-status-done"] { + transition-duration: 0ms; + } + } + + [data-slot="tool-status-active"] { + opacity: 0; + filter: blur(calc(var(--tool-motion-blur, 2px) * 0.45)); + transform: translateY(0.03em); + } + + [data-slot="tool-status-done"] { + color: var(--text-strong); + opacity: 1; + filter: blur(0); + transform: translateY(0); + } + + &[data-active="true"] { + [data-slot="tool-status-active"] { + opacity: 1; + filter: blur(0); + transform: translateY(0); + } + + [data-slot="tool-status-done"] { + opacity: 0; + filter: blur(calc(var(--tool-motion-blur, 2px) * 0.45)); + transform: translateY(0.03em); + } + } +} + +@media (prefers-reduced-motion: reduce) { + [data-component="tool-status-title"] [data-slot="tool-status-swap"], + [data-component="tool-status-title"] [data-slot="tool-status-tail"] { + transition-duration: 0ms; + } + + [data-component="tool-status-title"] [data-slot="tool-status-active"], + [data-component="tool-status-title"] [data-slot="tool-status-done"] { + transition-duration: 0ms; + } +} diff --git a/packages/ui/src/components/tool-status-title.tsx b/packages/ui/src/components/tool-status-title.tsx new file mode 100644 index 00000000000..68440b6c637 --- /dev/null +++ b/packages/ui/src/components/tool-status-title.tsx @@ -0,0 +1,133 @@ +import { Show, createEffect, createMemo, createSignal, on, onCleanup, onMount } from "solid-js" +import { TextShimmer } from "./text-shimmer" + +function common(active: string, done: string) { + const a = Array.from(active) + const b = Array.from(done) + let i = 0 + while (i < a.length && i < b.length && a[i] === b[i]) i++ + return { + prefix: a.slice(0, i).join(""), + active: a.slice(i).join(""), + done: b.slice(i).join(""), + } +} + +function contentWidth(el: HTMLSpanElement | undefined) { + if (!el) return 0 + const range = document.createRange() + range.selectNodeContents(el) + return Math.ceil(range.getBoundingClientRect().width) +} + +export function ToolStatusTitle(props: { + active: boolean + activeText: string + doneText: string + class?: string + split?: boolean +}) { + const split = createMemo(() => common(props.activeText, props.doneText)) + const suffix = createMemo( + () => (props.split ?? true) && split().prefix.length >= 2 && split().active.length > 0 && split().done.length > 0, + ) + const prefixLen = createMemo(() => Array.from(split().prefix).length) + const activeTail = createMemo(() => (suffix() ? split().active : props.activeText)) + const doneTail = createMemo(() => (suffix() ? split().done : props.doneText)) + + const [width, setWidth] = createSignal("auto") + const [ready, setReady] = createSignal(false) + let activeRef: HTMLSpanElement | undefined + let doneRef: HTMLSpanElement | undefined + let frame: number | undefined + let readyFrame: number | undefined + + const measure = () => { + const target = props.active ? activeRef : doneRef + const px = contentWidth(target) + if (px > 0) setWidth(`${px}px`) + } + + const schedule = () => { + if (typeof requestAnimationFrame !== "function") { + measure() + return + } + if (frame !== undefined) cancelAnimationFrame(frame) + frame = requestAnimationFrame(() => { + frame = undefined + measure() + }) + } + + const finish = () => { + if (typeof requestAnimationFrame !== "function") { + setReady(true) + return + } + if (readyFrame !== undefined) cancelAnimationFrame(readyFrame) + readyFrame = requestAnimationFrame(() => { + readyFrame = undefined + setReady(true) + }) + } + + createEffect(on([() => props.active, activeTail, doneTail, suffix], () => schedule())) + + onMount(() => { + measure() + const fonts = typeof document !== "undefined" ? document.fonts : undefined + if (!fonts) { + finish() + return + } + fonts.ready.finally(() => { + measure() + finish() + }) + }) + + onCleanup(() => { + if (frame !== undefined) cancelAnimationFrame(frame) + if (readyFrame !== undefined) cancelAnimationFrame(readyFrame) + }) + + return ( + + + + + + + + + + } + > + + + + + + + + + + + + + + + + ) +} diff --git a/packages/ui/src/hooks/create-auto-scroll.tsx b/packages/ui/src/hooks/create-auto-scroll.tsx index c32017739cd..3dc520c6213 100644 --- a/packages/ui/src/hooks/create-auto-scroll.tsx +++ b/packages/ui/src/hooks/create-auto-scroll.tsx @@ -48,14 +48,14 @@ export function createAutoScroll(options: AutoScrollOptions) { autoTimer = setTimeout(() => { auto = undefined autoTimer = undefined - }, 250) + }, 1500) } const isAuto = (el: HTMLElement) => { const a = auto if (!a) return false - if (Date.now() - a.time > 250) { + if (Date.now() - a.time > 1500) { auto = undefined return false } @@ -78,14 +78,19 @@ export function createAutoScroll(options: AutoScrollOptions) { const scrollToBottom = (force: boolean) => { if (!force && !active()) return + + if (force && store.userScrolled) setStore("userScrolled", false) + const el = scroll if (!el) return if (!force && store.userScrolled) return - if (force && store.userScrolled) setStore("userScrolled", false) const distance = distanceFromBottom(el) - if (distance < 2) return + if (distance < 2) { + markAuto(el) + return + } // For auto-following content we prefer immediate updates to avoid // visible "catch up" animations while content is still settling. @@ -142,7 +147,10 @@ export function createAutoScroll(options: AutoScrollOptions) { const handleInteraction = () => { if (!active()) return - stop() + const selection = window.getSelection() + if (selection && selection.toString().length > 0) { + stop() + } } const updateOverflowAnchor = (el: HTMLElement) => { diff --git a/packages/ui/src/i18n/ar.ts b/packages/ui/src/i18n/ar.ts index 4eb1b475567..3579eff5a8a 100644 --- a/packages/ui/src/i18n/ar.ts +++ b/packages/ui/src/i18n/ar.ts @@ -40,6 +40,9 @@ export const dict = { "ui.sessionTurn.retry.retrying": "إعادة المحاولة", "ui.sessionTurn.retry.inSeconds": "خلال {{seconds}} ثواني", + "ui.sessionTurn.retry.attempt": "المحاولة رقم {{attempt}}", + "ui.sessionTurn.retry.attemptLine": "{{line}} - المحاولة رقم {{attempt}}", + "ui.sessionTurn.retry.geminiHot": "gemini مزدحم حاليا", "ui.sessionTurn.error.freeUsageExceeded": "تم تجاوز حد الاستخدام المجاني", "ui.sessionTurn.error.addCredits": "إضافة رصيد", @@ -57,6 +60,7 @@ export const dict = { "ui.sessionTurn.status.consideringNextSteps": "النظر في الخطوات التالية", "ui.messagePart.questions.dismissed": "تم رفض الأسئلة", + "ui.messagePart.compaction": "تم ضغط السجل", "ui.messagePart.context.read.one": "{{count}} قراءة", "ui.messagePart.context.read.other": "{{count}} قراءات", "ui.messagePart.context.search.one": "{{count}} بحث", @@ -122,6 +126,7 @@ export const dict = { "ui.message.copyResponse": "نسخ الرد", "ui.message.copied": "تم النسخ!", "ui.message.interrupted": "تمت المقاطعة", + "ui.message.queued": "في الانتظار", "ui.message.attachment.alt": "مرفق", "ui.patch.action.deleted": "محذوف", diff --git a/packages/ui/src/i18n/br.ts b/packages/ui/src/i18n/br.ts index a2c3fb642c0..76028878f9c 100644 --- a/packages/ui/src/i18n/br.ts +++ b/packages/ui/src/i18n/br.ts @@ -40,6 +40,9 @@ export const dict = { "ui.sessionTurn.retry.retrying": "tentando novamente", "ui.sessionTurn.retry.inSeconds": "em {{seconds}}s", + "ui.sessionTurn.retry.attempt": "tentativa #{{attempt}}", + "ui.sessionTurn.retry.attemptLine": "{{line}} - tentativa #{{attempt}}", + "ui.sessionTurn.retry.geminiHot": "gemini está muito sobrecarregado agora", "ui.sessionTurn.error.freeUsageExceeded": "Limite de uso gratuito excedido", "ui.sessionTurn.error.addCredits": "Adicionar créditos", @@ -57,6 +60,7 @@ export const dict = { "ui.sessionTurn.status.consideringNextSteps": "Considerando próximos passos", "ui.messagePart.questions.dismissed": "Perguntas descartadas", + "ui.messagePart.compaction": "Histórico compactado", "ui.messagePart.context.read.one": "{{count}} leitura", "ui.messagePart.context.read.other": "{{count}} leituras", "ui.messagePart.context.search.one": "{{count}} pesquisa", @@ -122,6 +126,7 @@ export const dict = { "ui.message.copyResponse": "Copiar resposta", "ui.message.copied": "Copiado!", "ui.message.interrupted": "Interrompido", + "ui.message.queued": "Na fila", "ui.message.attachment.alt": "anexo", "ui.patch.action.deleted": "Excluído", diff --git a/packages/ui/src/i18n/bs.ts b/packages/ui/src/i18n/bs.ts index c75e158ba3b..9bc22933612 100644 --- a/packages/ui/src/i18n/bs.ts +++ b/packages/ui/src/i18n/bs.ts @@ -44,6 +44,9 @@ export const dict = { "ui.sessionTurn.retry.retrying": "ponovni pokušaj", "ui.sessionTurn.retry.inSeconds": "za {{seconds}}s", + "ui.sessionTurn.retry.attempt": "pokušaj #{{attempt}}", + "ui.sessionTurn.retry.attemptLine": "{{line}} - pokušaj #{{attempt}}", + "ui.sessionTurn.retry.geminiHot": "gemini je trenutno preopterećen", "ui.sessionTurn.error.freeUsageExceeded": "Besplatna upotreba premašena", "ui.sessionTurn.error.addCredits": "Dodaj kredite", @@ -61,6 +64,7 @@ export const dict = { "ui.sessionTurn.status.consideringNextSteps": "Razmatranje sljedećih koraka", "ui.messagePart.questions.dismissed": "Pitanja odbačena", + "ui.messagePart.compaction": "Historija sažeta", "ui.messagePart.context.read.one": "{{count}} čitanje", "ui.messagePart.context.read.other": "{{count}} čitanja", "ui.messagePart.context.search.one": "{{count}} pretraga", @@ -126,6 +130,7 @@ export const dict = { "ui.message.copyResponse": "Kopiraj odgovor", "ui.message.copied": "Kopirano!", "ui.message.interrupted": "Prekinuto", + "ui.message.queued": "U redu", "ui.message.attachment.alt": "prilog", "ui.patch.action.deleted": "Obrisano", diff --git a/packages/ui/src/i18n/da.ts b/packages/ui/src/i18n/da.ts index 59c18e8e92f..1bb4758568e 100644 --- a/packages/ui/src/i18n/da.ts +++ b/packages/ui/src/i18n/da.ts @@ -39,6 +39,9 @@ export const dict = { "ui.sessionTurn.retry.retrying": "prøver igen", "ui.sessionTurn.retry.inSeconds": "om {{seconds}}s", + "ui.sessionTurn.retry.attempt": "forsøg #{{attempt}}", + "ui.sessionTurn.retry.attemptLine": "{{line}} - forsøg #{{attempt}}", + "ui.sessionTurn.retry.geminiHot": "gemini er meget overbelastet lige nu", "ui.sessionTurn.error.freeUsageExceeded": "Gratis forbrug overskredet", "ui.sessionTurn.error.addCredits": "Tilføj kreditter", @@ -56,6 +59,7 @@ export const dict = { "ui.sessionTurn.status.consideringNextSteps": "Overvejer næste skridt", "ui.messagePart.questions.dismissed": "Spørgsmål afvist", + "ui.messagePart.compaction": "Historik komprimeret", "ui.messagePart.context.read.one": "{{count}} læsning", "ui.messagePart.context.read.other": "{{count}} læsninger", "ui.messagePart.context.search.one": "{{count}} søgning", @@ -121,6 +125,7 @@ export const dict = { "ui.message.copyResponse": "Kopier svar", "ui.message.copied": "Kopieret!", "ui.message.interrupted": "Afbrudt", + "ui.message.queued": "I kø", "ui.message.attachment.alt": "vedhæftning", "ui.patch.action.deleted": "Slettet", diff --git a/packages/ui/src/i18n/de.ts b/packages/ui/src/i18n/de.ts index b3fb610615d..951833c3091 100644 --- a/packages/ui/src/i18n/de.ts +++ b/packages/ui/src/i18n/de.ts @@ -45,6 +45,9 @@ export const dict = { "ui.sessionTurn.retry.retrying": "erneuter Versuch", "ui.sessionTurn.retry.inSeconds": "in {{seconds}}s", + "ui.sessionTurn.retry.attempt": "Versuch #{{attempt}}", + "ui.sessionTurn.retry.attemptLine": "{{line}} - Versuch #{{attempt}}", + "ui.sessionTurn.retry.geminiHot": "gemini ist gerade sehr überlastet", "ui.sessionTurn.error.freeUsageExceeded": "Kostenloses Nutzungslimit überschritten", "ui.sessionTurn.error.addCredits": "Guthaben aufladen", @@ -62,6 +65,7 @@ export const dict = { "ui.sessionTurn.status.consideringNextSteps": "Nächste Schritte erwägen", "ui.messagePart.questions.dismissed": "Fragen verworfen", + "ui.messagePart.compaction": "Verlauf komprimiert", "ui.messagePart.context.read.one": "{{count}} Lesevorgang", "ui.messagePart.context.read.other": "{{count}} Lesevorgänge", "ui.messagePart.context.search.one": "{{count}} Suche", @@ -127,6 +131,7 @@ export const dict = { "ui.message.copyResponse": "Antwort kopieren", "ui.message.copied": "Kopiert!", "ui.message.interrupted": "Unterbrochen", + "ui.message.queued": "In Warteschlange", "ui.message.attachment.alt": "Anhang", "ui.patch.action.deleted": "Gelöscht", diff --git a/packages/ui/src/i18n/en.ts b/packages/ui/src/i18n/en.ts index ba4ff62fb40..9c9ae6e27a5 100644 --- a/packages/ui/src/i18n/en.ts +++ b/packages/ui/src/i18n/en.ts @@ -41,6 +41,9 @@ export const dict: Record = { "ui.sessionTurn.retry.retrying": "retrying", "ui.sessionTurn.retry.inSeconds": "in {{seconds}}s", + "ui.sessionTurn.retry.attempt": "attempt #{{attempt}}", + "ui.sessionTurn.retry.attemptLine": "{{line}} - attempt #{{attempt}}", + "ui.sessionTurn.retry.geminiHot": "gemini is way too hot right now", "ui.sessionTurn.error.freeUsageExceeded": "Free usage exceeded", "ui.sessionTurn.error.addCredits": "Add credits", @@ -63,6 +66,7 @@ export const dict: Record = { "ui.messagePart.option.typeOwnAnswer": "Type your own answer", "ui.messagePart.review.title": "Review your answers", "ui.messagePart.questions.dismissed": "Questions dismissed", + "ui.messagePart.compaction": "History compacted", "ui.messagePart.context.read.one": "{{count}} read", "ui.messagePart.context.read.other": "{{count}} reads", "ui.messagePart.context.search.one": "{{count}} search", @@ -123,6 +127,7 @@ export const dict: Record = { "ui.message.copyResponse": "Copy response", "ui.message.copied": "Copied", "ui.message.interrupted": "Interrupted", + "ui.message.queued": "Queued", "ui.message.attachment.alt": "attachment", "ui.patch.action.deleted": "Deleted", diff --git a/packages/ui/src/i18n/es.ts b/packages/ui/src/i18n/es.ts index 0b4566c9bc5..6fb6eea5117 100644 --- a/packages/ui/src/i18n/es.ts +++ b/packages/ui/src/i18n/es.ts @@ -40,6 +40,9 @@ export const dict = { "ui.sessionTurn.retry.retrying": "reintentando", "ui.sessionTurn.retry.inSeconds": "en {{seconds}}s", + "ui.sessionTurn.retry.attempt": "intento #{{attempt}}", + "ui.sessionTurn.retry.attemptLine": "{{line}} - intento #{{attempt}}", + "ui.sessionTurn.retry.geminiHot": "gemini está demasiado saturado", "ui.sessionTurn.error.freeUsageExceeded": "Límite de uso gratuito excedido", "ui.sessionTurn.error.addCredits": "Añadir créditos", @@ -57,6 +60,7 @@ export const dict = { "ui.sessionTurn.status.consideringNextSteps": "Considerando siguientes pasos", "ui.messagePart.questions.dismissed": "Preguntas descartadas", + "ui.messagePart.compaction": "Historial compactado", "ui.messagePart.context.read.one": "{{count}} lectura", "ui.messagePart.context.read.other": "{{count}} lecturas", "ui.messagePart.context.search.one": "{{count}} búsqueda", @@ -122,6 +126,7 @@ export const dict = { "ui.message.copyResponse": "Copiar respuesta", "ui.message.copied": "¡Copiado!", "ui.message.interrupted": "Interrumpido", + "ui.message.queued": "En cola", "ui.message.attachment.alt": "adjunto", "ui.patch.action.deleted": "Eliminado", diff --git a/packages/ui/src/i18n/fr.ts b/packages/ui/src/i18n/fr.ts index 4092fac9a3f..3a77a3f5c63 100644 --- a/packages/ui/src/i18n/fr.ts +++ b/packages/ui/src/i18n/fr.ts @@ -40,6 +40,9 @@ export const dict = { "ui.sessionTurn.retry.retrying": "nouvelle tentative", "ui.sessionTurn.retry.inSeconds": "dans {{seconds}}s", + "ui.sessionTurn.retry.attempt": "tentative n°{{attempt}}", + "ui.sessionTurn.retry.attemptLine": "{{line}} - tentative n°{{attempt}}", + "ui.sessionTurn.retry.geminiHot": "gemini est en surchauffe", "ui.sessionTurn.error.freeUsageExceeded": "Limite d'utilisation gratuite dépassée", "ui.sessionTurn.error.addCredits": "Ajouter des crédits", @@ -57,6 +60,7 @@ export const dict = { "ui.sessionTurn.status.consideringNextSteps": "Examen des prochaines étapes", "ui.messagePart.questions.dismissed": "Questions ignorées", + "ui.messagePart.compaction": "Historique compacté", "ui.messagePart.context.read.one": "{{count}} lecture", "ui.messagePart.context.read.other": "{{count}} lectures", "ui.messagePart.context.search.one": "{{count}} recherche", @@ -122,6 +126,7 @@ export const dict = { "ui.message.copyResponse": "Copier la réponse", "ui.message.copied": "Copié !", "ui.message.interrupted": "Interrompu", + "ui.message.queued": "En file", "ui.message.attachment.alt": "pièce jointe", "ui.patch.action.deleted": "Supprimé", diff --git a/packages/ui/src/i18n/ja.ts b/packages/ui/src/i18n/ja.ts index ea855d1b726..9dfb03f76b1 100644 --- a/packages/ui/src/i18n/ja.ts +++ b/packages/ui/src/i18n/ja.ts @@ -39,6 +39,9 @@ export const dict = { "ui.sessionTurn.retry.retrying": "再試行中", "ui.sessionTurn.retry.inSeconds": "{{seconds}}秒後", + "ui.sessionTurn.retry.attempt": "{{attempt}}回目", + "ui.sessionTurn.retry.attemptLine": "{{line}} - {{attempt}}回目", + "ui.sessionTurn.retry.geminiHot": "gemini が混雑しています", "ui.sessionTurn.error.freeUsageExceeded": "無料使用制限に達しました", "ui.sessionTurn.error.addCredits": "クレジットを追加", @@ -56,6 +59,7 @@ export const dict = { "ui.sessionTurn.status.consideringNextSteps": "次のステップを検討中", "ui.messagePart.questions.dismissed": "質問をスキップしました", + "ui.messagePart.compaction": "履歴を圧縮しました", "ui.messagePart.context.read.one": "{{count}} 件の読み取り", "ui.messagePart.context.read.other": "{{count}} 件の読み取り", "ui.messagePart.context.search.one": "{{count}} 件の検索", @@ -121,6 +125,7 @@ export const dict = { "ui.message.copyResponse": "応答をコピー", "ui.message.copied": "コピーしました!", "ui.message.interrupted": "中断", + "ui.message.queued": "待機中", "ui.message.attachment.alt": "添付ファイル", "ui.patch.action.deleted": "削除済み", diff --git a/packages/ui/src/i18n/ko.ts b/packages/ui/src/i18n/ko.ts index 80de94e95d6..84d261ac89f 100644 --- a/packages/ui/src/i18n/ko.ts +++ b/packages/ui/src/i18n/ko.ts @@ -40,6 +40,9 @@ export const dict = { "ui.sessionTurn.retry.retrying": "재시도 중", "ui.sessionTurn.retry.inSeconds": "{{seconds}}초 후", + "ui.sessionTurn.retry.attempt": "{{attempt}}번째", + "ui.sessionTurn.retry.attemptLine": "{{line}} - {{attempt}}번째", + "ui.sessionTurn.retry.geminiHot": "gemini가 현재 과부하 상태입니다", "ui.sessionTurn.error.freeUsageExceeded": "무료 사용량 초과", "ui.sessionTurn.error.addCredits": "크레딧 추가", @@ -57,6 +60,7 @@ export const dict = { "ui.sessionTurn.status.consideringNextSteps": "다음 단계 고려 중", "ui.messagePart.questions.dismissed": "질문 무시됨", + "ui.messagePart.compaction": "기록이 압축됨", "ui.messagePart.context.read.one": "{{count}}개 읽음", "ui.messagePart.context.read.other": "{{count}}개 읽음", "ui.messagePart.context.search.one": "{{count}}개 검색", @@ -122,6 +126,7 @@ export const dict = { "ui.message.copyResponse": "응답 복사", "ui.message.copied": "복사됨!", "ui.message.interrupted": "중단됨", + "ui.message.queued": "대기 중", "ui.message.attachment.alt": "첨부 파일", "ui.patch.action.deleted": "삭제됨", diff --git a/packages/ui/src/i18n/no.ts b/packages/ui/src/i18n/no.ts index 77f6df55458..dd1822beee4 100644 --- a/packages/ui/src/i18n/no.ts +++ b/packages/ui/src/i18n/no.ts @@ -43,6 +43,9 @@ export const dict: Record = { "ui.sessionTurn.retry.retrying": "Prøver igjen", "ui.sessionTurn.retry.inSeconds": "om {{seconds}}s", + "ui.sessionTurn.retry.attempt": "forsøk #{{attempt}}", + "ui.sessionTurn.retry.attemptLine": "{{line}} - forsøk #{{attempt}}", + "ui.sessionTurn.retry.geminiHot": "gemini er veldig overbelastet nå", "ui.sessionTurn.error.freeUsageExceeded": "Gratis bruk overskredet", "ui.sessionTurn.error.addCredits": "Legg til kreditt", @@ -60,6 +63,7 @@ export const dict: Record = { "ui.sessionTurn.status.consideringNextSteps": "Vurderer neste trinn", "ui.messagePart.questions.dismissed": "Spørsmål avvist", + "ui.messagePart.compaction": "Historikk komprimert", "ui.messagePart.context.read.one": "{{count}} lest", "ui.messagePart.context.read.other": "{{count}} lest", "ui.messagePart.context.search.one": "{{count}} søk", @@ -125,6 +129,7 @@ export const dict: Record = { "ui.message.copyResponse": "Kopier svar", "ui.message.copied": "Kopiert!", "ui.message.interrupted": "Avbrutt", + "ui.message.queued": "I kø", "ui.message.attachment.alt": "vedlegg", "ui.patch.action.deleted": "Slettet", diff --git a/packages/ui/src/i18n/pl.ts b/packages/ui/src/i18n/pl.ts index 877e6505fcd..fcfedb2ef98 100644 --- a/packages/ui/src/i18n/pl.ts +++ b/packages/ui/src/i18n/pl.ts @@ -39,6 +39,9 @@ export const dict = { "ui.sessionTurn.retry.retrying": "ponawianie", "ui.sessionTurn.retry.inSeconds": "za {{seconds}}s", + "ui.sessionTurn.retry.attempt": "próba #{{attempt}}", + "ui.sessionTurn.retry.attemptLine": "{{line}} - próba #{{attempt}}", + "ui.sessionTurn.retry.geminiHot": "gemini jest teraz mocno przeciążony", "ui.sessionTurn.error.freeUsageExceeded": "Przekroczono limit darmowego użytkowania", "ui.sessionTurn.error.addCredits": "Dodaj kredyty", @@ -56,6 +59,7 @@ export const dict = { "ui.sessionTurn.status.consideringNextSteps": "Rozważanie kolejnych kroków", "ui.messagePart.questions.dismissed": "Pytania odrzucone", + "ui.messagePart.compaction": "Historia skompaktowana", "ui.messagePart.context.read.one": "{{count}} odczyt", "ui.messagePart.context.read.other": "{{count}} odczyty", "ui.messagePart.context.search.one": "{{count}} wyszukiwanie", @@ -121,6 +125,7 @@ export const dict = { "ui.message.copyResponse": "Kopiuj odpowiedź", "ui.message.copied": "Skopiowano!", "ui.message.interrupted": "Przerwano", + "ui.message.queued": "W kolejce", "ui.message.attachment.alt": "załącznik", "ui.patch.action.deleted": "Usunięto", diff --git a/packages/ui/src/i18n/ru.ts b/packages/ui/src/i18n/ru.ts index 545be1b63a6..713ff47d1e6 100644 --- a/packages/ui/src/i18n/ru.ts +++ b/packages/ui/src/i18n/ru.ts @@ -39,6 +39,9 @@ export const dict = { "ui.sessionTurn.retry.retrying": "повтор", "ui.sessionTurn.retry.inSeconds": "через {{seconds}}с", + "ui.sessionTurn.retry.attempt": "попытка №{{attempt}}", + "ui.sessionTurn.retry.attemptLine": "{{line}} - попытка №{{attempt}}", + "ui.sessionTurn.retry.geminiHot": "gemini сейчас перегружен", "ui.sessionTurn.error.freeUsageExceeded": "Лимит бесплатного использования превышен", "ui.sessionTurn.error.addCredits": "Добавить кредиты", @@ -56,6 +59,7 @@ export const dict = { "ui.sessionTurn.status.consideringNextSteps": "Рассмотрение следующих шагов", "ui.messagePart.questions.dismissed": "Вопросы отклонены", + "ui.messagePart.compaction": "История сжата", "ui.messagePart.context.read.one": "{{count}} чтение", "ui.messagePart.context.read.other": "{{count}} чтений", "ui.messagePart.context.search.one": "{{count}} поиск", @@ -121,6 +125,7 @@ export const dict = { "ui.message.copyResponse": "Копировать ответ", "ui.message.copied": "Скопировано!", "ui.message.interrupted": "Прервано", + "ui.message.queued": "В очереди", "ui.message.attachment.alt": "вложение", "ui.patch.action.deleted": "Удалено", diff --git a/packages/ui/src/i18n/th.ts b/packages/ui/src/i18n/th.ts index 54b3db649a6..44761a279e1 100644 --- a/packages/ui/src/i18n/th.ts +++ b/packages/ui/src/i18n/th.ts @@ -41,6 +41,9 @@ export const dict = { "ui.sessionTurn.retry.retrying": "กำลังลองใหม่", "ui.sessionTurn.retry.inSeconds": "ใน {{seconds}}วิ", + "ui.sessionTurn.retry.attempt": "ครั้งที่ {{attempt}}", + "ui.sessionTurn.retry.attemptLine": "{{line}} - ครั้งที่ {{attempt}}", + "ui.sessionTurn.retry.geminiHot": "gemini กำลังใช้งานหนาแน่นมาก", "ui.sessionTurn.error.freeUsageExceeded": "เกินขีดจำกัดการใช้งานฟรี", "ui.sessionTurn.error.addCredits": "เพิ่มเครดิต", @@ -58,6 +61,7 @@ export const dict = { "ui.sessionTurn.status.consideringNextSteps": "พิจารณาขั้นตอนถัดไป", "ui.messagePart.questions.dismissed": "ละทิ้งคำถามแล้ว", + "ui.messagePart.compaction": "ประวัติถูกบีบอัด", "ui.messagePart.context.read.one": "อ่าน {{count}} รายการ", "ui.messagePart.context.read.other": "อ่าน {{count}} รายการ", "ui.messagePart.context.search.one": "ค้นหา {{count}} รายการ", @@ -123,6 +127,7 @@ export const dict = { "ui.message.copyResponse": "คัดลอกคำตอบ", "ui.message.copied": "คัดลอกแล้ว!", "ui.message.interrupted": "ถูกขัดจังหวะ", + "ui.message.queued": "อยู่ในคิว", "ui.message.attachment.alt": "ไฟล์แนบ", "ui.patch.action.deleted": "ลบ", diff --git a/packages/ui/src/i18n/tr.ts b/packages/ui/src/i18n/tr.ts index b68a9b257b4..5ec108d4aa4 100644 --- a/packages/ui/src/i18n/tr.ts +++ b/packages/ui/src/i18n/tr.ts @@ -36,6 +36,9 @@ export const dict = { "ui.sessionTurn.retry.retrying": "yeniden deneniyor", "ui.sessionTurn.retry.inSeconds": "{{seconds}}sn içinde", + "ui.sessionTurn.retry.attempt": "deneme #{{attempt}}", + "ui.sessionTurn.retry.attemptLine": "{{line}} - deneme #{{attempt}}", + "ui.sessionTurn.retry.geminiHot": "gemini şu anda aşırı yoğun", "ui.sessionTurn.error.freeUsageExceeded": "Ücretsiz kullanım aşıldı", "ui.sessionTurn.error.addCredits": "Kredi ekle", @@ -53,6 +56,7 @@ export const dict = { "ui.sessionTurn.status.consideringNextSteps": "Sonraki adımlar değerlendiriliyor", "ui.messagePart.questions.dismissed": "Sorular reddedildi", + "ui.messagePart.compaction": "Geçmiş sıkıştırıldı", "ui.messagePart.context.read.one": "{{count}} okuma", "ui.messagePart.context.read.other": "{{count}} okuma", "ui.messagePart.context.search.one": "{{count}} arama", @@ -118,6 +122,7 @@ export const dict = { "ui.message.copyResponse": "Yanıtı kopyala", "ui.message.copied": "Kopyalandı", "ui.message.interrupted": "Kesildi", + "ui.message.queued": "Sırada", "ui.message.attachment.alt": "ek", "ui.patch.action.deleted": "Silindi", diff --git a/packages/ui/src/i18n/zh.ts b/packages/ui/src/i18n/zh.ts index 9a00e2dc96c..39226605b90 100644 --- a/packages/ui/src/i18n/zh.ts +++ b/packages/ui/src/i18n/zh.ts @@ -44,6 +44,9 @@ export const dict = { "ui.sessionTurn.retry.retrying": "重试中", "ui.sessionTurn.retry.inSeconds": "{{seconds}} 秒后", + "ui.sessionTurn.retry.attempt": "第 {{attempt}} 次", + "ui.sessionTurn.retry.attemptLine": "{{line}} - 第 {{attempt}} 次", + "ui.sessionTurn.retry.geminiHot": "gemini 当前过载", "ui.sessionTurn.error.freeUsageExceeded": "免费使用额度已用完", "ui.sessionTurn.error.addCredits": "添加积分", @@ -61,6 +64,7 @@ export const dict = { "ui.sessionTurn.status.consideringNextSteps": "正在考虑下一步", "ui.messagePart.questions.dismissed": "问题已忽略", + "ui.messagePart.compaction": "历史已压缩", "ui.messagePart.context.read.one": "{{count}} 次读取", "ui.messagePart.context.read.other": "{{count}} 次读取", "ui.messagePart.context.search.one": "{{count}} 次搜索", @@ -126,6 +130,7 @@ export const dict = { "ui.message.copyResponse": "复制回复", "ui.message.copied": "已复制!", "ui.message.interrupted": "已中断", + "ui.message.queued": "排队中", "ui.message.attachment.alt": "附件", "ui.patch.action.deleted": "已删除", diff --git a/packages/ui/src/i18n/zht.ts b/packages/ui/src/i18n/zht.ts index 81a140d933e..068e222d65d 100644 --- a/packages/ui/src/i18n/zht.ts +++ b/packages/ui/src/i18n/zht.ts @@ -44,6 +44,9 @@ export const dict = { "ui.sessionTurn.retry.retrying": "重試中", "ui.sessionTurn.retry.inSeconds": "{{seconds}} 秒後", + "ui.sessionTurn.retry.attempt": "第 {{attempt}} 次", + "ui.sessionTurn.retry.attemptLine": "{{line}} - 第 {{attempt}} 次", + "ui.sessionTurn.retry.geminiHot": "gemini 目前過載", "ui.sessionTurn.error.freeUsageExceeded": "免費使用額度已用完", "ui.sessionTurn.error.addCredits": "新增點數", @@ -61,6 +64,7 @@ export const dict = { "ui.sessionTurn.status.consideringNextSteps": "正在考慮下一步", "ui.messagePart.questions.dismissed": "問題已略過", + "ui.messagePart.compaction": "歷史已壓縮", "ui.messagePart.context.read.one": "{{count}} 次讀取", "ui.messagePart.context.read.other": "{{count}} 次讀取", "ui.messagePart.context.search.one": "{{count}} 次搜尋", @@ -126,6 +130,7 @@ export const dict = { "ui.message.copyResponse": "複製回覆", "ui.message.copied": "已複製!", "ui.message.interrupted": "已中斷", + "ui.message.queued": "排隊中", "ui.message.attachment.alt": "附件", "ui.patch.action.deleted": "已刪除", diff --git a/packages/ui/src/styles/base.css b/packages/ui/src/styles/base.css index 33a2457058b..b5604ad6191 100644 --- a/packages/ui/src/styles/base.css +++ b/packages/ui/src/styles/base.css @@ -86,6 +86,17 @@ a { app-region: drag; } +*[data-tauri-drag-region] button, +*[data-tauri-drag-region] a, +*[data-tauri-drag-region] input, +*[data-tauri-drag-region] textarea, +*[data-tauri-drag-region] select, +*[data-tauri-drag-region] [role="button"], +*[data-tauri-drag-region] [role="menuitem"], +*[data-tauri-drag-region] [contenteditable] { + app-region: no-drag; +} + /* Add the correct font weight in Edge and Safari. */ diff --git a/packages/ui/src/styles/index.css b/packages/ui/src/styles/index.css index f822371f709..cec42f5a0ca 100644 --- a/packages/ui/src/styles/index.css +++ b/packages/ui/src/styles/index.css @@ -7,6 +7,7 @@ @import "katex/dist/katex.min.css" layer(base); @import "../components/accordion.css" layer(components); +@import "../components/animated-number.css" layer(components); @import "../components/app-icon.css" layer(components); @import "../components/avatar.css" layer(components); @import "../components/basic-tool.css" layer(components); @@ -45,10 +46,16 @@ @import "../components/scroll-view.css" layer(components); @import "../components/session-review.css" layer(components); @import "../components/session-turn.css" layer(components); +@import "../components/shell-submessage.css" layer(components); @import "../components/sticky-accordion-header.css" layer(components); @import "../components/tabs.css" layer(components); @import "../components/tag.css" layer(components); +@import "../components/text-reveal.css" layer(components); +@import "../components/text-strikethrough.css" layer(components); @import "../components/text-shimmer.css" layer(components); +@import "../components/tool-count-label.css" layer(components); +@import "../components/tool-count-summary.css" layer(components); +@import "../components/tool-status-title.css" layer(components); @import "../components/toast.css" layer(components); @import "../components/tooltip.css" layer(components); @import "../components/typewriter.css" layer(components); diff --git a/packages/util/package.json b/packages/util/package.json index 36a235639ee..0ca8d23609c 100644 --- a/packages/util/package.json +++ b/packages/util/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/util", - "version": "1.2.15", + "version": "1.2.16", "private": true, "type": "module", "license": "MIT", diff --git a/packages/web/package.json b/packages/web/package.json index daf2ad3480f..f8f745eec52 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -2,7 +2,7 @@ "name": "@opencode-ai/web", "type": "module", "license": "MIT", - "version": "1.2.15", + "version": "1.2.16", "scripts": { "dev": "astro dev", "dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev", diff --git a/packages/web/src/content/docs/ar/config.mdx b/packages/web/src/content/docs/ar/config.mdx index d415969972d..5a1c294bf21 100644 --- a/packages/web/src/content/docs/ar/config.mdx +++ b/packages/web/src/content/docs/ar/config.mdx @@ -496,6 +496,7 @@ opencode run "Hello world" - `auto` - ضغط الجلسة تلقائيًا عند امتلاء السياق (الافتراضي: `true`). - `prune` - إزالة مخرجات الأدوات القديمة لتوفير الرموز (tokens) (الافتراضي: `true`). +- `reserved` - مخزن مؤقت للرموز (tokens) من أجل الضغط. يترك نافذة كافية لتجنب الفيضان أثناء الضغط. --- diff --git a/packages/web/src/content/docs/ar/custom-tools.mdx b/packages/web/src/content/docs/ar/custom-tools.mdx index 9382627fab6..62eeed9cdc7 100644 --- a/packages/web/src/content/docs/ar/custom-tools.mdx +++ b/packages/web/src/content/docs/ar/custom-tools.mdx @@ -79,6 +79,32 @@ export const multiply = tool({ --- +#### تضارب الأسماء مع الأدوات المدمجة + +تُصنّف الأدوات المخصصة حسب اسم الأداة. إذا استخدمت أداة مخصصة نفس اسم أداة مدمجة، فإن الأداة المخصصة تأخذ الأولوية. + +على سبيل المثال، يستبدل هذا الملف أداة `bash` المدمجة: + +```ts title=".opencode/tools/bash.ts" +import { tool } from "@opencode-ai/plugin" + +export default tool({ + description: "Restricted bash wrapper", + args: { + command: tool.schema.string(), + }, + async execute(args) { + return `blocked: ${args.command}` + }, +}) +``` + +:::note +فضّل استخدام أسماء فريدة ما لم تكن تريد استبدال أداة مدمجة عمدا. إذا كنت تريد تعطيل أداة مدمجة ولكن لا تريد استبدالها، استخدم [الأذونات](/docs/permissions). +::: + +--- + ### الوسائط يمكنك استخدام `tool.schema`، وهو في الأساس [Zod](https://zod.dev)، لتعريف أنواع الوسائط. diff --git a/packages/web/src/content/docs/ar/ecosystem.mdx b/packages/web/src/content/docs/ar/ecosystem.mdx index df3a4c7ddb5..bcd76eb1013 100644 --- a/packages/web/src/content/docs/ar/ecosystem.mdx +++ b/packages/web/src/content/docs/ar/ecosystem.mdx @@ -6,71 +6,72 @@ description: مشاريع وتكاملات مبنية باستخدام OpenCode. مجموعة من مشاريع المجتمع المبنية على OpenCode. :::note -هل تريد إضافة مشروع مرتبط بـ OpenCode إلى هذه القائمة؟ قدّم PR. +هل تريد إضافة مشروعك المتعلق بـ OpenCode إلى هذه القائمة؟ أرسل طلب سحب (PR). ::: -يمكنك أيضا الاطلاع على [awesome-opencode](https://github.com/awesome-opencode/awesome-opencode) و [opencode.cafe](https://opencode.cafe)؛ وهو مجتمع يجمع روابط النظام البيئي والمجتمع. +يمكنك أيضًا الاطلاع على [awesome-opencode](https://github.com/awesome-opencode/awesome-opencode) و [opencode.cafe](https://opencode.cafe)، وهو مجتمع يجمع النظام البيئي والمجتمع. --- ## الإضافات -| الاسم | الوصف | -| --------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | -| [opencode-daytona](https://github.com/jamesmurdza/daytona/blob/main/guides/typescript/opencode/README.md) | تشغيل جلسات OpenCode تلقائيا داخل بيئات Daytona معزولة مع مزامنة git ومعاينات حية | -| [opencode-helicone-session](https://github.com/H2Shami/opencode-helicone-session) | حقن ترويسات جلسة Helicone تلقائيا لتجميع الطلبات | -| [opencode-type-inject](https://github.com/nick-vi/opencode-type-inject) | حقن أنواع TypeScript/Svelte تلقائيا في قراءات الملفات باستخدام أدوات البحث | -| [opencode-openai-codex-auth](https://github.com/numman-ali/opencode-openai-codex-auth) | استخدام اشتراك ChatGPT Plus/Pro بدلا من أرصدة واجهة API | -| [opencode-gemini-auth](https://github.com/jenslys/opencode-gemini-auth) | استخدام خطة Gemini الحالية بدلا من فوترة API | -| [opencode-antigravity-auth](https://github.com/NoeFabris/opencode-antigravity-auth) | استخدام نماذج Antigravity المجانية بدلا من فوترة API | -| [opencode-devcontainers](https://github.com/athal7/opencode-devcontainers) | عزل devcontainer متعدد الفروع مع استنساخات shallow ومنافذ تُعيَّن تلقائيا | -| [opencode-google-antigravity-auth](https://github.com/shekohex/opencode-google-antigravity-auth) | ملحق Google Antigravity OAuth مع دعم Google Search ومعالجة API أكثر متانة | -| [opencode-dynamic-context-pruning](https://github.com/Tarquinen/opencode-dynamic-context-pruning) | تحسين استخدام الرموز (tokens) عبر تقليم مخرجات الأدوات القديمة | -| [opencode-websearch-cited](https://github.com/ghoulr/opencode-websearch-cited.git) | إضافة دعم websearch أصلي للمزوّدين المدعومين بأسلوب مستند إلى Google | -| [opencode-pty](https://github.com/shekohex/opencode-pty.git) | تمكين وكلاء الذكاء الاصطناعي من تشغيل عمليات بالخلفية داخل PTY وإرسال إدخال تفاعلي إليها. | -| [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | إرشادات لأوامر shell غير التفاعلية - تمنع التعليق الناتج عن عمليات تعتمد على TTY | -| [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | تتبع استخدام OpenCode عبر Wakatime | -| [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | تنظيف جداول Markdown التي تنتجها نماذج LLM | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | تحرير الشيفرة أسرع بـ 10x باستخدام Morph Fast Apply API وعلامات تعديل كسولة | -| [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | وكلاء خلفية وأدوات LSP/AST/MCP جاهزة ووكلاء منتقون وتوافق مع Claude Code | -| [opencode-notificator](https://github.com/panta82/opencode-notificator) | إشعارات سطح المكتب وتنبيهات صوتية لجلسات OpenCode | -| [opencode-notifier](https://github.com/mohak34/opencode-notifier) | إشعارات سطح المكتب وتنبيهات صوتية لأحداث الأذونات والإكمال والأخطاء | -| [opencode-zellij-namer](https://github.com/24601/opencode-zellij-namer) | تسمية جلسات Zellij تلقائيا بالاعتماد على سياق OpenCode وبمساعدة الذكاء الاصطناعي | -| [opencode-skillful](https://github.com/zenobi-us/opencode-skillful) | تمكين وكلاء OpenCode من تحميل الموجهات عند الطلب عبر اكتشاف المهارات وحقنها | -| [opencode-supermemory](https://github.com/supermemoryai/opencode-supermemory) | ذاكرة مستمرة عبر الجلسات باستخدام Supermemory | -| [@plannotator/opencode](https://github.com/backnotprop/plannotator/tree/main/apps/opencode-plugin) | مراجعة تفاعلية للخطة مع تعليقات توضيحية مرئية ومشاركة خاصة/دون اتصال | -| [@openspoon/subtask2](https://github.com/spoons-and-mirrors/subtask2) | توسيع /commands في opencode إلى نظام تنسيق قوي مع تحكم دقيق في التدفق | -| [opencode-scheduler](https://github.com/different-ai/opencode-scheduler) | جدولة مهام متكررة باستخدام launchd (Mac) أو systemd (Linux) بصياغة cron | -| [micode](https://github.com/vtemian/micode) | سير عمل منظم: عصف ذهني → خطة → تنفيذ مع استمرارية الجلسة | -| [octto](https://github.com/vtemian/octto) | واجهة متصفح تفاعلية للعصف الذهني بالذكاء الاصطناعي مع نماذج متعددة الأسئلة | -| [opencode-background-agents](https://github.com/kdcokenny/opencode-background-agents) | وكلاء خلفية على نمط Claude Code مع تفويض غير متزامن واستمرارية للسياق | -| [opencode-notify](https://github.com/kdcokenny/opencode-notify) | إشعارات نظام تشغيل أصلية لـ OpenCode - اعرف متى تكتمل المهام | -| [opencode-workspace](https://github.com/kdcokenny/opencode-workspace) | حزمة تنسيق متعددة الوكلاء - 16 مكوّنا، تثبيت واحد | -| [opencode-worktree](https://github.com/kdcokenny/opencode-worktree) | git worktrees بلا تعقيد لـ OpenCode | +| الاسم | الوصف | +| -------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | +| [opencode-daytona](https://github.com/daytonaio/daytona/tree/main/libs/opencode-plugin) | تشغيل جلسات OpenCode تلقائيًا في صناديق حماية Daytona معزولة مع مزامنة git ومعاينات حية | +| [opencode-helicone-session](https://github.com/H2Shami/opencode-helicone-session) | حقن ترويسات جلسة Helicone تلقائيًا لتجميع الطلبات | +| [opencode-type-inject](https://github.com/nick-vi/opencode-type-inject) | حقن أنواع TypeScript/Svelte تلقائيًا في عمليات قراءة الملفات باستخدام أدوات البحث | +| [opencode-openai-codex-auth](https://github.com/numman-ali/opencode-openai-codex-auth) | استخدام اشتراك ChatGPT Plus/Pro الخاص بك بدلاً من أرصدة API | +| [opencode-gemini-auth](https://github.com/jenslys/opencode-gemini-auth) | استخدام خطة Gemini الحالية الخاصة بك بدلاً من فوترة API | +| [opencode-antigravity-auth](https://github.com/NoeFabris/opencode-antigravity-auth) | استخدام نماذج Antigravity المجانية بدلاً من فوترة API | +| [opencode-devcontainers](https://github.com/athal7/opencode-devcontainers) | عزل devcontainer متعدد الفروع مع نسخ shallow ومنافذ معينة تلقائيًا | +| [opencode-google-antigravity-auth](https://github.com/shekohex/opencode-google-antigravity-auth) | إضافة Google Antigravity OAuth، مع دعم بحث Google، ومعالجة API أكثر قوة | +| [opencode-dynamic-context-pruning](https://github.com/Tarquinen/opencode-dynamic-context-pruning) | تحسين استخدام التوكنات عن طريق تقليم مخرجات الأدوات القديمة | +| [opencode-vibeguard](https://github.com/inkdust2021/opencode-vibeguard) | تنقيح الأسرار/بيانات التعريف الشخصي (PII) إلى نصوص بديلة بأسلوب VibeGuard قبل استدعاءات LLM؛ واستعادتها محليًا | +| [opencode-websearch-cited](https://github.com/ghoulr/opencode-websearch-cited.git) | إضافة دعم بحث الويب الأصلي للموفرين المدعومين بأسلوب Google grounded | +| [opencode-pty](https://github.com/shekohex/opencode-pty.git) | تمكين وكلاء الذكاء الاصطناعي من تشغيل عمليات الخلفية في PTY، وإرسال مدخلات تفاعلية إليها. | +| [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | تعليمات لأوامر shell غير التفاعلية - تمنع التعليق الناتج عن العمليات المعتمدة على TTY | +| [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | تتبع استخدام OpenCode باستخدام Wakatime | +| [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | تنظيف جداول markdown التي تنتجها LLMs | +| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | تحرير الكود أسرع بـ 10 مرات باستخدام Morph Fast Apply API وعلامات التحرير الكسولة | +| [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | وكلاء الخلفية، وأدوات LSP/AST/MCP المعدة مسبقًا، ووكلاء مختارون، متوافق مع Claude Code | +| [opencode-notificator](https://github.com/panta82/opencode-notificator) | إشعارات سطح المكتب وتنبيهات صوتية لجلسات OpenCode | +| [opencode-notifier](https://github.com/mohak34/opencode-notifier) | إشعارات سطح المكتب وتنبيهات صوتية لأحداث الإذن والاكتمال والخطأ | +| [opencode-zellij-namer](https://github.com/24601/opencode-zellij-namer) | تسمية جلسات Zellij تلقائيًا بدعم الذكاء الاصطناعي بناءً على سياق OpenCode | +| [opencode-skillful](https://github.com/zenobi-us/opencode-skillful) | السماح لوكلاء OpenCode بتحميل المطالبات (prompts) بشكل كسول عند الطلب مع اكتشاف المهارات وحقنها | +| [opencode-supermemory](https://github.com/supermemoryai/opencode-supermemory) | ذاكرة مستمرة عبر الجلسات باستخدام Supermemory | +| [@plannotator/opencode](https://github.com/backnotprop/plannotator/tree/main/apps/opencode-plugin) | مراجعة تفاعلية للخطة مع تعليقات توضيحية مرئية ومشاركة خاصة/بدون اتصال | +| [@openspoon/subtask2](https://github.com/spoons-and-mirrors/subtask2) | توسيع opencode /commands إلى نظام تنسيق قوي مع تحكم دقيق في التدفق | +| [opencode-scheduler](https://github.com/different-ai/opencode-scheduler) | جدولة الوظائف المتكررة باستخدام launchd (Mac) أو systemd (Linux) بصيغة cron | +| [micode](https://github.com/vtemian/micode) | سير عمل منظم: عصف ذهني ← تخطيط ← تنفيذ مع استمرارية الجلسة | +| [octto](https://github.com/vtemian/octto) | واجهة مستخدم تفاعلية للمتصفح للعصف الذهني بالذكاء الاصطناعي مع نماذج متعددة الأسئلة | +| [opencode-background-agents](https://github.com/kdcokenny/opencode-background-agents) | وكلاء خلفية بأسلوب Claude Code مع تفويض غير متزامن واستمرارية السياق | +| [opencode-notify](https://github.com/kdcokenny/opencode-notify) | إشعارات نظام التشغيل الأصلية لـ OpenCode – اعرف متى تكتمل المهام | +| [opencode-workspace](https://github.com/kdcokenny/opencode-workspace) | حزمة تنسيق متعددة الوكلاء – 16 مكونًا، تثبيت واحد | +| [opencode-worktree](https://github.com/kdcokenny/opencode-worktree) | أشجار عمل git (worktrees) خالية من الاحتكاك لـ OpenCode | --- ## المشاريع -| الاسم | الوصف | -| ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------ | -| [kimaki](https://github.com/remorses/kimaki) | بوت Discord للتحكم بجلسات OpenCode، مبني على SDK | -| [opencode.nvim](https://github.com/NickvanDyke/opencode.nvim) | ملحق Neovim لموجهات تراعي المحرر، مبني على API | -| [portal](https://github.com/hosenur/portal) | واجهة ويب تركز على الجوال لـ OpenCode عبر Tailscale/VPN | -| [opencode plugin template](https://github.com/zenobi-us/opencode-plugin-template/) | قالب لبناء ملحقات OpenCode | -| [opencode.nvim](https://github.com/sudo-tee/opencode.nvim) | واجهة Neovim لـ opencode - وكيل برمجة بالذكاء الاصطناعي يعمل في terminal | -| [ai-sdk-provider-opencode-sdk](https://github.com/ben-vargas/ai-sdk-provider-opencode-sdk) | موفر Vercel AI SDK لاستخدام OpenCode عبر @opencode-ai/sdk | -| [OpenChamber](https://github.com/btriapitsyn/openchamber) | تطبيق ويب/سطح مكتب وامتداد VS Code لـ OpenCode | -| [OpenCode-Obsidian](https://github.com/mtymek/opencode-obsidian) | ملحق Obsidian يدمج OpenCode داخل واجهة Obsidian | -| [OpenWork](https://github.com/different-ai/openwork) | بديل مفتوح المصدر لـ Claude Cowork، مدعوم بـ OpenCode | -| [ocx](https://github.com/kdcokenny/ocx) | مدير امتدادات OpenCode مع ملفات تعريف محمولة ومعزولة. | -| [CodeNomad](https://github.com/NeuralNomadsAI/CodeNomad) | تطبيق عميل لسطح المكتب والويب والجوال وعن بُعد لـ OpenCode | +| الاسم | الوصف | +| ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------- | +| [kimaki](https://github.com/remorses/kimaki) | بوت Discord للتحكم في جلسات OpenCode، مبني على SDK | +| [opencode.nvim](https://github.com/NickvanDyke/opencode.nvim) | إضافة Neovim للمطالبات المدركة للمحرر، مبنية على API | +| [portal](https://github.com/hosenur/portal) | واجهة ويب مخصصة للجوال لـ OpenCode عبر Tailscale/VPN | +| [opencode plugin template](https://github.com/zenobi-us/opencode-plugin-template/) | قالب لبناء إضافات OpenCode | +| [opencode.nvim](https://github.com/sudo-tee/opencode.nvim) | واجهة Neovim لـ opencode - وكيل برمجة بالذكاء الاصطناعي يعتمد على الطرفية | +| [ai-sdk-provider-opencode-sdk](https://github.com/ben-vargas/ai-sdk-provider-opencode-sdk) | موفر Vercel AI SDK لاستخدام OpenCode عبر @opencode-ai/sdk | +| [OpenChamber](https://github.com/btriapitsyn/openchamber) | تطبيق ويب / سطح مكتب وامتداد VS Code لـ OpenCode | +| [OpenCode-Obsidian](https://github.com/mtymek/opencode-obsidian) | إضافة Obsidian تدمج OpenCode في واجهة مستخدم Obsidian | +| [OpenWork](https://github.com/different-ai/openwork) | بديل مفتوح المصدر لـ Claude Cowork، مدعوم بواسطة OpenCode | +| [ocx](https://github.com/kdcokenny/ocx) | مدير امتدادات OpenCode مع ملفات تعريف محمولة ومعزولة. | +| [CodeNomad](https://github.com/NeuralNomadsAI/CodeNomad) | تطبيق عميل لسطح المكتب والويب والجوال وعن بعد لـ OpenCode | --- ## الوكلاء -| الاسم | الوصف | -| ----------------------------------------------------------------- | --------------------------------------------- | -| [Agentic](https://github.com/Cluster444/agentic) | وكلاء وأوامر ذكاء اصطناعي معيارية لتطوير منظم | -| [opencode-agents](https://github.com/darrenhinde/opencode-agents) | إعدادات وموجهات ووكلاء وملحقات لسير عمل محسّن | +| الاسم | الوصف | +| ----------------------------------------------------------------- | ------------------------------------------------ | +| [Agentic](https://github.com/Cluster444/agentic) | وكلاء ذكاء اصطناعي وأوامر معيارية للتطوير المنظم | +| [opencode-agents](https://github.com/darrenhinde/opencode-agents) | تكوينات، ومطالبات، ووكلاء، وإضافات لسير عمل محسن | diff --git a/packages/web/src/content/docs/ar/go.mdx b/packages/web/src/content/docs/ar/go.mdx new file mode 100644 index 00000000000..76dbecbd33e --- /dev/null +++ b/packages/web/src/content/docs/ar/go.mdx @@ -0,0 +1,145 @@ +--- +title: Go +description: اشتراك منخفض التكلفة لنماذج البرمجة المفتوحة. +--- + +import config from "../../../../config.mjs" +export const console = config.console +export const email = `mailto:${config.email}` + +OpenCode Go هو اشتراك منخفض التكلفة بقيمة **10 دولارات شهرياً** يمنحك وصولاً موثوقاً إلى نماذج البرمجة المفتوحة الشائعة. + +:::note +OpenCode Go حالياً في مرحلة تجريبية (beta). +::: + +يعمل Go مثل أي مزود آخر في OpenCode. تشترك في OpenCode Go وتحصل على مفتاح API الخاص بك. إنه **اختياري تماماً** ولا تحتاج إلى استخدامه لاستخدام OpenCode. + +صُمم بشكل أساسي للمستخدمين الدوليين، مع استضافة النماذج في الولايات المتحدة والاتحاد الأوروبي وسنغافورة لضمان وصول عالمي مستقر. + +--- + +## الخلفية (Background) + +أصبحت النماذج المفتوحة جيدة حقاً. فهي تصل الآن إلى أداء قريب من النماذج المملوكة لمهام البرمجة. ولأن العديد من المزودين يمكنهم تقديمها بشكل تنافسي، فهي عادة ما تكون أرخص بكثير. + +ومع ذلك، فإن الحصول على وصول موثوق ومنخفض الكمون (low latency) إليها قد يكون صعباً. يختلف المزودون في الجودة والتوافر. + +:::tip +قمنا باختبار مجموعة مختارة من النماذج والمزودين الذين يعملون بشكل جيد مع OpenCode. +::: + +لإصلاح ذلك، قمنا ببعض الأشياء: + +1. اختبرنا مجموعة مختارة من النماذج المفتوحة وتحدثنا مع فرقهم حول أفضل طريقة لتشغيلها. +2. عملنا بعد ذلك مع عدد قليل من المزودين للتأكد من تقديمها بشكل صحيح. +3. أخيراً، قمنا بقياس أداء (benchmark) مزيج النموذج/المزود وتوصلنا إلى قائمة نشعر بالراحة في التوصية بها. + +يمنحك OpenCode Go الوصول إلى هذه النماذج مقابل **10 دولارات شهرياً**. + +--- + +## كيف يعمل (How it works) + +يعمل OpenCode Go مثل أي مزود آخر في OpenCode. + +1. قم بتسجيل الدخول إلى **OpenCode Zen**، واشترك في Go، وانسخ مفتاح API الخاص بك. +2. قم بتشغيل الأمر `/connect` في واجهة TUI، وحدد `OpenCode Go`، والصق مفتاح API الخاص بك. +3. قم بتشغيل `/models` في واجهة TUI لرؤية قائمة النماذج المتاحة من خلال Go. + +:::note +يمكن لعضو واحد فقط لكل مساحة عمل (workspace) الاشتراك في OpenCode Go. +::: + +تتضمن القائمة الحالية للنماذج: + +- **GLM-5** +- **Kimi K2.5** +- **MiniMax M2.5** + +قد تتغير قائمة النماذج ونحن نختبر ونضيف نماذج جديدة. + +--- + +## حدود الاستخدام (Usage limits) + +يتضمن OpenCode Go الحدود التالية: + +- **حد 5 ساعات** — 12 دولاراً من الاستخدام +- **حد أسبوعي** — 30 دولاراً من الاستخدام +- **حد شهري** — 60 دولاراً من الاستخدام + +يتم تعريف الحدود بقيمة الدولار. هذا يعني أن عدد طلباتك الفعلي يعتمد على النموذج الذي تستخدمه. تسمح النماذج الأرخص مثل MiniMax M2.5 بمزيد من الطلبات، بينما تسمح النماذج الأعلى تكلفة مثل GLM-5 بعدد أقل. + +يوفر الجدول أدناه عدداً تقديرياً للطلبات بناءً على أنماط استخدام Go النموذجية: + +| | GLM-5 | Kimi K2.5 | MiniMax M2.5 | +| ---------------- | ----- | --------- | ------------ | +| طلبات كل 5 ساعات | 1,150 | 1,850 | 30,000 | +| طلبات أسبوعياً | 2,880 | 4,630 | 75,000 | +| طلبات شهرياً | 5,750 | 9,250 | 150,000 | + +تعتمد التقديرات على أنماط الطلبات المتوسطة الملحوظة: + +- GLM-5 — 700 input, 52,000 cached, 150 output tokens per request +- Kimi K2.5 — 870 input, 55,000 cached, 200 output tokens per request +- MiniMax M2.5 — 300 input, 55,000 cached, 125 output tokens per request + +يمكنك تتبع استخدامك الحالي في **console**. + +:::tip +إذا وصلت إلى حد الاستخدام، يمكنك الاستمرار في استخدام النماذج المجانية. +::: + +قد تتغير حدود الاستخدام ونحن نتعلم من الاستخدام المبكر والملاحظات. + +--- + +### التسعير (Pricing) + +OpenCode Go هي خطة اشتراك بقيمة **10 دولارات شهرياً**. أدناه الأسعار **لكل 1 مليون رمز (token)**. + +| Model | Input | Output | Cached Read | +| ------------ | ----- | ------ | ----------- | +| GLM-5 | $1.00 | $3.20 | $0.20 | +| Kimi K2.5 | $0.60 | $3.00 | $0.10 | +| MiniMax M2.5 | $0.30 | $1.20 | $0.03 | + +--- + +### الاستخدام خارج الحدود (Usage beyond limits) + +إذا كان لديك أيضاً أرصدة في رصيد Zen الخاص بك، يمكنك تمكين خيار **Use balance** في الـ console. عند التمكين، سيعود Go لاستخدام رصيد Zen الخاص بك بعد وصولك إلى حدود الاستخدام بدلاً من حظر الطلبات. + +--- + +## نقاط النهاية (Endpoints) + +يمكنك أيضاً الوصول إلى نماذج Go من خلال نقاط نهاية API التالية. + +| Model | Model ID | Endpoint | AI SDK Package | +| ------------ | ------------ | ------------------------------------------------ | --------------------------- | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | + +يستخدم [model id](/docs/config/#models) في تكوين OpenCode الخاص بك التنسيق `opencode-go/`. على سبيل المثال، بالنسبة لـ Kimi K2.5، ستستخدم `opencode-go/kimi-k2.5` في التكوين الخاص بك. + +--- + +## الخصوصية (Privacy) + +تم تصميم الخطة بشكل أساسي للمستخدمين الدوليين، مع استضافة النماذج في الولايات المتحدة والاتحاد الأوروبي وسنغافورة لضمان وصول عالمي مستقر. + +تواصل معنا إذا كان لديك أي أسئلة. + +--- + +## الأهداف (Goals) + +أنشأنا OpenCode Go لـ: + +1. جعل برمجة الذكاء الاصطناعي **في المتناول** لمزيد من الناس باشتراك منخفض التكلفة. +2. توفير وصول **موثوق** لأفضل نماذج البرمجة المفتوحة. +3. انتقاء نماذج **مختبرة وتم قياس أدائها** لاستخدام وكيل البرمجة. +4. عدم وجود **قيود (lock-in)** من خلال السماح لك باستخدام أي مزود آخر مع OpenCode أيضاً. diff --git a/packages/web/src/content/docs/ar/lsp.mdx b/packages/web/src/content/docs/ar/lsp.mdx index 541b48ecc9b..fd1b455340c 100644 --- a/packages/web/src/content/docs/ar/lsp.mdx +++ b/packages/web/src/content/docs/ar/lsp.mdx @@ -182,7 +182,7 @@ description: يتكامل OpenCode مع خوادم LSP لديك. يوفر PHP Intelephense ميزات مدفوعة عبر مفتاح ترخيص. يمكنك تزويده بمفتاح الترخيص عبر وضع (فقط) المفتاح داخل ملف نصي في: -- على macOS/Linux: `$HOME/intelephense/licence.txt` -- على Windows: `%USERPROFILE%/intelephense/licence.txt` +- على macOS/Linux: `$HOME/intelephense/license.txt` +- على Windows: `%USERPROFILE%/intelephense/license.txt` يجب أن يحتوي الملف على مفتاح الترخيص فقط دون أي محتوى إضافي. diff --git a/packages/web/src/content/docs/ar/providers.mdx b/packages/web/src/content/docs/ar/providers.mdx index 1d448986f31..f5dd70125f9 100644 --- a/packages/web/src/content/docs/ar/providers.mdx +++ b/packages/web/src/content/docs/ar/providers.mdx @@ -83,6 +83,37 @@ OpenCode Zen هي قائمة نماذج يوفّرها فريق OpenCode وقد --- +## OpenCode Go + +OpenCode Go هي خطة اشتراك منخفضة التكلفة توفّر وصولا موثوقا إلى نماذج البرمجة المفتوحة الشهيرة المقدّمة من فريق OpenCode، والتي تم اختبارها والتحقق من أنها تعمل بشكل جيد مع OpenCode. + +1. شغّل الأمر `/connect` في TUI، واختر `OpenCode Go`، ثم انتقل إلى [opencode.ai/auth](https://opencode.ai/zen). + + ```txt + /connect + ``` + +2. سجّل الدخول، وأضف تفاصيل الفوترة، ثم انسخ مفتاح API الخاص بك. + +3. الصق مفتاح API. + + ```txt + ┌ API key + │ + │ + └ enter + ``` + +4. شغّل `/models` في TUI لعرض قائمة النماذج التي نوصي بها. + + ```txt + /models + ``` + +يعمل مثل أي مزوّد آخر في OpenCode واستخدامه اختياري بالكامل. + +--- + ## الدليل لنلقِ نظرة على بعض المزوّدات بالتفصيل. إذا رغبت في إضافة مزوّد إلى القائمة، @@ -1476,6 +1507,39 @@ OpenCode Zen هي قائمة من النماذج التي تم اختبارها --- +### STACKIT + +توفّر خدمة STACKIT AI Model Serving بيئة استضافة سيادية مُدارة بالكامل لنماذج الذكاء الاصطناعي، مع التركيز على نماذج LLM مثل Llama وMistral وQwen، مع أقصى درجات سيادة البيانات على بنية تحتية أوروبية. + +1. توجّه إلى [STACKIT Portal](https://portal.stackit.cloud)، وانتقل إلى **AI Model Serving**، وأنشئ رمز مصادقة (auth token) لمشروعك. + + :::tip + تحتاج إلى حساب عميل STACKIT وحساب مستخدم ومشروع قبل إنشاء رموز المصادقة. + ::: + +2. شغّل الأمر `/connect` وابحث عن **STACKIT**. + + ```txt + /connect + ``` + +3. أدخل رمز مصادقة STACKIT AI Model Serving. + + ```txt + ┌ API key + │ + │ + └ enter + ``` + +4. شغّل الأمر `/models` للاختيار من النماذج المتاحة مثل _Qwen3-VL 235B_ أو _Llama 3.3 70B_. + + ```txt + /models + ``` + +--- + ### OVHcloud AI Endpoints 1. توجّه إلى [OVHcloud panel](https://ovh.com/manager). انتقل إلى قسم `Public Cloud`، ثم `AI & Machine Learning` > `AI Endpoints`، وفي تبويب `API Keys` انقر **Create a new API key**. diff --git a/packages/web/src/content/docs/ar/sdk.mdx b/packages/web/src/content/docs/ar/sdk.mdx index 83cdcad764a..bae101acf0f 100644 --- a/packages/web/src/content/docs/ar/sdk.mdx +++ b/packages/web/src/content/docs/ar/sdk.mdx @@ -117,6 +117,78 @@ try { --- +## المخرجات المنظمة + +يمكنك طلب مخرجات JSON منظمة من النموذج عن طريق تحديد `format` مع مخطط JSON. سيستخدم النموذج أداة `StructuredOutput` لإرجاع JSON تم التحقق من صحته ومطابق للمخطط الخاص بك. + +### الاستخدام الأساسي + +```typescript +const result = await client.session.prompt({ + path: { id: sessionId }, + body: { + parts: [{ type: "text", text: "Research Anthropic and provide company info" }], + format: { + type: "json_schema", + schema: { + type: "object", + properties: { + company: { type: "string", description: "Company name" }, + founded: { type: "number", description: "Year founded" }, + products: { + type: "array", + items: { type: "string" }, + description: "Main products", + }, + }, + required: ["company", "founded"], + }, + }, + }, +}) + +// Access the structured output +console.log(result.data.info.structured_output) +// { company: "Anthropic", founded: 2021, products: ["Claude", "Claude API"] } +``` + +### أنواع صيغ الإخراج + +| النوع | الوصف | +| ------------- | -------------------------------------------------- | +| `text` | الافتراضي. استجابة نصية قياسية (بدون مخرجات منظمة) | +| `json_schema` | يرجع JSON تم التحقق من صحته ومطابق للمخطط المقدم | + +### صيغة مخطط JSON + +عند استخدام `type: 'json_schema'`، يجب تقديم: + +| الحقل | النوع | الوصف | +| ------------ | --------------- | ------------------------------------------------ | +| `type` | `'json_schema'` | مطلوب. يحدد وضع مخطط JSON | +| `schema` | `object` | مطلوب. كائن مخطط JSON الذي يحدد بنية الإخراج | +| `retryCount` | `number` | اختياري. عدد محاولات إعادة التحقق (الافتراضي: 2) | + +### معالجة الأخطاء + +إذا فشل النموذج في إنتاج مخرجات منظمة صالحة بعد جميع المحاولات، ستتضمن الاستجابة `StructuredOutputError`: + +```typescript +if (result.data.info.error?.name === "StructuredOutputError") { + console.error("Failed to produce structured output:", result.data.info.error.message) + console.error("Attempts:", result.data.info.error.retries) +} +``` + +### أفضل الممارسات + +1. **قدّم أوصافا واضحة** في خصائص المخطط لمساعدة النموذج على فهم البيانات التي يجب استخراجها +2. **استخدم `required`** لتحديد الحقول التي يجب أن تكون موجودة +3. **حافظ على تركيز المخططات** - المخططات المتداخلة المعقدة قد تكون أصعب على النموذج لملئها بشكل صحيح +4. **عيّن `retryCount` مناسبا** - قم بزيادته للمخططات المعقدة، وتقليله للمخططات البسيطة + +--- + ## APIs توفر SDK جميع واجهات الخادم عبر عميل آمن للأنواع. @@ -226,27 +298,27 @@ const { providers, default: defaults } = await client.config.providers() ### الجلسات (`session`) -| الطريقة | الوصف | ملاحظات | -| ---------------------------------------------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | -| `session.list()` | سرد الجلسات | يعيد Session[] | -| `session.get({ path })` | جلب جلسة | يعيد Session | -| `session.children({ path })` | سرد الجلسات الفرعية | يعيد Session[] | -| `session.create({ body })` | إنشاء جلسة | يعيد Session | -| `session.delete({ path })` | حذف جلسة | يعيد `boolean` | -| `session.update({ path, body })` | تحديث خصائص الجلسة | يعيد Session | -| `session.init({ path, body })` | تحليل التطبيق وإنشاء `AGENTS.md` | يعيد `boolean` | -| `session.abort({ path })` | إيقاف جلسة قيد التشغيل | يعيد `boolean` | -| `session.share({ path })` | مشاركة جلسة | يعيد Session | -| `session.unshare({ path })` | إلغاء مشاركة جلسة | يعيد Session | -| `session.summarize({ path, body })` | تلخيص جلسة | يعيد `boolean` | -| `session.messages({ path })` | سرد الرسائل في جلسة | يعيد `{ info: `Message`, parts: `Part[]`}[]` | -| `session.message({ path })` | جلب تفاصيل الرسالة | يعيد `{ info: `Message`, parts: `Part[]`}` | -| `session.prompt({ path, body })` | إرسال رسالة مطالبة | `body.noReply: true` يعيد UserMessage (للسياق فقط). الافتراضي يعيد AssistantMessage مع استجابة AI | -| `session.command({ path, body })` | إرسال أمر إلى الجلسة | يعيد `{ info: `AssistantMessage`, parts: `Part[]`}` | -| `session.shell({ path, body })` | تشغيل أمر shell | يعيد AssistantMessage | -| `session.revert({ path, body })` | التراجع عن رسالة | يعيد Session | -| `session.unrevert({ path })` | استعادة الرسائل المتراجع عنها | يعيد Session | -| `postSessionByIdPermissionsByPermissionId({ path, body })` | الاستجابة لطلب إذن | يعيد `boolean` | +| الطريقة | الوصف | ملاحظات | +| ---------------------------------------------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `session.list()` | سرد الجلسات | يعيد Session[] | +| `session.get({ path })` | جلب جلسة | يعيد Session | +| `session.children({ path })` | سرد الجلسات الفرعية | يعيد Session[] | +| `session.create({ body })` | إنشاء جلسة | يعيد Session | +| `session.delete({ path })` | حذف جلسة | يعيد `boolean` | +| `session.update({ path, body })` | تحديث خصائص الجلسة | يعيد Session | +| `session.init({ path, body })` | تحليل التطبيق وإنشاء `AGENTS.md` | يعيد `boolean` | +| `session.abort({ path })` | إيقاف جلسة قيد التشغيل | يعيد `boolean` | +| `session.share({ path })` | مشاركة جلسة | يعيد Session | +| `session.unshare({ path })` | إلغاء مشاركة جلسة | يعيد Session | +| `session.summarize({ path, body })` | تلخيص جلسة | يعيد `boolean` | +| `session.messages({ path })` | سرد الرسائل في جلسة | يعيد `{ info: `Message`, parts: `Part[]`}[]` | +| `session.message({ path })` | جلب تفاصيل الرسالة | يعيد `{ info: `Message`, parts: `Part[]`}` | +| `session.prompt({ path, body })` | إرسال رسالة مطالبة | `body.noReply: true` يعيد UserMessage (للسياق فقط). الافتراضي يعيد AssistantMessage مع استجابة AI. يدعم `body.outputFormat` من أجل [المخرجات المنظمة](#المخرجات-المنظمة) | +| `session.command({ path, body })` | إرسال أمر إلى الجلسة | يعيد `{ info: `AssistantMessage`, parts: `Part[]`}` | +| `session.shell({ path, body })` | تشغيل أمر shell | يعيد AssistantMessage | +| `session.revert({ path, body })` | التراجع عن رسالة | يعيد Session | +| `session.unrevert({ path })` | استعادة الرسائل المتراجع عنها | يعيد Session | +| `postSessionByIdPermissionsByPermissionId({ path, body })` | الاستجابة لطلب إذن | يعيد `boolean` | --- diff --git a/packages/web/src/content/docs/ar/tui.mdx b/packages/web/src/content/docs/ar/tui.mdx index 149f2909390..bef6fda3fa3 100644 --- a/packages/web/src/content/docs/ar/tui.mdx +++ b/packages/web/src/content/docs/ar/tui.mdx @@ -370,6 +370,9 @@ How is auth handled in @packages/functions/src/api/index.ts? - `scroll_acceleration` - فعّل تسارع التمرير على نمط macOS لتمرير سلس وطبيعي. عند تفعيله، تزداد سرعة التمرير مع إيماءات التمرير السريعة وتبقى دقيقة للحركات الأبطأ. **يتقدّم هذا الإعداد على `scroll_speed` ويستبدله عند تفعيله.** - `scroll_speed` - يتحكم في سرعة تمرير واجهة TUI عند استخدام أوامر التمرير (الحد الأدنى: `1`). القيمة الافتراضية هي `3`. **ملاحظة: يتم تجاهل هذا إذا تم ضبط `scroll_acceleration.enabled` على `true`.** +- `diff_style` - يتحكم في عرض الفروقات (diff). القيمة `"auto"` تتكيف مع عرض terminal، و`"stacked"` تعرض عمودًا واحدًا دائمًا. + +استخدم `OPENCODE_TUI_CONFIG` لتحميل مسار إعدادات TUI مخصص. --- diff --git a/packages/web/src/content/docs/ar/zen.mdx b/packages/web/src/content/docs/ar/zen.mdx index f25b321a9b2..2810dea7dd3 100644 --- a/packages/web/src/content/docs/ar/zen.mdx +++ b/packages/web/src/content/docs/ar/zen.mdx @@ -59,6 +59,7 @@ OpenCode Zen هو بوابة للذكاء الاصطناعي تتيح لك ال | النموذج | معرّف النموذج | نقطة النهاية | حزمة AI SDK | | ------------------ | ------------------ | -------------------------------------------------- | --------------------------- | +| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | @@ -68,22 +69,24 @@ OpenCode Zen هو بوابة للذكاء الاصطناعي تتيح لك ال | GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | | Gemini 3 Pro | gemini-3-pro | `https://opencode.ai/zen/v1/models/gemini-3-pro` | `@ai-sdk/google` | | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.1 | minimax-m2.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.1 Free | minimax-m2.1-free | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| GLM 5 | glm-5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 4.7 | glm-4.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM 4.7 Free | glm-4.7-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 4.6 | glm-4.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 Free | kimi-k2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2 Thinking | kimi-k2-thinking | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2 | kimi-k2 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Qwen3 Coder 480B | qwen3-coder | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -111,29 +114,34 @@ https://opencode.ai/zen/v1/models | النموذج | الإدخال | الإخراج | قراءة مخزنة | كتابة مخزنة | | --------------------------------- | ------- | ------- | ----------- | ----------- | | Big Pickle | Free | Free | Free | - | -| MiniMax M2.1 Free | Free | Free | Free | - | +| MiniMax M2.5 Free | Free | Free | Free | - | +| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | - | | MiniMax M2.1 | $0.30 | $1.20 | $0.10 | - | -| GLM 4.7 Free | Free | Free | Free | - | +| GLM 5 | $1.00 | $3.20 | $0.20 | - | | GLM 4.7 | $0.60 | $2.20 | $0.10 | - | | GLM 4.6 | $0.60 | $2.20 | $0.10 | - | -| Kimi K2.5 Free | Free | Free | Free | - | | Kimi K2.5 | $0.60 | $3.00 | $0.08 | - | | Kimi K2 Thinking | $0.40 | $2.50 | - | - | | Kimi K2 | $0.40 | $2.50 | - | - | | Qwen3 Coder 480B | $0.45 | $1.50 | - | - | +| Claude Opus 4.6 (≤ 200K tokens) | $5.00 | $25.00 | $0.50 | $6.25 | +| Claude Opus 4.6 (> 200K tokens) | $10.00 | $37.50 | $1.00 | $12.50 | +| Claude Opus 4.5 | $5.00 | $25.00 | $0.50 | $6.25 | +| Claude Opus 4.1 | $15.00 | $75.00 | $1.50 | $18.75 | +| Claude Sonnet 4.6 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 | +| Claude Sonnet 4.6 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 | | Claude Sonnet 4.5 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 | | Claude Sonnet 4.5 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 | | Claude Sonnet 4 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 | | Claude Sonnet 4 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 | | Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 | | Claude Haiku 3.5 | $0.80 | $4.00 | $0.08 | $1.00 | -| Claude Opus 4.6 (≤ 200K tokens) | $5.00 | $25.00 | $0.50 | $6.25 | -| Claude Opus 4.6 (> 200K tokens) | $10.00 | $37.50 | $1.00 | $12.50 | -| Claude Opus 4.5 | $5.00 | $25.00 | $0.50 | $6.25 | -| Claude Opus 4.1 | $15.00 | $75.00 | $1.50 | $18.75 | +| Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | +| Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | +| GPT 5.3 Codex | $1.75 | $14.00 | $0.175 | - | | GPT 5.2 | $1.75 | $14.00 | $0.175 | - | | GPT 5.2 Codex | $1.75 | $14.00 | $0.175 | - | | GPT 5.1 | $1.07 | $8.50 | $0.107 | - | @@ -152,9 +160,7 @@ https://opencode.ai/zen/v1/models النماذج المجانية: -- GLM 4.7 Free متاح على OpenCode لفترة محدودة. يستخدم الفريق هذه الفترة لجمع الملاحظات وتحسين النموذج. -- Kimi K2.5 Free متاح على OpenCode لفترة محدودة. يستخدم الفريق هذه الفترة لجمع الملاحظات وتحسين النموذج. -- MiniMax M2.1 Free متاح على OpenCode لفترة محدودة. يستخدم الفريق هذه الفترة لجمع الملاحظات وتحسين النموذج. +- MiniMax M2.5 Free متاح على OpenCode لفترة محدودة. يستخدم الفريق هذه الفترة لجمع الملاحظات وتحسين النموذج. - Big Pickle نموذج خفي ومتاح مجانا على OpenCode لفترة محدودة. يستخدم الفريق هذه الفترة لجمع الملاحظات وتحسين النموذج. تواصل معنا إذا كانت لديك أي أسئلة. @@ -183,9 +189,7 @@ https://opencode.ai/zen/v1/models تتم استضافة جميع نماذجنا في الولايات المتحدة. يلتزم مزوّدونا بسياسة عدم الاحتفاظ بالبيانات (zero-retention) ولا يستخدمون بياناتك لتدريب النماذج، مع الاستثناءات التالية: - Big Pickle: خلال فترة إتاحته المجانية، قد تُستخدم البيانات المجمعة لتحسين النموذج. -- GLM 4.7 Free: خلال فترة إتاحته المجانية، قد تُستخدم البيانات المجمعة لتحسين النموذج. -- Kimi K2.5 Free: خلال فترة إتاحته المجانية، قد تُستخدم البيانات المجمعة لتحسين النموذج. -- MiniMax M2.1 Free: خلال فترة إتاحته المجانية، قد تُستخدم البيانات المجمعة لتحسين النموذج. +- MiniMax M2.5 Free: خلال فترة إتاحته المجانية، قد تُستخدم البيانات المجمعة لتحسين النموذج. - OpenAI APIs: يتم الاحتفاظ بالطلبات لمدة 30 يوما وفقا لـ [سياسات بيانات OpenAI](https://platform.openai.com/docs/guides/your-data). - Anthropic APIs: يتم الاحتفاظ بالطلبات لمدة 30 يوما وفقا لـ [سياسات بيانات Anthropic](https://docs.anthropic.com/en/docs/claude-code/data-usage). diff --git a/packages/web/src/content/docs/bs/cli.mdx b/packages/web/src/content/docs/bs/cli.mdx index 049b43497f7..7fc594e6c39 100644 --- a/packages/web/src/content/docs/bs/cli.mdx +++ b/packages/web/src/content/docs/bs/cli.mdx @@ -555,6 +555,7 @@ OpenCode se može konfigurirati pomoću varijabli okruženja. | `OPENCODE_AUTO_SHARE` | boolean | Automatski dijeli sesije | | `OPENCODE_GIT_BASH_PATH` | string | Putanja do Git Bash izvršne datoteke na Windows-u | | `OPENCODE_CONFIG` | string | Putanja do konfiguracijskog fajla | +| `OPENCODE_TUI_CONFIG` | string | Putanja do TUI konfiguracijskog fajla | | `OPENCODE_CONFIG_DIR` | string | Putanja do konfiguracijskog direktorija | | `OPENCODE_CONFIG_CONTENT` | string | Inline json konfiguracijski sadržaj | | `OPENCODE_DISABLE_AUTOUPDATE` | boolean | Onemogući automatske provjere ažuriranja | diff --git a/packages/web/src/content/docs/bs/config.mdx b/packages/web/src/content/docs/bs/config.mdx index 3e20f1890d4..3183a2f92df 100644 --- a/packages/web/src/content/docs/bs/config.mdx +++ b/packages/web/src/content/docs/bs/config.mdx @@ -14,10 +14,11 @@ OpenCode podržava i **JSON** i **JSONC** (JSON sa komentarima) formate. ```jsonc title="opencode.jsonc" { "$schema": "https://opencode.ai/config.json", - // Theme configuration - "theme": "opencode", "model": "anthropic/claude-sonnet-4-5", "autoupdate": true, + "server": { + "port": 4096, + }, } ``` @@ -30,8 +31,10 @@ Možete postaviti svoju konfiguraciju na nekoliko različitih lokacija i one ima :::note Konfiguracijski fajlovi se **spajaju**, ne zamjenjuju. ::: + Konfiguracijski fajlovi se spajaju, ne zamjenjuju. Kombiniraju se postavke sa sljedećih konfiguracijskih lokacija. Kasnije konfiguracije poništavaju prethodne samo za konfliktne ključeve. Nekonfliktne postavke iz svih konfiguracija su sačuvane. -Na primjer, ako vaša globalna konfiguracija postavlja `theme: "opencode"` i `autoupdate: true`, a vaša projektna konfiguracija postavlja `model: "anthropic/claude-sonnet-4-5"`, konačna konfiguracija će uključivati ​​sve tri postavke. + +Na primjer, ako vaša globalna konfiguracija postavlja `autoupdate: true`, a vaša projektna konfiguracija postavlja `model: "anthropic/claude-sonnet-4-5"`, konačna konfiguracija će uključivati ​​obje postavke. --- @@ -40,23 +43,26 @@ Na primjer, ako vaša globalna konfiguracija postavlja `theme: "opencode"` i `au Izvori konfiguracije se učitavaju ovim redoslijedom (kasniji izvori poništavaju ranije): 1. **Udaljena konfiguracija** (od `.well-known/opencode`) - organizacijske postavke -2. **Globalna konfiguracija** (`~/.config/opencode/opencode.json`) - korisničke postavke +2. **Globalna konfiguracija** (`~/.config/opencode/opencode.json`) - korisničke preferencije 3. **Prilagođena konfiguracija** (`OPENCODE_CONFIG` env var) - prilagođena preinačenja 4. **Konfiguracija projekta** (`opencode.json` u projektu) - postavke specifične za projekat 5. **`.opencode` direktoriji** - agenti, komande, dodaci 6. **Inline konfiguracija** (`OPENCODE_CONFIG_CONTENT` env var) - runtime preinačenja - To znači da konfiguracije projekta mogu nadjačati globalne zadane postavke, a globalne konfiguracije mogu nadjačati postavke udaljene organizacije. - :::note - Direktoriji `.opencode` i `~/.config/opencode` koriste **imena u množini** za poddirektorije: `agents/`, `commands/`, `modes/`, `plugins/`, `skills/`, `tools/` i `themes/`. Pojedinačna imena (npr. `agent/`) su također podržana za kompatibilnost unatrag. - ::: +To znači da konfiguracije projekta mogu nadjačati globalne zadane postavke, a globalne konfiguracije mogu nadjačati postavke udaljene organizacije. + +:::note +Direktoriji `.opencode` i `~/.config/opencode` koriste **imena u množini** za poddirektorije: `agents/`, `commands/`, `modes/`, `plugins/`, `skills/`, `tools/` i `themes/`. Pojedinačna imena (npr. `agent/`) su također podržana za kompatibilnost unatrag. +::: --- ### Udaljeno (Remote) Organizacije mogu pružiti zadanu konfiguraciju preko `.well-known/opencode` krajnje tačke. Ovo se automatski preuzima kada se autentifikujete kod provajdera koji to podržava. + Prvo se učitava udaljena konfiguracija koja služi kao osnovni sloj. Svi ostali izvori konfiguracije (globalni, projektni) mogu nadjačati ove zadane postavke. + Na primjer, ako vaša organizacija nudi MCP servere koji su po defaultu onemogućeni: ```json title="Remote config from .well-known/opencode" @@ -89,7 +95,10 @@ Možete omogućiti određene servere u vašoj lokalnoj konfiguraciji: ### Globalno -Postavite svoju globalnu OpenCode konfiguraciju u `~/.config/opencode/opencode.json`. Koristite globalnu konfiguraciju za korisničke preferencije kao što su teme, provajderi ili veze tipki. +Postavite svoju globalnu OpenCode konfiguraciju u `~/.config/opencode/opencode.json`. Koristite globalnu konfiguraciju za korisničke preferencije kao što su provajderi, modeli i dozvole. + +Za postavke specifične za TUI, koristite `~/.config/opencode/tui.json`. + Globalna konfiguracija poništava zadane postavke udaljene organizacije. --- @@ -97,10 +106,15 @@ Globalna konfiguracija poništava zadane postavke udaljene organizacije. ### Projekt Dodajte `opencode.json` u korijen projekta. Konfiguracija projekta ima najveći prioritet među standardnim konfiguracijskim datotekama - ona nadjačava globalne i udaljene konfiguracije. + +Za TUI postavke specifične za projekat, dodajte `tui.json` pored njega. + :::tip Postavite specifičnu konfiguraciju projekta u korijen vašeg projekta. ::: + Kada se OpenCode pokrene, traži konfiguracijsku datoteku u trenutnom direktoriju ili prelazi do najbližeg Git direktorija. + Ovo je također sigurno provjeriti u Git i koristi istu shemu kao globalna. --- @@ -134,33 +148,33 @@ Prilagođeni direktorij se učitava nakon direktorija globalne konfiguracije i ` ## Šema Konfiguracijski fajl ima šemu koja je definirana u [**`opencode.ai/config.json`**](https://opencode.ai/config.json). + +TUI konfiguracija koristi [**`opencode.ai/tui.json`**](https://opencode.ai/tui.json). + Vaš editor bi trebao biti u mogućnosti da validira i autodovršava na osnovu šeme. --- ### TUI -Možete konfigurirati postavke specifične za TUI putem opcije `tui`. +Koristite namjenski `tui.json` (ili `tui.jsonc`) fajl za postavke specifične za TUI. -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", - "tui": { - "scroll_speed": 3, - "scroll_acceleration": { - "enabled": true - }, - "diff_style": "auto" - } + "$schema": "https://opencode.ai/tui.json", + "scroll_speed": 3, + "scroll_acceleration": { + "enabled": true + }, + "diff_style": "auto" } ``` -Dostupne opcije: +Koristite `OPENCODE_TUI_CONFIG` da pokažete na prilagođeni TUI konfiguracijski fajl. + +Stari `theme`, `keybinds`, i `tui` ključevi u `opencode.json` su zastarjeli i automatski će se migrirati kada je to moguće. -- `scroll_acceleration.enabled` - Omogući ubrzanje skrolovanja u macOS stilu. **Ima prednost nad `scroll_speed`.** -- `scroll_speed` - Prilagođeni množitelj brzine pomicanja (podrazumevano: `3`, minimalno: `1`). Zanemareno ako je `scroll_acceleration.enabled` `true`. -- `diff_style` - Kontrola prikaza razlike. `"auto"` se prilagođava širini terminala, `"stacked"` uvijek prikazuje jednu kolonu. - [Saznajte više o korištenju TUI](/docs/tui) ovdje. +[Saznajte više o korištenju TUI ovdje](/docs/tui#configure). --- @@ -188,7 +202,8 @@ Dostupne opcije: - `mdns` - Omogući mDNS otkrivanje servisa. Ovo omogućava drugim uređajima na mreži da otkriju vaš OpenCode server. - `mdnsDomain` - Prilagođeno ime domene za mDNS servis. Zadano je `opencode.local`. Korisno za pokretanje više instanci na istoj mreži. - `cors` - Dodatni origini koji omogućavaju CORS kada koristite HTTP server iz klijenta baziranog na pretraživaču. Vrijednosti moraju biti puni origin (shema + host + opcijski port), npr. `https://app.example.com`. - [Saznajte više o serveru](/docs/server) ovdje. + +[Saznajte više o serveru ovdje](/docs/server). --- @@ -206,7 +221,7 @@ Možete upravljati alatima koje LLM može koristiti putem opcije `tools`. } ``` -[Saznajte više o alatima](/docs/tools) ovdje. +[Saznajte više o alatima ovdje](/docs/tools). --- @@ -224,6 +239,7 @@ Možete konfigurirati dobavljače i modele koje želite koristiti u svojoj OpenC ``` Opcija `small_model` konfigurira poseban model za lagane zadatke poput generiranja naslova. Podrazumevano, OpenCode pokušava da koristi jeftiniji model ako je dostupan od vašeg provajdera, inače se vraća na vaš glavni model. + Opcije provajdera mogu uključivati ​​`timeout` i `setCacheKey`: ```json title="opencode.json" @@ -242,7 +258,8 @@ Opcije provajdera mogu uključivati ​​`timeout` i `setCacheKey`: - `timeout` - Vrijeme čekanja zahtjeva u milisekundama (podrazumevano: 300000). Postavite na `false` da onemogućite. - `setCacheKey` - Osigurajte da je ključ keš memorije uvijek postavljen za određenog provajdera. - Također možete konfigurirati [lokalne modele](/docs/models#local). [Saznajte više](/docs/models). + +Također možete konfigurirati [lokalne modele](/docs/models#local). [Saznajte više](/docs/models). --- @@ -272,21 +289,23 @@ Amazon Bedrock podržava konfiguraciju specifičnu za AWS: - `region` - AWS regija za Bedrock (zadano na `AWS_REGION` env var ili `us-east-1`) - `profile` - AWS imenovani profil iz `~/.aws/credentials` (zadano na `AWS_PROFILE` env var) - `endpoint` - URL prilagođene krajnje tačke za VPC krajnje tačke. Ovo je alias za generičku opciju `baseURL` koristeći terminologiju specifičnu za AWS. Ako su oba navedena, `endpoint` ima prednost. - :::note - Tokeni nosioca (`AWS_BEARER_TOKEN_BEDROCK` ili `/connect`) imaju prednost nad autentifikacijom zasnovanom na profilu. Pogledajte [prednost autentifikacije](/docs/providers#authentication-precedence) za detalje. - ::: - [Saznajte više o konfiguraciji Amazon Bedrock](/docs/providers#amazon-bedrock). + +:::note +Tokeni nosioca (`AWS_BEARER_TOKEN_BEDROCK` ili `/connect`) imaju prednost nad autentifikacijom zasnovanom na profilu. Pogledajte [prednost autentifikacije](/docs/providers#authentication-precedence) za detalje. +::: + +[Saznajte više o konfiguraciji Amazon Bedrock](/docs/providers#amazon-bedrock). --- -### Tema +### Teme -Možete konfigurirati temu koju želite koristiti u svojoj OpenCode konfiguraciji putem opcije `theme`. +Postavite vašu UI temu u `tui.json`. -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", - "theme": "" + "$schema": "https://opencode.ai/tui.json", + "theme": "tokyonight" } ``` @@ -332,6 +351,7 @@ Možete postaviti zadanog agenta koristeći opciju `default_agent`. Ovo određuj ``` Zadani agent mora biti primarni agent (ne podagent). Ovo može biti ugrađeni agent kao što je `"build"` ili `"plan"`, ili [prilagođeni agent](/docs/agents) koji ste definirali. Ako navedeni agent ne postoji ili je podagent, OpenCode će se vratiti na `"build"` s upozorenjem. + Ova postavka se primjenjuje na sva sučelja: TUI, CLI (`opencode run`), desktop aplikaciju i GitHub Action. --- @@ -352,7 +372,8 @@ Ovo prihvata: - `"manual"` - Dozvoli ručno dijeljenje putem naredbi (podrazumevano) - `"auto"` - Automatski dijelite nove razgovore - `"disabled"` - Onemogući dijeljenje u potpunosti - Podrazumevano, dijeljenje je postavljeno na ručni način rada gdje trebate eksplicitno dijeliti razgovore pomoću naredbe `/share`. + +Podrazumevano, dijeljenje je postavljeno na ručni način rada gdje trebate eksplicitno dijeliti razgovore pomoću naredbe `/share`. --- @@ -384,11 +405,11 @@ Također možete definirati naredbe koristeći markdown fajlove u `~/.config/ope ### Prečice tipki -Možete prilagoditi svoje veze tipki putem opcije `keybinds`. +Prilagodite prečice tipki u `tui.json`. -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "keybinds": {} } ``` @@ -442,6 +463,7 @@ Možete konfigurirati formatere koda putem opcije `formatter`. ### Dozvole Prema zadanim postavkama, OpenCode **dopušta sve operacije** bez potrebe za eksplicitnim dopuštenjem. Ovo možete promijeniti koristeći opciju `permission`. + Na primjer, da osigurate da alati `edit` i `bash` zahtijevaju odobrenje korisnika: ```json title="opencode.json" @@ -467,13 +489,15 @@ Možete kontrolirati ponašanje sažimanja konteksta putem opcije `compaction`. "$schema": "https://opencode.ai/config.json", "compaction": { "auto": true, - "prune": true + "prune": true, + "reserved": 10000 } } ``` - `auto` - Automatski sažimanje sesije kada je kontekst pun (podrazumevano: `true`). - `prune` - Uklonite stare izlaze alata da sačuvate tokene (podrazumevano: `true`). +- `reserved` - Token buffer za sažimanje. Ostavlja dovoljno prostora da se izbjegne prelijevanje tokom sažimanja --- @@ -512,6 +536,7 @@ Možete konfigurirati MCP servere koje želite koristiti putem opcije `mcp`. ### Dodaci [Plugins](/docs/plugins) proširuju OpenCode sa prilagođenim alatima, kukicama i integracijama. + Postavite datoteke dodataka u `.opencode/plugins/` ili `~/.config/opencode/plugins/`. Također možete učitati dodatke iz npm-a preko opcije `plugin`. ```json title="opencode.json" @@ -554,6 +579,7 @@ Možete onemogućiti dobavljače koji se automatski učitavaju preko opcije `dis :::note `disabled_providers` ima prioritet nad `enabled_providers`. ::: + Opcija `disabled_providers` prihvata niz ID-ova provajdera. Kada je provajder onemogućen: - Neće se učitati čak i ako su varijable okruženja postavljene. @@ -574,9 +600,11 @@ Možete odrediti listu dozvoljenih dobavljača putem opcije `enabled_providers`. ``` Ovo je korisno kada želite da ograničite OpenCode da koristi samo određene provajdere umjesto da ih onemogućavate jednog po jednog. + :::note `disabled_providers` ima prioritet nad `enabled_providers`. ::: + Ako se provajder pojavljuje i u `enabled_providers` i `disabled_providers`, `disabled_providers` ima prioritet za kompatibilnost unatrag. --- @@ -649,7 +677,9 @@ Putanja fajla mogu biti: - U odnosu na direktorij konfiguracijskih datoteka - Ili apsolutne staze koje počinju sa `/` ili `~` - Ovo je korisno za: + +Ovo je korisno za: + - Pohranjivanje osjetljivih podataka poput API ključeva u odvojenim datotekama. - Uključujući velike datoteke instrukcija bez zatrpavanja vaše konfiguracije. - Dijeljenje zajedničkih isječaka konfiguracije u više konfiguracijskih datoteka. diff --git a/packages/web/src/content/docs/bs/custom-tools.mdx b/packages/web/src/content/docs/bs/custom-tools.mdx index e452ce6a562..e933c7cb12e 100644 --- a/packages/web/src/content/docs/bs/custom-tools.mdx +++ b/packages/web/src/content/docs/bs/custom-tools.mdx @@ -79,6 +79,32 @@ Ovo stvara dva alata: `math_add` i `math_multiply`. --- +#### Sukob imena s ugrađenim alatima + +Prilagođeni alati su prepoznati po imenu. Ako prilagođeni alat koristi isto ime kao ugrađeni alat, prilagođeni alat ima prednost. + +Na primjer, ova datoteka zamjenjuje ugrađeni `bash` alat: + +```ts title=".opencode/tools/bash.ts" +import { tool } from "@opencode-ai/plugin" + +export default tool({ + description: "Restricted bash wrapper", + args: { + command: tool.schema.string(), + }, + async execute(args) { + return `blocked: ${args.command}` + }, +}) +``` + +:::note +Preferirajte jedinstvena imena osim ako namjerno ne želite zamijeniti ugrađeni alat. Ako želite onemogućiti ugrađeni alat, ali ne i nadjačati ga, koristite [dozvole](/docs/permissions). +::: + +--- + ### Argumenti Možete koristiti `tool.schema`, što je samo [Zod](https://zod.dev), da definirate tipove argumenata. @@ -166,4 +192,4 @@ export default tool({ }) ``` -Ovdje koristimo [`Bun.$`\_](https://bun.com/docs/runtime/shell) uslužni program za pokretanje Python skripte. +Ovdje koristimo [`Bun.$`](https://bun.com/docs/runtime/shell) uslužni program za pokretanje Python skripte. diff --git a/packages/web/src/content/docs/bs/ecosystem.mdx b/packages/web/src/content/docs/bs/ecosystem.mdx index c7dea0c6e2d..be4816f8f0d 100644 --- a/packages/web/src/content/docs/bs/ecosystem.mdx +++ b/packages/web/src/content/docs/bs/ecosystem.mdx @@ -4,45 +4,50 @@ description: Projekti i integracije izgrađeni uz OpenCode. --- Kolekcija projekata zajednice izgrađenih na OpenCode. + :::note Želite li na ovu listu dodati svoj OpenCode projekat? Pošaljite PR. ::: + Također možete pogledati [awesome-opencode](https://github.com/awesome-opencode/awesome-opencode) i [opencode.cafe](https://opencode.cafe), zajednicu koja spaja ekosistem i zajednicu. --- ## Dodaci -| Ime | Opis | -| --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | --- | ------------------------------------------------------------------- | -------------------------------------------------------------------------------- | -| [opencode-daytona](https://github.com/jamesmurdza/daytona/blob/main/guides/typescript/opencode/README.md) | Automatski pokrenite OpenCode sesije u izoliranim Daytona sandboxovima uz git sinhronizaciju i preglede uživo | -| [opencode-helicone-session](https://github.com/H2Shami/opencode-helicone-session) | Automatski ubacite Helicone zaglavlja sesije za grupisanje zahtjeva | -| [opencode-type-inject](https://github.com/nick-vi/opencode-type-inject) | Automatski ubaci TypeScript/Svelte tipove u čitanje datoteka pomoću alata za pretraživanje | -| [opencode-openai-codex-auth](https://github.com/numman-ali/opencode-openai-codex-auth) | Koristite svoju ChatGPT Plus/Pro pretplatu umjesto API kredita | -| [opencode-gemini-auth](https://github.com/jenslys/opencode-gemini-auth) | Koristite svoj postojeći Gemini plan umjesto API naplate | -| [opencodentigravity-auth](https://github.com/NoeFabris/opencodentigravity-auth) | Koristite besplatne modele Antigravity umjesto API naplate | -| [opencode-devcontainers](https://github.com/athal7/opencode-devcontainers) | Izolacija devcontainer-a s više grana s plitkim klonovima i automatski dodijeljenim portovima | -| [opencode-google-antigravity-auth](https://github.com/shekohex/opencode-google-antigravity-auth) | Google Antigravity OAuth dodatak, s podrškom za Google pretraživanje i robusnijim API rukovanjem | -| [opencode-dynamic-context-pruning](https://github.com/Tarquinen/opencode-dynamic-context-pruning) | Optimizirajte korištenje tokena smanjenjem izlaza zastarjelih alata | -| [opencode-websearch-cited](https://github.com/ghoulr/opencode-websearch-cited.git) | Dodajte podršku za izvorno web pretraživanje za podržane provajdere sa stilom utemeljenim na Googleu | -| [opencode-pty](https://github.com/shekohex/opencode-pty.git) | Omogućuje AI agentima da pokreću pozadinske procese u PTY-u, šalju im interaktivni ulaz. | -| [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Upute za neinteraktivne naredbe ljuske - sprječava visi od TTY ovisnih operacija | | [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Pratite upotrebu OpenCode sa Wakatime | -| [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Očistite tabele umanjenja vrijednosti koje su izradili LLM | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | 10x brže uređivanje koda s Morph Fast Apply API-jem i markerima za lijeno uređivanje | -| [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Pozadinski agenti, unapred izgrađeni LSP/AST/MCP alati, kurirani agenti, kompatibilni sa Claude Code | -| [opencode-notificator](https://github.com/panta82/opencode-notificator) | Obavještenja na radnoj površini i zvučna upozorenja za OpenCode sesije | -| [opencode-notifier](https://github.com/mohak34/opencode-notifier) | Obavještenja na radnoj površini i zvučna upozorenja za dozvole, završetak i događaje greške | -| [opencode-zellij-namer](https://github.com/24601/opencode-zellij-namer) | Automatsko imenovanje Zellij sesije na bazi OpenCode konteksta | -| [opencode-skillful](https://github.com/zenobi-us/opencode-skillful) | Dozvolite OpenCode agentima da lijeno učitavaju upite na zahtjev uz otkrivanje vještina i ubrizgavanje | -| [opencode-supermemory](https://github.com/supermemoryai/opencode-supermemory) | Trajna memorija kroz sesije koristeći Supermemory | -| [@plannotator/opencode](https://github.com/backnotprop/plannotator/tree/main/apps/opencode-plugin) | Interaktivni pregled plana s vizualnim napomenama i privatnim/offline dijeljenjem | -| [@openspoon/subtask2](https://github.com/spoons-and-mirrors/subtask2) | Proširite opencode /komande u moćan sistem orkestracije sa granularnom kontrolom toka | -| [opencode-scheduler](https://github.com/different-ai/opencode-scheduler) | Planirajte ponavljajuće poslove koristeći launchd (Mac) ili systemd (Linux) sa cron sintaksom | | [micode](https://github.com/vtemian/micode) | Strukturirana Brainstorm → Plan → Implementacija toka rada uz kontinuitet sesije | -| [octto](https://github.com/vtemian/octto) | Interaktivno korisničko sučelje pretraživača za AI brainstorming sa obrascima za više pitanja | -| [opencode-background-agents](https://github.com/kdcokenny/opencode-background-agents) | Pozadinski agenti u stilu Claudea s asinhroniziranim delegiranjem i postojanošću konteksta | -| [opencode-notify](https://github.com/kdcokenny/opencode-notify) | Notifikacije izvornog OS-a za OpenCode – znajte kada se zadaci dovrše | -| [opencode-workspace](https://github.com/kdcokenny/opencode-workspace) | Uvezeni višeagentni orkestracijski pojas – 16 komponenti, jedna instalacija | -| [opencode-worktree](https://github.com/kdcokenny/opencode-worktree) | Git radna stabla bez trenja za OpenCode | +| Ime | Opis | +| -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | +| [opencode-daytona](https://github.com/daytonaio/daytona/tree/main/libs/opencode-plugin) | Automatski pokrenite OpenCode sesije u izoliranim Daytona sandboxovima uz git sinhronizaciju i preglede uživo | +| [opencode-helicone-session](https://github.com/H2Shami/opencode-helicone-session) | Automatski ubacite Helicone zaglavlja sesije za grupisanje zahtjeva | +| [opencode-type-inject](https://github.com/nick-vi/opencode-type-inject) | Automatski ubaci TypeScript/Svelte tipove u čitanje datoteka pomoću alata za pretraživanje | +| [opencode-openai-codex-auth](https://github.com/numman-ali/opencode-openai-codex-auth) | Koristite svoju ChatGPT Plus/Pro pretplatu umjesto API kredita | +| [opencode-gemini-auth](https://github.com/jenslys/opencode-gemini-auth) | Koristite svoj postojeći Gemini plan umjesto API naplate | +| [opencode-antigravity-auth](https://github.com/NoeFabris/opencode-antigravity-auth) | Koristite besplatne modele Antigravity umjesto API naplate | +| [opencode-devcontainers](https://github.com/athal7/opencode-devcontainers) | Izolacija devcontainer-a s više grana s plitkim klonovima i automatski dodijeljenim portovima | +| [opencode-google-antigravity-auth](https://github.com/shekohex/opencode-google-antigravity-auth) | Google Antigravity OAuth dodatak, s podrškom za Google pretraživanje i robusnijim API rukovanjem | +| [opencode-dynamic-context-pruning](https://github.com/Tarquinen/opencode-dynamic-context-pruning) | Optimizirajte korištenje tokena smanjenjem izlaza zastarjelih alata | +| [opencode-vibeguard](https://github.com/inkdust2021/opencode-vibeguard) | Redigujte tajne/PII u rezervirana mjesta u stilu VibeGuarda prije LLM poziva; vratite lokalno | +| [opencode-websearch-cited](https://github.com/ghoulr/opencode-websearch-cited.git) | Dodajte podršku za izvorno web pretraživanje za podržane provajdere sa stilom utemeljenim na Googleu | +| [opencode-pty](https://github.com/shekohex/opencode-pty.git) | Omogućuje AI agentima da pokreću pozadinske procese u PTY-u, šalju im interaktivni ulaz. | +| [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Upute za neinteraktivne naredbe ljuske - sprječava visi od TTY ovisnih operacija | +| [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Pratite upotrebu OpenCode sa Wakatime | +| [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Očistite tabele umanjenja vrijednosti koje su izradili LLM | +| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | 10x brže uređivanje koda s Morph Fast Apply API-jem i markerima za lijeno uređivanje | +| [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Pozadinski agenti, unapred izgrađeni LSP/AST/MCP alati, kurirani agenti, kompatibilni sa Claude Code | +| [opencode-notificator](https://github.com/panta82/opencode-notificator) | Obavještenja na radnoj površini i zvučna upozorenja za OpenCode sesije | +| [opencode-notifier](https://github.com/mohak34/opencode-notifier) | Obavještenja na radnoj površini i zvučna upozorenja za dozvole, završetak i događaje greške | +| [opencode-zellij-namer](https://github.com/24601/opencode-zellij-namer) | Automatsko imenovanje Zellij sesije na bazi OpenCode konteksta | +| [opencode-skillful](https://github.com/zenobi-us/opencode-skillful) | Dozvolite OpenCode agentima da lijeno učitavaju upite na zahtjev uz otkrivanje vještina i ubrizgavanje | +| [opencode-supermemory](https://github.com/supermemoryai/opencode-supermemory) | Trajna memorija kroz sesije koristeći Supermemory | +| [@plannotator/opencode](https://github.com/backnotprop/plannotator/tree/main/apps/opencode-plugin) | Interaktivni pregled plana s vizualnim napomenama i privatnim/offline dijeljenjem | +| [@openspoon/subtask2](https://github.com/spoons-and-mirrors/subtask2) | Proširite opencode /komande u moćan sistem orkestracije sa granularnom kontrolom toka | +| [opencode-scheduler](https://github.com/different-ai/opencode-scheduler) | Planirajte ponavljajuće poslove koristeći launchd (Mac) ili systemd (Linux) sa cron sintaksom | +| [micode](https://github.com/vtemian/micode) | Strukturirana Brainstorm → Plan → Implementacija toka rada uz kontinuitet sesije | +| [octto](https://github.com/vtemian/octto) | Interaktivno korisničko sučelje pretraživača za AI brainstorming sa obrascima za više pitanja | +| [opencode-background-agents](https://github.com/kdcokenny/opencode-background-agents) | Pozadinski agenti u stilu Claudea s asinhroniziranim delegiranjem i postojanošću konteksta | +| [opencode-notify](https://github.com/kdcokenny/opencode-notify) | Notifikacije izvornog OS-a za OpenCode – znajte kada se zadaci dovrše | +| [opencode-workspace](https://github.com/kdcokenny/opencode-workspace) | Uvezeni višeagentni orkestracijski pojas – 16 komponenti, jedna instalacija | +| [opencode-worktree](https://github.com/kdcokenny/opencode-worktree) | Git radna stabla bez trenja za OpenCode | --- @@ -55,7 +60,7 @@ Također možete pogledati [awesome-opencode](https://github.com/awesome-opencod | [portal](https://github.com/hosenur/portal) | Mobilni korisnički interfejs za OpenCode preko Tailscale/VPN | | [opencode plugin template](https://github.com/zenobi-us/opencode-plugin-template/) | Predložak za izgradnju OpenCode dodataka | | [opencode.nvim](https://github.com/sudo-tee/opencode.nvim) | Neovim frontend za opencode - terminal baziran AI agent za kodiranje | -| [ai-sdk-provider-opencode-sdk](https://github.com/ben-vargas/ai-sdk-provider-opencode-sdk) | Vercel AI SDK dobavljač za korištenje OpenCode putem @opencodei/sdk | +| [ai-sdk-provider-opencode-sdk](https://github.com/ben-vargas/ai-sdk-provider-opencode-sdk) | Vercel AI SDK dobavljač za korištenje OpenCode putem @opencode-ai/sdk | | [OpenChamber](https://github.com/btriapitsyn/openchamber) | Web / Desktop App i VS Code Extension za OpenCode | | [OpenCode-Obsidian](https://github.com/mtymek/opencode-obsidian) | Obsidian dodatak koji ugrađuje OpenCode u Obsidian-ov UI | | [OpenWork](https://github.com/different-ai/openwork) | Alternativa otvorenog koda Claudeu Coworku, pokretana pomoću OpenCode | @@ -66,7 +71,7 @@ Također možete pogledati [awesome-opencode](https://github.com/awesome-opencod ## Agenti -| Ime | Opis | -| ------------------------------------------------------------- | --------------------------------------------------------------- | -| [Agentic](https://github.com/Cluster444/agentic) | Modularni AI agenti i komande za strukturirani razvoj | -| [opencodegents](https://github.com/darrenhinde/opencodegents) | Konfiguracije, upiti, agenti i dodaci za poboljšane tokove rada | +| Ime | Opis | +| ----------------------------------------------------------------- | --------------------------------------------------------------- | +| [Agentic](https://github.com/Cluster444/agentic) | Modularni AI agenti i komande za strukturirani razvoj | +| [opencode-agents](https://github.com/darrenhinde/opencode-agents) | Konfiguracije, upiti, agenti i dodaci za poboljšane tokove rada | diff --git a/packages/web/src/content/docs/bs/go.mdx b/packages/web/src/content/docs/bs/go.mdx new file mode 100644 index 00000000000..ec7bf25945a --- /dev/null +++ b/packages/web/src/content/docs/bs/go.mdx @@ -0,0 +1,159 @@ +--- +title: Go +description: Povoljna pretplata za otvorene modele kodiranja. +--- + +import config from "../../../../config.mjs" +export const console = config.console +export const email = `mailto:${config.email}` + +OpenCode Go je povoljna pretplata od **$10/mjesečno** koja vam daje pouzdan pristup popularnim otvorenim modelima kodiranja. + +:::note +OpenCode Go je trenutno u beta fazi. +::: + +Go funkcioniše kao bilo koji drugi pružatelj usluga u OpenCode-u. Pretplatite se na OpenCode Go i +dobijete svoj API ključ. To je **potpuno opcionalno** i ne morate ga koristiti da biste +koristili OpenCode. + +Dizajniran je prvenstveno za međunarodne korisnike, sa modelima hostovanim u SAD-u, EU i Singapuru za stabilan globalni pristup. + +--- + +## Pozadina + +Otvoreni modeli su postali zaista dobri. Sada dostižu performanse bliske +vlasničkim modelima za zadatke kodiranja. A budući da ih mnogi pružatelji usluga mogu posluživati +konkurentno, obično su daleko jeftiniji. + +Međutim, dobivanje pouzdanog pristupa s malim kašnjenjem može biti teško. Pružatelji usluga +variraju u kvaliteti i dostupnosti. + +:::tip +Testirali smo odabranu grupu modela i pružatelja usluga koji dobro rade sa OpenCode-om. +::: + +Da bismo ovo riješili, uradili smo nekoliko stvari: + +1. Testirali smo odabranu grupu otvorenih modela i razgovarali sa njihovim timovima o tome kako ih + najbolje pokrenuti. +2. Zatim smo radili sa nekoliko pružatelja usluga kako bismo bili sigurni da su ispravno + posluživani. +3. Konačno, benchmarkirali smo kombinaciju modela/pružatelja usluga i došli do + liste koju se osjećamo dobro preporučiti. + +OpenCode Go vam daje pristup ovim modelima za **$10/mjesečno**. + +--- + +## Kako to funkcioniše + +OpenCode Go funkcioniše kao bilo koji drugi pružatelj usluga u OpenCode-u. + +1. Prijavite se na **OpenCode Zen**, pretplatite se na Go i + kopirajte svoj API ključ. +2. Pokrenite `/connect` komandu u TUI-ju, odaberite `OpenCode Go` i zalijepite + svoj API ključ. +3. Pokrenite `/models` u TUI-ju da vidite listu modela dostupnih putem Go. + +:::note +Samo jedan član po radnom prostoru se može pretplatiti na OpenCode Go. +::: + +Trenutna lista modela uključuje: + +- **GLM-5** +- **Kimi K2.5** +- **MiniMax M2.5** + +Lista modela se može mijenjati kako testiramo i dodajemo nove. + +--- + +## Ograničenja korištenja + +OpenCode Go uključuje sljedeća ograničenja: + +- **Limit od 5 sati** — $12 korištenja +- **Sedmični limit** — $30 korištenja +- **Mjesečni limit** — $60 korištenja + +Limiti su definisani u dolarskoj vrijednosti. To znači da vaš stvarni broj zahtjeva zavisi od modela koji koristite. Jeftiniji modeli kao što je MiniMax M2.5 omogućavaju više zahtjeva, dok skuplji modeli kao što je GLM-5 omogućavaju manje. + +Tabela ispod pruža procijenjeni broj zahtjeva na osnovu tipičnih Go obrazaca korištenja: + +| | GLM-5 | Kimi K2.5 | MiniMax M2.5 | +| ------------------ | ----- | --------- | ------------ | +| zahtjeva po 5 sati | 1,150 | 1,850 | 30,000 | +| zahtjeva sedmično | 2,880 | 4,630 | 75,000 | +| zahtjeva mjesečno | 5,750 | 9,250 | 150,000 | + +Procjene su zasnovane na uočenim prosječnim obrascima zahtjeva: + +- GLM-5 — 700 ulaznih, 52,000 keširanih, 150 izlaznih tokena po zahtjevu +- Kimi K2.5 — 870 ulaznih, 55,000 keširanih, 200 izlaznih tokena po zahtjevu +- MiniMax M2.5 — 300 ulaznih, 55,000 keširanih, 125 izlaznih tokena po zahtjevu + +Možete pratiti svoje trenutno korištenje u **konzoli**. + +:::tip +Ako dosegnete limit korištenja, možete nastaviti koristiti besplatne modele. +::: + +Ograničenja korištenja se mogu mijenjati kako učimo iz rane upotrebe i povratnih informacija. + +--- + +### Cijene + +OpenCode Go je pretplatnički plan od **$10/mjesečno**. Ispod su cijene **po 1M tokena**. + +| Model | Ulaz | Izlaz | Keširano čitanje | +| ------------ | ----- | ----- | ---------------- | +| GLM-5 | $1.00 | $3.20 | $0.20 | +| Kimi K2.5 | $0.60 | $3.00 | $0.10 | +| MiniMax M2.5 | $0.30 | $1.20 | $0.03 | + +--- + +### Korištenje izvan limita + +Ako također imate kredita na svom Zen saldu, možete omogućiti opciju **Use balance** +u konzoli. Kada je omogućeno, Go će se prebaciti na vaš Zen saldo +nakon što dosegnete limite korištenja umjesto blokiranja zahtjeva. + +--- + +## Endpointi + +Također možete pristupiti Go modelima putem sljedećih API endpointa. + +| Model | ID modela | Endpoint | AI SDK Paket | +| ------------ | ------------ | ------------------------------------------------ | --------------------------- | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | + +[Model id](/docs/config/#models) u vašoj OpenCode konfiguraciji +koristi format `opencode-go/`. Na primjer, za Kimi K2.5, biste +koristili `opencode-go/kimi-k2.5` u vašoj konfiguraciji. + +--- + +## Privatnost + +Plan je dizajniran prvenstveno za međunarodne korisnike, sa modelima hostovanim u SAD-u, EU i Singapuru za stabilan globalni pristup. + +Kontaktirajte nas ako imate bilo kakvih pitanja. + +--- + +## Ciljevi + +Kreirali smo OpenCode Go da: + +1. Učinimo AI kodiranje **pristupačnim** većem broju ljudi uz povoljnu pretplatu. +2. Pružimo **pouzdan** pristup najboljim otvorenim modelima kodiranja. +3. Kurišemo modele koji su **testirani i benchmarkirani** za upotrebu agenata za kodiranje. +4. Nemamo **zaključavanja (lock-in)** omogućavajući vam da koristite bilo kojeg drugog pružatelja usluga sa OpenCode-om također. diff --git a/packages/web/src/content/docs/bs/index.mdx b/packages/web/src/content/docs/bs/index.mdx index 5d21e994f9b..4af699485aa 100644 --- a/packages/web/src/content/docs/bs/index.mdx +++ b/packages/web/src/content/docs/bs/index.mdx @@ -80,9 +80,10 @@ Također ga možete instalirati pomoću sljedećih naredbi: - **Korištenje Parua na Arch Linuxu** -```bash - paru -S opencode-bin -``` + ```bash + sudo pacman -S opencode # Arch Linux (Stable) + paru -S opencode-bin # Arch Linux (Latest from AUR) + ``` #### Windows diff --git a/packages/web/src/content/docs/bs/keybinds.mdx b/packages/web/src/content/docs/bs/keybinds.mdx index f35dd385257..a7a6d34a0de 100644 --- a/packages/web/src/content/docs/bs/keybinds.mdx +++ b/packages/web/src/content/docs/bs/keybinds.mdx @@ -3,11 +3,11 @@ title: Prečice tipki description: Prilagodite svoje veze tipki. --- -OpenCode ima listu veza tipki koje možete prilagoditi preko OpenCode konfiguracije. +OpenCode ima listu veza tipki koje možete prilagoditi putem `tui.json`. -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "keybinds": { "leader": "ctrl+x", "app_exit": "ctrl+c,ctrl+d,q", @@ -108,18 +108,20 @@ OpenCode ima listu veza tipki koje možete prilagoditi preko OpenCode konfigurac ## Leader tipka OpenCode koristi `leader` (vodeću) tipku za većinu povezivanja tipki. Ovo izbjegava sukobe u vašem terminalu. + Prema zadanim postavkama, `ctrl+x` je vodeća tipka i većina radnji zahtijeva da prvo pritisnete vodeću tipku, a zatim i prečicu. Na primjer, da biste započeli novu sesiju, prvo pritisnite `ctrl+x`, a zatim pritisnite `n`. + Ne morate koristiti vodeću tipku za svoje veze tipki, ali preporučujemo da to učinite. --- ## Onemogućavanje prečica tipki -Možete onemogućiti spajanje tipki dodavanjem ključa u svoju konfiguraciju s vrijednošću "none". +Možete onemogućiti spajanje tipki dodavanjem ključa u `tui.json` s vrijednošću "none". -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "keybinds": { "session_compact": "none" } diff --git a/packages/web/src/content/docs/bs/lsp.mdx b/packages/web/src/content/docs/bs/lsp.mdx index 463d9a0ad99..8f52f3bf617 100644 --- a/packages/web/src/content/docs/bs/lsp.mdx +++ b/packages/web/src/content/docs/bs/lsp.mdx @@ -8,40 +8,43 @@ OpenCode se integriše sa vašim Language Server Protocol (LSP) serverima kako b ## Ugrađeni OpenCode dolazi sa nekoliko ugrađenih LSP servera za popularne jezike: -| LSP server | Ekstenzije | Zahtjevi -|------------------ | ------------------------------------------------------------------- | ------------------------------------------------------------ | -| astro | .astro | Automatske instalacije za Astro projekte | -| bash | .sh, .bash, .zsh, .ksh | Automatski instalira bash-language-server | -| clangd | .c, .cpp, .cc, .cxx, .c++, .h, .hpp, .hh, .hxx, .h++ | Automatske instalacije za C/C++ projekte | -| csharp | .cs | `.NET SDK` instaliran | -| clojure-lsp | .clj, .cljs, .cljc, .edn | `clojure-lsp` komanda dostupna | -| dart | .dart | `dart` komanda dostupna | -| deno | .ts, .tsx, .js, .jsx, .mjs | `deno` komanda dostupna (automatski detektuje deno.json/deno.jsonc) | -| elixir-ls | .ex, .exs | `elixir` komanda dostupna | -| eslint | .ts, .tsx, .js, .jsx, .mjs, .cjs, .mts, .cts, .vue | `eslint` ovisnost u projektu | -| fsharp | .fs, .fsi, .fsx, .fsscript | `.NET SDK` instaliran | -| gleam | .bleam | `gleam` komanda dostupna | -| gopls | .go | `go` komanda dostupna | -| hls | .hs, .lhs | `haskell-language-server-wrapper` komanda dostupna | -| jdtls | .java | `Java SDK (version 21+)` instaliran | -| kotlin-ls | .kt, .kts | Automatske instalacije za Kotlin projekte | -| lua-ls | .lua | Automatske instalacije za Lua projekte | -| nixd | .nix | `nixd` komanda dostupna | -| ocaml-lsp | .ml, .mli | `ocamllsp` komanda dostupna | -| oxlint | .ts, .tsx, .js, .jsx, .mjs, .cjs, .mts, .cts, .vue, .astro, .svelte | `oxlint` zavisnost u projektu | -| php intelephense | .php | Automatske instalacije za PHP projekte | -| prisma | .prisma | `prisma` komanda dostupna | -| pyright | .py, .pyi | `pyright` ovisnost instalirana | -| ruby-lsp (rubocop) | .rb, .rake, .gemspec, .ru | `ruby` i `gem` komande dostupne | -| rust | .rs | `rust-analyzer` komanda dostupna | -| sourcekit-lsp | .swift, .objc, .objcpp | `swift` instaliran (`xcode` na macOS-u) | -| svelte | .svelte | Automatske instalacije za Svelte projekte | -| terraform | .tf, .tfvars | Automatske instalacije iz GitHub izdanja | -| tinymist | .typ, .typc | Automatske instalacije iz GitHub izdanja | -| typescript | .ts, .tsx, .js, .jsx, .mjs, .cjs, .mts, .cts | `typescript` zavisnost u projektu | -| vue | .vue | Automatske instalacije za Vue projekte | -| yaml-ls | .yaml, .yml | Automatski instalira Red Hat yaml-language-server | -| zls | .zig, .zon | `zig` komanda dostupna | + +| LSP server | Ekstenzije | Zahtjevi | +| ------------------ | ------------------------------------------------------------------- | -------------------------------------------------------- | +| astro | .astro | Automatske instalacije za Astro projekte | +| bash | .sh, .bash, .zsh, .ksh | Automatski instalira bash-language-server | +| clangd | .c, .cpp, .cc, .cxx, .c++, .h, .hpp, .hh, .hxx, .h++ | Automatske instalacije za C/C++ projekte | +| csharp | .cs | `.NET SDK` instaliran | +| clojure-lsp | .clj, .cljs, .cljc, .edn | `clojure-lsp` komanda dostupna | +| dart | .dart | `dart` komanda dostupna | +| deno | .ts, .tsx, .js, .jsx, .mjs | `deno` komanda dostupna (automatski detektuje deno.json) | +| elixir-ls | .ex, .exs | `elixir` komanda dostupna | +| eslint | .ts, .tsx, .js, .jsx, .mjs, .cjs, .mts, .cts, .vue | `eslint` ovisnost u projektu | +| fsharp | .fs, .fsi, .fsx, .fsscript | `.NET SDK` instaliran | +| gleam | .gleam | `gleam` komanda dostupna | +| gopls | .go | `go` komanda dostupna | +| hls | .hs, .lhs | `haskell-language-server-wrapper` komanda dostupna | +| jdtls | .java | `Java SDK (version 21+)` instaliran | +| julials | .jl | `julia` i `LanguageServer.jl` instalirani | +| kotlin-ls | .kt, .kts | Automatske instalacije za Kotlin projekte | +| lua-ls | .lua | Automatske instalacije za Lua projekte | +| nixd | .nix | `nixd` komanda dostupna | +| ocaml-lsp | .ml, .mli | `ocamllsp` komanda dostupna | +| oxlint | .ts, .tsx, .js, .jsx, .mjs, .cjs, .mts, .cts, .vue, .astro, .svelte | `oxlint` zavisnost u projektu | +| php intelephense | .php | Automatske instalacije za PHP projekte | +| prisma | .prisma | `prisma` komanda dostupna | +| pyright | .py, .pyi | `pyright` ovisnost instalirana | +| ruby-lsp (rubocop) | .rb, .rake, .gemspec, .ru | `ruby` i `gem` komande dostupne | +| rust | .rs | `rust-analyzer` komanda dostupna | +| sourcekit-lsp | .swift, .objc, .objcpp | `swift` instaliran (`xcode` na macOS-u) | +| svelte | .svelte | Automatske instalacije za Svelte projekte | +| terraform | .tf, .tfvars | Automatske instalacije iz GitHub izdanja | +| tinymist | .typ, .typc | Automatske instalacije iz GitHub izdanja | +| typescript | .ts, .tsx, .js, .jsx, .mjs, .cjs, .mts, .cts | `typescript` zavisnost u projektu | +| vue | .vue | Automatske instalacije za Vue projekte | +| yaml-ls | .yaml, .yml | Automatski instalira Red Hat yaml-language-server | +| zls | .zig, .zon | `zig` komanda dostupna | + LSP serveri su automatski omogućeni kada se otkrije jedna od gore navedenih ekstenzija datoteke i zahtjevi su ispunjeni. :::note Možete onemogućiti automatska preuzimanja LSP servera tako što ćete postaviti varijablu okruženja `OPENCODE_DISABLE_LSP_DOWNLOAD` na `true`. @@ -70,13 +73,15 @@ Možete prilagoditi LSP servere kroz `lsp` odjeljak u vašoj opencode konfigurac ``` Svaki LSP server podržava sljedeće: -| Svojstvo | Vrsta | Opis -|---------------- | -------- | ------------------------------------------------- | -| `disabled` | boolean | Postavite ovo na `true` da onemogućite LSP server | -| `command` | string[] | Naredba za pokretanje LSP servera | -| `extensions` | string[] | Ekstenzije datoteka koje ovaj LSP server treba da rukuje | -| `env` | objekt | Varijable okruženja koje treba postaviti prilikom pokretanja servera | -| `initialization` | objekt | Opcije inicijalizacije za slanje na LSP server | + +| Svojstvo | Vrsta | Opis | +| ---------------- | -------- | -------------------------------------------------------------------- | +| `disabled` | boolean | Postavite ovo na `true` da onemogućite LSP server | +| `command` | string[] | Naredba za pokretanje LSP servera | +| `extensions` | string[] | Ekstenzije datoteka koje ovaj LSP server treba da rukuje | +| `env` | objekt | Varijable okruženja koje treba postaviti prilikom pokretanja servera | +| `initialization` | objekt | Opcije inicijalizacije za slanje na LSP server | + Pogledajmo neke primjere. --- diff --git a/packages/web/src/content/docs/bs/plugins.mdx b/packages/web/src/content/docs/bs/plugins.mdx index 9b7e9f12b74..7e046cb83df 100644 --- a/packages/web/src/content/docs/bs/plugins.mdx +++ b/packages/web/src/content/docs/bs/plugins.mdx @@ -225,7 +225,7 @@ export const NotificationPlugin = async ({ project, client, $, directory, worktr Koristimo `osascript` za pokretanje AppleScript-a na macOS-u. Ovdje ga koristimo za slanje obavještenja. :::note -Ako koristite desktop aplikaciju OpenCode, ona može automatski slati sistemske obavijesti kada je odgovor spreman ili kada dođe do greške u sesiji. +Ako alat dodatka koristi isto ime kao ugrađeni alat, alat dodatka ima prednost. ::: --- diff --git a/packages/web/src/content/docs/bs/providers.mdx b/packages/web/src/content/docs/bs/providers.mdx index 2415cda2778..6bdcf457785 100644 --- a/packages/web/src/content/docs/bs/providers.mdx +++ b/packages/web/src/content/docs/bs/providers.mdx @@ -84,6 +84,37 @@ Radi kao i svaki drugi provajder u OpenCode i potpuno je opcionalan za korišten --- +## OpenCode Go + +OpenCode Go je jeftin plan pretplate koji pruža pouzdan pristup popularnim modelima otvorenog kodiranja koje pruža OpenCode tim i koji su testirani i verificirani da dobro rade s OpenCode-om. + +1. Pokrenite naredbu `/connect` u TUI-u, odaberite `OpenCode Go` i idite na [opencode.ai/auth](https://opencode.ai/zen). + + ```txt + /connect + ``` + +2. Prijavite se, dodajte svoje detalje naplate i kopirajte svoj API ključ. + +3. Zalijepite svoj API ključ. + + ```txt + ┌ API key + │ + │ + └ enter + ``` + +4. Pokrenite naredbu `/models` u TUI da vidite listu modela koje preporučujemo. + + ```txt + /models + ``` + +Radi kao i svaki drugi provajder u OpenCode i potpuno je opcionalan za korištenje. + +--- + ## Direktorij Pogledajmo neke od provajdera detaljno. Ako želite dodati provajdera na @@ -1479,9 +1510,42 @@ Ove postavke su opcione i treba ih konfigurirati u skladu s vašim SAP AI Core p 5. Pokrenite naredbu `/models` da odaberete između 40+ dostupnih modela. -```txt + ```txt /models -``` + ``` + +--- + +### STACKIT + +STACKIT AI Model Serving pruža potpuno upravljano suvereno hosting okruženje za AI modele, fokusirajući se na LLM-ove kao što su Llama, Mistral i Qwen, uz maksimalan suverenitet podataka na evropskoj infrastrukturi. + +1. Idite na [STACKIT Portal](https://portal.stackit.cloud), idite na **AI Model Serving** i kreirajte token za autentifikaciju za svoj projekat. + + :::tip + Potreban vam je STACKIT korisnički račun, korisnički nalog i projekat prije kreiranja tokena za autentifikaciju. + ::: + +2. Pokrenite naredbu `/connect` i potražite **STACKIT**. + + ```txt + /connect + ``` + +3. Unesite svoj STACKIT AI Model Serving token za autentifikaciju. + + ```txt + ┌ API key + │ + │ + └ enter + ``` + +4. Pokrenite naredbu `/models` da odaberete dostupne modele kao što su _Qwen3-VL 235B_ ili _Llama 3.3 70B_. + + ```txt + /models + ``` --- diff --git a/packages/web/src/content/docs/bs/sdk.mdx b/packages/web/src/content/docs/bs/sdk.mdx index fe6ab4ef141..cc9c2b3bf43 100644 --- a/packages/web/src/content/docs/bs/sdk.mdx +++ b/packages/web/src/content/docs/bs/sdk.mdx @@ -117,6 +117,78 @@ try { --- +## Strukturirani izlaz + +Možete zatražiti strukturirani JSON izlaz od modela specificiranjem `format` sa JSON šemom. Model će koristiti `StructuredOutput` alat da vrati validirani JSON koji odgovara vašoj šemi. + +### Osnovna upotreba + +```typescript +const result = await client.session.prompt({ + path: { id: sessionId }, + body: { + parts: [{ type: "text", text: "Research Anthropic and provide company info" }], + format: { + type: "json_schema", + schema: { + type: "object", + properties: { + company: { type: "string", description: "Company name" }, + founded: { type: "number", description: "Year founded" }, + products: { + type: "array", + items: { type: "string" }, + description: "Main products", + }, + }, + required: ["company", "founded"], + }, + }, + }, +}) + +// Access the structured output +console.log(result.data.info.structured_output) +// { company: "Anthropic", founded: 2021, products: ["Claude", "Claude API"] } +``` + +### Tipovi formata izlaza + +| Tip | Opis | +| ------------- | ------------------------------------------------------------------- | +| `text` | Default. Standardni tekstualni odgovor (nema strukturiranog izlaza) | +| `json_schema` | Vraća validirani JSON koji odgovara pruženoj šemi | + +### Format JSON šeme + +Kada koristite `type: 'json_schema'`, navedite: + +| Polje | Tip | Opis | +| ------------ | --------------- | ----------------------------------------------------------- | +| `type` | `'json_schema'` | Obavezno. Određuje JSON schema način rada | +| `schema` | `object` | Obavezno. JSON Schema objekt koji definira strukturu izlaza | +| `retryCount` | `number` | Opcionalno. Broj ponovnih pokušaja validacije (default: 2) | + +### Rukovanje greškama + +Ako model ne uspije proizvesti validan strukturirani izlaz nakon svih ponovnih pokušaja, odgovor će uključivati `StructuredOutputError`: + +```typescript +if (result.data.info.error?.name === "StructuredOutputError") { + console.error("Failed to produce structured output:", result.data.info.error.message) + console.error("Attempts:", result.data.info.error.retries) +} +``` + +### Najbolje prakse + +1. **Navedite jasne opise** u svojstvima vaše šeme kako biste pomogli modelu da razumije koje podatke treba izdvojiti +2. **Koristite `required`** da odredite koja polja moraju biti prisutna +3. **Držite šeme fokusiranim** - složene ugniježđene šeme modelu mogu biti teže za ispravno popunjavanje +4. **Postavite odgovarajući `retryCount`** - povećajte za složene šeme, smanjite za jednostavne + +--- + ## API-ji SDK izlaže sve server API-je kroz type-safe klijent. @@ -226,27 +298,27 @@ const { providers, default: defaults } = await client.config.providers() ### Sesije -| Metoda | Opis | Napomene | -| ---------------------------------------------------------- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | -| `session.list()` | List sessions | Returns Session[] | -| `session.get({ path })` | Get session | Returns Session | -| `session.children({ path })` | List child sessions | Returns Session[] | -| `session.create({ body })` | Create session | Returns Session | -| `session.delete({ path })` | Delete session | Returns `boolean` | -| `session.update({ path, body })` | Update session properties | Returns Session | -| `session.init({ path, body })` | Analyze app and create `AGENTS.md` | Returns `boolean` | -| `session.abort({ path })` | Abort a running session | Returns `boolean` | -| `session.share({ path })` | Share session | Returns Session | -| `session.unshare({ path })` | Unshare session | Returns Session | -| `session.summarize({ path, body })` | Summarize session | Returns `boolean` | -| `session.messages({ path })` | List messages in a session | Returns `{ info: `Message`, parts: `Part[]`}[]` | -| `session.message({ path })` | Get message details | Returns `{ info: `Message`, parts: `Part[]`}` | -| `session.prompt({ path, body })` | Send prompt message | `body.noReply: true` returns UserMessage (context only). Default returns AssistantMessage with AI response | -| `session.command({ path, body })` | Send command to session | Returns `{ info: `AssistantMessage`, parts: `Part[]`}` | -| `session.shell({ path, body })` | Run a shell command | Returns AssistantMessage | -| `session.revert({ path, body })` | Revert a message | Returns Session | -| `session.unrevert({ path })` | Restore reverted messages | Returns Session | -| `postSessionByIdPermissionsByPermissionId({ path, body })` | Respond to a permission request | Returns `boolean` | +| Metoda | Opis | Napomene | +| ---------------------------------------------------------- | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `session.list()` | List sessions | Returns Session[] | +| `session.get({ path })` | Get session | Returns Session | +| `session.children({ path })` | List child sessions | Returns Session[] | +| `session.create({ body })` | Create session | Returns Session | +| `session.delete({ path })` | Delete session | Returns `boolean` | +| `session.update({ path, body })` | Update session properties | Returns Session | +| `session.init({ path, body })` | Analyze app and create `AGENTS.md` | Returns `boolean` | +| `session.abort({ path })` | Abort a running session | Returns `boolean` | +| `session.share({ path })` | Share session | Returns Session | +| `session.unshare({ path })` | Unshare session | Returns Session | +| `session.summarize({ path, body })` | Summarize session | Returns `boolean` | +| `session.messages({ path })` | List messages in a session | Returns `{ info: `Message`, parts: `Part[]`}[]` | +| `session.message({ path })` | Get message details | Returns `{ info: `Message`, parts: `Part[]`}` | +| `session.prompt({ path, body })` | Send prompt message | `body.noReply: true` returns UserMessage (context only). Default returns AssistantMessage with AI response. Supports `body.outputFormat` for [structured output](#strukturirani-izlaz) | +| `session.command({ path, body })` | Send command to session | Returns `{ info: `AssistantMessage`, parts: `Part[]`}` | +| `session.shell({ path, body })` | Run a shell command | Returns AssistantMessage | +| `session.revert({ path, body })` | Revert a message | Returns Session | +| `session.unrevert({ path })` | Restore reverted messages | Returns Session | +| `postSessionByIdPermissionsByPermissionId({ path, body })` | Respond to a permission request | Returns `boolean` | --- diff --git a/packages/web/src/content/docs/bs/server.mdx b/packages/web/src/content/docs/bs/server.mdx index 7d270fb4aae..5237873d5f7 100644 --- a/packages/web/src/content/docs/bs/server.mdx +++ b/packages/web/src/content/docs/bs/server.mdx @@ -86,116 +86,116 @@ opencode server izlaže sljedece API-je. ### Globalno -| Metoda | Putanja | Opis | Odgovor | -| ------ | ---------------- | ------------------------------ | ------------------------------------ | -| `GET` | `/global/health` | Get server health and version | `{ healthy: true, version: string }` | -| `GET` | `/global/event` | Get global events (SSE stream) | Event stream | +| Metoda | Putanja | Opis | Odgovor | +| ------ | ---------------- | ----------------------------------- | ------------------------------------ | +| `GET` | `/global/health` | Dohvati zdravlje i verziju servera | `{ healthy: true, version: string }` | +| `GET` | `/global/event` | Dohvati globalne događaje (SSE tok) | Event stream | --- ### Projekt -| Metoda | Putanja | Opis | Odgovor | -| ------ | ------------------ | ----------------------- | --------------------------------------------- | -| `GET` | `/project` | List all projects | Project[] | -| `GET` | `/project/current` | Get the current project | Project | +| Metoda | Putanja | Opis | Odgovor | +| ------ | ------------------ | ------------------------ | --------------------------------------------- | +| `GET` | `/project` | Izlistaj sve projekte | Project[] | +| `GET` | `/project/current` | Dohvati trenutni projekt | Project | --- ### Putanja i VCS -| Metoda | Putanja | Opis | Odgovor | -| ------ | ------- | ------------------------------------ | ------------------------------------------- | -| `GET` | `/path` | Get the current path | Path | -| `GET` | `/vcs` | Get VCS info for the current project | VcsInfo | +| Metoda | Putanja | Opis | Odgovor | +| ------ | ------- | ------------------------------------------- | ------------------------------------------- | +| `GET` | `/path` | Dohvati trenutnu putanju | Path | +| `GET` | `/vcs` | Dohvati VCS informacije za trenutni projekt | VcsInfo | --- ### Instanca -| Metoda | Putanja | Opis | Odgovor | -| ------ | ------------------- | ---------------------------- | --------- | -| `POST` | `/instance/dispose` | Dispose the current instance | `boolean` | +| Metoda | Putanja | Opis | Odgovor | +| ------ | ------------------- | ----------------------- | --------- | +| `POST` | `/instance/dispose` | Ugasi trenutnu instancu | `boolean` | --- ### Konfiguracija -| Metoda | Putanja | Opis | Odgovor | -| ------- | ------------------- | --------------------------------- | ---------------------------------------------------------------------------------------- | -| `GET` | `/config` | Get config info | Config | -| `PATCH` | `/config` | Update config | Config | -| `GET` | `/config/providers` | List providers and default models | `{ providers: `Provider[]`, default: { [key: string]: string } }` | +| Metoda | Putanja | Opis | Odgovor | +| ------- | ------------------- | ----------------------------------- | ---------------------------------------------------------------------------------------- | +| `GET` | `/config` | Dohvati informacije o konfiguraciji | Config | +| `PATCH` | `/config` | Ažuriraj konfiguraciju | Config | +| `GET` | `/config/providers` | Izlistaj provajdere i zadane modele | `{ providers: `Provider[]`, default: { [key: string]: string } }` | --- ### Provajder -| Metoda | Putanja | Opis | Odgovor | -| ------ | -------------------------------- | ------------------------------------ | ----------------------------------------------------------------------------------- | -| `GET` | `/provider` | List all providers | `{ all: `Provider[]`, default: {...}, connected: string[] }` | -| `GET` | `/provider/auth` | Get provider authentication methods | `{ [providerID: string]: `ProviderAuthMethod[]` }` | -| `POST` | `/provider/{id}/oauth/authorize` | Authorize a provider using OAuth | ProviderAuthAuthorization | -| `POST` | `/provider/{id}/oauth/callback` | Handle OAuth callback for a provider | `boolean` | +| Metoda | Putanja | Opis | Odgovor | +| ------ | -------------------------------- | ----------------------------------------- | ----------------------------------------------------------------------------------- | +| `GET` | `/provider` | Izlistaj sve provajdere | `{ all: `Provider[]`, default: {...}, connected: string[] }` | +| `GET` | `/provider/auth` | Dohvati metode autentifikacije provajdera | `{ [providerID: string]: `ProviderAuthMethod[]` }` | +| `POST` | `/provider/{id}/oauth/authorize` | Autoriziraj provajdera koristeći OAuth | ProviderAuthAuthorization | +| `POST` | `/provider/{id}/oauth/callback` | Obradi OAuth povratni poziv za provajdera | `boolean` | --- ### Sesije -| Metoda | Putanja | Opis | Napomene | -| -------- | ---------------------------------------- | ------------------------------------- | ---------------------------------------------------------------------------------- | -| `GET` | `/session` | List all sessions | Returns Session[] | -| `POST` | `/session` | Create a new session | body: `{ parentID?, title? }`, returns Session | -| `GET` | `/session/status` | Get session status for all sessions | Returns `{ [sessionID: string]: `SessionStatus` }` | -| `GET` | `/session/:id` | Get session details | Returns Session | -| `DELETE` | `/session/:id` | Delete a session and all its data | Returns `boolean` | -| `PATCH` | `/session/:id` | Update session properties | body: `{ title? }`, returns Session | -| `GET` | `/session/:id/children` | Get a session's child sessions | Returns Session[] | -| `GET` | `/session/:id/todo` | Get the todo list for a session | Returns Todo[] | -| `POST` | `/session/:id/init` | Analyze app and create `AGENTS.md` | body: `{ messageID, providerID, modelID }`, returns `boolean` | -| `POST` | `/session/:id/fork` | Fork an existing session at a message | body: `{ messageID? }`, returns Session | -| `POST` | `/session/:id/abort` | Abort a running session | Returns `boolean` | -| `POST` | `/session/:id/share` | Share a session | Returns Session | -| `DELETE` | `/session/:id/share` | Unshare a session | Returns Session | -| `GET` | `/session/:id/diff` | Get the diff for this session | query: `messageID?`, returns FileDiff[] | -| `POST` | `/session/:id/summarize` | Summarize the session | body: `{ providerID, modelID }`, returns `boolean` | -| `POST` | `/session/:id/revert` | Revert a message | body: `{ messageID, partID? }`, returns `boolean` | -| `POST` | `/session/:id/unrevert` | Restore all reverted messages | Returns `boolean` | -| `POST` | `/session/:id/permissions/:permissionID` | Respond to a permission request | body: `{ response, remember? }`, returns `boolean` | +| Metoda | Putanja | Opis | Napomene | +| -------- | ---------------------------------------- | ------------------------------------------- | ---------------------------------------------------------------------------------- | +| `GET` | `/session` | Izlistaj sve sesije | Returns Session[] | +| `POST` | `/session` | Kreiraj novu sesiju | body: `{ parentID?, title? }`, returns Session | +| `GET` | `/session/status` | Dohvati status sesije za sve sesije | Returns `{ [sessionID: string]: `SessionStatus` }` | +| `GET` | `/session/:id` | Dohvati detalje sesije | Returns Session | +| `DELETE` | `/session/:id` | Obriši sesiju i sve njene podatke | Returns `boolean` | +| `PATCH` | `/session/:id` | Ažuriraj svojstva sesije | body: `{ title? }`, returns Session | +| `GET` | `/session/:id/children` | Dohvati pod-sesije sesije | Returns Session[] | +| `GET` | `/session/:id/todo` | Dohvati listu zadataka za sesiju | Returns Todo[] | +| `POST` | `/session/:id/init` | Analiziraj aplikaciju i kreiraj `AGENTS.md` | body: `{ messageID, providerID, modelID }`, returns `boolean` | +| `POST` | `/session/:id/fork` | Granaj postojeću sesiju na poruci | body: `{ messageID? }`, returns Session | +| `POST` | `/session/:id/abort` | Prekini sesiju u toku | Returns `boolean` | +| `POST` | `/session/:id/share` | Podijeli sesiju | Returns Session | +| `DELETE` | `/session/:id/share` | Prestani dijeliti sesiju | Returns Session | +| `GET` | `/session/:id/diff` | Dohvati razlike za ovu sesiju | query: `messageID?`, returns FileDiff[] | +| `POST` | `/session/:id/summarize` | Rezimiraj sesiju | body: `{ providerID, modelID }`, returns `boolean` | +| `POST` | `/session/:id/revert` | Vrati poruku | body: `{ messageID, partID? }`, returns `boolean` | +| `POST` | `/session/:id/unrevert` | Vrati sve vraćene poruke | Returns `boolean` | +| `POST` | `/session/:id/permissions/:permissionID` | Odgovori na zahtjev za dozvolu | body: `{ response, remember? }`, returns `boolean` | --- ### Poruke -| Metoda | Putanja | Opis | Napomene | -| ------ | --------------------------------- | --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `GET` | `/session/:id/message` | List messages in a session | query: `limit?`, returns `{ info: `Message`, parts: `Part[]`}[]` | -| `POST` | `/session/:id/message` | Send a message and wait for response | body: `{ messageID?, model?, agent?, noReply?, system?, tools?, parts }`, returns `{ info: `Message`, parts: `Part[]`}` | -| `GET` | `/session/:id/message/:messageID` | Get message details | Returns `{ info: `Message`, parts: `Part[]`}` | -| `POST` | `/session/:id/prompt_async` | Send a message asynchronously (no wait) | body: same as `/session/:id/message`, returns `204 No Content` | -| `POST` | `/session/:id/command` | Execute a slash command | body: `{ messageID?, agent?, model?, command, arguments }`, returns `{ info: `Message`, parts: `Part[]`}` | -| `POST` | `/session/:id/shell` | Run a shell command | body: `{ agent, model?, command }`, returns `{ info: `Message`, parts: `Part[]`}` | +| Metoda | Putanja | Opis | Napomene | +| ------ | --------------------------------- | -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `GET` | `/session/:id/message` | Izlistaj poruke u sesiji | query: `limit?`, returns `{ info: `Message`, parts: `Part[]`}[]` | +| `POST` | `/session/:id/message` | Pošalji poruku i čekaj odgovor | body: `{ messageID?, model?, agent?, noReply?, system?, tools?, parts }`, returns `{ info: `Message`, parts: `Part[]`}` | +| `GET` | `/session/:id/message/:messageID` | Dohvati detalje poruke | Returns `{ info: `Message`, parts: `Part[]`}` | +| `POST` | `/session/:id/prompt_async` | Pošalji poruku asinkrono (bez čekanja) | body: same as `/session/:id/message`, returns `204 No Content` | +| `POST` | `/session/:id/command` | Izvrši slash naredbu | body: `{ messageID?, agent?, model?, command, arguments }`, returns `{ info: `Message`, parts: `Part[]`}` | +| `POST` | `/session/:id/shell` | Pokreni shell naredbu | body: `{ agent, model?, command }`, returns `{ info: `Message`, parts: `Part[]`}` | --- ### Naredbe -| Metoda | Putanja | Opis | Odgovor | -| ------ | ---------- | ----------------- | --------------------------------------------- | -| `GET` | `/command` | List all commands | Command[] | +| Metoda | Putanja | Opis | Odgovor | +| ------ | ---------- | -------------------- | --------------------------------------------- | +| `GET` | `/command` | Izlistaj sve naredbe | Command[] | --- ### Datoteke -| Metoda | Putanja | Opis | Odgovor | -| ------ | ------------------------ | ---------------------------------- | ------------------------------------------------------------------------------------------- | -| `GET` | `/find?pattern=` | Search for text in files | Array of match objects with `path`, `lines`, `line_number`, `absolute_offset`, `submatches` | -| `GET` | `/find/file?query=` | Find files and directories by name | `string[]` (paths) | -| `GET` | `/find/symbol?query=` | Find workspace symbols | Symbol[] | -| `GET` | `/file?path=` | List files and directories | FileNode[] | -| `GET` | `/file/content?path=

` | Read a file | FileContent | -| `GET` | `/file/status` | Get status for tracked files | File[] | +| Metoda | Putanja | Opis | Odgovor | +| ------ | ------------------------ | --------------------------------------- | ------------------------------------------------------------------------------------------- | +| `GET` | `/find?pattern=` | Traži tekst u datotekama | Array of match objects with `path`, `lines`, `line_number`, `absolute_offset`, `submatches` | +| `GET` | `/find/file?query=` | Pronađi datoteke i direktorije po imenu | `string[]` (paths) | +| `GET` | `/find/symbol?query=` | Pronađi simbole radnog prostora | Symbol[] | +| `GET` | `/file?path=` | Izlistaj datoteke i direktorije | FileNode[] | +| `GET` | `/file/content?path=

` | Pročitaj datoteku | FileContent | +| `GET` | `/file/status` | Dohvati status za praćene datoteke | File[] | #### `/find/file` parametri upita @@ -209,10 +209,10 @@ opencode server izlaže sljedece API-je. ### Alati (Eksperimentalno) -| Metoda | Putanja | Opis | Odgovor | -| ------ | ------------------------------------------- | ---------------------------------------- | -------------------------------------------- | -| `GET` | `/experimental/tool/ids` | List all tool IDs | ToolIDs | -| `GET` | `/experimental/tool?provider=

&model=` | List tools with JSON schemas for a model | ToolList | +| Metoda | Putanja | Opis | Odgovor | +| ------ | ------------------------------------------- | -------------------------------------- | -------------------------------------------- | +| `GET` | `/experimental/tool/ids` | Izlistaj sve ID-ove alata | ToolIDs | +| `GET` | `/experimental/tool?provider=

&model=` | Izlistaj alate sa JSON šemama za model | ToolList | --- @@ -220,65 +220,65 @@ opencode server izlaže sljedece API-je. | Metoda | Putanja | Opis | Odgovor | | ------ | ------------ | -------------------------- | -------------------------------------------------------- | -| `GET` | `/lsp` | Get LSP server status | LSPStatus[] | -| `GET` | `/formatter` | Get formatter status | FormatterStatus[] | -| `GET` | `/mcp` | Get MCP server status | `{ [name: string]: `MCPStatus` }` | -| `POST` | `/mcp` | Add MCP server dynamically | body: `{ name, config }`, returns MCP status object | +| `GET` | `/lsp` | Dohvati status LSP servera | LSPStatus[] | +| `GET` | `/formatter` | Dohvati status formatera | FormatterStatus[] | +| `GET` | `/mcp` | Dohvati status MCP servera | `{ [name: string]: `MCPStatus` }` | +| `POST` | `/mcp` | Dodaj MCP server dinamički | body: `{ name, config }`, returns MCP status object | --- ### Agenti -| Metoda | Putanja | Opis | Odgovor | -| ------ | -------- | ------------------------- | ------------------------------------------- | -| `GET` | `/agent` | List all available agents | Agent[] | +| Metoda | Putanja | Opis | Odgovor | +| ------ | -------- | ---------------------------- | ------------------------------------------- | +| `GET` | `/agent` | Izlistaj sve dostupne agente | Agent[] | --- ### Bilježenje -| Metoda | Putanja | Opis | Odgovor | -| ------ | ------- | ------------------------------------------------------------ | --------- | -| `POST` | `/log` | Write log entry. Body: `{ service, level, message, extra? }` | `boolean` | +| Metoda | Putanja | Opis | Odgovor | +| ------ | ------- | ------------------------------------------------------------------- | --------- | +| `POST` | `/log` | Upiši zapis dnevnika. Tijelo: `{ service, level, message, extra? }` | `boolean` | --- ### TUI -| Metoda | Putanja | Opis | Odgovor | -| ------ | ----------------------- | ------------------------------------------- | ---------------------- | -| `POST` | `/tui/append-prompt` | Append text to the prompt | `boolean` | -| `POST` | `/tui/open-help` | Open the help dialog | `boolean` | -| `POST` | `/tui/open-sessions` | Open the session selector | `boolean` | -| `POST` | `/tui/open-themes` | Open the theme selector | `boolean` | -| `POST` | `/tui/open-models` | Open the model selector | `boolean` | -| `POST` | `/tui/submit-prompt` | Submit the current prompt | `boolean` | -| `POST` | `/tui/clear-prompt` | Clear the prompt | `boolean` | -| `POST` | `/tui/execute-command` | Execute a command (`{ command }`) | `boolean` | -| `POST` | `/tui/show-toast` | Show toast (`{ title?, message, variant }`) | `boolean` | -| `GET` | `/tui/control/next` | Wait for the next control request | Control request object | -| `POST` | `/tui/control/response` | Respond to a control request (`{ body }`) | `boolean` | +| Metoda | Putanja | Opis | Odgovor | +| ------ | ----------------------- | -------------------------------------------------------- | ---------------------- | +| `POST` | `/tui/append-prompt` | Dodaj tekst na prompt | `boolean` | +| `POST` | `/tui/open-help` | Otvori dijalog za pomoć | `boolean` | +| `POST` | `/tui/open-sessions` | Otvori selektor sesija | `boolean` | +| `POST` | `/tui/open-themes` | Otvori selektor tema | `boolean` | +| `POST` | `/tui/open-models` | Otvori selektor modela | `boolean` | +| `POST` | `/tui/submit-prompt` | Pošalji trenutni prompt | `boolean` | +| `POST` | `/tui/clear-prompt` | Očisti prompt | `boolean` | +| `POST` | `/tui/execute-command` | Izvrši naredbu (`{ command }`) | `boolean` | +| `POST` | `/tui/show-toast` | Prikaži toast obavijest (`{ title?, message, variant }`) | `boolean` | +| `GET` | `/tui/control/next` | Čekaj sljedeći kontrolni zahtjev | Control request object | +| `POST` | `/tui/control/response` | Odgovori na kontrolni zahtjev (`{ body }`) | `boolean` | --- ### Autentifikacija -| Metoda | Putanja | Opis | Odgovor | -| ------ | ----------- | --------------------------------------------------------------- | --------- | -| `PUT` | `/auth/:id` | Set authentication credentials. Body must match provider schema | `boolean` | +| Metoda | Putanja | Opis | Odgovor | +| ------ | ----------- | ------------------------------------------------------------------------------- | --------- | +| `PUT` | `/auth/:id` | Postavi autentifikacijske vjerodajnice. Tijelo mora odgovarati shemi provajdera | `boolean` | --- ### Događaji -| Metoda | Putanja | Opis | Odgovor | -| ------ | -------- | ----------------------------------------------------------------------------- | ------------------------- | -| `GET` | `/event` | Server-sent events stream. First event is `server.connected`, then bus events | Server-sent events stream | +| Metoda | Putanja | Opis | Odgovor | +| ------ | -------- | -------------------------------------------------------------------------------------- | ------------------------- | +| `GET` | `/event` | Tok događaja koje šalje server. Prvi događaj je `server.connected`, zatim bus događaji | Server-sent events stream | --- ### Dokumentacija -| Metoda | Putanja | Opis | Odgovor | -| ------ | ------- | ------------------------- | --------------------------- | -| `GET` | `/doc` | OpenAPI 3.1 specification | HTML page with OpenAPI spec | +| Metoda | Putanja | Opis | Odgovor | +| ------ | ------- | ------------------------- | --------------------------------------- | +| `GET` | `/doc` | OpenAPI 3.1 specifikacija | HTML stranica sa OpenAPI specifikacijom | diff --git a/packages/web/src/content/docs/bs/themes.mdx b/packages/web/src/content/docs/bs/themes.mdx index a513e5a2092..edb17e6fa4a 100644 --- a/packages/web/src/content/docs/bs/themes.mdx +++ b/packages/web/src/content/docs/bs/themes.mdx @@ -61,11 +61,11 @@ System tema je za korisnike koji: ## Korištenje teme -Temu mozete izabrati preko selektora tema komandom `/theme`. Mozete je navesti i u [configu](/docs/config). +Temu mozete izabrati preko selektora tema komandom `/theme`. Ili je možete navesti u `tui.json`. -```json title="opencode.json" {3} +```json title="tui.json" {3} { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "theme": "tokyonight" } ``` diff --git a/packages/web/src/content/docs/bs/tui.mdx b/packages/web/src/content/docs/bs/tui.mdx index 96b075aa130..ef09a4caa02 100644 --- a/packages/web/src/content/docs/bs/tui.mdx +++ b/packages/web/src/content/docs/bs/tui.mdx @@ -235,7 +235,7 @@ Podijelite trenutnu sesiju. [Saznajte više](/docs/share). Navedite dostupne teme. ```bash frame="none" -/theme +/themes ``` **Tastatura:** `ctrl+x t` @@ -358,24 +358,34 @@ Nekim uređivačima su potrebni argumenti komandne linije da bi se pokrenuli u n ## Konfiguracija -Možete prilagoditi TUI ponašanje putem vašeg OpenCode konfiguracionog fajla. +Možete prilagoditi TUI ponašanje putem `tui.json` (ili `tui.jsonc`). -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", - "tui": { - "scroll_speed": 3, - "scroll_acceleration": { - "enabled": true - } - } + "$schema": "https://opencode.ai/tui.json", + "theme": "opencode", + "keybinds": { + "leader": "ctrl+x" + }, + "scroll_speed": 3, + "scroll_acceleration": { + "enabled": true + }, + "diff_style": "auto" } ``` +Ovo je odvojeno od `opencode.json`, koji konfiguriše ponašanje servera/izvršavanja. + ### Opcije -- `scroll_acceleration` - ​​Omogućite ubrzanje pomicanja u macOS stilu za glatko, prirodno pomicanje. Kada je omogućeno, brzina pomicanja se povećava brzim pokretima pomicanja i ostaje precizna za sporije pokrete. **Ova postavka ima prednost nad `scroll_speed` i nadjačava je kada je omogućena.** -- `scroll_speed` - ​​Kontrolira koliko brzo TUI skroluje kada se koriste komande za pomeranje (minimalno: `1`). Podrazumevano je `3`. **Napomena: Ovo se zanemaruje ako je `scroll_acceleration.enabled` postavljeno na `true`.** +- `theme` - Postavlja vašu UI temu. [Saznajte više](/docs/themes). +- `keybinds` - Prilagođava prečice na tastaturi. [Saznajte više](/docs/keybinds). +- `scroll_acceleration.enabled` - ​​Omogućite ubrzanje pomicanja u macOS stilu za glatko, prirodno pomicanje. Kada je omogućeno, brzina pomicanja se povećava brzim pokretima pomicanja i ostaje precizna za sporije pokrete. **Ova postavka ima prednost nad `scroll_speed` i nadjačava je kada je omogućena.** +- `scroll_speed` - ​​Kontrolira koliko brzo TUI skroluje kada se koriste komande za pomeranje (minimum: `0.001`, podržava decimalne vrijednosti). Podrazumevano je `3`. **Napomena: Ovo se zanemaruje ako je `scroll_acceleration.enabled` postavljeno na `true`.** +- `diff_style` - Kontrolira prikazivanje razlike. `"auto"` se prilagođava širini terminala, `"stacked"` uvijek prikazuje raspored u jednoj koloni. + +Koristite `OPENCODE_TUI_CONFIG` da učitate prilagođenu putanju TUI konfiguracije. --- diff --git a/packages/web/src/content/docs/bs/zen.mdx b/packages/web/src/content/docs/bs/zen.mdx index f9733370861..ad428884d33 100644 --- a/packages/web/src/content/docs/bs/zen.mdx +++ b/packages/web/src/content/docs/bs/zen.mdx @@ -55,6 +55,7 @@ Nasim modelima mozete pristupiti i preko sljedecih API endpointa. | Model | Model ID | Endpoint | AI SDK Package | | ------------------ | ------------------ | -------------------------------------------------- | --------------------------- | +| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | @@ -64,22 +65,24 @@ Nasim modelima mozete pristupiti i preko sljedecih API endpointa. | GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | | Gemini 3 Pro | gemini-3-pro | `https://opencode.ai/zen/v1/models/gemini-3-pro` | `@ai-sdk/google` | | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.1 | minimax-m2.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.1 Free | minimax-m2.1-free | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| GLM 5 | glm-5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 4.7 | glm-4.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM 4.7 Free | glm-4.7-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 4.6 | glm-4.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 Free | kimi-k2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2 Thinking | kimi-k2-thinking | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2 | kimi-k2 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Qwen3 Coder 480B | qwen3-coder | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -106,29 +109,34 @@ Podrzavamo pay-as-you-go model. Ispod su cijene **po 1M tokena**. | Model | Input | Output | Cached Read | Cached Write | | --------------------------------- | ------ | ------ | ----------- | ------------ | | Big Pickle | Free | Free | Free | - | -| MiniMax M2.1 Free | Free | Free | Free | - | +| MiniMax M2.5 Free | Free | Free | Free | - | +| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | - | | MiniMax M2.1 | $0.30 | $1.20 | $0.10 | - | -| GLM 4.7 Free | Free | Free | Free | - | +| GLM 5 | $1.00 | $3.20 | $0.20 | - | | GLM 4.7 | $0.60 | $2.20 | $0.10 | - | | GLM 4.6 | $0.60 | $2.20 | $0.10 | - | -| Kimi K2.5 Free | Free | Free | Free | - | | Kimi K2.5 | $0.60 | $3.00 | $0.08 | - | | Kimi K2 Thinking | $0.40 | $2.50 | - | - | | Kimi K2 | $0.40 | $2.50 | - | - | | Qwen3 Coder 480B | $0.45 | $1.50 | - | - | +| Claude Opus 4.6 (≤ 200K tokens) | $5.00 | $25.00 | $0.50 | $6.25 | +| Claude Opus 4.6 (> 200K tokens) | $10.00 | $37.50 | $1.00 | $12.50 | +| Claude Opus 4.5 | $5.00 | $25.00 | $0.50 | $6.25 | +| Claude Opus 4.1 | $15.00 | $75.00 | $1.50 | $18.75 | +| Claude Sonnet 4.6 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 | +| Claude Sonnet 4.6 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 | | Claude Sonnet 4.5 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 | | Claude Sonnet 4.5 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 | | Claude Sonnet 4 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 | | Claude Sonnet 4 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 | | Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 | | Claude Haiku 3.5 | $0.80 | $4.00 | $0.08 | $1.00 | -| Claude Opus 4.6 (≤ 200K tokens) | $5.00 | $25.00 | $0.50 | $6.25 | -| Claude Opus 4.6 (> 200K tokens) | $10.00 | $37.50 | $1.00 | $12.50 | -| Claude Opus 4.5 | $5.00 | $25.00 | $0.50 | $6.25 | -| Claude Opus 4.1 | $15.00 | $75.00 | $1.50 | $18.75 | +| Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | +| Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | +| GPT 5.3 Codex | $1.75 | $14.00 | $0.175 | - | | GPT 5.2 | $1.75 | $14.00 | $0.175 | - | | GPT 5.2 Codex | $1.75 | $14.00 | $0.175 | - | | GPT 5.1 | $1.07 | $8.50 | $0.107 | - | @@ -147,10 +155,8 @@ Naknade kartica se prenose po stvarnom trosku (4.4% + $0.30 po transakciji) i ne Besplatni modeli: -- GLM 4.7 Free je dostupan na OpenCode ograniceno vrijeme. Tim koristi taj period za prikupljanje feedbacka i unapredenje modela. -- Kimi K2.5 Free je dostupan na OpenCode ograniceno vrijeme. Tim koristi taj period za prikupljanje feedbacka i unapredenje modela. -- MiniMax M2.1 Free je dostupan na OpenCode ograniceno vrijeme. Tim koristi taj period za prikupljanje feedbacka i unapredenje modela. -- Big Pickle je stealth model koji je besplatan na OpenCode ograniceno vrijeme. Tim koristi taj period za prikupljanje feedbacka i unapredenje modela. +- MiniMax M2.5 Free je dostupan na OpenCode ograniceno vrijeme. Tim koristi taj period za prikupljanje povratnih informacija i poboljsanje modela. +- Big Pickle je stealth model koji je besplatan na OpenCode ograniceno vrijeme. Tim koristi taj period za prikupljanje povratnih informacija i poboljsanje modela. Ako imate pitanja, kontaktirajte nas. @@ -177,11 +183,9 @@ Na primjer, ako postavite mjesecni limit na $20, Zen nece potrositi vise od $20 Svi nasi modeli su hostovani u SAD-u. Provajderi prate zero-retention politiku i ne koriste vase podatke za treniranje modela, uz sljedece izuzetke: - Big Pickle: Tokom besplatnog perioda, prikupljeni podaci mogu se koristiti za poboljsanje modela. -- GLM 4.7 Free: Tokom besplatnog perioda, prikupljeni podaci mogu se koristiti za poboljsanje modela. -- Kimi K2.5 Free: Tokom besplatnog perioda, prikupljeni podaci mogu se koristiti za poboljsanje modela. -- MiniMax M2.1 Free: Tokom besplatnog perioda, prikupljeni podaci mogu se koristiti za poboljsanje modela. -- OpenAI API-ji: Zahtjevi se cuvaju 30 dana prema [OpenAI Data Policies](https://platform.openai.com/docs/guides/your-data). -- Anthropic API-ji: Zahtjevi se cuvaju 30 dana prema [Anthropic Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage). +- MiniMax M2.5 Free: Tokom besplatnog perioda, prikupljeni podaci mogu se koristiti za poboljsanje modela. +- OpenAI API-ji: Zahtjevi se cuvaju 30 dana prema [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). +- Anthropic API-ji: Zahtjevi se cuvaju 30 dana prema [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage). --- diff --git a/packages/web/src/content/docs/da/cli.mdx b/packages/web/src/content/docs/da/cli.mdx index c29559df261..5eece6fd0ae 100644 --- a/packages/web/src/content/docs/da/cli.mdx +++ b/packages/web/src/content/docs/da/cli.mdx @@ -558,6 +558,7 @@ OpenCode kan konfigureres ved hjælp af miljøvariabler. | `OPENCODE_AUTO_SHARE` | boolean | Del automatisk session | | `OPENCODE_GIT_BASH_PATH` | string | Sti til Git Bash eksekverbar på Windows | | `OPENCODE_CONFIG` | string | Sti til konfigurationsfil | +| `OPENCODE_TUI_CONFIG` | string | Sti til TUI-konfigurationsfil | | `OPENCODE_CONFIG_DIR` | string | Sti til konfigurationsmappe | | `OPENCODE_CONFIG_CONTENT` | string | Indbygget json-konfigurationsindhold | | `OPENCODE_DISABLE_AUTOUPDATE` | boolean | Deaktiver automatisk opdateringskontrol | @@ -582,7 +583,7 @@ OpenCode kan konfigureres ved hjælp af miljøvariabler. --- -### Flag +### Eksperimentel Disse miljøvariabler muliggør eksperimentelle funktioner, der kan ændres eller fjernes. diff --git a/packages/web/src/content/docs/da/config.mdx b/packages/web/src/content/docs/da/config.mdx index 5b45f8b0486..18b462580b7 100644 --- a/packages/web/src/content/docs/da/config.mdx +++ b/packages/web/src/content/docs/da/config.mdx @@ -14,10 +14,11 @@ OpenCode understøtter både **JSON** og **JSONC** (JSON med kommentarer) format ```jsonc title="opencode.jsonc" { "$schema": "https://opencode.ai/config.json", - // Theme configuration - "theme": "opencode", "model": "anthropic/claude-sonnet-4-5", "autoupdate": true, + "server": { + "port": 4096, + }, } ``` @@ -34,7 +35,7 @@ Konfigurationsfiler **flettes sammen**, erstattes ikke. Konfigurationsfiler flettes sammen, erstattes ikke. Indstillinger fra følgende konfigurationssteder kombineret. Senere konfigurationer tilsidesætter kun tidligere konfigurationer for modstridende nøgler. Ikke-modstridende indstillinger fra alle konfigurationer bevares. -For eksempel, hvis dine globale konfigurationssæt `theme: "opencode"` og `autoupdate: true`, og dine projektkonfigurationssæt `model: "anthropic/claude-sonnet-4-5"`, vil den endelige konfiguration integrere alle tre indstillinger. +For eksempel, hvis dine globale konfigurationssæt `autoupdate: true`, og dine projektkonfigurationssæt `model: "anthropic/claude-sonnet-4-5"`, vil den endelige konfiguration integrere begge indstillinger. --- @@ -490,13 +491,15 @@ Du kan styre kontekstkomprimeringsadfærd gennem indstillingen `compaction`. "$schema": "https://opencode.ai/config.json", "compaction": { "auto": true, - "prune": true + "prune": true, + "reserved": 10000 } } ``` - `auto` - Komprimer automatisk sessionen, når konteksten er fuld (standard: `true`). - `prune` - Fjern gamle værktøjsudgange for at gemme tokens (standard: `true`). +- `reserved` - Tokenbuffer til komprimering. Efterlader nok vindue til at undgå overløb under komprimering --- @@ -582,8 +585,8 @@ Du kan deaktivere udbydere, der indlæses automatisk gennem `disabled_providers` Indstillingen `disabled_providers` accepterer en række udbyder-id'er. Når en udbyder er deaktiveret: -- Det vil ikke blive indlæst, omgivelserne miljøvariabler er indstillet. -- Den vil ikke blive indlæst, gennem API-nøgler er konfigureret kommandoen `/connect`. +- Den vil ikke blive indlæst, selvom miljøvariabler er indstillet. +- Den vil ikke blive indlæst, selvom API-nøgler er konfigureret via `/connect`-kommandoen. - Udbyderens modeller vises ikke på modelvalgslisten. --- diff --git a/packages/web/src/content/docs/da/custom-tools.mdx b/packages/web/src/content/docs/da/custom-tools.mdx index b0839b649c7..75746dcb49d 100644 --- a/packages/web/src/content/docs/da/custom-tools.mdx +++ b/packages/web/src/content/docs/da/custom-tools.mdx @@ -79,6 +79,32 @@ Dette skaber værktøjer: `math_add` og `math_multiply`. --- +#### Navnekollisioner med indbyggede værktøjer + +Brugerdefinerede værktøjer er identificeret ved værktøjsnavn. Hvis et brugerdefineret værktøj bruger samme navn som et indbygget værktøj, har det brugerdefinerede værktøj forrang. + +For eksempel erstatter denne fil det indbyggede `bash` værktøj: + +```ts title=".opencode/tools/bash.ts" +import { tool } from "@opencode-ai/plugin" + +export default tool({ + description: "Restricted bash wrapper", + args: { + command: tool.schema.string(), + }, + async execute(args) { + return `blocked: ${args.command}` + }, +}) +``` + +:::note +Foretræk unikke navne, medmindre du bevidst ønsker at erstatte et indbygget værktøj. Hvis du vil deaktivere et indbygget værktøj, men ikke overskrive det, skal du bruge [tilladelser](/docs/permissions). +::: + +--- + ### Argumenter Du kan bruge `tool.schema`, som kun er [Zod](https://zod.dev), til at definere argumenttyper. diff --git a/packages/web/src/content/docs/da/ecosystem.mdx b/packages/web/src/content/docs/da/ecosystem.mdx index 8da7e42d10a..e7111a5196c 100644 --- a/packages/web/src/content/docs/da/ecosystem.mdx +++ b/packages/web/src/content/docs/da/ecosystem.mdx @@ -15,38 +15,39 @@ Du kan også tjekke [awesome-opencode](https://github.com/awesome-opencode/aweso ## Plugins -| Navn | Beskrivelse | -| --------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | -| [opencode-daytona](https://github.com/jamesmurdza/daytona/blob/main/guides/typescript/opencode/README.md) | Kør automatisk OpenCode-sessioner i isolerede Daytona-sandkasser med git-synkronisering og live forhåndsvisninger | -| [opencode-helicone-session](https://github.com/H2Shami/opencode-helicone-session) | Injicer automatisk Helicone-sessionsoverskrifter til anmodningsgruppering | -| [opencode-type-inject](https://github.com/nick-vi/opencode-type-inject) | Autoinjicer TypeScript/Svelte-typer i fillæsninger med opslagsværktøjer | -| [opencode-openai-codex-auth](https://github.com/numman-ali/opencode-openai-codex-auth) | Brug dit ChatGPT Plus/Pro abonnement i stedet for API kreditter | -| [opencode-gemini-auth](https://github.com/jenslys/opencode-gemini-auth) | Brug din eksisterende Gemini-plan i stedet for API fakturering | -| [opencode-antigravity-auth](https://github.com/NoeFabris/opencode-antigravity-auth) | Brug Antigravitys gratis modeller i stedet for API fakturering | -| [opencode-devcontainers](https://github.com/athal7/opencode-devcontainers) | Multi-branch devcontainer isolation med lavvandede kloner og automatisk tildelte porte | -| [opencode-google-antigravity-auth](https://github.com/shekohex/opencode-google-antigravity-auth) | Google Antigravity OAuth Plugin, med understøttelse af Google søgning og mere robust API håndtering | -| [opencode-dynamic-context-pruning](https://github.com/Tarquinen/opencode-dynamic-context-pruning) | Optimer tokenbrug ved at beskære forældede værktøjsoutput | -| [opencode-websearch-cited](https://github.com/ghoulr/opencode-websearch-cited.git) | Tilføj native websearch-understøttelse for understøttede udbydere med Google jordet stil | -| [opencode-pty](https://github.com/shekohex/opencode-pty.git) | Gør det muligt for AI-agenter at køre baggrundsprocesser i en PTY, send interaktive input til dem. | -| [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Instruktioner til ikke-interaktive shell-kommandoer - forhindrer hænger fra TTY-afhængige operationer | -| [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Spor OpenCode brug med Wakatime | -| [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Ryd op afmærkningstabeller produceret af LLMs | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | 10x hurtigere koderedigering med Morph Fast Apply API og dovne redigeringsmarkører | -| [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Baggrundsagenter, præbyggede LSP/AST/MCP værktøjer, kuraterede agenter, Claude Kodekompatibel | -| [opencode-notificator](https://github.com/panta82/opencode-notificator) | Skrivebordsmeddelelser og lydadvarsler for OpenCode-sessioner | -| [opencode-notifier](https://github.com/mohak34/opencode-notifier) | Skrivebordsmeddelelser og lydadvarsler for tilladelser, fuldførelse og fejlhændelser | -| [opencode-zellij-namer](https://github.com/24601/opencode-zellij-namer) | AI-drevet automatisk Zellij-sessionsnavngivning baseret på OpenCode-kontekst | -| [opencode-skillful](https://github.com/zenobi-us/opencode-skillful) | Tillad OpenCode-agenter til dovne load-prompter på efterspørgsel med færdighedsopdagelse og -injektion | -| [opencode-supermemory](https://github.com/supermemoryai/opencode-supermemory) | Vedvarende hukommelse på tværs af sessioner ved hjælp af Supermemory | -| [@plannotator/opencode](https://github.com/backnotprop/plannotator/tree/main/apps/opencode-plugin) | Interaktiv plangennemgang med visuel annotering og private/offline deling | -| [@openspoon/subtask2](https://github.com/spoons-and-mirrors/subtask2) | Udvid opencode /commands til et kraftfuldt orkestreringssystem med granulær flowkontrol | -| [opencode-scheduler](https://github.com/different-ai/opencode-scheduler) | Planlæg tilbagevendende job ved hjælp af launchd (Mac) eller systemd (Linux) med cron-syntaks | -| [micode](https://github.com/vtemian/micode) | Struktureret brainstorm → Plan → Implementer workflow med session kontinuitet | -| [octto](https://github.com/vtemian/octto) | Interaktiv browser-UI til AI-brainstorming med formularer med flere spørgsmål | -| [opencode-background-agents](https://github.com/kdcokenny/opencode-background-agents) | Claude Baggrundsagenter i kodestil med asynkron-delegering og kontekstvedholdenhed | -| [opencode-notify](https://github.com/kdcokenny/opencode-notify) | Native OS-meddelelser for OpenCode – ved, hvornår opgaver er fuldført | -| [opencode-workspace](https://github.com/kdcokenny/opencode-workspace) | Bundled multi-agent orkestreringssele – 16 komponenter, én installation | -| [opencode-worktree](https://github.com/kdcokenny/opencode-worktree) | Nulfriktions git-arbejdstræer for OpenCode | +| Navn | Beskrivelse | +| -------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | +| [opencode-daytona](https://github.com/daytonaio/daytona/tree/main/libs/opencode-plugin) | Kør automatisk OpenCode-sessioner i isolerede Daytona-sandkasser med git-synkronisering og live forhåndsvisninger | +| [opencode-helicone-session](https://github.com/H2Shami/opencode-helicone-session) | Injicer automatisk Helicone-sessionsoverskrifter til anmodningsgruppering | +| [opencode-type-inject](https://github.com/nick-vi/opencode-type-inject) | Autoinjicer TypeScript/Svelte-typer i fillæsninger med opslagsværktøjer | +| [opencode-openai-codex-auth](https://github.com/numman-ali/opencode-openai-codex-auth) | Brug dit ChatGPT Plus/Pro abonnement i stedet for API kreditter | +| [opencode-gemini-auth](https://github.com/jenslys/opencode-gemini-auth) | Brug din eksisterende Gemini-plan i stedet for API fakturering | +| [opencode-antigravity-auth](https://github.com/NoeFabris/opencode-antigravity-auth) | Brug Antigravitys gratis modeller i stedet for API fakturering | +| [opencode-devcontainers](https://github.com/athal7/opencode-devcontainers) | Multi-branch devcontainer isolation med lavvandede kloner og automatisk tildelte porte | +| [opencode-google-antigravity-auth](https://github.com/shekohex/opencode-google-antigravity-auth) | Google Antigravity OAuth Plugin, med understøttelse af Google søgning og mere robust API håndtering | +| [opencode-dynamic-context-pruning](https://github.com/Tarquinen/opencode-dynamic-context-pruning) | Optimer tokenbrug ved at beskære forældede værktøjsoutput | +| [opencode-vibeguard](https://github.com/inkdust2021/opencode-vibeguard) | Masker hemmeligheder/PII til pladsholdere i VibeGuard-stil før LLM-kald; gendan lokalt | +| [opencode-websearch-cited](https://github.com/ghoulr/opencode-websearch-cited.git) | Tilføj native websearch-understøttelse for understøttede udbydere med Google jordet stil | +| [opencode-pty](https://github.com/shekohex/opencode-pty.git) | Gør det muligt for AI-agenter at køre baggrundsprocesser i en PTY, send interaktive input til dem. | +| [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Instruktioner til ikke-interaktive shell-kommandoer - forhindrer hænger fra TTY-afhængige operationer | +| [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Spor OpenCode brug med Wakatime | +| [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Ryd op afmærkningstabeller produceret af LLMs | +| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | 10x hurtigere koderedigering med Morph Fast Apply API og dovne redigeringsmarkører | +| [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Baggrundsagenter, præbyggede LSP/AST/MCP værktøjer, kuraterede agenter, Claude Kodekompatibel | +| [opencode-notificator](https://github.com/panta82/opencode-notificator) | Skrivebordsmeddelelser og lydadvarsler for OpenCode-sessioner | +| [opencode-notifier](https://github.com/mohak34/opencode-notifier) | Skrivebordsmeddelelser og lydadvarsler for tilladelser, fuldførelse og fejlhændelser | +| [opencode-zellij-namer](https://github.com/24601/opencode-zellij-namer) | AI-drevet automatisk Zellij-sessionsnavngivning baseret på OpenCode-kontekst | +| [opencode-skillful](https://github.com/zenobi-us/opencode-skillful) | Tillad OpenCode-agenter til dovne load-prompter på efterspørgsel med færdighedsopdagelse og -injektion | +| [opencode-supermemory](https://github.com/supermemoryai/opencode-supermemory) | Vedvarende hukommelse på tværs af sessioner ved hjælp af Supermemory | +| [@plannotator/opencode](https://github.com/backnotprop/plannotator/tree/main/apps/opencode-plugin) | Interaktiv plangennemgang med visuel annotering og private/offline deling | +| [@openspoon/subtask2](https://github.com/spoons-and-mirrors/subtask2) | Udvid opencode /commands til et kraftfuldt orkestreringssystem med granulær flowkontrol | +| [opencode-scheduler](https://github.com/different-ai/opencode-scheduler) | Planlæg tilbagevendende job ved hjælp af launchd (Mac) eller systemd (Linux) med cron-syntaks | +| [micode](https://github.com/vtemian/micode) | Struktureret brainstorm → Plan → Implementer workflow med session kontinuitet | +| [octto](https://github.com/vtemian/octto) | Interaktiv browser-UI til AI-brainstorming med formularer med flere spørgsmål | +| [opencode-background-agents](https://github.com/kdcokenny/opencode-background-agents) | Claude Baggrundsagenter i kodestil med asynkron-delegering og kontekstvedholdenhed | +| [opencode-notify](https://github.com/kdcokenny/opencode-notify) | Native OS-meddelelser for OpenCode – ved, hvornår opgaver er fuldført | +| [opencode-workspace](https://github.com/kdcokenny/opencode-workspace) | Bundled multi-agent orkestreringssele – 16 komponenter, én installation | +| [opencode-worktree](https://github.com/kdcokenny/opencode-worktree) | Nulfriktions git-arbejdstræer for OpenCode | --- diff --git a/packages/web/src/content/docs/da/go.mdx b/packages/web/src/content/docs/da/go.mdx new file mode 100644 index 00000000000..4d6ca61accf --- /dev/null +++ b/packages/web/src/content/docs/da/go.mdx @@ -0,0 +1,145 @@ +--- +title: Go +description: Lavprisabonnement for åbne kodningsmodeller. +--- + +import config from "../../../../config.mjs" +export const console = config.console +export const email = `mailto:${config.email}` + +OpenCode Go er et billigt **$10/måned** abonnement, der giver dig pålidelig adgang til populære åbne kodningsmodeller. + +:::note +OpenCode Go er i øjeblikket i beta. +::: + +Go fungerer ligesom enhver anden udbyder i OpenCode. Du abonnerer på OpenCode Go og får din API-nøgle. Det er **helt valgfrit**, og du behøver ikke at bruge det for at bruge OpenCode. + +Det er primært designet til internationale brugere, med modeller hostet i USA, EU og Singapore for stabil global adgang. + +--- + +## Baggrund + +Åbne modeller er blevet virkelig gode. De når nu en ydeevne tæt på proprietære modeller til kodningsopgaver. Og fordi mange udbydere kan tilbyde dem konkurrencedygtigt, er de normalt langt billigere. + +Det kan dog være svært at få pålidelig adgang med lav latenstid til dem. Udbydere varierer i kvalitet og tilgængelighed. + +:::tip +Vi testede en udvalgt gruppe af modeller og udbydere, der fungerer godt med OpenCode. +::: + +For at løse dette gjorde vi et par ting: + +1. Vi testede en udvalgt gruppe af åbne modeller og talte med deres teams om, hvordan man bedst kører dem. +2. Vi arbejdede derefter sammen med nogle få udbydere for at sikre, at disse blev leveret korrekt. +3. Endelig benchmarkede vi kombinationen af model/udbyder og kom frem til en liste, som vi har det godt med at anbefale. + +OpenCode Go giver dig adgang til disse modeller for **$10/måned**. + +--- + +## Sådan fungerer det + +OpenCode Go fungerer ligesom enhver anden udbyder i OpenCode. + +1. Du logger ind på **OpenCode Zen**, abonnerer på Go, og kopierer din API-nøgle. +2. Du kører kommandoen `/connect` i TUI'en, vælger `OpenCode Go`, og indsætter din API-nøgle. +3. Kør `/models` i TUI'en for at se listen over modeller, der er tilgængelige gennem Go. + +:::note +Kun ét medlem pr. workspace kan abonnere på OpenCode Go. +::: + +Den nuværende liste over modeller inkluderer: + +- **GLM-5** +- **Kimi K2.5** +- **MiniMax M2.5** + +Listen over modeller kan ændre sig, efterhånden som vi tester og tilføjer nye. + +--- + +## Forbrugsgrænser + +OpenCode Go inkluderer følgende grænser: + +- **5 timers grænse** — $12 forbrug +- **Ugentlig grænse** — $30 forbrug +- **Månedlig grænse** — $60 forbrug + +Grænser er defineret i dollarværdi. Det betyder, at dit faktiske antal forespørgsler afhænger af den model, du bruger. Billigere modeller som MiniMax M2.5 tillader flere forespørgsler, mens dyrere modeller som GLM-5 tillader færre. + +Tabellen nedenfor giver et estimeret antal forespørgsler baseret på typiske Go-brugsmønstre: + +| | GLM-5 | Kimi K2.5 | MiniMax M2.5 | +| ------------------------- | ----- | --------- | ------------ | +| forespørgsler pr. 5 timer | 1.150 | 1.850 | 30.000 | +| forespørgsler pr. uge | 2.880 | 4.630 | 75.000 | +| forespørgsler pr. måned | 5.750 | 9.250 | 150.000 | + +Estimater er baseret på observerede gennemsnitlige forespørgselsmønstre: + +- GLM-5 — 700 input, 52.000 cached, 150 output tokens pr. forespørgsel +- Kimi K2.5 — 870 input, 55.000 cached, 200 output tokens pr. forespørgsel +- MiniMax M2.5 — 300 input, 55.000 cached, 125 output tokens pr. forespørgsel + +Du kan spore dit nuværende forbrug i **konsollen**. + +:::tip +Hvis du når forbrugsgrænsen, kan du fortsætte med at bruge de gratis modeller. +::: + +Forbrugsgrænser kan ændre sig, efterhånden som vi lærer fra tidlig brug og feedback. + +--- + +### Priser + +OpenCode Go er en **$10/måned** abonnementsplan. Nedenfor er priserne **pr. 1M tokens**. + +| Model | Input | Output | Cached Læsning | +| ------------ | ----- | ------ | -------------- | +| GLM-5 | $1,00 | $3,20 | $0,20 | +| Kimi K2.5 | $0,60 | $3,00 | $0,10 | +| MiniMax M2.5 | $0,30 | $1,20 | $0,03 | + +--- + +### Forbrug ud over grænser + +Hvis du også har kreditter på din Zen-saldo, kan du aktivere **Brug saldo**-indstillingen i konsollen. Når den er aktiveret, vil Go falde tilbage på din Zen-saldo, efter du har nået dine forbrugsgrænser, i stedet for at blokere forespørgsler. + +--- + +## Endepunkter + +Du kan også få adgang til Go-modeller gennem følgende API-endepunkter. + +| Model | Model ID | Endpoint | AI SDK Pakke | +| ------------ | ------------ | ------------------------------------------------ | --------------------------- | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | + +[Model-id'et](/docs/config/#models) i din OpenCode-konfiguration bruger formatet `opencode-go/`. For eksempel, for Kimi K2.5, ville du bruge `opencode-go/kimi-k2.5` i din konfiguration. + +--- + +## Privatliv + +Planen er primært designet til internationale brugere, med modeller hostet i USA, EU og Singapore for stabil global adgang. + +Kontakt os hvis du har spørgsmål. + +--- + +## Mål + +Vi skabte OpenCode Go for at: + +1. Gøre AI-kodning **tilgængelig** for flere mennesker med et billigt abonnement. +2. Tilbyde **pålidelig** adgang til de bedste åbne kodningsmodeller. +3. Udvælge modeller, der er **testet og benchmarked** til brug med kodningsagenter. +4. Have **ingen lock-in** ved at tillade dig også at bruge enhver anden udbyder med OpenCode. diff --git a/packages/web/src/content/docs/da/keybinds.mdx b/packages/web/src/content/docs/da/keybinds.mdx index 2da3ff9426c..9c066fcb49c 100644 --- a/packages/web/src/content/docs/da/keybinds.mdx +++ b/packages/web/src/content/docs/da/keybinds.mdx @@ -3,11 +3,11 @@ title: Tastebindinger description: Tilpas dine nøglebindinger. --- -OpenCode har en liste over nøglebindinger, som du kan tilpasse gennem OpenCode-konfigurationen. +OpenCode har en liste over nøglebindinger, som du kan tilpasse gennem `tui.json`. -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "keybinds": { "leader": "ctrl+x", "app_exit": "ctrl+c,ctrl+d,q", @@ -117,11 +117,11 @@ Du behøver ikke bruge en ledernøgle til dine nøglebindinger, men vi anbefaler ## Deaktiver tastebinding -Du kan deaktivere en nøglebinding ved at tilføje nøglen til din konfiguration med værdien "ingen". +Du kan deaktivere en nøglebinding ved at tilføje nøglen til `tui.json` med værdien "none". -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "keybinds": { "session_compact": "none" } diff --git a/packages/web/src/content/docs/da/lsp.mdx b/packages/web/src/content/docs/da/lsp.mdx index b0e0d353723..27d43b192b8 100644 --- a/packages/web/src/content/docs/da/lsp.mdx +++ b/packages/web/src/content/docs/da/lsp.mdx @@ -20,13 +20,14 @@ OpenCode leveres med flere indbyggede LSP-servere til populære sprog: | clojure-lsp | .clj,.cljs,.cljc,.edn | `clojure-lsp` kommando tilgængelig | | dart | .dart | `dart` kommando tilgængelig | | deno | .ts,.tsx,.js,.jsx,.mjs | `deno` kommando tilgængelig (auto-detects deno.json/deno.jsonc) | -| eliksir-ls | .ex,.exs | `elixir` kommando tilgængelig | +| elixir-ls | .ex,.exs | `elixir` kommando tilgængelig | | eslint | .ts,.tsx,.js,.jsx,.mjs,.cjs,.mts,.cts,.vue | `eslint` afhængighed i projekt | | fsharp | .fs,.fsi,.fsx,.fsscript | `.NET SDK` installere | | gleam | .gleam | `gleam` kommando tilgængelig | | gopls | .go | `go` kommando tilgængelig | | hls | .hs,.lhs | `haskell-language-server-wrapper` kommando tilgængelig | | jdtls | .java | `Java SDK (version 21+)` installere | +| julials | .jl | `julia` og `LanguageServer.jl` installeret | | kotlin-ls | .kt,.kts | Autoinstallationer til Kotlin-projekter | | lua-ls | .lua | Autoinstallationer til Lua-projekter | | nixd | .nix | `nixd` kommando tilgængelig | @@ -46,7 +47,7 @@ OpenCode leveres med flere indbyggede LSP-servere til populære sprog: | yaml-ls | .yaml,.yml | Autoinstallerer Red Hat yaml-language-server | | zls | .zig,.zon | `zig` kommando tilgængelig | -LSP-servere aktiveres automatisk, når en af ​​ovnstående filtypenavne opdages, og kravene er opfyldt. +LSP-servere aktiveres automatisk, når en af ​​ovenstående filtypenavne opdages, og kravene er opfyldt. :::note Du kan deaktivere automatisk LSP-serverdownloads ved at indstille miljøvariablen `OPENCODE_DISABLE_LSP_DOWNLOAD` til `true`. diff --git a/packages/web/src/content/docs/da/plugins.mdx b/packages/web/src/content/docs/da/plugins.mdx index a8532d599ce..908c6e11113 100644 --- a/packages/web/src/content/docs/da/plugins.mdx +++ b/packages/web/src/content/docs/da/plugins.mdx @@ -119,7 +119,7 @@ Plugin-funktionen modtager: - `directory`: Den aktuelle arbejdsmappe. - `worktree`: Git worktree-stien. - `client`: En opencode SDK klient til interaktion med AI. -- `-: Buns [shell API](https://bun.com/docs/runtime/shell) til udførelse af kommandoer. +- `$`: Buns [shell API](https://bun.com/docs/runtime/shell) til udførelse af kommandoer. --- @@ -308,6 +308,10 @@ export const CustomToolsPlugin: Plugin = async (ctx) => { Dine tilpassede værktøjer vil være tilgængelige for opencode sammen med indbyggede værktøjer. +:::note +Hvis et plugin-værktøj bruger samme navn som et indbygget værktøj, har plugin-værktøjet forrang. +::: + --- ### Logning diff --git a/packages/web/src/content/docs/da/providers.mdx b/packages/web/src/content/docs/da/providers.mdx index 829ae46134b..c5cfe23fa88 100644 --- a/packages/web/src/content/docs/da/providers.mdx +++ b/packages/web/src/content/docs/da/providers.mdx @@ -81,6 +81,37 @@ Det fungerer som alle andre udbydere i OpenCode og er helt valgfrit at bruge. --- +## OpenCode Go + +OpenCode Go er en billig abonnementsplan, der giver pålidelig adgang til populære åbne kodningsmodeller leveret af OpenCode-teamet, som er testet og verificeret til at fungere godt med OpenCode. + +1. Kør kommandoen `/connect` i TUI, vælg `OpenCode Go`, og gå til [opencode.ai/auth](https://opencode.ai/zen). + + ```txt + /connect + ``` + +2. Log ind, tilføj dine faktureringsoplysninger og kopier din API-nøgle. + +3. Indsæt din API-nøgle. + + ```txt + ┌ API key + │ + │ + └ enter + ``` + +4. Kør `/models` i TUI for at se listen over modeller, vi anbefaler. + + ```txt + /models + ``` + +Det fungerer som alle andre udbydere i OpenCode og er helt valgfrit at bruge. + +--- + ## Katalog Lad os se på nogle af udbyderne i detaljer. Hvis du vil tilføje en udbyder til listen, er du velkommen til at åbne en PR. @@ -1474,6 +1505,39 @@ SAP AI Core giver adgang til 40+ modeller fra OpenAI, Anthropic, Google, Amazon, --- +### STACKIT + +STACKIT AI Model Serving leverer fuldt administreret suverænt hostingmiljø til AI-modeller, med fokus på LLM'er som Llama, Mistral og Qwen, med maksimal datasuverænitet på europæisk infrastruktur. + +1. Gå til [STACKIT Portal](https://portal.stackit.cloud), naviger til **AI Model Serving**, og opret en auth-token til dit projekt. + + :::tip + Du skal have en STACKIT-kundekonto, brugerkonto og projekt, før du opretter auth-tokens. + ::: + +2. Kør kommandoen `/connect` og søg efter **STACKIT**. + + ```txt + /connect + ``` + +3. Indtast din STACKIT AI Model Serving auth-token. + + ```txt + ┌ API key + │ + │ + └ enter + ``` + +4. Kør kommandoen `/models` for at vælge fra tilgængelige modeller som _Qwen3-VL 235B_ eller _Llama 3.3 70B_. + + ```txt + /models + ``` + +--- + ### OVHcloud AI Endpoints 1. Gå til [OVHcloud-panelet](https://ovh.com/manager). Naviger til `Public Cloud`-delen, `AI & Machine Learning` > `AI Endpoints` og i `API Keys`-fanen klikker du på **Opret en ny API-nøgle**. diff --git a/packages/web/src/content/docs/da/sdk.mdx b/packages/web/src/content/docs/da/sdk.mdx index 3feff27b3b0..cde874d5a2f 100644 --- a/packages/web/src/content/docs/da/sdk.mdx +++ b/packages/web/src/content/docs/da/sdk.mdx @@ -117,6 +117,78 @@ try { --- +## Struktureret output + +Du kan anmode om struktureret JSON-output fra modellen ved at angive et `format` med et JSON-skema. Modellen vil bruge et `StructuredOutput`-værktøj til at returnere valideret JSON, der matcher dit skema. + +### Grundlæggende brug + +```typescript +const result = await client.session.prompt({ + path: { id: sessionId }, + body: { + parts: [{ type: "text", text: "Research Anthropic and provide company info" }], + format: { + type: "json_schema", + schema: { + type: "object", + properties: { + company: { type: "string", description: "Company name" }, + founded: { type: "number", description: "Year founded" }, + products: { + type: "array", + items: { type: "string" }, + description: "Main products", + }, + }, + required: ["company", "founded"], + }, + }, + }, +}) + +// Access the structured output +console.log(result.data.info.structured_output) +// { company: "Anthropic", founded: 2021, products: ["Claude", "Claude API"] } +``` + +### Outputformat-typer + +| Type | Beskrivelse | +| ------------- | -------------------------------------------------------- | +| `text` | Standard. Standard tekstsvar (intet struktureret output) | +| `json_schema` | Returnerer valideret JSON, der matcher det angivne skema | + +### JSON-skemaformat + +Når du bruger `type: 'json_schema'`, skal du angive: + +| Felt | Type | Beskrivelse | +| ------------ | --------------- | ---------------------------------------------------------- | +| `type` | `'json_schema'` | Påkrævet. Angiver JSON-skematilstand | +| `schema` | `object` | Påkrævet. JSON-skemaobjekt, der definerer outputstrukturen | +| `retryCount` | `number` | Valgfri. Antal valideringsforsøg (standard: 2) | + +### Fejlhåndtering + +Hvis modellen ikke formår at producere gyldigt struktureret output efter alle forsøg, vil svaret inkludere en `StructuredOutputError`: + +```typescript +if (result.data.info.error?.name === "StructuredOutputError") { + console.error("Failed to produce structured output:", result.data.info.error.message) + console.error("Attempts:", result.data.info.error.retries) +} +``` + +### Bedste praksis + +1. **Giv klare beskrivelser** i dine skemaegenskaber for at hjælpe modellen med at forstå, hvilke data der skal udtrækkes +2. **Brug `required`** til at angive, hvilke felter der skal være til stede +3. **Hold skemaer fokuserede** - komplekse indlejrede skemaer kan være sværere for modellen at udfylde korrekt +4. **Indstil passende `retryCount`** - øg for komplekse skemaer, sænk for enkle + +--- + ## API'er SDK avslører alle server-APIer gjennom en typesikker klient. @@ -226,27 +298,27 @@ const { providers, default: defaults } = await client.config.providers() ### Sessioner -| Metode | Beskrivelse | Noter | -| ---------------------------------------------------------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | -| `session.list()` | Liste sessioner | Returnerer Session[] | -| `session.get({ path })` | Få session | Returnerer Session | -| `session.children({ path })` | Liste over barnesessioner | Returnerer Session[] | -| `session.create({ body })` | Opret session | Returnerer Session | -| `session.delete({ path })` | Slett session | Returnerer `boolean` | -| `session.update({ path, body })` | Opdater sessionegenskaper | Returnerer Session | -| `session.init({ path, body })` | Analyser appen og lag `AGENTS.md` | Returnerer `boolean` | -| `session.abort({ path })` | Avbryt en løpesession | Returnerer `boolean` | -| `session.share({ path })` | Del sessionen | Returnerer Session | -| `session.unshare({ path })` | Slutt at dele sessionen | Returnerer Session | -| `session.summarize({ path, body })` | Oppsummer sessionen | Returnerer `boolean` | -| `session.messages({ path })` | Liste meldinger i en session | Returnerer `{ info: `Message`, parts: `Part[]`}[]` | -| `session.message({ path })` | Få meldingsdetaljer | Returnerer `{ info: `Message`, parts: `Part[]`}` | -| `session.prompt({ path, body })` | Send melding | `body.noReply: true` returnerer UserMessage (kun kontekst). Standard returnerer AssistantMessage med AI svar | -| `session.command({ path, body })` | Send kommando til session | Returnerer `{ info: `AssistantMessage`, parts: `Part[]`}` | -| `session.shell({ path, body })` | Kjør en shell-kommando | Returnerer AssistantMessage | -| `session.revert({ path, body })` | Tilbakestill en melding | Returnerer Session | -| `session.unrevert({ path })` | Gjenopret nulstillete meldinger | Returnerer Session | -| `postSessionByIdPermissionsByPermissionId({ path, body })` | Svar på en tillatelsesforespørsel | Returnerer `boolean` | +| Metode | Beskrivelse | Noter | +| ---------------------------------------------------------- | --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `session.list()` | Liste sessioner | Returnerer Session[] | +| `session.get({ path })` | Få session | Returnerer Session | +| `session.children({ path })` | Liste over barnesessioner | Returnerer Session[] | +| `session.create({ body })` | Opret session | Returnerer Session | +| `session.delete({ path })` | Slett session | Returnerer `boolean` | +| `session.update({ path, body })` | Opdater sessionegenskaper | Returnerer Session | +| `session.init({ path, body })` | Analyser appen og lag `AGENTS.md` | Returnerer `boolean` | +| `session.abort({ path })` | Avbryt en løpesession | Returnerer `boolean` | +| `session.share({ path })` | Del sessionen | Returnerer Session | +| `session.unshare({ path })` | Slutt at dele sessionen | Returnerer Session | +| `session.summarize({ path, body })` | Oppsummer sessionen | Returnerer `boolean` | +| `session.messages({ path })` | Liste meldinger i en session | Returnerer `{ info: `Message`, parts: `Part[]`}[]` | +| `session.message({ path })` | Få meldingsdetaljer | Returnerer `{ info: `Message`, parts: `Part[]`}` | +| `session.prompt({ path, body })` | Send melding | `body.noReply: true` returnerer UserMessage (kun kontekst). Standard returnerer AssistantMessage med AI svar. Understøtter `body.outputFormat` for [struktureret output](#struktureret-output) | +| `session.command({ path, body })` | Send kommando til session | Returnerer `{ info: `AssistantMessage`, parts: `Part[]`}` | +| `session.shell({ path, body })` | Kjør en shell-kommando | Returnerer AssistantMessage | +| `session.revert({ path, body })` | Tilbakestill en melding | Returnerer Session | +| `session.unrevert({ path })` | Gjenopret nulstillete meldinger | Returnerer Session | +| `postSessionByIdPermissionsByPermissionId({ path, body })` | Svar på en tillatelsesforespørsel | Returnerer `boolean` | --- diff --git a/packages/web/src/content/docs/da/themes.mdx b/packages/web/src/content/docs/da/themes.mdx index 533bef30eb0..d250e7f02f7 100644 --- a/packages/web/src/content/docs/da/themes.mdx +++ b/packages/web/src/content/docs/da/themes.mdx @@ -61,11 +61,11 @@ Systemtemaet er for brugere som: ## Brug et tema -Du kan velge et tema ved at hente frem temavalg med kommandoen `/theme`. Eller du kan spesifisere det i [config](/docs/config). +Du kan velge et tema ved at hente frem temavalg med kommandoen `/theme`. Eller du kan angive det i `tui.json`. -```json title="opencode.json" {3} +```json title="tui.json" {3} { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "theme": "tokyonight" } ``` diff --git a/packages/web/src/content/docs/da/tui.mdx b/packages/web/src/content/docs/da/tui.mdx index 21cdbe320e0..c1c0de0fd21 100644 --- a/packages/web/src/content/docs/da/tui.mdx +++ b/packages/web/src/content/docs/da/tui.mdx @@ -352,24 +352,34 @@ Nogle editorer kræver kommandolinjeargumenter for at køre i blokeringstilstand ## Konfigurer -Du kan tilpasse TUI-adfærden gennem OpenCode-konfigurationsfilen. +Du kan tilpasse TUI-adfærd gennem `tui.json` (eller `tui.jsonc`). -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", - "tui": { - "scroll_speed": 3, - "scroll_acceleration": { - "enabled": true - } - } + "$schema": "https://opencode.ai/tui.json", + "theme": "opencode", + "keybinds": { + "leader": "ctrl+x" + }, + "scroll_speed": 3, + "scroll_acceleration": { + "enabled": true + }, + "diff_style": "auto" } ``` +Dette er adskilt fra `opencode.json`, som konfigurerer server-/kørselstidsadfærd. + ### Indstillinger -- `scroll_acceleration` - Aktiver rulleacceleration i macOS-stil for jævn, naturlig rulning. Når aktiveret, øger rullehastigheden med hurtige rullebevægelser og forbliver præcis for langsommere bevægelser. **Denne indstilling har forrang over `scroll_speed` og tilsidesætter den, når den er aktiveret.** -- `scroll_speed` - Styrer hvor hurtigt TUI ruller, når du bruger rullekommandoer (minimum: `1`). Standard er `3`. **Bemærk: Dette ignoreres hvis `scroll_acceleration.enabled` er sat til `true`.** +- `theme` - Indstiller dit brugergrænsefladetema. [Læs mere](/docs/themes). +- `keybinds` - Tilpasser tastaturgenveje. [Læs mere](/docs/keybinds). +- `scroll_acceleration.enabled` - Aktiver rulleacceleration i macOS-stil for jævn, naturlig rulning. Når aktiveret, øger rullehastigheden med hurtige rullebevægelser og forbliver præcis for langsommere bevægelser. **Denne indstilling har forrang over `scroll_speed` og tilsidesætter den, når den er aktiveret.** +- `scroll_speed` - Styrer hvor hurtigt TUI ruller, når du bruger rullekommandoer (minimum: `0.001`, understøtter decimalværdier). Standard er `3`. **Bemærk: Dette ignoreres hvis `scroll_acceleration.enabled` er sat til `true`.** +- `diff_style` - Styrer diff-gengivelse. `"auto"` tilpasser sig terminalbredde, `"stacked"` viser altid et enkeltkolonne-layout. + +Brug `OPENCODE_TUI_CONFIG` til at indlæse en brugerdefineret TUI-konfigurationssti. --- diff --git a/packages/web/src/content/docs/da/zen.mdx b/packages/web/src/content/docs/da/zen.mdx index 128583ad7ab..e99c626c575 100644 --- a/packages/web/src/content/docs/da/zen.mdx +++ b/packages/web/src/content/docs/da/zen.mdx @@ -64,6 +64,7 @@ Du kan også få adgang til vores modeller gennem følgende API-endpoints. | Model | Model ID | Endpoint | AI SDK Pakke | | ------------------- | ------------------ | -------------------------------------------------- | --------------------------- | +| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | @@ -73,22 +74,24 @@ Du kan også få adgang til vores modeller gennem følgende API-endpoints. | GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| Claude Sonnet 4.5 | claude-sonnett-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4 | claude-sonnett-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | | Gemini 3 Pro | gemini-3-pro | `https://opencode.ai/zen/v1/models/gemini-3-pro` | `@ai-sdk/google` | | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 Gratis | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.1 | minimax-m2.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.1 Gratis | minimax-m2.1-free | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| GLM 5 | glm-5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 4.7 | glm-4.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM 4.7 Gratis | glm-4.7-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 4.6 | glm-4.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 Gratis | kimi-k2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2 Tenker | kimi-k2-thinking | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2 | kimi-k2 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Qwen3-koder 480B | qwen3-coder | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -117,29 +120,34 @@ Vi støtter en pay-as-you-go-model. Nedenfor er priserne **per 1 million tokens* | Model | Input | Output | Cached Læs | Cached Skriv | | --------------------------------- | ------ | ------ | ---------- | ------------ | | Stor sylteagurk | Gratis | Gratis | Gratis | - | -| MiniMax M2.1 Gratis | Gratis | Gratis | Gratis | - | +| MiniMax M2.5 Gratis | Gratis | Gratis | Gratis | - | +| MiniMax M2.5 | $0,30 | $1,20 | $0,06 | - | | MiniMax M2.1 | $0,30 | $1,20 | $0,10 | - | -| GLM 4.7 Gratis | Gratis | Gratis | Gratis | - | +| GLM 5 | $1,00 | $3,20 | $0,20 | - | | GLM 4.7 | $0,60 | $2,20 | $0,10 | - | | GLM 4.6 | $0,60 | $2,20 | $0,10 | - | -| Kimi K2.5 Gratis | Gratis | Gratis | Gratis | - | | Kimi K2.5 | $0,60 | $3,00 | $0,08 | - | | Kimi K2 Tenker | $0,40 | $2,50 | - | - | | Kimi K2 | $0,40 | $2,50 | - | - | | Qwen3-koder 480B | $0,45 | $1,50 | - | - | +| Claude Opus 4.6 (≤ 200K tokens) | $5,00 | $25,00 | $0,50 | $6,25 | +| Claude Opus 4.6 (> 200K tokens) | $10,00 | $37,50 | $1,00 | $12,50 | +| Claude Opus 4.5 | $5,00 | $25,00 | $0,50 | $6,25 | +| Claude Opus 4.1 | $15,00 | $75,00 | $1,50 | $18,75 | +| Claude Sonnet 4.6 (≤ 200K tokens) | $3,00 | $15,00 | $0,30 | $3,75 | +| Claude Sonnet 4.6 (> 200K tokens) | $6,00 | $22,50 | $0,60 | $7,50 | | Claude Sonnet 4.5 (≤ 200K tokens) | $3,00 | $15,00 | $0,30 | $3,75 | | Claude Sonnet 4.5 (> 200K tokens) | $6,00 | $22,50 | $0,60 | $7,50 | | Claude Sonnet 4 (≤ 200K tokens) | $3,00 | $15,00 | $0,30 | $3,75 | | Claude Sonnet 4 (> 200K tokens) | $6,00 | $22,50 | $0,60 | $7,50 | | Claude Haiku 4.5 | $1,00 | $5,00 | $0,10 | $1,25 | | Claude Haiku 3.5 | $0,80 | $4,00 | $0,08 | $1,00 | -| Claude Opus 4.6 (≤ 200K tokens) | $5,00 | $25,00 | $0,50 | $6,25 | -| Claude Opus 4.6 (> 200K tokens) | $10,00 | $37,50 | $1,00 | $12,50 | -| Claude Opus 4.5 | $5,00 | $25,00 | $0,50 | $6,25 | -| Claude Opus 4.1 | $15,00 | $75,00 | $1,50 | $18,75 | +| Gemini 3.1 Pro (≤ 200K tokens) | $2,00 | $12,00 | $0,20 | - | +| Gemini 3.1 Pro (> 200K tokens) | $4,00 | $18,00 | $0,40 | - | | Gemini 3 Pro (≤ 200K tokens) | $2,00 | $12,00 | $0,20 | - | | Gemini 3 Pro (> 200K tokens) | $4,00 | $18,00 | $0,40 | - | | Gemini 3 Flash | $0,50 | $3,00 | $0,05 | - | +| GPT 5.3 Codex | $1,75 | $14,00 | $0,175 | - | | GPT 5.2 | $1,75 | $14,00 | $0,175 | - | | GPT 5.2 Codex | $1,75 | $14,00 | $0,175 | - | | GPT 5.1 | $1,07 | $8,50 | $0,107 | - | @@ -158,9 +166,7 @@ Kreditkortgebyrer overføres til kostpris (4,4 % + $0,30 per transaktion); vi op De gratis modeller: -- GLM 4.7 Gratis er tilgængelig på OpenCode i en begrænset periode. Teamet bruger denne tid til at samle feedback og forbedre modellen. -- Kimi K2.5 Gratis er tilgængelig på OpenCode i en begrænset periode. Teamet bruger denne tid til at samle feedback og forbedre modellen. -- MiniMax M2.1 Gratis er tilgængelig på OpenCode i en begrænset periode. Teamet bruger denne tid til at samle feedback og forbedre modellen. +- MiniMax M2.5 Gratis er tilgængelig på OpenCode i en begrænset periode. Teamet bruger denne tid til at samle feedback og forbedre modellen. - Stor sylteagurk er en stealth-model som er gratis på OpenCode i en begrænset periode. Teamet bruger denne tid til at samle feedback og forbedre modellen. Kontakt os hvis du har spørgsmål. @@ -191,9 +197,7 @@ at opkræve dig mere end $20, hvis din saldo går under $5. Alle vores modeller er hostet i USA. Vores udbydere følger en nul-opbevaringspolitik og bruger ikke dine data til modeltræning, med følgende undtagelser: - Stor sylteagurk: I løbet af gratisperioden kan indsamlede data bruges til at forbedre modellen. -- GLM 4.7 Gratis: I løbet af gratisperioden kan indsamlede data bruges til at forbedre modellen. -- Kimi K2.5 Gratis: I løbet af gratisperioden kan indsamlede data bruges til at forbedre modellen. -- MiniMax M2.1 Gratis: I løbet af gratisperioden kan indsamlede data bruges til at forbedre modellen. +- MiniMax M2.5 Gratis: I løbet af gratisperioden kan indsamlede data bruges til at forbedre modellen. - OpenAI API'er: Anmodninger opbevares i 30 dage i overensstemmelse med [OpenAIs datapolitikker](https://platform.openai.com/docs/guides/your-data). - Anthropic API'er: Anmodninger opbevares i 30 dage i overensstemmelse med [Anthropics datapolitikker](https://docs.anthropic.com/en/docs/claude-code/data-usage). diff --git a/packages/web/src/content/docs/de/ecosystem.mdx b/packages/web/src/content/docs/de/ecosystem.mdx index ea1bc589a4b..356de3832fe 100644 --- a/packages/web/src/content/docs/de/ecosystem.mdx +++ b/packages/web/src/content/docs/de/ecosystem.mdx @@ -15,38 +15,39 @@ Sie können sich auch [awesome-opencode](https://github.com/awesome-opencode/awe ## Plugins -| Name | Beschreibung | -| --------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | -| [opencode-daytona](https://github.com/jamesmurdza/daytona/blob/main/guides/typescript/opencode/README.md) | Führen Sie OpenCode-Sitzungen automatisch in isolierten Daytona-Sandboxes mit Git-Synchronisierung und Live-Vorschauen aus | -| [opencode-helicone-session](https://github.com/H2Shami/opencode-helicone-session) | Helicone-Sitzungsheader für die Anforderungsgruppierung automatisch einfügen | -| [opencode-type-inject](https://github.com/nick-vi/opencode-type-inject) | TypeScript/Svelte-Typen mit Suchtools automatisch in Dateilesevorgänge einfügen | -| [opencode-openai-codex-auth](https://github.com/numman-ali/opencode-openai-codex-auth) | Verwenden Sie Ihr ChatGPT Plus/Pro-Abonnement anstelle von API Credits | -| [opencode-gemini-auth](https://github.com/jenslys/opencode-gemini-auth) | Verwenden Sie Ihren bestehenden Gemini-Plan anstelle der API-Abrechnung | -| [opencode-antigravity-auth](https://github.com/NoeFabris/opencode-antigravity-auth) | Nutzen Sie die kostenlosen Modelle von Antigravity anstelle der API-Abrechnung | -| [opencode-devcontainers](https://github.com/athal7/opencode-devcontainers) | Multi-Branch-Devcontainer-Isolierung mit flachen Klonen und automatisch zugewiesenen Ports | -| [opencode-google-antigravity-auth](https://github.com/shekohex/opencode-google-antigravity-auth) | Google Antigravity OAuth-Plugin mit Unterstützung für die Google-Suche und robustere API-Verarbeitung | -| [opencode-dynamic-context-pruning](https://github.com/Tarquinen/opencode-dynamic-context-pruning) | Optimieren Sie die Token-Nutzung, indem Sie veraltete Tool-Ausgaben bereinigen | -| [opencode-websearch-cited](https://github.com/ghoulr/opencode-websearch-cited.git) | Fügen Sie native Websuchunterstützung für unterstützte Anbieter mit Google Grounded Style hinzu | -| [opencode-pty](https://github.com/shekohex/opencode-pty.git) | Ermöglicht AI-Agenten, Hintergrundprozesse in einem PTY auszuführen und ihnen interaktive Eingaben zu senden. | -| [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Anweisungen für nicht interaktive Shell-Befehle – verhindert Abstürze bei TTY-abhängigen Vorgängen | -| [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Verfolgen Sie die Nutzung von OpenCode mit Wakatime | -| [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Von LLMs erstellte Abschriftentabellen bereinigen | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | 10x schnellere Codebearbeitung mit Morph Fast Apply API und Lazy-Edit-Markern | -| [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Hintergrundagenten, vorgefertigte LSP/AST/MCP-Tools, kuratierte Agenten, Claude Code-kompatibel | -| [opencode-notificator](https://github.com/panta82/opencode-notificator) | Desktop-Benachrichtigungen und akustische Warnungen für OpenCode-Sitzungen | -| [opencode-notifier](https://github.com/mohak34/opencode-notifier) | Desktop-Benachrichtigungen und akustische Warnungen für Berechtigungs-, Abschluss- und Fehlerereignisse | -| [opencode-zellij-namer](https://github.com/24601/opencode-zellij-namer) | AI-gestützte automatische Benennung von Zellij-Sitzungen basierend auf dem OpenCode-Kontext | -| [opencode-skillful](https://github.com/zenobi-us/opencode-skillful) | Ermöglichen Sie OpenCode-Agenten das verzögerte Laden von Eingabeaufforderungen bei Bedarf mit Skill-Erkennung und -Injektion | -| [opencode-supermemory](https://github.com/supermemoryai/opencode-supermemory) | Persistenter Speicher über Sitzungen hinweg mit Supermemory | -| [@plannotator/opencode](https://github.com/backnotprop/plannotator/tree/main/apps/opencode-plugin) | Interaktive Planüberprüfung mit visueller Anmerkung und private/offline-Freigabe | -| [@openspoon/subtask2](https://github.com/spoons-and-mirrors/subtask2) | Erweitern Sie OpenCode /commands zu einem leistungsstarken Orchestrierungssystem mit granularer Flusskontrolle | -| [opencode-scheduler](https://github.com/different-ai/opencode-scheduler) | Planen Sie wiederkehrende Jobs mit launchd (Mac) oder systemd (Linux) mit Cron-Syntax | -| [micode](https://github.com/vtemian/micode) | Strukturiertes Brainstorming → Planen → Workflow mit Sitzungskontinuität Implementierung | -| [octto](https://github.com/vtemian/octto) | Interaktiver Browser UI für AI Brainstorming mit Formularen mit mehreren Fragen | -| [opencode-background-agents](https://github.com/kdcokenny/opencode-background-agents) | Hintergrundagenten im Claude Code-Stil mit asynchroner Delegation und Kontextpersistenz | -| [opencode-notify](https://github.com/kdcokenny/opencode-notify) | Native OS-Benachrichtigungen für OpenCode – wissen, wann Aufgaben erledigt sind | -| [opencode-workspace](https://github.com/kdcokenny/opencode-workspace) | Gebündelter Multi-Agent-Orchestrierungs-Harness – 16 Komponenten, eine Installation | -| [opencode-worktree](https://github.com/kdcokenny/opencode-worktree) | Reibungslose Git-Arbeitsbäume für OpenCode | +| Name | Beschreibung | +| -------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | +| [opencode-daytona](https://github.com/daytonaio/daytona/tree/main/libs/opencode-plugin) | Führen Sie OpenCode-Sitzungen automatisch in isolierten Daytona-Sandboxes mit Git-Synchronisierung und Live-Vorschauen aus | +| [opencode-helicone-session](https://github.com/H2Shami/opencode-helicone-session) | Helicone-Sitzungsheader für die Anforderungsgruppierung automatisch einfügen | +| [opencode-type-inject](https://github.com/nick-vi/opencode-type-inject) | TypeScript/Svelte-Typen mit Suchtools automatisch in Dateilesevorgänge einfügen | +| [opencode-openai-codex-auth](https://github.com/numman-ali/opencode-openai-codex-auth) | Verwenden Sie Ihr ChatGPT Plus/Pro-Abonnement anstelle von API Credits | +| [opencode-gemini-auth](https://github.com/jenslys/opencode-gemini-auth) | Verwenden Sie Ihren bestehenden Gemini-Plan anstelle der API-Abrechnung | +| [opencode-antigravity-auth](https://github.com/NoeFabris/opencode-antigravity-auth) | Nutzen Sie die kostenlosen Modelle von Antigravity anstelle der API-Abrechnung | +| [opencode-devcontainers](https://github.com/athal7/opencode-devcontainers) | Multi-Branch-Devcontainer-Isolierung mit flachen Klonen und automatisch zugewiesenen Ports | +| [opencode-google-antigravity-auth](https://github.com/shekohex/opencode-google-antigravity-auth) | Google Antigravity OAuth-Plugin mit Unterstützung für die Google-Suche und robustere API-Verarbeitung | +| [opencode-dynamic-context-pruning](https://github.com/Tarquinen/opencode-dynamic-context-pruning) | Optimieren Sie die Token-Nutzung, indem Sie veraltete Tool-Ausgaben bereinigen | +| [opencode-vibeguard](https://github.com/inkdust2021/opencode-vibeguard) | Schwärzen Sie Geheimnisse/PII in VibeGuard-ähnliche Platzhalter vor LLM-Aufrufen; lokal wiederherstellen | +| [opencode-websearch-cited](https://github.com/ghoulr/opencode-websearch-cited.git) | Fügen Sie native Websuchunterstützung für unterstützte Anbieter mit Google Grounded Style hinzu | +| [opencode-pty](https://github.com/shekohex/opencode-pty.git) | Ermöglicht AI-Agenten, Hintergrundprozesse in einem PTY auszuführen und ihnen interaktive Eingaben zu senden. | +| [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Anweisungen für nicht interaktive Shell-Befehle – verhindert Abstürze bei TTY-abhängigen Vorgängen | +| [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Verfolgen Sie die Nutzung von OpenCode mit Wakatime | +| [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Von LLMs erstellte Abschriftentabellen bereinigen | +| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | 10x schnellere Codebearbeitung mit Morph Fast Apply API und Lazy-Edit-Markern | +| [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Hintergrundagenten, vorgefertigte LSP/AST/MCP-Tools, kuratierte Agenten, Claude Code-kompatibel | +| [opencode-notificator](https://github.com/panta82/opencode-notificator) | Desktop-Benachrichtigungen und akustische Warnungen für OpenCode-Sitzungen | +| [opencode-notifier](https://github.com/mohak34/opencode-notifier) | Desktop-Benachrichtigungen und akustische Warnungen für Berechtigungs-, Abschluss- und Fehlerereignisse | +| [opencode-zellij-namer](https://github.com/24601/opencode-zellij-namer) | AI-gestützte automatische Benennung von Zellij-Sitzungen basierend auf dem OpenCode-Kontext | +| [opencode-skillful](https://github.com/zenobi-us/opencode-skillful) | Ermöglichen Sie OpenCode-Agenten das verzögerte Laden von Eingabeaufforderungen bei Bedarf mit Skill-Erkennung und -Injektion | +| [opencode-supermemory](https://github.com/supermemoryai/opencode-supermemory) | Persistenter Speicher über Sitzungen hinweg mit Supermemory | +| [@plannotator/opencode](https://github.com/backnotprop/plannotator/tree/main/apps/opencode-plugin) | Interaktive Planüberprüfung mit visueller Anmerkung und private/offline-Freigabe | +| [@openspoon/subtask2](https://github.com/spoons-and-mirrors/subtask2) | Erweitern Sie OpenCode /commands zu einem leistungsstarken Orchestrierungssystem mit granularer Flusskontrolle | +| [opencode-scheduler](https://github.com/different-ai/opencode-scheduler) | Planen Sie wiederkehrende Jobs mit launchd (Mac) oder systemd (Linux) mit Cron-Syntax | +| [micode](https://github.com/vtemian/micode) | Strukturiertes Brainstorming → Planen → Workflow mit Sitzungskontinuität Implementierung | +| [octto](https://github.com/vtemian/octto) | Interaktiver Browser UI für AI Brainstorming mit Formularen mit mehreren Fragen | +| [opencode-background-agents](https://github.com/kdcokenny/opencode-background-agents) | Hintergrundagenten im Claude Code-Stil mit asynchroner Delegation und Kontextpersistenz | +| [opencode-notify](https://github.com/kdcokenny/opencode-notify) | Native OS-Benachrichtigungen für OpenCode – wissen, wann Aufgaben erledigt sind | +| [opencode-workspace](https://github.com/kdcokenny/opencode-workspace) | Gebündelter Multi-Agent-Orchestrierungs-Harness – 16 Komponenten, eine Installation | +| [opencode-worktree](https://github.com/kdcokenny/opencode-worktree) | Reibungslose Git-Arbeitsbäume für OpenCode | --- diff --git a/packages/web/src/content/docs/de/go.mdx b/packages/web/src/content/docs/de/go.mdx new file mode 100644 index 00000000000..fe4a600cd9d --- /dev/null +++ b/packages/web/src/content/docs/de/go.mdx @@ -0,0 +1,145 @@ +--- +title: Go +description: Kostengünstiges Abonnement für Open-Coding-Modelle. +--- + +import config from "../../../../config.mjs" +export const console = config.console +export const email = `mailto:${config.email}` + +OpenCode Go ist ein kostengünstiges Abonnement für **10 $/Monat**, das dir zuverlässigen Zugriff auf beliebte Open-Coding-Modelle bietet. + +:::note +OpenCode Go befindet sich derzeit in der Beta-Phase. +::: + +Go funktioniert wie jeder andere Anbieter in OpenCode. Du abonnierst OpenCode Go und erhältst deinen API-Schlüssel. Es ist **völlig optional** und du musst es nicht nutzen, um OpenCode zu verwenden. + +Es wurde primär für internationale Nutzer entwickelt, mit Modellen, die in den USA, der EU und Singapur gehostet werden, um einen stabilen weltweiten Zugriff zu gewährleisten. + +--- + +## Hintergrund + +Offene Modelle sind wirklich gut geworden. Sie erreichen bei Coding-Aufgaben mittlerweile eine Leistung, die der von proprietären Modellen nahekommt. Und da viele Anbieter sie wettbewerbsfähig bereitstellen können, sind sie in der Regel deutlich günstiger. + +Es kann jedoch schwierig sein, einen zuverlässigen Zugang mit niedriger Latenz zu erhalten. Die Anbieter variieren in Qualität und Verfügbarkeit. + +:::tip +Wir haben eine ausgewählte Gruppe von Modellen und Anbietern getestet, die gut mit OpenCode funktionieren. +::: + +Um dies zu lösen, haben wir einige Dinge getan: + +1. Wir haben eine ausgewählte Gruppe offener Modelle getestet und mit deren Teams darüber gesprochen, wie man sie am besten betreibt. +2. Anschließend haben wir mit einigen Anbietern zusammengearbeitet, um sicherzustellen, dass diese korrekt bereitgestellt werden. +3. Schließlich haben wir die Kombination aus Modell und Anbieter einem Benchmark unterzogen und eine Liste erstellt, die wir guten Gewissens empfehlen können. + +OpenCode Go gibt dir Zugriff auf diese Modelle für **10 $/Monat**. + +--- + +## Wie es funktioniert + +OpenCode Go funktioniert wie jeder andere Anbieter in OpenCode. + +1. Du meldest dich bei **OpenCode Zen** an, abonnierst Go und kopierst deinen API-Schlüssel. +2. Du führst den Befehl `/connect` in der TUI aus, wählst `OpenCode Go` und fügst deinen API-Schlüssel ein. +3. Führe `/models` in der TUI aus, um die Liste der über Go verfügbaren Modelle zu sehen. + +:::note +Nur ein Mitglied pro Workspace kann OpenCode Go abonnieren. +::: + +Die aktuelle Liste der Modelle umfasst: + +- **GLM-5** +- **Kimi K2.5** +- **MiniMax M2.5** + +Die Liste der Modelle kann sich ändern, wenn wir neue testen und hinzufügen. + +--- + +## Nutzungslimits + +OpenCode Go beinhaltet die folgenden Limits: + +- **5-Stunden-Limit** — 12 $ Nutzung +- **Wöchentliches Limit** — 30 $ Nutzung +- **Monatliches Limit** — 60 $ Nutzung + +Die Limits sind in Dollarwerten definiert. Das bedeutet, dass deine tatsächliche Anzahl an Anfragen von dem verwendeten Modell abhängt. Günstigere Modelle wie MiniMax M2.5 ermöglichen mehr Anfragen, während kostenintensivere Modelle wie GLM-5 weniger zulassen. + +Die untenstehende Tabelle bietet eine geschätzte Anzahl an Anfragen basierend auf typischen Go-Nutzungsmustern: + +| | GLM-5 | Kimi K2.5 | MiniMax M2.5 | +| ------------------- | ----- | --------- | ------------ | +| Anfragen pro 5 Std. | 1.150 | 1.850 | 30.000 | +| Anfragen pro Woche | 2.880 | 4.630 | 75.000 | +| Anfragen pro Monat | 5.750 | 9.250 | 150.000 | + +Die Schätzungen basieren auf beobachteten durchschnittlichen Nutzungsmustern: + +- GLM-5 — 700 Input-, 52.000 Cached-, 150 Output-Token pro Anfrage +- Kimi K2.5 — 870 Input-, 55.000 Cached-, 200 Output-Token pro Anfrage +- MiniMax M2.5 — 300 Input-, 55.000 Cached-, 125 Output-Token pro Anfrage + +Du kannst deine aktuelle Nutzung in der **Konsole** verfolgen. + +:::tip +Wenn du das Nutzungslimit erreichst, kannst du weiterhin die kostenlosen Modelle verwenden. +::: + +Nutzungslimits können sich ändern, da wir aus der frühen Nutzung und dem Feedback lernen. + +--- + +### Preise + +OpenCode Go ist ein Abonnementplan für **10 $/Monat**. Unten stehen die Preise **pro 1 Mio. Token**. + +| Modell | Input | Output | Cached Read | +| ------------ | ------ | ------ | ----------- | +| GLM-5 | 1,00 $ | 3,20 $ | 0,20 $ | +| Kimi K2.5 | 0,60 $ | 3,00 $ | 0,10 $ | +| MiniMax M2.5 | 0,30 $ | 1,20 $ | 0,03 $ | + +--- + +### Nutzung über Limits hinaus + +Wenn du auch Guthaben auf deinem Zen-Konto hast, kannst du die Option **Use balance** in der Konsole aktivieren. Wenn aktiviert, greift Go auf dein Zen-Guthaben zurück, nachdem du deine Nutzungslimits erreicht hast, anstatt Anfragen zu blockieren. + +--- + +## Endpunkte + +Du kannst auch über die folgenden API-Endpunkte auf Go-Modelle zugreifen. + +| Modell | Modell-ID | Endpunkt | AI SDK Paket | +| ------------ | ------------ | ------------------------------------------------ | --------------------------- | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | + +Die [Modell-ID](/docs/config/#models) in deiner OpenCode-Konfiguration verwendet das Format `opencode-go/`. Zum Beispiel würdest du für Kimi K2.5 `opencode-go/kimi-k2.5` in deiner Konfiguration verwenden. + +--- + +## Datenschutz + +Der Plan wurde primär für internationale Nutzer entwickelt, mit Modellen, die in den USA, der EU und Singapur gehostet werden, um einen stabilen weltweiten Zugriff zu gewährleisten. + +Kontaktiere uns, wenn du Fragen hast. + +--- + +## Ziele + +Wir haben OpenCode Go erstellt, um: + +1. AI-Coding für mehr Menschen durch ein kostengünstiges Abonnement **zugänglich** zu machen. +2. **Zuverlässigen** Zugriff auf die besten Open-Coding-Modelle zu bieten. +3. Modelle zu kuratieren, die für den Einsatz von Coding-Agents **getestet und gebenchmarkt** sind. +4. **Keinen Lock-in** zu haben, indem wir dir ermöglichen, jeden anderen Anbieter ebenfalls mit OpenCode zu nutzen. diff --git a/packages/web/src/content/docs/de/providers.mdx b/packages/web/src/content/docs/de/providers.mdx index d72ac5af3de..fa447594d62 100644 --- a/packages/web/src/content/docs/de/providers.mdx +++ b/packages/web/src/content/docs/de/providers.mdx @@ -84,6 +84,37 @@ Es funktioniert wie jeder andere Anbieter in OpenCode und ist völlig optional. --- +## OpenCode Go + +OpenCode Go ist ein kostenguenstiges Abonnement, das zuverlaessigen Zugriff auf beliebte Open-Coding-Modelle bietet, die vom OpenCode-Team getestet und verifiziert wurden, dass sie gut mit OpenCode funktionieren. + +1. Führen Sie den Befehl `/connect` in der TUI aus, waehlen Sie `OpenCode Go` und gehen Sie zu [opencode.ai/auth](https://opencode.ai/zen). + + ```txt + /connect + ``` + +2. Melden Sie sich an, geben Sie Ihre Rechnungsdaten ein und kopieren Sie Ihren API-Schlüssel. + +3. Fügen Sie Ihren API-Schlüssel ein. + + ```txt + ┌ API key + │ + │ + └ enter + ``` + +4. Führen Sie `/models` in der TUI aus, um die Liste der empfohlenen Modelle zu sehen. + + ```txt + /models + ``` + +Es funktioniert wie jeder andere Anbieter in OpenCode und ist völlig optional. + +--- + ## Verzeichnis Schauen wir uns einige der Anbieter im Detail an. Wenn Sie einen Anbieter hinzufügen möchten @@ -1480,6 +1511,39 @@ SAP AI Core bietet Zugriff auf 40+ Modelle von OpenAI, Anthropic, Google, Amazon --- +### STACKIT + +STACKIT AI Model Serving bietet eine voll verwaltete, souveraene Hosting-Umgebung fuer AI-Modelle, mit Fokus auf LLMs wie Llama, Mistral und Qwen, mit maximaler Datensouveraenitaet auf europaeischer Infrastruktur. + +1. Gehen Sie zum [STACKIT Portal](https://portal.stackit.cloud), navigieren Sie zu **AI Model Serving** und erstellen Sie ein Auth-Token fuer Ihr Projekt. + + :::tip + Sie benoetigen ein STACKIT-Kundenkonto, Benutzerkonto und Projekt, bevor Sie Auth-Tokens erstellen koennen. + ::: + +2. Führen Sie den Befehl `/connect` aus und suchen Sie nach **STACKIT**. + + ```txt + /connect + ``` + +3. Geben Sie Ihr STACKIT AI Model Serving Auth-Token ein. + + ```txt + ┌ API key + │ + │ + └ enter + ``` + +4. Führen Sie den Befehl `/models` aus, um aus verfügbaren Modellen wie _Qwen3-VL 235B_ oder _Llama 3.3 70B_ auszuwählen. + + ```txt + /models + ``` + +--- + ### OVHcloud AI Endpoints 1. Gehen Sie zum [OVHcloud panel](https://ovh.com/manager). Navigieren Sie zum Abschnitt `Public Cloud`, `AI & Machine Learning` > `AI Endpoints` und klicken Sie auf der Registerkarte `API Keys` auf **Neuen API-Schlüssel erstellen**. diff --git a/packages/web/src/content/docs/de/sdk.mdx b/packages/web/src/content/docs/de/sdk.mdx index 21791db05de..22e70071d62 100644 --- a/packages/web/src/content/docs/de/sdk.mdx +++ b/packages/web/src/content/docs/de/sdk.mdx @@ -119,6 +119,78 @@ try { --- +## Structured Output + +Du kannst eine strukturierte JSON-Ausgabe vom Modell anfordern, indem du ein `format` mit einem JSON-Schema angibst. Das Modell verwendet dann ein `StructuredOutput`-Tool, um validiertes JSON zurueckzugeben, das deinem Schema entspricht. + +### Grundlegende Verwendung + +```typescript +const result = await client.session.prompt({ + path: { id: sessionId }, + body: { + parts: [{ type: "text", text: "Recherchiere Anthropic und gib Firmeninfos zurueck" }], + format: { + type: "json_schema", + schema: { + type: "object", + properties: { + company: { type: "string", description: "Firmenname" }, + founded: { type: "number", description: "Gruendungsjahr" }, + products: { + type: "array", + items: { type: "string" }, + description: "Hauptprodukte", + }, + }, + required: ["company", "founded"], + }, + }, + }, +}) + +// Zugriff auf die strukturierte Ausgabe +console.log(result.data.info.structured_output) +// { company: "Anthropic", founded: 2021, products: ["Claude", "Claude API"] } +``` + +### Ausgabeformate + +| Type | Description | +| ------------- | ----------------------------------------------------------- | +| `text` | Standard. Normale Textantwort (keine strukturierte Ausgabe) | +| `json_schema` | Gibt validiertes JSON zurueck, das dem Schema entspricht | + +### JSON-Schema-Format + +Bei Verwendung von `type: 'json_schema'` musst du Folgendes angeben: + +| Field | Type | Description | +| ------------ | --------------- | ------------------------------------------------------------- | +| `type` | `'json_schema'` | Erforderlich. Gibt den JSON-Schema-Modus an | +| `schema` | `object` | Erforderlich. JSON-Schema-Objekt, das die Struktur definiert | +| `retryCount` | `number` | Optional. Anzahl der Validierungswiederholungen (Standard: 2) | + +### Fehlerbehandlung + +Wenn das Modell nach allen Wiederholungen keine valide strukturierte Ausgabe liefert, enthaelt die Antwort einen `StructuredOutputError`: + +```typescript +if (result.data.info.error?.name === "StructuredOutputError") { + console.error("Strukturierte Ausgabe fehlgeschlagen:", result.data.info.error.message) + console.error("Versuche:", result.data.info.error.retries) +} +``` + +### Best Practices + +1. **Klare Beschreibungen**: Gib in deinen Schema-Properties klare Beschreibungen an, damit das Modell versteht, welche Daten extrahiert werden sollen. +2. **`required` nutzen**: Definiere, welche Felder zwingend vorhanden sein muessen. +3. **Schemas einfach halten**: Komplexe verschachtelte Schemas sind fuer das Modell schwerer korrekt auszufuellen. +4. **`retryCount` anpassen**: Erhoehe den Wert bei komplexen Schemas, verringere ihn bei einfachen. + +--- + ## APIs Das SDK stellt alle Server-APIs ueber einen typsicheren Client bereit. @@ -127,9 +199,9 @@ Das SDK stellt alle Server-APIs ueber einen typsicheren Client bereit. ### Global -| Method | Description | Response | -| ----------------- | ------------------------------- | ------------------------------------ | -| `global.health()` | Check server health and version | `{ healthy: true, version: string }` | +| Method | Description | Response | +| ----------------- | -------------------------------- | ------------------------------------ | +| `global.health()` | Prueft Server-Status und Version | `{ healthy: true, version: string }` | --- @@ -144,10 +216,10 @@ console.log(health.data.version) ### App -| Method | Description | Response | -| -------------- | ------------------------- | ------------------------------------------- | -| `app.log()` | Write a log entry | `boolean` | -| `app.agents()` | List all available agents | Agent[] | +| Method | Description | Response | +| -------------- | -------------------------------- | ------------------------------------------- | +| `app.log()` | Schreibt einen Log-Eintrag | `boolean` | +| `app.agents()` | Listet alle verfuegbaren Agenten | Agent[] | --- @@ -171,10 +243,10 @@ const agents = await client.app.agents() ### Project -| Method | Description | Response | -| ------------------- | ------------------- | --------------------------------------------- | -| `project.list()` | List all projects | Project[] | -| `project.current()` | Get current project | Project | +| Method | Description | Response | +| ------------------- | ---------------------------- | --------------------------------------------- | +| `project.list()` | Listet alle Projekte | Project[] | +| `project.current()` | Ruft das aktuelle Projekt ab | Project | --- @@ -192,9 +264,9 @@ const currentProject = await client.project.current() ### Path -| Method | Description | Response | -| ------------ | ---------------- | ---------------------------------------- | -| `path.get()` | Get current path | Path | +| Method | Description | Response | +| ------------ | -------------------------- | ---------------------------------------- | +| `path.get()` | Ruft den aktuellen Pfad ab | Path | --- @@ -209,10 +281,10 @@ const pathInfo = await client.path.get() ### Config -| Method | Description | Response | -| -------------------- | --------------------------------- | ----------------------------------------------------------------------------------------------------- | -| `config.get()` | Get config info | Config | -| `config.providers()` | List providers and default models | `{ providers: `Provider[]`, default: { [key: string]: string } }` | +| Method | Description | Response | +| -------------------- | ------------------------------------ | ----------------------------------------------------------------------------------------------------- | +| `config.get()` | Ruft Konfigurationsinfos ab | Config | +| `config.providers()` | Listet Provider und Standard-Modelle | `{ providers: `Provider[]`, default: { [key: string]: string } }` | --- @@ -228,27 +300,27 @@ const { providers, default: defaults } = await client.config.providers() ### Sessions -| Method | Description | Notes | -| ---------------------------------------------------------- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | -| `session.list()` | List sessions | Returns Session[] | -| `session.get({ path })` | Get session | Returns Session | -| `session.children({ path })` | List child sessions | Returns Session[] | -| `session.create({ body })` | Create session | Returns Session | -| `session.delete({ path })` | Delete session | Returns `boolean` | -| `session.update({ path, body })` | Update session properties | Returns Session | -| `session.init({ path, body })` | Analyze app and create `AGENTS.md` | Returns `boolean` | -| `session.abort({ path })` | Abort a running session | Returns `boolean` | -| `session.share({ path })` | Share session | Returns Session | -| `session.unshare({ path })` | Unshare session | Returns Session | -| `session.summarize({ path, body })` | Summarize session | Returns `boolean` | -| `session.messages({ path })` | List messages in a session | Returns `{ info: `Message`, parts: `Part[]`}[]` | -| `session.message({ path })` | Get message details | Returns `{ info: `Message`, parts: `Part[]`}` | -| `session.prompt({ path, body })` | Send prompt message | `body.noReply: true` returns UserMessage (context only). Default returns AssistantMessage with AI response | -| `session.command({ path, body })` | Send command to session | Returns `{ info: `AssistantMessage`, parts: `Part[]`}` | -| `session.shell({ path, body })` | Run a shell command | Returns AssistantMessage | -| `session.revert({ path, body })` | Revert a message | Returns Session | -| `session.unrevert({ path })` | Restore reverted messages | Returns Session | -| `postSessionByIdPermissionsByPermissionId({ path, body })` | Respond to a permission request | Returns `boolean` | +| Method | Description | Notes | +| ---------------------------------------------------------- | --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `session.list()` | Listet Sessions | Gibt Session[] zurueck | +| `session.get({ path })` | Ruft Session ab | Gibt Session zurueck | +| `session.children({ path })` | Listet Kind-Sessions | Gibt Session[] zurueck | +| `session.create({ body })` | Erstellt Session | Gibt Session zurueck | +| `session.delete({ path })` | Loescht Session | Gibt `boolean` zurueck | +| `session.update({ path, body })` | Aktualisiert Session-Eigenschaften | Gibt Session zurueck | +| `session.init({ path, body })` | Analysiert App und erstellt `AGENTS.md` | Gibt `boolean` zurueck | +| `session.abort({ path })` | Bricht eine laufende Session ab | Gibt `boolean` zurueck | +| `session.share({ path })` | Teilt Session | Gibt Session zurueck | +| `session.unshare({ path })` | Hebt Teilen der Session auf | Gibt Session zurueck | +| `session.summarize({ path, body })` | Fasst Session zusammen | Gibt `boolean` zurueck | +| `session.messages({ path })` | Listet Nachrichten einer Session | Gibt `{ info: `Message`, parts: `Part[]`}[]` zurueck | +| `session.message({ path })` | Ruft Nachrichtendetails ab | Gibt `{ info: `Message`, parts: `Part[]`}` zurueck | +| `session.prompt({ path, body })` | Sendet Prompt-Nachricht | `body.noReply: true` gibt UserMessage (nur Kontext) zurueck. Standard gibt AssistantMessage mit AI-Antwort zurueck. Unterstuetzt `body.outputFormat` fuer [strukturierte Ausgabe](#structured-output) | +| `session.command({ path, body })` | Sendet Befehl an Session | Gibt `{ info: `AssistantMessage`, parts: `Part[]`}` zurueck | +| `session.shell({ path, body })` | Fuehrt Shell-Befehl aus | Gibt AssistantMessage zurueck | +| `session.revert({ path, body })` | Setzt Nachricht zurueck | Gibt Session zurueck | +| `session.unrevert({ path })` | Stellt zurueckgesetzte Nachrichten wieder her | Gibt Session zurueck | +| `postSessionByIdPermissionsByPermissionId({ path, body })` | Antwortet auf eine Berechtigungsanfrage | Gibt `boolean` zurueck | --- @@ -285,19 +357,19 @@ await client.session.prompt({ ### Files -| Method | Description | Response | -| ------------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------- | -| `find.text({ query })` | Search for text in files | Array of match objects with `path`, `lines`, `line_number`, `absolute_offset`, `submatches` | -| `find.files({ query })` | Find files and directories by name | `string[]` (paths) | -| `find.symbols({ query })` | Find workspace symbols | Symbol[] | -| `file.read({ query })` | Read a file | `{ type: "raw" \| "patch", content: string }` | -| `file.status({ query? })` | Get status for tracked files | File[] | +| Method | Description | Response | +| ------------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------- | +| `find.text({ query })` | Sucht Text in Dateien | Array of match objects with `path`, `lines`, `line_number`, `absolute_offset`, `submatches` | +| `find.files({ query })` | Findet Dateien und Verzeichnisse nach Namen | `string[]` (paths) | +| `find.symbols({ query })` | Findet Workspace-Symbole | Symbol[] | +| `file.read({ query })` | Liest eine Datei | `{ type: "raw" \| "patch", content: string }` | +| `file.status({ query? })` | Ruft Status fuer getrackte Dateien ab | File[] | -`find.files` supports a few optional query fields: +`find.files` unterstuetzt einige optionale Query-Felder: -- `type`: `"file"` or `"directory"` -- `directory`: override the project root for the search -- `limit`: max results (1–200) +- `type`: `"file"` oder `"directory"` +- `directory`: Ueberschreibt das Projekt-Root fuer die Suche +- `limit`: Maximale Ergebnisse (1–200) --- @@ -326,17 +398,17 @@ const content = await client.file.read({ ### TUI -| Method | Description | Response | -| ------------------------------ | ------------------------- | --------- | -| `tui.appendPrompt({ body })` | Append text to the prompt | `boolean` | -| `tui.openHelp()` | Open the help dialog | `boolean` | -| `tui.openSessions()` | Open the session selector | `boolean` | -| `tui.openThemes()` | Open the theme selector | `boolean` | -| `tui.openModels()` | Open the model selector | `boolean` | -| `tui.submitPrompt()` | Submit the current prompt | `boolean` | -| `tui.clearPrompt()` | Clear the prompt | `boolean` | -| `tui.executeCommand({ body })` | Execute a command | `boolean` | -| `tui.showToast({ body })` | Show toast notification | `boolean` | +| Method | Description | Response | +| ------------------------------ | ------------------------------ | --------- | +| `tui.appendPrompt({ body })` | Haengt Text an den Prompt an | `boolean` | +| `tui.openHelp()` | Oeffnet den Hilfedialog | `boolean` | +| `tui.openSessions()` | Oeffnet die Session-Auswahl | `boolean` | +| `tui.openThemes()` | Oeffnet die Theme-Auswahl | `boolean` | +| `tui.openModels()` | Oeffnet die Modell-Auswahl | `boolean` | +| `tui.submitPrompt()` | Sendet den aktuellen Prompt ab | `boolean` | +| `tui.clearPrompt()` | Leert den Prompt | `boolean` | +| `tui.executeCommand({ body })` | Fuehrt einen Befehl aus | `boolean` | +| `tui.showToast({ body })` | Zeigt Toast-Benachrichtigung | `boolean` | --- @@ -357,9 +429,9 @@ await client.tui.showToast({ ### Auth -| Method | Description | Response | -| ------------------- | ------------------------------ | --------- | -| `auth.set({ ... })` | Set authentication credentials | `boolean` | +| Method | Description | Response | +| ------------------- | ----------------------------- | --------- | +| `auth.set({ ... })` | Setzt Authentifizierungsdaten | `boolean` | --- @@ -378,7 +450,7 @@ await client.auth.set({ | Method | Description | Response | | ------------------- | ------------------------- | ------------------------- | -| `event.subscribe()` | Server-sent events stream | Server-sent events stream | +| `event.subscribe()` | Server-Sent Events Stream | Server-sent events stream | --- diff --git a/packages/web/src/content/docs/de/server.mdx b/packages/web/src/content/docs/de/server.mdx index dd4a565faff..64322bda856 100644 --- a/packages/web/src/content/docs/de/server.mdx +++ b/packages/web/src/content/docs/de/server.mdx @@ -93,28 +93,28 @@ Der opencode-Server stellt folgende APIs bereit. ### Global -| Method | Path | Description | Response | -| ------ | ---------------- | ------------------------------ | ------------------------------------ | -| `GET` | `/global/health` | Get server health and version | `{ healthy: true, version: string }` | -| `GET` | `/global/event` | Get global events (SSE stream) | Event stream | +| Method | Path | Description | Response | +| ------ | ---------------- | ----------------------------------- | ------------------------------------ | +| `GET` | `/global/health` | Ruft Server-Status und Version ab | `{ healthy: true, version: string }` | +| `GET` | `/global/event` | Ruft globale Events ab (SSE-Stream) | Event stream | --- ### Project -| Method | Path | Description | Response | -| ------ | ------------------ | ----------------------- | --------------------------------------------- | -| `GET` | `/project` | List all projects | Project[] | -| `GET` | `/project/current` | Get the current project | Project | +| Method | Path | Description | Response | +| ------ | ------------------ | ---------------------------- | --------------------------------------------- | +| `GET` | `/project` | Listet alle Projekte | Project[] | +| `GET` | `/project/current` | Ruft das aktuelle Projekt ab | Project | --- ### Path & VCS -| Method | Path | Description | Response | -| ------ | ------- | ------------------------------------ | ------------------------------------------- | -| `GET` | `/path` | Get the current path | Path | -| `GET` | `/vcs` | Get VCS info for the current project | VcsInfo | +| Method | Path | Description | Response | +| ------ | ------- | ------------------------------------------- | ------------------------------------------- | +| `GET` | `/path` | Ruft den aktuellen Pfad ab | Path | +| `GET` | `/vcs` | Ruft VCS-Infos fuer das aktuelle Projekt ab | VcsInfo | --- @@ -122,87 +122,87 @@ Der opencode-Server stellt folgende APIs bereit. | Method | Path | Description | Response | | ------ | ------------------- | ---------------------------- | --------- | -| `POST` | `/instance/dispose` | Dispose the current instance | `boolean` | +| `POST` | `/instance/dispose` | Beendet die aktuelle Instanz | `boolean` | --- ### Konfiguration -| Method | Path | Description | Response | -| ------- | ------------------- | --------------------------------- | ---------------------------------------------------------------------------------------- | -| `GET` | `/config` | Get config info | Config | -| `PATCH` | `/config` | Update config | Config | -| `GET` | `/config/providers` | List providers and default models | `{ providers: `Provider[]`, default: { [key: string]: string } }` | +| Method | Path | Description | Response | +| ------- | ------------------- | ------------------------------------ | ---------------------------------------------------------------------------------------- | +| `GET` | `/config` | Ruft Konfigurationsinfos ab | Config | +| `PATCH` | `/config` | Aktualisiert Konfiguration | Config | +| `GET` | `/config/providers` | Listet Provider und Standard-Modelle | `{ providers: `Provider[]`, default: { [key: string]: string } }` | --- ### Anbieter -| Method | Path | Description | Response | -| ------ | -------------------------------- | ------------------------------------ | ----------------------------------------------------------------------------------- | -| `GET` | `/provider` | List all providers | `{ all: `Provider[]`, default: {...}, connected: string[] }` | -| `GET` | `/provider/auth` | Get provider authentication methods | `{ [providerID: string]: `ProviderAuthMethod[]` }` | -| `POST` | `/provider/{id}/oauth/authorize` | Authorize a provider using OAuth | ProviderAuthAuthorization | -| `POST` | `/provider/{id}/oauth/callback` | Handle OAuth callback for a provider | `boolean` | +| Method | Path | Description | Response | +| ------ | -------------------------------- | ----------------------------------------------- | ----------------------------------------------------------------------------------- | +| `GET` | `/provider` | Listet alle Provider | `{ all: `Provider[]`, default: {...}, connected: string[] }` | +| `GET` | `/provider/auth` | Ruft Authentifizierungsmethoden der Provider ab | `{ [providerID: string]: `ProviderAuthMethod[]` }` | +| `POST` | `/provider/{id}/oauth/authorize` | Autorisiert einen Provider per OAuth | ProviderAuthAuthorization | +| `POST` | `/provider/{id}/oauth/callback` | Behandelt OAuth-Callback fuer einen Provider | `boolean` | --- ### Sitzungen -| Method | Path | Description | Notes | -| -------- | ---------------------------------------- | ------------------------------------- | ---------------------------------------------------------------------------------- | -| `GET` | `/session` | List all sessions | Returns Session[] | -| `POST` | `/session` | Create a new session | body: `{ parentID?, title? }`, returns Session | -| `GET` | `/session/status` | Get session status for all sessions | Returns `{ [sessionID: string]: `SessionStatus` }` | -| `GET` | `/session/:id` | Get session details | Returns Session | -| `DELETE` | `/session/:id` | Delete a session and all its data | Returns `boolean` | -| `PATCH` | `/session/:id` | Update session properties | body: `{ title? }`, returns Session | -| `GET` | `/session/:id/children` | Get a session's child sessions | Returns Session[] | -| `GET` | `/session/:id/todo` | Get the todo list for a session | Returns Todo[] | -| `POST` | `/session/:id/init` | Analyze app and create `AGENTS.md` | body: `{ messageID, providerID, modelID }`, returns `boolean` | -| `POST` | `/session/:id/fork` | Fork an existing session at a message | body: `{ messageID? }`, returns Session | -| `POST` | `/session/:id/abort` | Abort a running session | Returns `boolean` | -| `POST` | `/session/:id/share` | Share a session | Returns Session | -| `DELETE` | `/session/:id/share` | Unshare a session | Returns Session | -| `GET` | `/session/:id/diff` | Get the diff for this session | query: `messageID?`, returns FileDiff[] | -| `POST` | `/session/:id/summarize` | Summarize the session | body: `{ providerID, modelID }`, returns `boolean` | -| `POST` | `/session/:id/revert` | Revert a message | body: `{ messageID, partID? }`, returns `boolean` | -| `POST` | `/session/:id/unrevert` | Restore all reverted messages | Returns `boolean` | -| `POST` | `/session/:id/permissions/:permissionID` | Respond to a permission request | body: `{ response, remember? }`, returns `boolean` | +| Method | Path | Description | Notes | +| -------- | ---------------------------------------- | --------------------------------------------------- | ---------------------------------------------------------------------------------- | +| `GET` | `/session` | Listet alle Sitzungen | Gibt Session[] zurueck | +| `POST` | `/session` | Erstellt eine neue Sitzung | body: `{ parentID?, title? }`, returns Session | +| `GET` | `/session/status` | Ruft Status aller Sitzungen ab | Gibt `{ [sessionID: string]: `SessionStatus` }` zurueck | +| `GET` | `/session/:id` | Ruft Sitzungsdetails ab | Gibt Session zurueck | +| `DELETE` | `/session/:id` | Loescht eine Sitzung und alle Daten | Gibt `boolean` zurueck | +| `PATCH` | `/session/:id` | Aktualisiert Sitzungseigenschaften | body: `{ title? }`, returns Session | +| `GET` | `/session/:id/children` | Ruft Kind-Sitzungen einer Sitzung ab | Gibt Session[] zurueck | +| `GET` | `/session/:id/todo` | Ruft die Todo-Liste einer Sitzung ab | Gibt Todo[] zurueck | +| `POST` | `/session/:id/init` | Analysiert App und erstellt `AGENTS.md` | body: `{ messageID, providerID, modelID }`, returns `boolean` | +| `POST` | `/session/:id/fork` | Forkt eine bestehende Sitzung an einer Nachricht | body: `{ messageID? }`, returns Session | +| `POST` | `/session/:id/abort` | Bricht eine laufende Sitzung ab | Gibt `boolean` zurueck | +| `POST` | `/session/:id/share` | Teilt eine Sitzung | Gibt Session zurueck | +| `DELETE` | `/session/:id/share` | Hebt Teilen einer Sitzung auf | Gibt Session zurueck | +| `GET` | `/session/:id/diff` | Ruft den Diff fuer diese Sitzung ab | query: `messageID?`, returns FileDiff[] | +| `POST` | `/session/:id/summarize` | Fasst die Sitzung zusammen | body: `{ providerID, modelID }`, returns `boolean` | +| `POST` | `/session/:id/revert` | Setzt eine Nachricht zurueck | body: `{ messageID, partID? }`, returns `boolean` | +| `POST` | `/session/:id/unrevert` | Stellt alle zurueckgesetzten Nachrichten wieder her | Gibt `boolean` zurueck | +| `POST` | `/session/:id/permissions/:permissionID` | Antwortet auf eine Berechtigungsanfrage | body: `{ response, remember? }`, returns `boolean` | --- ### Nachrichten -| Method | Path | Description | Notes | -| ------ | --------------------------------- | --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `GET` | `/session/:id/message` | List messages in a session | query: `limit?`, returns `{ info: `Message`, parts: `Part[]`}[]` | -| `POST` | `/session/:id/message` | Send a message and wait for response | body: `{ messageID?, model?, agent?, noReply?, system?, tools?, parts }`, returns `{ info: `Message`, parts: `Part[]`}` | -| `GET` | `/session/:id/message/:messageID` | Get message details | Returns `{ info: `Message`, parts: `Part[]`}` | -| `POST` | `/session/:id/prompt_async` | Send a message asynchronously (no wait) | body: same as `/session/:id/message`, returns `204 No Content` | -| `POST` | `/session/:id/command` | Execute a slash command | body: `{ messageID?, agent?, model?, command, arguments }`, returns `{ info: `Message`, parts: `Part[]`}` | -| `POST` | `/session/:id/shell` | Run a shell command | body: `{ agent, model?, command }`, returns `{ info: `Message`, parts: `Part[]`}` | +| Method | Path | Description | Notes | +| ------ | --------------------------------- | --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `GET` | `/session/:id/message` | Listet Nachrichten in einer Sitzung | query: `limit?`, returns `{ info: `Message`, parts: `Part[]`}[]` | +| `POST` | `/session/:id/message` | Sendet eine Nachricht und wartet auf Antwort | body: `{ messageID?, model?, agent?, noReply?, system?, tools?, parts }`, returns `{ info: `Message`, parts: `Part[]`}` | +| `GET` | `/session/:id/message/:messageID` | Ruft Nachrichtendetails ab | Returns `{ info: `Message`, parts: `Part[]`}` | +| `POST` | `/session/:id/prompt_async` | Sendet eine Nachricht asynchron (ohne Warten) | body: same as `/session/:id/message`, returns `204 No Content` | +| `POST` | `/session/:id/command` | Fuehrt einen Slash-Befehl aus | body: `{ messageID?, agent?, model?, command, arguments }`, returns `{ info: `Message`, parts: `Part[]`}` | +| `POST` | `/session/:id/shell` | Fuehrt einen Shell-Befehl aus | body: `{ agent, model?, command }`, returns `{ info: `Message`, parts: `Part[]`}` | --- ### Befehle -| Method | Path | Description | Response | -| ------ | ---------- | ----------------- | --------------------------------------------- | -| `GET` | `/command` | List all commands | Command[] | +| Method | Path | Description | Response | +| ------ | ---------- | ------------------- | --------------------------------------------- | +| `GET` | `/command` | Listet alle Befehle | Command[] | --- ### Dateien -| Method | Path | Description | Response | -| ------ | ------------------------ | ---------------------------------- | ------------------------------------------------------------------------------------------- | -| `GET` | `/find?pattern=` | Search for text in files | Array of match objects with `path`, `lines`, `line_number`, `absolute_offset`, `submatches` | -| `GET` | `/find/file?query=` | Find files and directories by name | `string[]` (paths) | -| `GET` | `/find/symbol?query=` | Find workspace symbols | Symbol[] | -| `GET` | `/file?path=` | List files and directories | FileNode[] | -| `GET` | `/file/content?path=

` | Read a file | FileContent | -| `GET` | `/file/status` | Get status for tracked files | File[] | +| Method | Path | Description | Response | +| ------ | ------------------------ | ------------------------------------------- | ------------------------------------------------------------------------------------------- | +| `GET` | `/find?pattern=` | Sucht Text in Dateien | Array of match objects with `path`, `lines`, `line_number`, `absolute_offset`, `submatches` | +| `GET` | `/find/file?query=` | Findet Dateien und Verzeichnisse nach Namen | `string[]` (paths) | +| `GET` | `/find/symbol?query=` | Findet Workspace-Symbole | Symbol[] | +| `GET` | `/file?path=` | Listet Dateien und Verzeichnisse | FileNode[] | +| `GET` | `/file/content?path=

` | Liest eine Datei | FileContent | +| `GET` | `/file/status` | Ruft Status fuer getrackte Dateien ab | File[] | #### `/find/file` Abfrageparameter @@ -216,71 +216,71 @@ Der opencode-Server stellt folgende APIs bereit. ### Werkzeuge (Experimentell) -| Method | Path | Description | Response | -| ------ | ------------------------------------------- | ---------------------------------------- | -------------------------------------------- | -| `GET` | `/experimental/tool/ids` | List all tool IDs | ToolIDs | -| `GET` | `/experimental/tool?provider=

&model=` | List tools with JSON schemas for a model | ToolList | +| Method | Path | Description | Response | +| ------ | ------------------------------------------- | --------------------------------------------- | -------------------------------------------- | +| `GET` | `/experimental/tool/ids` | Listet alle Tool-IDs | ToolIDs | +| `GET` | `/experimental/tool?provider=

&model=` | Listet Tools mit JSON-Schemas fuer ein Modell | ToolList | --- ### LSP, Formatierer & MCP -| Method | Path | Description | Response | -| ------ | ------------ | -------------------------- | -------------------------------------------------------- | -| `GET` | `/lsp` | Get LSP server status | LSPStatus[] | -| `GET` | `/formatter` | Get formatter status | FormatterStatus[] | -| `GET` | `/mcp` | Get MCP server status | `{ [name: string]: `MCPStatus` }` | -| `POST` | `/mcp` | Add MCP server dynamically | body: `{ name, config }`, returns MCP status object | +| Method | Path | Description | Response | +| ------ | ------------ | -------------------------------- | -------------------------------------------------------- | +| `GET` | `/lsp` | Ruft LSP-Server-Status ab | LSPStatus[] | +| `GET` | `/formatter` | Ruft Formatter-Status ab | FormatterStatus[] | +| `GET` | `/mcp` | Ruft MCP-Server-Status ab | `{ [name: string]: `MCPStatus` }` | +| `POST` | `/mcp` | Fuegt MCP-Server dynamisch hinzu | body: `{ name, config }`, returns MCP status object | --- ### Agenten -| Method | Path | Description | Response | -| ------ | -------- | ------------------------- | ------------------------------------------- | -| `GET` | `/agent` | List all available agents | Agent[] | +| Method | Path | Description | Response | +| ------ | -------- | -------------------------------- | ------------------------------------------- | +| `GET` | `/agent` | Listet alle verfuegbaren Agenten | Agent[] | --- ### Logging -| Method | Path | Description | Response | -| ------ | ------ | ------------------------------------------------------------ | --------- | -| `POST` | `/log` | Write log entry. Body: `{ service, level, message, extra? }` | `boolean` | +| Method | Path | Description | Response | +| ------ | ------ | ----------------------------------------------------------------- | --------- | +| `POST` | `/log` | Schreibt Log-Eintrag. Body: `{ service, level, message, extra? }` | `boolean` | --- ### TUI -| Method | Path | Description | Response | -| ------ | ----------------------- | ------------------------------------------- | ---------------------- | -| `POST` | `/tui/append-prompt` | Append text to the prompt | `boolean` | -| `POST` | `/tui/open-help` | Open the help dialog | `boolean` | -| `POST` | `/tui/open-sessions` | Open the session selector | `boolean` | -| `POST` | `/tui/open-themes` | Open the theme selector | `boolean` | -| `POST` | `/tui/open-models` | Open the model selector | `boolean` | -| `POST` | `/tui/submit-prompt` | Submit the current prompt | `boolean` | -| `POST` | `/tui/clear-prompt` | Clear the prompt | `boolean` | -| `POST` | `/tui/execute-command` | Execute a command (`{ command }`) | `boolean` | -| `POST` | `/tui/show-toast` | Show toast (`{ title?, message, variant }`) | `boolean` | -| `GET` | `/tui/control/next` | Wait for the next control request | Control request object | -| `POST` | `/tui/control/response` | Respond to a control request (`{ body }`) | `boolean` | +| Method | Path | Description | Response | +| ------ | ----------------------- | ----------------------------------------------- | ---------------------- | +| `POST` | `/tui/append-prompt` | Haengt Text an den Prompt an | `boolean` | +| `POST` | `/tui/open-help` | Oeffnet den Hilfedialog | `boolean` | +| `POST` | `/tui/open-sessions` | Oeffnet die Session-Auswahl | `boolean` | +| `POST` | `/tui/open-themes` | Oeffnet die Theme-Auswahl | `boolean` | +| `POST` | `/tui/open-models` | Oeffnet die Modell-Auswahl | `boolean` | +| `POST` | `/tui/submit-prompt` | Sendet den aktuellen Prompt ab | `boolean` | +| `POST` | `/tui/clear-prompt` | Leert den Prompt | `boolean` | +| `POST` | `/tui/execute-command` | Fuehrt einen Befehl aus (`{ command }`) | `boolean` | +| `POST` | `/tui/show-toast` | Zeigt Toast (`{ title?, message, variant }`) | `boolean` | +| `GET` | `/tui/control/next` | Wartet auf die naechste Kontrollanfrage | Control request object | +| `POST` | `/tui/control/response` | Antwortet auf eine Kontrollanfrage (`{ body }`) | `boolean` | --- ### Authentifizierung -| Method | Path | Description | Response | -| ------ | ----------- | --------------------------------------------------------------- | --------- | -| `PUT` | `/auth/:id` | Set authentication credentials. Body must match provider schema | `boolean` | +| Method | Path | Description | Response | +| ------ | ----------- | ------------------------------------------------------------------------ | --------- | +| `PUT` | `/auth/:id` | Setzt Authentifizierungsdaten. Body muss dem Provider-Schema entsprechen | `boolean` | --- ### Events -| Method | Path | Description | Response | -| ------ | -------- | ----------------------------------------------------------------------------- | ------------------------- | -| `GET` | `/event` | Server-sent events stream. First event is `server.connected`, then bus events | Server-sent events stream | +| Method | Path | Description | Response | +| ------ | -------- | ------------------------------------------------------------------------------- | ------------------------- | +| `GET` | `/event` | Server-Sent Events Stream. Erstes Event ist `server.connected`, dann Bus-Events | Server-sent events stream | --- @@ -288,4 +288,4 @@ Der opencode-Server stellt folgende APIs bereit. | Method | Path | Description | Response | | ------ | ------ | ------------------------- | --------------------------- | -| `GET` | `/doc` | OpenAPI 3.1 specification | HTML page with OpenAPI spec | +| `GET` | `/doc` | OpenAPI 3.1 Spezifikation | HTML page with OpenAPI spec | diff --git a/packages/web/src/content/docs/de/skills.mdx b/packages/web/src/content/docs/de/skills.mdx index 63dbb126bbe..72fba3eb099 100644 --- a/packages/web/src/content/docs/de/skills.mdx +++ b/packages/web/src/content/docs/de/skills.mdx @@ -36,11 +36,11 @@ Globale Definitionen kommen zusaetzlich aus `~/.config/opencode/skills/*/SKILL.m Jede `SKILL.md` muss mit YAML-Frontmatter beginnen. Nur diese Felder werden ausgewertet: -- `name` (required) -- `description` (required) +- `name` (erforderlich) +- `description` (erforderlich) - `license` (optional) - `compatibility` (optional) -- `metadata` (optional, string-to-string map) +- `metadata` (optional, String-zu-String-Map) Unbekannte Frontmatter-Felder werden ignoriert. diff --git a/packages/web/src/content/docs/de/themes.mdx b/packages/web/src/content/docs/de/themes.mdx index b97816801d6..f3671220f96 100644 --- a/packages/web/src/content/docs/de/themes.mdx +++ b/packages/web/src/content/docs/de/themes.mdx @@ -83,29 +83,29 @@ Damit lassen sich Themes einfach erstellen und anpassen. ### Hierarchie -Themes are loaded from multiple directories in the following order where later directories override earlier ones: +Themes werden aus mehreren Verzeichnissen in folgender Reihenfolge geladen, wobei spaetere Verzeichnisse fruehere ueberschreiben: -1. **Built-in themes** - These are embedded in the binary -2. **User config directory** - Defined in `~/.config/opencode/themes/*.json` or `$XDG_CONFIG_HOME/opencode/themes/*.json` -3. **Project root directory** - Defined in the `/.opencode/themes/*.json` -4. **Current working directory** - Defined in `./.opencode/themes/*.json` +1. **Eingebaute Themes** - Diese sind im Binary eingebettet +2. **Benutzer-Config-Verzeichnis** - Definiert in `~/.config/opencode/themes/*.json` oder `$XDG_CONFIG_HOME/opencode/themes/*.json` +3. **Projekt-Root-Verzeichnis** - Definiert in `/.opencode/themes/*.json` +4. **Aktuelles Arbeitsverzeichnis** - Definiert in `./.opencode/themes/*.json` -If multiple directories contain a theme with the same name, the theme from the directory with higher priority will be used. +Wenn mehrere Verzeichnisse ein Theme mit demselben Namen enthalten, wird das Theme aus dem Verzeichnis mit der hoeheren Prioritaet verwendet. --- ### Theme erstellen -To create a custom theme, create a JSON file in one of the theme directories. +Um ein eigenes Theme zu erstellen, lege eine JSON-Datei in einem der Theme-Verzeichnisse an. -For user-wide themes: +Fuer benutzerweite Themes: ```bash no-frame mkdir -p ~/.config/opencode/themes vim ~/.config/opencode/themes/my-theme.json ``` -And for project-specific themes. +Und fuer projektspezifische Themes: ```bash no-frame mkdir -p .opencode/themes @@ -116,34 +116,34 @@ vim .opencode/themes/my-theme.json ### JSON-Format -Themes use a flexible JSON format with support for: +Themes nutzen ein flexibles JSON-Format mit Unterstuetzung fuer: -- **Hex colors**: `"#ffffff"` -- **ANSI colors**: `3` (0-255) -- **Color references**: `"primary"` or custom definitions -- **Dark/light variants**: `{"dark": "#000", "light": "#fff"}` -- **No color**: `"none"` - Uses the terminal's default color or transparent +- **Hex-Farben**: `"#ffffff"` +- **ANSI-Farben**: `3` (0-255) +- **Farbreferenzen**: `"primary"` oder eigene Definitionen +- **Dunkel/Hell-Varianten**: `{"dark": "#000", "light": "#fff"}` +- **Keine Farbe**: `"none"` - Nutzt die Standardfarbe des Terminals oder transparent --- ### Farbdefinitionen -The `defs` section is optional and it allows you to define reusable colors that can be referenced in the theme. +Der `defs`-Abschnitt ist optional und erlaubt es dir, wiederverwendbare Farben zu definieren, die im Theme referenziert werden koennen. --- ### Terminal-Standardwerte -The special value `"none"` can be used for any color to inherit the terminal's default color. This is particularly useful for creating themes that blend seamlessly with your terminal's color scheme: +Der spezielle Wert `"none"` kann fuer jede Farbe verwendet werden, um die Standardfarbe des Terminals zu erben. Das ist besonders nuetzlich, um Themes zu erstellen, die nahtlos mit deinem Terminal-Farbschema verschmelzen: -- `"text": "none"` - Uses terminal's default foreground color -- `"background": "none"` - Uses terminal's default background color +- `"text": "none"` - Nutzt die Standard-Vordergrundfarbe des Terminals +- `"background": "none"` - Nutzt die Standard-Hintergrundfarbe des Terminals --- ### Beispiel -Here's an example of a custom theme: +Hier ist ein Beispiel fuer ein eigenes Theme: ```json title="my-theme.json" { diff --git a/packages/web/src/content/docs/de/tools.mdx b/packages/web/src/content/docs/de/tools.mdx index b7a41d87bb7..0038f251847 100644 --- a/packages/web/src/content/docs/de/tools.mdx +++ b/packages/web/src/content/docs/de/tools.mdx @@ -109,7 +109,7 @@ Das Tool `write` wird ueber die Berechtigung `edit` gesteuert. ### read -Read file contents from your codebase. +Liest Dateiinhalte aus deiner Codebasis. ```json title="opencode.json" {4} { @@ -120,13 +120,13 @@ Read file contents from your codebase. } ``` -This tool reads files and returns their contents. It supports reading specific line ranges for large files. +Dieses Tool liest Dateien und gibt deren Inhalt zurueck. Es unterstuetzt das Lesen spezifischer Zeilenbereiche bei grossen Dateien. --- ### grep -Search file contents using regular expressions. +Durchsucht Dateiinhalte mit regulaeren Ausdruecken. ```json title="opencode.json" {4} { @@ -137,13 +137,13 @@ Search file contents using regular expressions. } ``` -Fast content search across your codebase. Supports full regex syntax and file pattern filtering. +Schnelle Inhaltssuche in deiner Codebasis. Unterstuetzt volle Regex-Syntax und Filterung nach Dateimustern. --- ### glob -Find files by pattern matching. +Findet Dateien per Musterabgleich. ```json title="opencode.json" {4} { @@ -154,13 +154,13 @@ Find files by pattern matching. } ``` -Search for files using glob patterns like `**/*.js` or `src/**/*.ts`. Returns matching file paths sorted by modification time. +Sucht nach Dateien mit Glob-Mustern wie `**/*.js` oder `src/**/*.ts`. Gibt passende Dateipfade sortiert nach Aenderungsdatum zurueck. --- ### list -List files and directories in a given path. +Listet Dateien und Verzeichnisse in einem Pfad auf. ```json title="opencode.json" {4} { @@ -171,16 +171,16 @@ List files and directories in a given path. } ``` -This tool lists directory contents. It accepts glob patterns to filter results. +Dieses Tool listet Verzeichnisinhalte auf. Es akzeptiert Glob-Muster zum Filtern der Ergebnisse. --- -### lsp (experimental) +### lsp (experimentell) -Interact with your configured LSP servers to get code intelligence features like definitions, references, hover info, and call hierarchy. +Interagiere mit deinen konfigurierten LSP-Servern fuer Code-Intelligence-Features wie Definitionen, Referenzen, Hover-Infos und Call-Hierarchien. :::note -This tool is only available when `OPENCODE_EXPERIMENTAL_LSP_TOOL=true` (or `OPENCODE_EXPERIMENTAL=true`). +Dieses Tool ist nur verfuegbar, wenn `OPENCODE_EXPERIMENTAL_LSP_TOOL=true` (oder `OPENCODE_EXPERIMENTAL=true`) gesetzt ist. ::: ```json title="opencode.json" {4} @@ -192,15 +192,15 @@ This tool is only available when `OPENCODE_EXPERIMENTAL_LSP_TOOL=true` (or `OPEN } ``` -Supported operations include `goToDefinition`, `findReferences`, `hover`, `documentSymbol`, `workspaceSymbol`, `goToImplementation`, `prepareCallHierarchy`, `incomingCalls`, and `outgoingCalls`. +Unterstuetzte Operationen sind `goToDefinition`, `findReferences`, `hover`, `documentSymbol`, `workspaceSymbol`, `goToImplementation`, `prepareCallHierarchy`, `incomingCalls` und `outgoingCalls`. -To configure which LSP servers are available for your project, see [LSP Servers](/docs/lsp). +Um verfuegbare LSP-Server fuer dein Projekt zu konfigurieren, siehe [LSP-Server](/docs/lsp). --- ### patch -Apply patches to files. +Wendet Patches auf Dateien an. ```json title="opencode.json" {4} { @@ -211,17 +211,17 @@ Apply patches to files. } ``` -This tool applies patch files to your codebase. Useful for applying diffs and patches from various sources. +Dieses Tool wendet Patch-Dateien auf deine Codebasis an. Nuetzlich fuer Diffs und Patches aus verschiedenen Quellen. :::note -The `patch` tool is controlled by the `edit` permission, which covers all file modifications (`edit`, `write`, `patch`, `multiedit`). +Das Tool `patch` wird ueber die Berechtigung `edit` gesteuert, welche alle Datei-Aenderungen abdeckt (`edit`, `write`, `patch`, `multiedit`). ::: --- ### skill -Load a [skill](/docs/skills) (a `SKILL.md` file) and return its content in the conversation. +Laedt einen [Skill](/docs/skills) (eine `SKILL.md`-Datei) und gibt dessen Inhalt in der Unterhaltung zurueck. ```json title="opencode.json" {4} { @@ -236,7 +236,7 @@ Load a [skill](/docs/skills) (a `SKILL.md` file) and return its content in the c ### todowrite -Manage todo lists during coding sessions. +Verwaltet Todo-Listen waehrend Coding-Sessions. ```json title="opencode.json" {4} { @@ -247,17 +247,17 @@ Manage todo lists during coding sessions. } ``` -Creates and updates task lists to track progress during complex operations. The LLM uses this to organize multi-step tasks. +Erstellt und aktualisiert Aufgabenlisten, um den Fortschritt bei komplexen Operationen zu verfolgen. Das LLM nutzt dies, um mehrstufige Aufgaben zu organisieren. :::note -This tool is disabled for subagents by default, but you can enable it manually. [Learn more](/docs/agents/#permissions) +Dieses Tool ist fuer Sub-Agenten standardmaessig deaktiviert, kann aber manuell aktiviert werden. [Mehr dazu](/docs/agents/#permissions) ::: --- ### todoread -Read existing todo lists. +Liest existierende Todo-Listen. ```json title="opencode.json" {4} { @@ -268,17 +268,17 @@ Read existing todo lists. } ``` -Reads the current todo list state. Used by the LLM to track what tasks are pending or completed. +Liest den aktuellen Status der Todo-Liste. Wird vom LLM genutzt, um offene oder erledigte Aufgaben zu verfolgen. :::note -This tool is disabled for subagents by default, but you can enable it manually. [Learn more](/docs/agents/#permissions) +Dieses Tool ist fuer Sub-Agenten standardmaessig deaktiviert, kann aber manuell aktiviert werden. [Mehr dazu](/docs/agents/#permissions) ::: --- ### webfetch -Fetch web content. +Ruft Webinhalte ab. ```json title="opencode.json" {4} { @@ -289,18 +289,18 @@ Fetch web content. } ``` -Allows the LLM to fetch and read web pages. Useful for looking up documentation or researching online resources. +Erlaubt dem LLM, Webseiten abzurufen und zu lesen. Nuetzlich zum Nachschlagen von Dokumentation oder fuer Online-Recherche. --- ### websearch -Search the web for information. +Durchsucht das Web nach Informationen. :::note -This tool is only available when using the OpenCode provider or when the `OPENCODE_ENABLE_EXA` environment variable is set to any truthy value (e.g., `true` or `1`). +Dieses Tool ist nur verfuegbar, wenn der OpenCode-Provider genutzt wird oder die Umgebungsvariable `OPENCODE_ENABLE_EXA` auf einen 'truthy' Wert (z. B. `true` oder `1`) gesetzt ist. -To enable when launching OpenCode: +Zum Aktivieren beim Start von OpenCode: ```bash OPENCODE_ENABLE_EXA=1 opencode @@ -317,19 +317,19 @@ OPENCODE_ENABLE_EXA=1 opencode } ``` -Performs web searches using Exa AI to find relevant information online. Useful for researching topics, finding current events, or gathering information beyond the training data cutoff. +Fuehrt Websuchen mit Exa AI durch, um relevante Informationen online zu finden. Nuetzlich fuer Recherche, aktuelle Ereignisse oder Informationen jenseits des Trainingsdatums. -No API key is required — the tool connects directly to Exa AI's hosted MCP service without authentication. +Kein API-Key erforderlich — das Tool verbindet sich direkt mit dem gehosteten MCP-Service von Exa AI ohne Authentifizierung. :::tip -Use `websearch` when you need to find information (discovery), and `webfetch` when you need to retrieve content from a specific URL (retrieval). +Nutze `websearch` zum Finden von Informationen (Discovery) und `webfetch` zum Abrufen von Inhalten einer spezifischen URL (Retrieval). ::: --- ### question -Ask the user questions during execution. +Stellt dem Benutzer waehrend der Ausfuehrung Fragen. ```json title="opencode.json" {4} { @@ -340,14 +340,14 @@ Ask the user questions during execution. } ``` -This tool allows the LLM to ask the user questions during a task. It's useful for: +Dieses Tool erlaubt dem LLM, dem Benutzer waehrend einer Aufgabe Fragen zu stellen. Nuetzlich fuer: -- Gathering user preferences or requirements -- Clarifying ambiguous instructions -- Getting decisions on implementation choices -- Offering choices about what direction to take +- Sammeln von Benutzerpraeferenzen oder Anforderungen +- Klaerung mehrdeutiger Anweisungen +- Entscheidungen bei Implementierungsoptionen einholen +- Auswahlmoeglichkeiten fuer das weitere Vorgehen anbieten -Each question includes a header, the question text, and a list of options. Users can select from the provided options or type a custom answer. When there are multiple questions, users can navigate between them before submitting all answers. +Jede Frage enthaelt eine Ueberschrift, den Fragetext und eine Liste von Optionen. Benutzer koennen aus den Optionen waehlen oder eine eigene Antwort eingeben. Bei mehreren Fragen koennen Benutzer zwischen ihnen navigieren, bevor sie alle Antworten absenden. --- diff --git a/packages/web/src/content/docs/de/troubleshooting.mdx b/packages/web/src/content/docs/de/troubleshooting.mdx index 076200cbd95..e8286faca09 100644 --- a/packages/web/src/content/docs/de/troubleshooting.mdx +++ b/packages/web/src/content/docs/de/troubleshooting.mdx @@ -12,7 +12,7 @@ Wenn OpenCode Probleme macht, starte mit Logs und lokal gespeicherten Daten auf Logdateien werden hier gespeichert: - **macOS/Linux**: `~/.local/share/opencode/log/` -- **Windows**: Press `WIN+R` and paste `%USERPROFILE%\.local\share\opencode\log` +- **Windows**: Druecke `WIN+R` und fuege ein: `%USERPROFILE%\.local\share\opencode\log` Dateinamen enthalten Zeitstempel (z. B. `2025-01-09T123456.log`) und es bleiben die letzten 10 Logs erhalten. @@ -25,7 +25,7 @@ Mit `--log-level` bekommst du detailliertere Diagnoseinfos, z. B. `opencode --lo opencode speichert Sitzungs- und App-Daten auf der Festplatte unter: - **macOS/Linux**: `~/.local/share/opencode/` -- **Windows**: Press `WIN+R` and paste `%USERPROFILE%\.local\share\opencode` +- **Windows**: Druecke `WIN+R` und fuege ein: `%USERPROFILE%\.local\share\opencode` Dieses Verzeichnis enthaelt: @@ -52,17 +52,17 @@ Viele Probleme kommen von fehlerhaften Plugins, kaputtem Cache oder falschen Ser ### Plugins deaktivieren -If the desktop app is crashing on launch, hanging, or behaving strangely, start by disabling plugins. +Wenn die Desktop-App beim Start abstuerzt, haengt oder sich seltsam verhaelt, deaktiviere zunaechst Plugins. #### Globale Konfiguration prüfen -Open your global config file and look for a `plugin` key. +Oeffne deine globale Konfigurationsdatei und suche nach dem `plugin`-Schluessel. -- **macOS/Linux**: `~/.config/opencode/opencode.jsonc` (or `~/.config/opencode/opencode.json`) -- **macOS/Linux** (older installs): `~/.local/share/opencode/opencode.jsonc` -- **Windows**: Press `WIN+R` and paste `%USERPROFILE%\.config\opencode\opencode.jsonc` +- **macOS/Linux**: `~/.config/opencode/opencode.jsonc` (oder `~/.config/opencode/opencode.json`) +- **macOS/Linux** (aeltere Installationen): `~/.local/share/opencode/opencode.jsonc` +- **Windows**: Druecke `WIN+R` und fuege ein: `%USERPROFILE%\.config\opencode\opencode.jsonc` -If you have plugins configured, temporarily disable them by removing the key or setting it to an empty array: +Wenn du Plugins konfiguriert hast, deaktiviere sie voruebergehend, indem du den Schluessel entfernst oder auf ein leeres Array setzt: ```jsonc { @@ -73,100 +73,100 @@ If you have plugins configured, temporarily disable them by removing the key or #### Plugin-Verzeichnisse prüfen -OpenCode can also load local plugins from disk. Temporarily move these out of the way (or rename the folder) and restart the desktop app: +OpenCode kann auch lokale Plugins von der Festplatte laden. Verschiebe diese voruebergehend (oder benenne den Ordner um) und starte die Desktop-App neu: -- **Global plugins** +- **Globale Plugins** - **macOS/Linux**: `~/.config/opencode/plugins/` - - **Windows**: Press `WIN+R` and paste `%USERPROFILE%\.config\opencode\plugins` -- **Project plugins** (only if you use per-project config) + - **Windows**: Druecke `WIN+R` und fuege ein: `%USERPROFILE%\.config\opencode\plugins` +- **Projekt-Plugins** (nur bei projektspezifischer Konfig) - `/.opencode/plugins/` -If the app starts working again, re-enable plugins one at a time to find which one is causing the issue. +Wenn die App wieder funktioniert, aktiviere Plugins nacheinander, um den Verursacher zu finden. --- ### Cache leeren -If disabling plugins doesn't help (or a plugin install is stuck), clear the cache so OpenCode can rebuild it. +Wenn das Deaktivieren von Plugins nicht hilft (oder eine Plugin-Installation haengt), leere den Cache, damit OpenCode ihn neu aufbauen kann. -1. Quit OpenCode Desktop completely. -2. Delete the cache directory: +1. Beende OpenCode Desktop komplett. +2. Loesche das Cache-Verzeichnis: -- **macOS**: Finder -> `Cmd+Shift+G` -> paste `~/.cache/opencode` -- **Linux**: delete `~/.cache/opencode` (or run `rm -rf ~/.cache/opencode`) -- **Windows**: Press `WIN+R` and paste `%USERPROFILE%\.cache\opencode` +- **macOS**: Finder -> `Cmd+Shift+G` -> einfuegen: `~/.cache/opencode` +- **Linux**: loesche `~/.cache/opencode` (oder fuehre aus: `rm -rf ~/.cache/opencode`) +- **Windows**: Druecke `WIN+R` und fuege ein: `%USERPROFILE%\.cache\opencode` -3. Restart OpenCode Desktop. +3. Starte OpenCode Desktop neu. --- ### Server-Verbindungsprobleme beheben -OpenCode Desktop can either start its own local server (default) or connect to a server URL you configured. +OpenCode Desktop kann entweder einen eigenen lokalen Server starten (Standard) oder sich mit einer konfigurierten Server-URL verbinden. -If you see a **"Connection Failed"** dialog (or the app never gets past the splash screen), check for a custom server URL. +Wenn du einen **"Connection Failed"**-Dialog siehst (oder die App beim Splash-Screen haengen bleibt), pruefe auf eine benutzerdefinierte Server-URL. #### Desktop-Standard-Server-URL löschen -From the Home screen, click the server name (with the status dot) to open the Server picker. In the **Default server** section, click **Clear**. +Klicke im Startbildschirm auf den Servernamen (mit dem Statuspunkt), um die Serverauswahl zu oeffnen. Klicke im Bereich **Default server** auf **Clear**. #### `server.port` / `server.hostname` aus Konfiguration entfernen -If your `opencode.json(c)` contains a `server` section, temporarily remove it and restart the desktop app. +Wenn deine `opencode.json(c)` einen `server`-Abschnitt enthaelt, entferne ihn voruebergehend und starte die Desktop-App neu. #### Umgebungsvariablen prüfen -If you have `OPENCODE_PORT` set in your environment, the desktop app will try to use that port for the local server. +Wenn du `OPENCODE_PORT` in deiner Umgebung gesetzt hast, versucht die Desktop-App diesen Port fuer den lokalen Server zu nutzen. -- Unset `OPENCODE_PORT` (or pick a free port) and restart. +- Setze `OPENCODE_PORT` zurueck (oder waehle einen freien Port) und starte neu. --- ### Linux: Wayland / X11-Probleme -On Linux, some Wayland setups can cause blank windows or compositor errors. +Unter Linux koennen manche Wayland-Setups leere Fenster oder Compositor-Fehler verursachen. -- If you're on Wayland and the app is blank/crashing, try launching with `OC_ALLOW_WAYLAND=1`. -- If that makes things worse, remove it and try launching under an X11 session instead. +- Wenn du Wayland nutzt und die App leer ist/abstuerzt, versuche den Start mit `OC_ALLOW_WAYLAND=1`. +- Wenn das es verschlimmert, entferne es und versuche den Start in einer X11-Session. --- ### Windows: WebView2-Laufzeit -On Windows, OpenCode Desktop requires the Microsoft Edge **WebView2 Runtime**. If the app opens to a blank window or won't start, install/update WebView2 and try again. +Unter Windows benoetigt OpenCode Desktop die Microsoft Edge **WebView2 Runtime**. Wenn die App ein leeres Fenster zeigt oder nicht startet, installiere/aktualisiere WebView2 und versuche es erneut. --- ### Windows: Allgemeine Performance-Probleme -If you're experiencing slow performance, file access issues, or terminal problems on Windows, try using [WSL (Windows Subsystem for Linux)](/docs/windows-wsl). WSL provides a Linux environment that works more seamlessly with OpenCode's features. +Wenn du langsame Performance, Dateizugriffsprobleme oder Terminal-Probleme unter Windows hast, versuche [WSL (Windows Subsystem for Linux)](/docs/windows-wsl). WSL bietet eine Linux-Umgebung, die nahtloser mit OpenCode-Features funktioniert. --- ### Benachrichtigungen werden nicht angezeigt -OpenCode Desktop only shows system notifications when: +OpenCode Desktop zeigt Systembenachrichtigungen nur wenn: -- notifications are enabled for OpenCode in your OS settings, and -- the app window is not focused. +- Benachrichtigungen fuer OpenCode in den OS-Einstellungen aktiviert sind, und +- das App-Fenster nicht fokussiert ist. --- ### Desktop-App-Speicher zurücksetzen (letzter Ausweg) -If the app won't start and you can't clear settings from inside the UI, reset the desktop app's saved state. +Wenn die App nicht startet und du Einstellungen nicht in der UI loeschen kannst, setze den gespeicherten Zustand der Desktop-App zurueck. -1. Quit OpenCode Desktop. -2. Find and delete these files (they live in the OpenCode Desktop app data directory): +1. Beende OpenCode Desktop. +2. Finde und loesche diese Dateien (im App-Data-Verzeichnis von OpenCode Desktop): - `opencode.settings.dat` (desktop default server URL) -- `opencode.global.dat` and `opencode.workspace.*.dat` (UI state like recent servers/projects) +- `opencode.global.dat` und `opencode.workspace.*.dat` (UI state like recent servers/projects) -To find the directory quickly: +So findest du das Verzeichnis schnell: -- **macOS**: Finder -> `Cmd+Shift+G` -> `~/Library/Application Support` (then search for the filenames above) -- **Linux**: search under `~/.local/share` for the filenames above -- **Windows**: Press `WIN+R` -> `%APPDATA%` (then search for the filenames above) +- **macOS**: Finder -> `Cmd+Shift+G` -> `~/Library/Application Support` (dann suche nach den Dateinamen oben) +- **Linux**: suche unter `~/.local/share` nach den Dateinamen oben +- **Windows**: Druecke `WIN+R` -> `%APPDATA%` (dann suche nach den Dateinamen oben) --- @@ -198,84 +198,83 @@ Hier sind typische Fehlerbilder und wie du sie loest. ### OpenCode startet nicht -1. Check the logs for error messages -2. Try running with `--print-logs` to see output in the terminal -3. Ensure you have the latest version with `opencode upgrade` +1. Pruefe die Logs auf Fehlermeldungen +2. Versuche den Start mit `--print-logs`, um Ausgaben im Terminal zu sehen +3. Stelle sicher, dass du die neueste Version hast: `opencode upgrade` --- ### Authentifizierungsprobleme -1. Try re-authenticating with the `/connect` command in the TUI -2. Check that your API keys are valid -3. Ensure your network allows connections to the provider's API +1. Versuche erneute Authentifizierung mit `/connect` in der TUI +2. Pruefe, ob deine API-Keys gueltig sind +3. Stelle sicher, dass dein Netzwerk Verbindungen zur Provider-API erlaubt --- ### Modell nicht verfügbar -1. Check that you've authenticated with the provider -2. Verify the model name in your config is correct -3. Some models may require specific access or subscriptions +1. Pruefe, ob du dich beim Provider authentifiziert hast +2. Verifiziere, dass der Modellname in deiner Config korrekt ist +3. Manche Modelle erfordern speziellen Zugriff oder Abonnements -If you encounter `ProviderModelNotFoundError` you are most likely incorrectly -referencing a model somewhere. -Models should be referenced like so: `/` +Wenn du `ProviderModelNotFoundError` erhaeltst, referenzierst du ein Modell wahrscheinlich falsch. +Modelle sollten so referenziert werden: `/` -Examples: +Beispiele: - `openai/gpt-4.1` - `openrouter/google/gemini-2.5-flash` - `opencode/kimi-k2` -To figure out what models you have access to, run `opencode models` +Um zu sehen, auf welche Modelle du Zugriff hast, fuehre `opencode models` aus. --- ### ProviderInitError -If you encounter a ProviderInitError, you likely have an invalid or corrupted configuration. +Wenn du einen ProviderInitError erhaeltst, hast du wahrscheinlich eine ungueltige oder korrupte Konfiguration. -To resolve this: +Zur Loesung: -1. First, verify your provider is set up correctly by following the [providers guide](/docs/providers) -2. If the issue persists, try clearing your stored configuration: +1. Pruefe zuerst, ob dein Provider korrekt eingerichtet ist, gemaess dem [Provider-Guide](/docs/providers) +2. Wenn das Problem besteht, versuche deine gespeicherte Konfiguration zu loeschen: ```bash rm -rf ~/.local/share/opencode ``` - On Windows, press `WIN+R` and delete: `%USERPROFILE%\.local\share\opencode` + Unter Windows druecke `WIN+R` und loesche: `%USERPROFILE%\.local\share\opencode` -3. Re-authenticate with your provider using the `/connect` command in the TUI. +3. Authentifiziere dich erneut beim Provider mit dem `/connect`-Befehl in der TUI. --- ### AI_APICallError und Provider-Paket-Probleme -If you encounter API call errors, this may be due to outdated provider packages. opencode dynamically installs provider packages (OpenAI, Anthropic, Google, etc.) as needed and caches them locally. +Wenn du API-Call-Fehler erhaeltst, kann das an veralteten Provider-Paketen liegen. opencode installiert Provider-Pakete (OpenAI, Anthropic, Google, etc.) dynamisch bei Bedarf und cached sie lokal. -To resolve provider package issues: +Um Provider-Paket-Probleme zu loesen: -1. Clear the provider package cache: +1. Leere den Provider-Paket-Cache: ```bash rm -rf ~/.cache/opencode ``` - On Windows, press `WIN+R` and delete: `%USERPROFILE%\.cache\opencode` + Unter Windows druecke `WIN+R` und loesche: `%USERPROFILE%\.cache\opencode` -2. Restart opencode to reinstall the latest provider packages +2. Starte opencode neu, um die neuesten Provider-Pakete zu installieren -This will force opencode to download the most recent versions of provider packages, which often resolves compatibility issues with model parameters and API changes. +Dies zwingt opencode, die neuesten Versionen der Provider-Pakete herunterzuladen, was oft Kompatibilitaetsprobleme mit Modellparametern und API-Aenderungen loest. --- ### Copy/Paste funktioniert nicht unter Linux -Linux users need to have one of the following clipboard utilities installed for copy/paste functionality to work: +Linux-Nutzer muessen eines der folgenden Clipboard-Utilities installiert haben, damit Copy/Paste funktioniert: -**For X11 systems:** +**Fuer X11-Systeme:** ```bash apt install -y xclip @@ -283,13 +282,13 @@ apt install -y xclip apt install -y xsel ``` -**For Wayland systems:** +**Fuer Wayland-Systeme:** ```bash apt install -y wl-clipboard ``` -**For headless environments:** +**Fuer Headless-Umgebungen:** ```bash apt install -y xvfb @@ -298,4 +297,4 @@ Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & export DISPLAY=:99.0 ``` -opencode will detect if you're using Wayland and prefer `wl-clipboard`, otherwise it will try to find clipboard tools in order of: `xclip` and `xsel`. +opencode erkennt, ob du Wayland nutzt und bevorzugt `wl-clipboard`, sonst versucht es `xclip` und `xsel` (in dieser Reihenfolge). diff --git a/packages/web/src/content/docs/de/tui.mdx b/packages/web/src/content/docs/de/tui.mdx index ebd16fa76aa..144662c1195 100644 --- a/packages/web/src/content/docs/de/tui.mdx +++ b/packages/web/src/content/docs/de/tui.mdx @@ -74,7 +74,7 @@ Hier sind alle verfuegbaren Slash-Commands: ### connect -Add a provider to OpenCode. Allows you to select from available providers and add their API keys. +Fuegt einen Provider zu OpenCode hinzu. Erlaubt die Auswahl aus verfuegbaren Providern und das Hinzufuegen ihrer API-Keys. ```bash frame="none" /connect @@ -84,7 +84,7 @@ Add a provider to OpenCode. Allows you to select from available providers and ad ### compact -Compact the current session. _Alias_: `/summarize` +Kompaktiert die aktuelle Session. _Alias_: `/summarize` ```bash frame="none" /compact @@ -96,7 +96,7 @@ Compact the current session. _Alias_: `/summarize` ### details -Toggle tool execution details. +Schaltet Tool-Ausfuehrungsdetails um. ```bash frame="none" /details @@ -108,7 +108,7 @@ Toggle tool execution details. ### editor -Open external editor for composing messages. Uses the editor set in your `EDITOR` environment variable. [Learn more](#editor-setup). +Oeffnet externen Editor zum Verfassen von Nachrichten. Nutzt den in der `EDITOR`-Umgebungsvariable gesetzten Editor. [Mehr dazu](#editor-setup). ```bash frame="none" /editor @@ -120,7 +120,7 @@ Open external editor for composing messages. Uses the editor set in your `EDITOR ### exit -Exit OpenCode. _Aliases_: `/quit`, `/q` +Beendet OpenCode. _Aliase_: `/quit`, `/q` ```bash frame="none" /exit @@ -132,7 +132,7 @@ Exit OpenCode. _Aliases_: `/quit`, `/q` ### export -Export current conversation to Markdown and open in your default editor. Uses the editor set in your `EDITOR` environment variable. [Learn more](#editor-setup). +Exportiert die aktuelle Unterhaltung als Markdown und oeffnet sie in deinem Standard-Editor. Nutzt den in der `EDITOR`-Umgebungsvariable gesetzten Editor. [Mehr dazu](#editor-setup). ```bash frame="none" /export @@ -144,7 +144,7 @@ Export current conversation to Markdown and open in your default editor. Uses th ### help -Show the help dialog. +Zeigt den Hilfedialog. ```bash frame="none" /help @@ -156,7 +156,7 @@ Show the help dialog. ### init -Create or update `AGENTS.md` file. [Learn more](/docs/rules). +Erstellt oder aktualisiert die `AGENTS.md`-Datei. [Mehr dazu](/docs/rules). ```bash frame="none" /init @@ -168,7 +168,7 @@ Create or update `AGENTS.md` file. [Learn more](/docs/rules). ### models -List available models. +Listet verfuegbare Modelle. ```bash frame="none" /models @@ -180,7 +180,7 @@ List available models. ### new -Start a new session. _Alias_: `/clear` +Startet eine neue Session. _Alias_: `/clear` ```bash frame="none" /new @@ -192,14 +192,13 @@ Start a new session. _Alias_: `/clear` ### redo -Redo a previously undone message. Only available after using `/undo`. +Wiederholt eine zuvor rueckgaengig gemachte Nachricht. Nur verfuegbar nach Verwendung von `/undo`. :::tip -Any file changes will also be restored. +Auch Dateiaenderungen werden wiederhergestellt. ::: -Internally, this uses Git to manage the file changes. So your project **needs to -be a Git repository**. +Intern nutzt dies Git, um die Dateiaenderungen zu verwalten. Dein Projekt muss also **ein Git-Repository sein**. ```bash frame="none" /redo @@ -211,7 +210,7 @@ be a Git repository**. ### sessions -List and switch between sessions. _Aliases_: `/resume`, `/continue` +Listet Sessions und wechselt zwischen ihnen. _Aliase_: `/resume`, `/continue` ```bash frame="none" /sessions @@ -223,7 +222,7 @@ List and switch between sessions. _Aliases_: `/resume`, `/continue` ### share -Share current session. [Learn more](/docs/share). +Teilt die aktuelle Session. [Mehr dazu](/docs/share). ```bash frame="none" /share @@ -235,7 +234,7 @@ Share current session. [Learn more](/docs/share). ### themes -List available themes. +Listet verfuegbare Themes. ```bash frame="none" /theme @@ -247,10 +246,10 @@ List available themes. ### thinking -Toggle the visibility of thinking/reasoning blocks in the conversation. When enabled, you can see the model's reasoning process for models that support extended thinking. +Schaltet die Sichtbarkeit von Thinking/Reasoning-Bloecken in der Unterhaltung um. Wenn aktiviert, kannst du den Denkprozess des Modells sehen (bei Modellen, die das unterstuetzen). :::note -This command only controls whether thinking blocks are **displayed** - it does not enable or disable the model's reasoning capabilities. To toggle actual reasoning capabilities, use `ctrl+t` to cycle through model variants. +Dieser Befehl steuert nur, ob Thinking-Bloecke **angezeigt** werden - er aktiviert oder deaktiviert nicht die Reasoning-Faehigkeiten des Modells. Um die Reasoning-Faehigkeiten umzuschalten, nutze `ctrl+t`, um durch die Modell-Varianten zu wechseln. ::: ```bash frame="none" @@ -261,14 +260,13 @@ This command only controls whether thinking blocks are **displayed** - it does n ### undo -Undo last message in the conversation. Removes the most recent user message, all subsequent responses, and any file changes. +Macht die letzte Nachricht in der Unterhaltung rueckgaengig. Entfernt die letzte Benutzernachricht, alle folgenden Antworten und alle Dateiaenderungen. :::tip -Any file changes made will also be reverted. +Auch durchgefuehrte Dateiaenderungen werden rueckgaengig gemacht. ::: -Internally, this uses Git to manage the file changes. So your project **needs to -be a Git repository**. +Intern nutzt dies Git, um die Dateiaenderungen zu verwalten. Dein Projekt muss also **ein Git-Repository sein**. ```bash frame="none" /undo @@ -280,7 +278,7 @@ be a Git repository**. ### unshare -Unshare current session. [Learn more](/docs/share#un-sharing). +Hebt das Teilen der aktuellen Session auf. [Mehr dazu](/docs/share#un-sharing). ```bash frame="none" /unshare @@ -290,7 +288,7 @@ Unshare current session. [Learn more](/docs/share#un-sharing). ## Editor-Einrichtung -Both the `/editor` and `/export` commands use the editor specified in your `EDITOR` environment variable. +Sowohl `/editor` als auch `/export` nutzen den in deiner `EDITOR`-Umgebungsvariable spezifizierten Editor. @@ -304,7 +302,7 @@ Both the `/editor` and `/export` commands use the editor specified in your `EDIT export EDITOR="code --wait" ``` - To make it permanent, add this to your shell profile; + Um es dauerhaft zu machen, fuege dies zu deinem Shell-Profil hinzu; `~/.bashrc`, `~/.zshrc`, etc. @@ -318,8 +316,7 @@ Both the `/editor` and `/export` commands use the editor specified in your `EDIT set EDITOR=code --wait ``` - To make it permanent, use **System Properties** > **Environment - Variables**. + Um es dauerhaft zu machen, nutze **Systemeigenschaften** > **Umgebungsvariablen**. @@ -332,62 +329,72 @@ Both the `/editor` and `/export` commands use the editor specified in your `EDIT $env:EDITOR = "code --wait" ``` - To make it permanent, add this to your PowerShell profile. + Um es dauerhaft zu machen, fuege dies zu deinem PowerShell-Profil hinzu. -Popular editor options include: +Beliebte Editoren sind: - `code` - Visual Studio Code - `cursor` - Cursor - `windsurf` - Windsurf -- `nvim` - Neovim editor -- `vim` - Vim editor -- `nano` - Nano editor +- `nvim` - Neovim +- `vim` - Vim +- `nano` - Nano - `notepad` - Windows Notepad - `subl` - Sublime Text :::note -Some editors like VS Code need to be started with the `--wait` flag. +Einige Editoren wie VS Code muessen mit dem `--wait`-Flag gestartet werden. ::: -Some editors need command-line arguments to run in blocking mode. The `--wait` flag makes the editor process block until closed. +Einige Editoren benoetigen Befehlszeilenargumente, um im blockierenden Modus zu laufen. Das `--wait`-Flag sorgt dafuer, dass der Editor-Prozess blockiert, bis er geschlossen wird. --- ## Konfiguration -You can customize TUI behavior through your OpenCode config file. +Du kannst das Verhalten der TUI ueber die Datei `tui.json` (oder `tui.jsonc`) anpassen. -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", - "tui": { - "scroll_speed": 3, - "scroll_acceleration": { - "enabled": true - } - } + "$schema": "https://opencode.ai/tui.json", + "theme": "opencode", + "keybinds": { + "leader": "ctrl+x" + }, + "scroll_speed": 3, + "scroll_acceleration": { + "enabled": true + }, + "diff_style": "auto" } ``` +Dies ist getrennt von `opencode.json`, welche das Server-/Runtime-Verhalten konfiguriert. + ### Optionen -- `scroll_acceleration` - Enable macOS-style scroll acceleration for smooth, natural scrolling. When enabled, scroll speed increases with rapid scrolling gestures and stays precise for slower movements. **This setting takes precedence over `scroll_speed` and overrides it when enabled.** -- `scroll_speed` - Controls how fast the TUI scrolls when using scroll commands (minimum: `1`). Defaults to `3`. **Note: This is ignored if `scroll_acceleration.enabled` is set to `true`.** +- `theme` - Setzt dein UI-Theme. [Mehr dazu](/docs/themes). +- `keybinds` - Passt Tastenkombinationen an. [Mehr dazu](/docs/keybinds). +- `scroll_acceleration.enabled` - Aktiviert Scroll-Beschleunigung im macOS-Stil fuer weiches, natuerliches Scrollen. Wenn aktiviert, erhoeht sich die Scroll-Geschwindigkeit bei schnellen Gesten und bleibt praezise bei langsamen Bewegungen. **Diese Einstellung hat Vorrang vor `scroll_speed` und ueberschreibt es, wenn aktiviert.** +- `scroll_speed` - Steuert, wie schnell die TUI scrollt (Minimum: `0.001`, unterstuetzt Dezimalwerte). Standard ist `3`. **Hinweis: Wird ignoriert, wenn `scroll_acceleration.enabled` auf `true` gesetzt ist.** +- `diff_style` - Steuert die Diff-Darstellung. `"auto"` passt sich der Terminalbreite an, `"stacked"` zeigt immer ein einspaltiges Layout. + +Verwende `OPENCODE_TUI_CONFIG`, um einen benutzerdefinierten TUI-Konfigurationspfad zu laden. --- ## Anpassung -You can customize various aspects of the TUI view using the command palette (`ctrl+x h` or `/help`). These settings persist across restarts. +Du kannst verschiedene Aspekte der TUI-Ansicht ueber die Befehlspalette (`ctrl+x h` oder `/help`) anpassen. Diese Einstellungen bleiben ueber Neustarts hinweg erhalten. --- #### Benutzername-Anzeige -Toggle whether your username appears in chat messages. Access this through: +Schaltet um, ob dein Benutzername in Chat-Nachrichten erscheint. Zugriff hierueber: -- Command palette: Search for "username" or "hide username" -- The setting persists automatically and will be remembered across TUI sessions +- Befehlspalette: Suche nach "username" oder "hide username" +- Die Einstellung wird automatisch gespeichert und bleibt ueber TUI-Sessions hinweg erhalten. diff --git a/packages/web/src/content/docs/de/zen.mdx b/packages/web/src/content/docs/de/zen.mdx index f52ed5f363f..7545b10deb2 100644 --- a/packages/web/src/content/docs/de/zen.mdx +++ b/packages/web/src/content/docs/de/zen.mdx @@ -57,6 +57,7 @@ Du kannst unsere Modelle auch ueber die folgenden API-Endpunkte aufrufen. | Model | Model ID | Endpoint | AI SDK Package | | ------------------ | ------------------ | -------------------------------------------------- | --------------------------- | +| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | @@ -66,22 +67,24 @@ Du kannst unsere Modelle auch ueber die folgenden API-Endpunkte aufrufen. | GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | | Gemini 3 Pro | gemini-3-pro | `https://opencode.ai/zen/v1/models/gemini-3-pro` | `@ai-sdk/google` | | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.1 | minimax-m2.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.1 Free | minimax-m2.1-free | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| GLM 5 | glm-5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 4.7 | glm-4.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM 4.7 Free | glm-4.7-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 4.6 | glm-4.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 Free | kimi-k2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2 Thinking | kimi-k2-thinking | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2 | kimi-k2 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Qwen3 Coder 480B | qwen3-coder | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -110,29 +113,34 @@ Unten siehst du die Preise **pro 1 Mio. Tokens**. | Model | Input | Output | Cached Read | Cached Write | | --------------------------------- | ------ | ------ | ----------- | ------------ | | Big Pickle | Free | Free | Free | - | -| MiniMax M2.1 Free | Free | Free | Free | - | +| MiniMax M2.5 Free | Free | Free | Free | - | +| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | - | | MiniMax M2.1 | $0.30 | $1.20 | $0.10 | - | -| GLM 4.7 Free | Free | Free | Free | - | +| GLM 5 | $1.00 | $3.20 | $0.20 | - | | GLM 4.7 | $0.60 | $2.20 | $0.10 | - | | GLM 4.6 | $0.60 | $2.20 | $0.10 | - | -| Kimi K2.5 Free | Free | Free | Free | - | | Kimi K2.5 | $0.60 | $3.00 | $0.08 | - | | Kimi K2 Thinking | $0.40 | $2.50 | - | - | | Kimi K2 | $0.40 | $2.50 | - | - | | Qwen3 Coder 480B | $0.45 | $1.50 | - | - | +| Claude Opus 4.6 (≤ 200K tokens) | $5.00 | $25.00 | $0.50 | $6.25 | +| Claude Opus 4.6 (> 200K tokens) | $10.00 | $37.50 | $1.00 | $12.50 | +| Claude Opus 4.5 | $5.00 | $25.00 | $0.50 | $6.25 | +| Claude Opus 4.1 | $15.00 | $75.00 | $1.50 | $18.75 | +| Claude Sonnet 4.6 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 | +| Claude Sonnet 4.6 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 | | Claude Sonnet 4.5 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 | | Claude Sonnet 4.5 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 | | Claude Sonnet 4 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 | | Claude Sonnet 4 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 | | Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 | | Claude Haiku 3.5 | $0.80 | $4.00 | $0.08 | $1.00 | -| Claude Opus 4.6 (≤ 200K tokens) | $5.00 | $25.00 | $0.50 | $6.25 | -| Claude Opus 4.6 (> 200K tokens) | $10.00 | $37.50 | $1.00 | $12.50 | -| Claude Opus 4.5 | $5.00 | $25.00 | $0.50 | $6.25 | -| Claude Opus 4.1 | $15.00 | $75.00 | $1.50 | $18.75 | +| Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | +| Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | +| GPT 5.3 Codex | $1.75 | $14.00 | $0.175 | - | | GPT 5.2 | $1.75 | $14.00 | $0.175 | - | | GPT 5.2 Codex | $1.75 | $14.00 | $0.175 | - | | GPT 5.1 | $1.07 | $8.50 | $0.107 | - | @@ -152,10 +160,8 @@ Kreditkartengebuehren geben wir zum Selbstkostenpreis weiter (4,4 % + $0.30 pro Die kostenlosen Modelle: -- GLM 4.7 Free ist fuer begrenzte Zeit verfuegbar, um Feedback zu sammeln und das Modell zu verbessern. -- Kimi K2.5 Free ist fuer begrenzte Zeit verfuegbar, um Feedback zu sammeln und das Modell zu verbessern. -- MiniMax M2.1 Free ist fuer begrenzte Zeit verfuegbar, um Feedback zu sammeln und das Modell zu verbessern. -- Big Pickle ist ein Stealth-Modell und ebenfalls zeitlich begrenzt kostenlos verfuegbar. +- MiniMax M2.5 Free ist fuer begrenzte Zeit auf OpenCode verfuegbar. Das Team nutzt diese Zeit, um Feedback zu sammeln und das Modell zu verbessern. +- Big Pickle ist ein Stealth-Modell, das fuer begrenzte Zeit kostenlos auf OpenCode verfuegbar ist. Das Team nutzt diese Zeit, um Feedback zu sammeln und das Modell zu verbessern. Wenn du Fragen hast, kontaktiere uns. @@ -183,12 +189,10 @@ Mit aktiviertem Auto-Reload kann die Abrechnung dennoch darueber liegen, falls d Alle Modelle werden in den USA gehostet. Unsere Provider arbeiten grundsaetzlich mit Zero-Retention und nutzen deine Daten nicht zum Training, mit folgenden Ausnahmen: -- Big Pickle: During its free period, collected data may be used to improve the model. -- GLM 4.7 Free: During its free period, collected data may be used to improve the model. -- Kimi K2.5 Free: During its free period, collected data may be used to improve the model. -- MiniMax M2.1 Free: During its free period, collected data may be used to improve the model. -- OpenAI APIs: Requests are retained for 30 days in accordance with [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). -- Anthropic APIs: Requests are retained for 30 days in accordance with [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage). +- Big Pickle: Waehrend der kostenlosen Phase koennen gesammelte Daten zur Verbesserung des Modells genutzt werden. +- MiniMax M2.5 Free: Waehrend der kostenlosen Phase koennen gesammelte Daten zur Verbesserung des Modells genutzt werden. +- OpenAI APIs: Anfragen werden fuer 30 Tage gemaess [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data) aufbewahrt. +- Anthropic APIs: Anfragen werden fuer 30 Tage gemaess [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage) aufbewahrt. --- @@ -232,8 +236,7 @@ Du kannst eigene OpenAI- oder Anthropic-API-Keys verwenden und trotzdem andere Z Bei eigenen Keys erfolgt die Token-Abrechnung direkt ueber den Provider, nicht ueber Zen. -For example, your organization might already have a key for OpenAI or Anthropic -and you want to use that instead of the one that Zen provides. +Zum Beispiel hat deine Organisation vielleicht bereits einen Key fuer OpenAI oder Anthropic und du moechtest diesen anstelle des von Zen bereitgestellten nutzen. --- diff --git a/packages/web/src/content/docs/ecosystem.mdx b/packages/web/src/content/docs/ecosystem.mdx index 85839a2d82b..03301ed0aa1 100644 --- a/packages/web/src/content/docs/ecosystem.mdx +++ b/packages/web/src/content/docs/ecosystem.mdx @@ -12,43 +12,48 @@ Want to add your OpenCode related project to this list? Submit a PR. You can also check out [awesome-opencode](https://github.com/awesome-opencode/awesome-opencode) and [opencode.cafe](https://opencode.cafe), a community that aggregates the ecosystem and community. --- +| [OpenGUI](https://git.idunara.com/emmanuel/OpenGUI) | Electron and React desktop GUI for OpenCode with multi-project sessions and MCP management | ## Plugins -| Name | Description | -| --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | -| [opencode-daytona](https://github.com/jamesmurdza/daytona/blob/main/guides/typescript/opencode/README.md) | Automatically run OpenCode sessions in isolated Daytona sandboxes with git sync and live previews | -| [opencode-helicone-session](https://github.com/H2Shami/opencode-helicone-session) | Automatically inject Helicone session headers for request grouping | -| [opencode-type-inject](https://github.com/nick-vi/opencode-type-inject) | Auto-inject TypeScript/Svelte types into file reads with lookup tools | -| [opencode-openai-codex-auth](https://github.com/numman-ali/opencode-openai-codex-auth) | Use your ChatGPT Plus/Pro subscription instead of API credits | -| [opencode-gemini-auth](https://github.com/jenslys/opencode-gemini-auth) | Use your existing Gemini plan instead of API billing | -| [opencode-antigravity-auth](https://github.com/NoeFabris/opencode-antigravity-auth) | Use Antigravity's free models instead of API billing | -| [opencode-devcontainers](https://github.com/athal7/opencode-devcontainers) | Multi-branch devcontainer isolation with shallow clones and auto-assigned ports | -| [opencode-google-antigravity-auth](https://github.com/shekohex/opencode-google-antigravity-auth) | Google Antigravity OAuth Plugin, with support for Google Search, and more robust API handling | -| [opencode-dynamic-context-pruning](https://github.com/Tarquinen/opencode-dynamic-context-pruning) | Optimize token usage by pruning obsolete tool outputs | -| [opencode-websearch-cited](https://github.com/ghoulr/opencode-websearch-cited.git) | Add native websearch support for supported providers with Google grounded style | -| [opencode-pty](https://github.com/shekohex/opencode-pty.git) | Enables AI agents to run background processes in a PTY, send interactive input to them. | -| [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Instructions for non-interactive shell commands - prevents hangs from TTY-dependent operations | -| [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Track OpenCode usage with Wakatime | -| [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Clean up markdown tables produced by LLMs | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | 10x faster code editing with Morph Fast Apply API and lazy edit markers | -| [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Background agents, pre-built LSP/AST/MCP tools, curated agents, Claude Code compatible | -| [opencode-notificator](https://github.com/panta82/opencode-notificator) | Desktop notifications and sound alerts for OpenCode sessions | -| [opencode-notifier](https://github.com/mohak34/opencode-notifier) | Desktop notifications and sound alerts for permission, completion, and error events | -| [opencode-zellij-namer](https://github.com/24601/opencode-zellij-namer) | AI-powered automatic Zellij session naming based on OpenCode context | -| [opencode-skillful](https://github.com/zenobi-us/opencode-skillful) | Allow OpenCode agents to lazy load prompts on demand with skill discovery and injection | -| [opencode-supermemory](https://github.com/supermemoryai/opencode-supermemory) | Persistent memory across sessions using Supermemory | -| [@plannotator/opencode](https://github.com/backnotprop/plannotator/tree/main/apps/opencode-plugin) | Interactive plan review with visual annotation and private/offline sharing | -| [@openspoon/subtask2](https://github.com/spoons-and-mirrors/subtask2) | Extend opencode /commands into a powerful orchestration system with granular flow control | -| [opencode-scheduler](https://github.com/different-ai/opencode-scheduler) | Schedule recurring jobs using launchd (Mac) or systemd (Linux) with cron syntax | -| [micode](https://github.com/vtemian/micode) | Structured Brainstorm → Plan → Implement workflow with session continuity | -| [octto](https://github.com/vtemian/octto) | Interactive browser UI for AI brainstorming with multi-question forms | -| [opencode-background-agents](https://github.com/kdcokenny/opencode-background-agents) | Claude Code-style background agents with async delegation and context persistence | -| [opencode-notify](https://github.com/kdcokenny/opencode-notify) | Native OS notifications for OpenCode – know when tasks complete | -| [opencode-workspace](https://github.com/kdcokenny/opencode-workspace) | Bundled multi-agent orchestration harness – 16 components, one install | -| [opencode-worktree](https://github.com/kdcokenny/opencode-worktree) | Zero-friction git worktrees for OpenCode | +| Name | Description | +| -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | +| [opencode-daytona](https://github.com/daytonaio/daytona/tree/main/libs/opencode-plugin) | Automatically run OpenCode sessions in isolated Daytona sandboxes with git sync and live previews | +| [opencode-helicone-session](https://github.com/H2Shami/opencode-helicone-session) | Automatically inject Helicone session headers for request grouping | +| [opencode-type-inject](https://github.com/nick-vi/opencode-type-inject) | Auto-inject TypeScript/Svelte types into file reads with lookup tools | +| [opencode-openai-codex-auth](https://github.com/numman-ali/opencode-openai-codex-auth) | Use your ChatGPT Plus/Pro subscription instead of API credits | +| [opencode-gemini-auth](https://github.com/jenslys/opencode-gemini-auth) | Use your existing Gemini plan instead of API billing | +| [opencode-antigravity-auth](https://github.com/NoeFabris/opencode-antigravity-auth) | Use Antigravity's free models instead of API billing | +| [opencode-devcontainers](https://github.com/athal7/opencode-devcontainers) | Multi-branch devcontainer isolation with shallow clones and auto-assigned ports | +| [opencode-google-antigravity-auth](https://github.com/shekohex/opencode-google-antigravity-auth) | Google Antigravity OAuth Plugin, with support for Google Search, and more robust API handling | +| [opencode-dynamic-context-pruning](https://github.com/Tarquinen/opencode-dynamic-context-pruning) | Optimize token usage by pruning obsolete tool outputs | +| [opencode-vibeguard](https://github.com/inkdust2021/opencode-vibeguard) | Redact secrets/PII into VibeGuard-style placeholders before LLM calls; restore locally | +| [opencode-websearch-cited](https://github.com/ghoulr/opencode-websearch-cited.git) | Add native websearch support for supported providers with Google grounded style | +| [opencode-pty](https://github.com/shekohex/opencode-pty.git) | Enables AI agents to run background processes in a PTY, send interactive input to them. | +| [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Instructions for non-interactive shell commands - prevents hangs from TTY-dependent operations | +| [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Track OpenCode usage with Wakatime | +| [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Clean up markdown tables produced by LLMs | +| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | 10x faster code editing with Morph Fast Apply API and lazy edit markers | +| [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Background agents, pre-built LSP/AST/MCP tools, curated agents, Claude Code compatible | +| [opencode-notificator](https://github.com/panta82/opencode-notificator) | Desktop notifications and sound alerts for OpenCode sessions | +| [opencode-notifier](https://github.com/mohak34/opencode-notifier) | Desktop notifications and sound alerts for permission, completion, and error events | +| [opencode-zellij-namer](https://github.com/24601/opencode-zellij-namer) | AI-powered automatic Zellij session naming based on OpenCode context | +| [opencode-skillful](https://github.com/zenobi-us/opencode-skillful) | Allow OpenCode agents to lazy load prompts on demand with skill discovery and injection | +| [opencode-loamlog](https://github.com/tongsh6/loamlog/tree/master/plugins/opencode) | Captures OpenCode session on session.idle event, POSTs payload to local loam daemon for archival as JSON + Markdown | +| [opencode-supermemory](https://github.com/supermemoryai/opencode-supermemory) | Persistent memory across sessions using Supermemory | +| [@plannotator/opencode](https://github.com/backnotprop/plannotator/tree/main/apps/opencode-plugin) | Interactive plan review with visual annotation and private/offline sharing | +| [@openspoon/subtask2](https://github.com/spoons-and-mirrors/subtask2) | Extend opencode /commands into a powerful orchestration system with granular flow control | +| [opencode-scheduler](https://github.com/different-ai/opencode-scheduler) | Schedule recurring jobs using launchd (Mac) or systemd (Linux) with cron syntax | +| [micode](https://github.com/vtemian/micode) | Structured Brainstorm → Plan → Implement workflow with session continuity | +| [octto](https://github.com/vtemian/octto) | Interactive browser UI for AI brainstorming with multi-question forms | +| [opencode-background-agents](https://github.com/kdcokenny/opencode-background-agents) | Claude Code-style background agents with async delegation and context persistence | +| [opencode-notify](https://github.com/kdcokenny/opencode-notify) | Native OS notifications for OpenCode – know when tasks complete | +| [opencode-workspace](https://github.com/kdcokenny/opencode-workspace) | Bundled multi-agent orchestration harness – 16 components, one install | +| [opencode-worktree](https://github.com/kdcokenny/opencode-worktree) | Zero-friction git worktrees for OpenCode | +| [MPM-Coding](https://github.com/halflifezyf2680/MPM-Coding) | Tool-first MCP server with project-anchored AST indexing, symbol search, call-graph impact analysis, and resumable task chains | --- +| [OpenGUI](https://git.idunara.com/emmanuel/OpenGUI) | Electron and React desktop GUI for OpenCode with multi-project sessions and MCP management | ## Projects @@ -67,6 +72,7 @@ You can also check out [awesome-opencode](https://github.com/awesome-opencode/aw | [CodeNomad](https://github.com/NeuralNomadsAI/CodeNomad) | Desktop, Web, Mobile and Remote Client App for OpenCode | --- +| [OpenGUI](https://git.idunara.com/emmanuel/OpenGUI) | Electron and React desktop GUI for OpenCode with multi-project sessions and MCP management | ## Agents diff --git a/packages/web/src/content/docs/es/custom-tools.mdx b/packages/web/src/content/docs/es/custom-tools.mdx index 76188e184e3..133abf4ab23 100644 --- a/packages/web/src/content/docs/es/custom-tools.mdx +++ b/packages/web/src/content/docs/es/custom-tools.mdx @@ -79,6 +79,32 @@ Esto crea dos herramientas: `math_add` y `math_multiply`. --- +#### Colisiones de nombres con herramientas integradas + +Las herramientas personalizadas están codificadas por el nombre de la herramienta. Si una herramienta personalizada utiliza el mismo nombre que una herramienta integrada, la herramienta personalizada tiene prioridad. + +Por ejemplo, este archivo reemplaza la herramienta `bash` integrada: + +```ts title=".opencode/tools/bash.ts" +import { tool } from "@opencode-ai/plugin" + +export default tool({ + description: "Restricted bash wrapper", + args: { + command: tool.schema.string(), + }, + async execute(args) { + return `blocked: ${args.command}` + }, +}) +``` + +:::note +Prefiera nombres únicos a menos que intencionalmente desee reemplazar una herramienta integrada. Si desea deshabilitar una herramienta integrada pero no anularla, use [permisos](/docs/permissions). +::: + +--- + ### Argumentos Puedes usar `tool.schema`, que es simplemente [Zod](https://zod.dev), para definir tipos de argumentos. diff --git a/packages/web/src/content/docs/es/ecosystem.mdx b/packages/web/src/content/docs/es/ecosystem.mdx index b1f4bdc9a40..dcd20713723 100644 --- a/packages/web/src/content/docs/es/ecosystem.mdx +++ b/packages/web/src/content/docs/es/ecosystem.mdx @@ -15,38 +15,39 @@ También puedes consultar [awesome-opencode](https://github.com/awesome-opencode ## Complementos -| Nombre | Descripción | -| --------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | -| [opencode-daytona](https://github.com/jamesmurdza/daytona/blob/main/guides/typescript/opencode/README.md) | Ejecute automáticamente sesiones OpenCode en entornos sandbox aislados de Daytona con git sync y vistas previas en vivo | -| [opencode-helicone-session](https://github.com/H2Shami/opencode-helicone-session) | Inyecte automáticamente encabezados de sesión de Helicone para agrupación de solicitudes | -| [opencode-type-inject](https://github.com/nick-vi/opencode-type-inject) | Inyecte automáticamente tipos TypeScript/Svelte en lecturas de archivos con herramientas de búsqueda | -| [opencode-openai-codex-auth](https://github.com/numman-ali/opencode-openai-codex-auth) | Utilice su suscripción ChatGPT Plus/Pro en lugar de créditos API | -| [opencode-gemini-auth](https://github.com/jenslys/opencode-gemini-auth) | Utilice su plan Gemini existente en lugar de la facturación API | -| [opencode-antigravity-auth](https://github.com/NoeFabris/opencode-antigravity-auth) | Utilice los modelos gratuitos de Antigravity en lugar de la facturación API | -| [opencode-devcontainers](https://github.com/athal7/opencode-devcontainers) | Aislamiento de contenedores de desarrollo de múltiples ramas con clones superficiales y puertos asignados automáticamente | -| [opencode-google-antigravity-auth](https://github.com/shekohex/opencode-google-antigravity-auth) | Complemento Google Antigravity OAuth, compatible con la Búsqueda de Google y manejo más sólido de API | -| [opencode-dynamic-context-pruning](https://github.com/Tarquinen/opencode-dynamic-context-pruning) | Optimice el uso de tokens eliminando los resultados de herramientas obsoletas | -| [opencode-websearch-cited](https://github.com/ghoulr/opencode-websearch-cited.git) | Agregue soporte de búsqueda web nativa para proveedores compatibles con el estilo basado en Google | -| [opencode-pty](https://github.com/shekohex/opencode-pty.git) | Permite a los agentes de IA ejecutar procesos en segundo plano en un PTY y enviarles información interactiva. | -| [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Instrucciones para comandos de shell no interactivos: evita bloqueos de operaciones dependientes de TTY | -| [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Seguimiento del uso de OpenCode con Wakatime | -| [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Limpiar tablas de Markdown producidas por LLMs | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | Edición de código 10 veces más rápida con Morph Fast Apply API y marcadores de edición diferidos | -| [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Agentes en segundo plano, herramientas LSP/AST/MCP prediseñadas, agentes seleccionados, compatible con Claude Code | -| [opencode-notificator](https://github.com/panta82/opencode-notificator) | Notificaciones de escritorio y alertas sonoras para sesiones OpenCode | -| [opencode-notifier](https://github.com/mohak34/opencode-notifier) | Notificaciones de escritorio y alertas sonoras para eventos de permiso, finalización y error | -| [opencode-zellij-namer](https://github.com/24601/opencode-zellij-namer) | Nomenclatura automática de sesiones Zellij impulsada por IA basada en el contexto OpenCode | -| [opencode-skillful](https://github.com/zenobi-us/opencode-skillful) | Permitir que los agentes OpenCode carguen mensajes de forma diferida a pedido con descubrimiento e inyección de habilidades | -| [opencode-supermemory](https://github.com/supermemoryai/opencode-supermemory) | Memoria persistente entre sesiones utilizando Supermemoria | -| [@plannotator/opencode](https://github.com/backnotprop/plannotator/tree/main/apps/opencode-plugin) | Revisión interactiva del plan con anotaciones visuales y uso compartido privado/sin conexión | -| [@openspoon/subtask2](https://github.com/spoons-and-mirrors/subtask2) | Amplíe opencode /commands a un potente sistema de orquestación con control de flujo granular | -| [opencode-scheduler](https://github.com/different-ai/opencode-scheduler) | Programe trabajos recurrentes usando launchd (Mac) o systemd (Linux) con sintaxis cron | -| [micode](https://github.com/vtemian/micode) | Lluvia de ideas estructurada → Planificar → Implementar flujo de trabajo con continuidad de sesión | -| [octto](https://github.com/vtemian/octto) | Interfaz de usuario interactiva del navegador para lluvia de ideas de IA con formularios de preguntas múltiples | -| [opencode-background-agents](https://github.com/kdcokenny/opencode-background-agents) | Agentes en segundo plano estilo Claude Code con delegación asíncrona y persistencia de contexto | -| [opencode-notify](https://github.com/kdcokenny/opencode-notify) | Notificaciones nativas del sistema operativo para OpenCode: sepa cuándo se completan las tareas | -| [opencode-workspace](https://github.com/kdcokenny/opencode-workspace) | Arnés de orquestación multiagente incluido: 16 componentes, una instalación | -| [opencode-worktree](https://github.com/kdcokenny/opencode-worktree) | Árboles de trabajo de Git de fricción cero para OpenCode | +| Nombre | Descripción | +| -------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | +| [opencode-daytona](https://github.com/daytonaio/daytona/tree/main/libs/opencode-plugin) | Ejecute automáticamente sesiones OpenCode en entornos sandbox aislados de Daytona con git sync y vistas previas en vivo | +| [opencode-helicone-session](https://github.com/H2Shami/opencode-helicone-session) | Inyecte automáticamente encabezados de sesión de Helicone para agrupación de solicitudes | +| [opencode-type-inject](https://github.com/nick-vi/opencode-type-inject) | Inyecte automáticamente tipos TypeScript/Svelte en lecturas de archivos con herramientas de búsqueda | +| [opencode-openai-codex-auth](https://github.com/numman-ali/opencode-openai-codex-auth) | Utilice su suscripción ChatGPT Plus/Pro en lugar de créditos API | +| [opencode-gemini-auth](https://github.com/jenslys/opencode-gemini-auth) | Utilice su plan Gemini existente en lugar de la facturación API | +| [opencode-antigravity-auth](https://github.com/NoeFabris/opencode-antigravity-auth) | Utilice los modelos gratuitos de Antigravity en lugar de la facturación API | +| [opencode-devcontainers](https://github.com/athal7/opencode-devcontainers) | Aislamiento de contenedores de desarrollo de múltiples ramas con clones superficiales y puertos asignados automáticamente | +| [opencode-google-antigravity-auth](https://github.com/shekohex/opencode-google-antigravity-auth) | Complemento Google Antigravity OAuth, compatible con la Búsqueda de Google y manejo más sólido de API | +| [opencode-dynamic-context-pruning](https://github.com/Tarquinen/opencode-dynamic-context-pruning) | Optimice el uso de tokens eliminando los resultados de herramientas obsoletas | +| [opencode-vibeguard](https://github.com/inkdust2021/opencode-vibeguard) | Redacta secretos/PII en marcadores de posición estilo VibeGuard antes de las llamadas a LLM; restaura localmente | +| [opencode-websearch-cited](https://github.com/ghoulr/opencode-websearch-cited.git) | Agregue soporte de búsqueda web nativa para proveedores compatibles con el estilo basado en Google | +| [opencode-pty](https://github.com/shekohex/opencode-pty.git) | Permite a los agentes de IA ejecutar procesos en segundo plano en un PTY y enviarles información interactiva. | +| [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Instrucciones para comandos de shell no interactivos: evita bloqueos de operaciones dependientes de TTY | +| [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Seguimiento del uso de OpenCode con Wakatime | +| [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Limpiar tablas de Markdown producidas por LLMs | +| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | Edición de código 10 veces más rápida con Morph Fast Apply API y marcadores de edición diferidos | +| [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Agentes en segundo plano, herramientas LSP/AST/MCP prediseñadas, agentes seleccionados, compatible con Claude Code | +| [opencode-notificator](https://github.com/panta82/opencode-notificator) | Notificaciones de escritorio y alertas sonoras para sesiones OpenCode | +| [opencode-notifier](https://github.com/mohak34/opencode-notifier) | Notificaciones de escritorio y alertas sonoras para eventos de permiso, finalización y error | +| [opencode-zellij-namer](https://github.com/24601/opencode-zellij-namer) | Nomenclatura automática de sesiones Zellij impulsada por IA basada en el contexto OpenCode | +| [opencode-skillful](https://github.com/zenobi-us/opencode-skillful) | Permitir que los agentes OpenCode carguen mensajes de forma diferida a pedido con descubrimiento e inyección de habilidades | +| [opencode-supermemory](https://github.com/supermemoryai/opencode-supermemory) | Memoria persistente entre sesiones utilizando Supermemory | +| [@plannotator/opencode](https://github.com/backnotprop/plannotator/tree/main/apps/opencode-plugin) | Revisión interactiva del plan con anotaciones visuales y uso compartido privado/sin conexión | +| [@openspoon/subtask2](https://github.com/spoons-and-mirrors/subtask2) | Amplíe opencode /commands a un potente sistema de orquestación con control de flujo granular | +| [opencode-scheduler](https://github.com/different-ai/opencode-scheduler) | Programe trabajos recurrentes usando launchd (Mac) o systemd (Linux) con sintaxis cron | +| [micode](https://github.com/vtemian/micode) | Lluvia de ideas estructurada → Planificar → Implementar flujo de trabajo con continuidad de sesión | +| [octto](https://github.com/vtemian/octto) | Interfaz de usuario interactiva del navegador para lluvia de ideas de IA con formularios de preguntas múltiples | +| [opencode-background-agents](https://github.com/kdcokenny/opencode-background-agents) | Agentes en segundo plano estilo Claude Code con delegación asíncrona y persistencia de contexto | +| [opencode-notify](https://github.com/kdcokenny/opencode-notify) | Notificaciones nativas del sistema operativo para OpenCode: sepa cuándo se completan las tareas | +| [opencode-workspace](https://github.com/kdcokenny/opencode-workspace) | Arnés de orquestación multiagente incluido: 16 componentes, una instalación | +| [opencode-worktree](https://github.com/kdcokenny/opencode-worktree) | Árboles de trabajo de Git de fricción cero para OpenCode | --- diff --git a/packages/web/src/content/docs/es/go.mdx b/packages/web/src/content/docs/es/go.mdx new file mode 100644 index 00000000000..49e6d585fdf --- /dev/null +++ b/packages/web/src/content/docs/es/go.mdx @@ -0,0 +1,145 @@ +--- +title: Go +description: Suscripción de bajo coste para modelos de código abiertos. +--- + +import config from "../../../../config.mjs" +export const console = config.console +export const email = `mailto:${config.email}` + +OpenCode Go es una suscripción de bajo coste de **10 $/mes** que te ofrece acceso fiable a modelos populares de código abierto. + +:::note +OpenCode Go está actualmente en beta. +::: + +Go funciona como cualquier otro proveedor en OpenCode. Te suscribes a OpenCode Go y obtienes tu clave API. Es **completamente opcional** y no necesitas usarlo para utilizar OpenCode. + +Está diseñado principalmente para usuarios internacionales, con modelos alojados en EE. UU., la UE y Singapur para un acceso global estable. + +--- + +## Contexto + +Los modelos abiertos han mejorado mucho. Ahora alcanzan un rendimiento cercano al de los modelos propietarios para tareas de programación. Y como muchos proveedores pueden servirlos de forma competitiva, suelen ser mucho más baratos. + +Sin embargo, conseguir un acceso fiable y de baja latencia a ellos puede ser difícil. Los proveedores varían en calidad y disponibilidad. + +:::tip +Hemos probado un grupo selecto de modelos y proveedores que funcionan bien con OpenCode. +::: + +Para solucionar esto, hicimos un par de cosas: + +1. Probamos un grupo selecto de modelos abiertos y hablamos con sus equipos sobre la mejor manera de ejecutarlos. +2. Luego trabajamos con algunos proveedores para asegurarnos de que se sirvieran correctamente. +3. Finalmente, evaluamos la combinación de modelo/proveedor y elaboramos una lista que nos sentimos cómodos recomendando. + +OpenCode Go te da acceso a estos modelos por **10 $/mes**. + +--- + +## Cómo funciona + +OpenCode Go funciona como cualquier otro proveedor en OpenCode. + +1. Inicias sesión en **OpenCode Zen**, te suscribes a Go y copias tu clave API. +2. Ejecutas el comando `/connect` en la TUI, seleccionas `OpenCode Go` y pegas tu clave API. +3. Ejecuta `/models` en la TUI para ver la lista de modelos disponibles a través de Go. + +:::note +Solo un miembro por espacio de trabajo puede suscribirse a OpenCode Go. +::: + +La lista actual de modelos incluye: + +- **GLM-5** +- **Kimi K2.5** +- **MiniMax M2.5** + +La lista de modelos puede cambiar a medida que probamos y añadimos nuevos. + +--- + +## Límites de uso + +OpenCode Go incluye los siguientes límites: + +- **Límite de 5 horas** — 12 $ de uso +- **Límite semanal** — 30 $ de uso +- **Límite mensual** — 60 $ de uso + +Los límites se definen en valor monetario. Esto significa que tu recuento real de solicitudes depende del modelo que uses. Los modelos más baratos como MiniMax M2.5 permiten más solicitudes, mientras que los modelos de mayor coste como GLM-5 permiten menos. + +La siguiente tabla proporciona una estimación del recuento de solicitudes basada en patrones de uso típicos de Go: + +| | GLM-5 | Kimi K2.5 | MiniMax M2.5 | +| ----------------------- | ----- | --------- | ------------ | +| solicitudes por 5 horas | 1.150 | 1.850 | 30.000 | +| solicitudes por semana | 2.880 | 4.630 | 75.000 | +| solicitudes por mes | 5.750 | 9.250 | 150.000 | + +Las estimaciones se basan en patrones de solicitud promedio observados: + +- GLM-5 — 700 de entrada, 52.000 en caché, 150 tokens de salida por solicitud +- Kimi K2.5 — 870 de entrada, 55.000 en caché, 200 tokens de salida por solicitud +- MiniMax M2.5 — 300 de entrada, 55.000 en caché, 125 tokens de salida por solicitud + +Puedes realizar un seguimiento de tu uso actual en la **consola**. + +:::tip +Si alcanzas el límite de uso, puedes seguir usando los modelos gratuitos. +::: + +Los límites de uso pueden cambiar a medida que aprendamos del uso temprano y los comentarios. + +--- + +### Precios + +OpenCode Go es un plan de suscripción de **10 $/mes**. A continuación se muestran los precios **por 1M de tokens**. + +| Modelo | Entrada | Salida | Lectura en caché | +| ------------ | ------- | ------ | ---------------- | +| GLM-5 | 1,00 $ | 3,20 $ | 0,20 $ | +| Kimi K2.5 | 0,60 $ | 3,00 $ | 0,10 $ | +| MiniMax M2.5 | 0,30 $ | 1,20 $ | 0,03 $ | + +--- + +### Uso más allá de los límites + +Si también tienes créditos en tu saldo de Zen, puedes habilitar la opción **Usar saldo** en la consola. Cuando está habilitada, Go recurrirá a tu saldo de Zen después de que hayas alcanzado tus límites de uso en lugar de bloquear las solicitudes. + +--- + +## Endpoints + +También puedes acceder a los modelos de Go a través de los siguientes endpoints de API. + +| Modelo | ID del modelo | Endpoint | Paquete AI SDK | +| ------------ | ------------- | ------------------------------------------------ | --------------------------- | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | + +El [model id](/docs/config/#models) en tu configuración de OpenCode usa el formato `opencode-go/`. Por ejemplo, para Kimi K2.5, usarías `opencode-go/kimi-k2.5` en tu configuración. + +--- + +## Privacidad + +El plan está diseñado principalmente para usuarios internacionales, con modelos alojados en EE. UU., la UE y Singapur para un acceso global estable. + +Contáctanos si tienes alguna pregunta. + +--- + +## Objetivos + +Creamos OpenCode Go para: + +1. Hacer que la programación con IA sea **accesible** a más personas con una suscripción de bajo coste. +2. Proporcionar acceso **fiable** a los mejores modelos de código abierto. +3. Seleccionar modelos que han sido **probados y evaluados** para su uso con agentes de programación. +4. **Sin ataduras**, permitiéndote usar cualquier otro proveedor con OpenCode también. diff --git a/packages/web/src/content/docs/es/keybinds.mdx b/packages/web/src/content/docs/es/keybinds.mdx index 7ce014f0a22..d4880db2fa5 100644 --- a/packages/web/src/content/docs/es/keybinds.mdx +++ b/packages/web/src/content/docs/es/keybinds.mdx @@ -3,11 +3,11 @@ title: Combinaciones de teclas description: Personaliza tus combinaciones de teclas. --- -OpenCode tiene una lista de combinaciones de teclas que puede personalizar a través de la configuración OpenCode. +OpenCode tiene una lista de combinaciones de teclas que puede personalizar a través de `tui.json`. -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "keybinds": { "leader": "ctrl+x", "app_exit": "ctrl+c,ctrl+d,q", @@ -117,11 +117,11 @@ No es necesario utilizar una tecla líder para las combinaciones de teclas, pero ## Desactivar combinación de teclas -Puede deshabilitar una combinación de teclas agregando la clave a su configuración con un valor de "ninguno". +Puede deshabilitar una combinación de teclas agregando la clave a `tui.json` con un valor de "none". -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "keybinds": { "session_compact": "none" } diff --git a/packages/web/src/content/docs/es/lsp.mdx b/packages/web/src/content/docs/es/lsp.mdx index fc741ca898f..27eab4cadf1 100644 --- a/packages/web/src/content/docs/es/lsp.mdx +++ b/packages/web/src/content/docs/es/lsp.mdx @@ -27,6 +27,7 @@ OpenCode viene con varios servidores LSP integrados para idiomas populares: | gopls | .go | Comando `go` disponible | | hls | .hs, .lhs | Comando `haskell-language-server-wrapper` disponible | | jdtls | .java | `Java SDK (version 21+)` instalado | +| julials | .jl | `julia` y `LanguageServer.jl` instalados | | kotlin-ls | .kt, .kts | Autoinstalaciones para proyectos Kotlin | | lua-ls | .lua | Autoinstalaciones para proyectos Lua | | nixd | .nix | Comando `nixd` disponible | diff --git a/packages/web/src/content/docs/es/plugins.mdx b/packages/web/src/content/docs/es/plugins.mdx index 904821bdb83..dc441807251 100644 --- a/packages/web/src/content/docs/es/plugins.mdx +++ b/packages/web/src/content/docs/es/plugins.mdx @@ -308,6 +308,10 @@ El ayudante `tool` crea una herramienta personalizada a la que opencode puede ll Sus herramientas personalizadas estarán disponibles para opencode junto con las herramientas integradas. +:::note +Si una herramienta de complemento utiliza el mismo nombre que una herramienta integrada, la herramienta de complemento tiene prioridad. +::: + --- ### Registro diff --git a/packages/web/src/content/docs/es/providers.mdx b/packages/web/src/content/docs/es/providers.mdx index 8d86612538b..2ee033f00d0 100644 --- a/packages/web/src/content/docs/es/providers.mdx +++ b/packages/web/src/content/docs/es/providers.mdx @@ -84,6 +84,38 @@ Funciona como cualquier otro proveedor en OpenCode y su uso es completamente opc --- +## OpenCode Go + +OpenCode Go es un plan de suscripción de bajo costo que brinda acceso confiable a modelos de codificación abiertos populares proporcionados por el equipo de OpenCode que han sido +probado y verificado para funcionar bien con OpenCode. + +1. Ejecute el comando `/connect` en TUI, seleccione `OpenCode Go` y diríjase a [opencode.ai/auth](https://opencode.ai/zen). + + ```txt + /connect + ``` + +2. Inicie sesión, agregue sus datos de facturación y copie su clave API. + +3. Pegue su clave API. + + ```txt + ┌ API key + │ + │ + └ enter + ``` + +4. Ejecute `/models` en TUI para ver la lista de modelos que recomendamos. + + ```txt + /models + ``` + +Funciona como cualquier otro proveedor en OpenCode y su uso es completamente opcional. + +--- + ## Directorio Veamos algunos de los proveedores en detalle. Si desea agregar un proveedor a la @@ -792,8 +824,6 @@ Para utilizar su suscripción GitHub Copilot con opencode: :::note Algunos modelos pueden necesitar un [Pro+ suscripción](https://github.com/features/copilot/plans) para usar. - -Algunos modelos deben habilitarse manualmente en su [GitHub configuración del copiloto](https://docs.github.com/en/copilot/how-tos/use-ai-models/configure-access-to-ai-models#setup-for-individual-use). ::: 1. Ejecute el comando `/connect` y busque GitHub Copilot. @@ -1483,6 +1513,39 @@ SAP AI Core brinda acceso a más de 40 modelos de OpenAI, Anthropic, Google, Ama --- +### STACKIT + +STACKIT AI Model Serving proporciona un entorno de alojamiento soberano totalmente gestionado para modelos de IA, centrándose en LLM como Llama, Mistral y Qwen, con máxima soberanía de datos en infraestructura europea. + +1. Diríjase al [Portal STACKIT](https://portal.stackit.cloud), navegue hasta **AI Model Serving** y cree un token de autenticación para su proyecto. + + :::tip + Necesita una cuenta de cliente STACKIT, una cuenta de usuario y un proyecto antes de crear tokens de autenticación. + ::: + +2. Ejecute el comando `/connect` y busque **STACKIT**. + + ```txt + /connect + ``` + +3. Ingrese su token de autenticación de STACKIT AI Model Serving. + + ```txt + ┌ API key + │ + │ + └ enter + ``` + +4. Ejecute el comando `/models` para seleccionar entre los modelos disponibles como _Qwen3-VL 235B_ o _Llama 3.3 70B_. + + ```txt + /models + ``` + +--- + ### OVHcloud AI Endpoints 1. Dirígete al [panel de OVHcloud](https://ovh.com/manager). Navegue a la sección `Public Cloud`, `AI & Machine Learning` > `AI Endpoints` y en la pestaña `API Keys`, haga clic en **Crear una nueva clave API**. diff --git a/packages/web/src/content/docs/es/sdk.mdx b/packages/web/src/content/docs/es/sdk.mdx index d1282b95e9a..d75149cde85 100644 --- a/packages/web/src/content/docs/es/sdk.mdx +++ b/packages/web/src/content/docs/es/sdk.mdx @@ -117,6 +117,78 @@ try { --- +## Salida Estructurada + +Puede solicitar una salida JSON estructurada del modelo especificando un `format` con un esquema JSON. El modelo utilizará una herramienta `StructuredOutput` para devolver un JSON validado que coincida con su esquema. + +### Uso Básico + +```typescript +const result = await client.session.prompt({ + path: { id: sessionId }, + body: { + parts: [{ type: "text", text: "Research Anthropic and provide company info" }], + format: { + type: "json_schema", + schema: { + type: "object", + properties: { + company: { type: "string", description: "Company name" }, + founded: { type: "number", description: "Year founded" }, + products: { + type: "array", + items: { type: "string" }, + description: "Main products", + }, + }, + required: ["company", "founded"], + }, + }, + }, +}) + +// Access the structured output +console.log(result.data.info.structured_output) +// { company: "Anthropic", founded: 2021, products: ["Claude", "Claude API"] } +``` + +### Tipos de Formato de Salida + +| Tipo | Descripción | +| ------------- | --------------------------------------------------------------------- | +| `text` | Predeterminado. Respuesta de texto estándar (sin salida estructurada) | +| `json_schema` | Devuelve JSON validado que coincide con el esquema proporcionado | + +### Formato de Esquema JSON + +Cuando use `type: 'json_schema'`, proporcione: + +| Campo | Tipo | Descripción | +| ------------ | --------------- | ---------------------------------------------------------------- | +| `type` | `'json_schema'` | Requerido. Especifica el modo de esquema JSON | +| `schema` | `object` | Requerido. Objeto JSON Schema que define la estructura de salida | +| `retryCount` | `number` | Opcional. Número de reintentos de validación (predeterminado: 2) | + +### Manejo de Errores + +Si el modelo no logra producir una salida estructurada válida después de todos los reintentos, la respuesta incluirá un `StructuredOutputError`: + +```typescript +if (result.data.info.error?.name === "StructuredOutputError") { + console.error("Failed to produce structured output:", result.data.info.error.message) + console.error("Attempts:", result.data.info.error.retries) +} +``` + +### Mejores Prácticas + +1. **Proporcione descripciones claras** en las propiedades de su esquema para ayudar al modelo a entender qué datos extraer +2. **Use `required`** para especificar qué campos deben estar presentes +3. **Mantenga los esquemas enfocados** - los esquemas anidados complejos pueden ser más difíciles de completar correctamente para el modelo +4. **Establezca un `retryCount` apropiado** - aumente para esquemas complejos, disminuya para simples + +--- + ## API El SDK expone todas las API del servidor a través de un cliente con seguridad de tipos. @@ -226,27 +298,27 @@ const { providers, default: defaults } = await client.config.providers() ### Sesiones -| Método | Descripción | Notas | -| ---------------------------------------------------------- | --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `session.list()` | Listar sesiones | Devuelve Session[] | -| `session.get({ path })` | Obtener sesión | Devuelve Session | -| `session.children({ path })` | Listar sesiones secundarias | Devuelve Session[] | -| `session.create({ body })` | Crear sesión | Devuelve Session | -| `session.delete({ path })` | Eliminar sesión | Devuelve `boolean` | -| `session.update({ path, body })` | Actualizar propiedades de sesión | Devuelve Session | -| `session.init({ path, body })` | Analizar aplicación y crear `AGENTS.md` | Devuelve `boolean` | -| `session.abort({ path })` | Cancelar una sesión en ejecución | Devuelve `boolean` | -| `session.share({ path })` | Compartir sesión | Devuelve Session | -| `session.unshare({ path })` | Dejar de compartir sesión | Devuelve Session | -| `session.summarize({ path, body })` | Resumir sesión | Devuelve `boolean` | -| `session.messages({ path })` | Listar mensajes en una sesión | Devuelve `{ info: `Message`, parts: `Part[]`}[]` | -| `session.message({ path })` | Obtener detalles del mensaje | Devuelve `{ info: `Message`, parts: `Part[]`}` | -| `session.prompt({ path, body })` | Enviar mensaje rápido | `body.noReply: true` devuelve UserMessage (solo contexto). El valor predeterminado devuelve AssistantMessage con respuesta de IA | -| `session.command({ path, body })` | Enviar comando a la sesión | Devuelve `{ info: `AssistantMessage`, parts: `Part[]`}` | -| `session.shell({ path, body })` | Ejecute un comando de shell | Devuelve AssistantMessage | -| `session.revert({ path, body })` | Revertir un mensaje | Devuelve Session | -| `session.unrevert({ path })` | Restaurar mensajes revertidos | Devuelve Session | -| `postSessionByIdPermissionsByPermissionId({ path, body })` | Responder a una solicitud de permiso | Devuelve `boolean` | +| Método | Descripción | Notas | +| ---------------------------------------------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `session.list()` | Listar sesiones | Devuelve Session[] | +| `session.get({ path })` | Obtener sesión | Devuelve Session | +| `session.children({ path })` | Listar sesiones secundarias | Devuelve Session[] | +| `session.create({ body })` | Crear sesión | Devuelve Session | +| `session.delete({ path })` | Eliminar sesión | Devuelve `boolean` | +| `session.update({ path, body })` | Actualizar propiedades de sesión | Devuelve Session | +| `session.init({ path, body })` | Analizar aplicación y crear `AGENTS.md` | Devuelve `boolean` | +| `session.abort({ path })` | Cancelar una sesión en ejecución | Devuelve `boolean` | +| `session.share({ path })` | Compartir sesión | Devuelve Session | +| `session.unshare({ path })` | Dejar de compartir sesión | Devuelve Session | +| `session.summarize({ path, body })` | Resumir sesión | Devuelve `boolean` | +| `session.messages({ path })` | Listar mensajes en una sesión | Devuelve `{ info: `Message`, parts: `Part[]`}[]` | +| `session.message({ path })` | Obtener detalles del mensaje | Devuelve `{ info: `Message`, parts: `Part[]`}` | +| `session.prompt({ path, body })` | Enviar mensaje rápido | `body.noReply: true` devuelve UserMessage (solo contexto). El valor predeterminado devuelve AssistantMessage con respuesta de IA. Admite `body.outputFormat` para [salida estructurada](#salida-estructurada) | +| `session.command({ path, body })` | Enviar comando a la sesión | Devuelve `{ info: `AssistantMessage`, parts: `Part[]`}` | +| `session.shell({ path, body })` | Ejecute un comando de shell | Devuelve AssistantMessage | +| `session.revert({ path, body })` | Revertir un mensaje | Devuelve Session | +| `session.unrevert({ path })` | Restaurar mensajes revertidos | Devuelve Session | +| `postSessionByIdPermissionsByPermissionId({ path, body })` | Responder a una solicitud de permiso | Devuelve `boolean` | --- diff --git a/packages/web/src/content/docs/es/themes.mdx b/packages/web/src/content/docs/es/themes.mdx index d7c7426267c..8c8028eb6e0 100644 --- a/packages/web/src/content/docs/es/themes.mdx +++ b/packages/web/src/content/docs/es/themes.mdx @@ -61,11 +61,11 @@ El tema del sistema es para usuarios que: ## Usar un tema -Puede seleccionar un tema abriendo la selección de tema con el comando `/theme`. O puede especificarlo en su [config](/docs/config). +Puede seleccionar un tema abriendo la selección de tema con el comando `/theme`. O puede especificarlo en `tui.json`. -```json title="opencode.json" {3} +```json title="tui.json" {3} { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "theme": "tokyonight" } ``` diff --git a/packages/web/src/content/docs/es/tui.mdx b/packages/web/src/content/docs/es/tui.mdx index ba7430f5020..02f14d55ad8 100644 --- a/packages/web/src/content/docs/es/tui.mdx +++ b/packages/web/src/content/docs/es/tui.mdx @@ -355,24 +355,34 @@ Algunos editores necesitan argumentos de línea de comandos para ejecutarse en m ## Configurar -Puede personalizar el comportamiento de TUI a través de su archivo de configuración OpenCode. +Puede personalizar el comportamiento de TUI a través de `tui.json` (o `tui.jsonc`). -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", - "tui": { - "scroll_speed": 3, - "scroll_acceleration": { - "enabled": true - } - } + "$schema": "https://opencode.ai/tui.json", + "theme": "opencode", + "keybinds": { + "leader": "ctrl+x" + }, + "scroll_speed": 3, + "scroll_acceleration": { + "enabled": true + }, + "diff_style": "auto" } ``` +Esto es independiente de `opencode.json`, que configura el comportamiento del servidor/tiempo de ejecución. + ### Opciones -- `scroll_acceleration`: habilite la aceleración de desplazamiento estilo macOS para un desplazamiento suave y natural. Cuando está habilitado, la velocidad de desplazamiento aumenta con gestos de desplazamiento rápido y se mantiene precisa para movimientos más lentos. **Esta configuración tiene prioridad sobre `scroll_speed` y la anula cuando está habilitada.** -- `scroll_speed`: controla la rapidez con la que se desplaza el TUI cuando se utilizan comandos de desplazamiento (mínimo: `1`). El valor predeterminado es `3`. **Nota: Esto se ignora si `scroll_acceleration.enabled` está configurado en `true`.** +- `theme`: establece su tema de interfaz de usuario. [Más información](/docs/themes). +- `keybinds`: personaliza los atajos de teclado. [Más información](/docs/keybinds). +- `scroll_acceleration.enabled`: habilite la aceleración de desplazamiento estilo macOS para un desplazamiento suave y natural. Cuando está habilitado, la velocidad de desplazamiento aumenta con gestos de desplazamiento rápido y se mantiene precisa para movimientos más lentos. **Esta configuración tiene prioridad sobre `scroll_speed` y la anula cuando está habilitada.** +- `scroll_speed`: controla la rapidez con la que se desplaza el TUI cuando se utilizan comandos de desplazamiento (mínimo: `0.001`, admite valores decimales). El valor predeterminado es `3`. **Nota: Esto se ignora si `scroll_acceleration.enabled` está configurado en `true`.** +- `diff_style`: controla la representación de diferencias. `"auto"` se adapta al ancho del terminal, `"stacked"` siempre muestra un diseño de una sola columna. + +Utilice `OPENCODE_TUI_CONFIG` para cargar una ruta de configuración de TUI personalizada. --- diff --git a/packages/web/src/content/docs/es/zen.mdx b/packages/web/src/content/docs/es/zen.mdx index 10847b452e4..94838902a51 100644 --- a/packages/web/src/content/docs/es/zen.mdx +++ b/packages/web/src/content/docs/es/zen.mdx @@ -62,6 +62,7 @@ También puede acceder a nuestros modelos a través de los siguientes puntos fin | Modelo | Model ID | Endpoint | AI SDK package | | ------------------ | ------------------ | -------------------------------------------------- | --------------------------- | +| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | @@ -71,22 +72,24 @@ También puede acceder a nuestros modelos a través de los siguientes puntos fin | GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | | Gemini 3 Pro | gemini-3-pro | `https://opencode.ai/zen/v1/models/gemini-3-pro` | `@ai-sdk/google` | | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.1 | minimax-m2.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.1 Free | minimax-m2.1-free | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| GLM 5 | glm-5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 4.7 | glm-4.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM 4.7 Free | glm-4.7-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 4.6 | glm-4.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 Free | kimi-k2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2 Thinking | kimi-k2-thinking | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2 | kimi-k2 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Qwen3 Coder 480B | qwen3-coder | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -115,29 +118,34 @@ Apoyamos un modelo de pago por uso. A continuación se muestran los precios **po | Modelo | Entrada | Salida | Lectura en caché | Escritura en caché | | ------------------------------------ | ------- | ------ | ---------------- | ------------------ | | Big Pickle | Gratis | Gratis | Gratis | - | -| MiniMax M2.1 Free | Gratis | Gratis | Gratis | - | +| MiniMax M2.5 Free | Gratis | Gratis | Gratis | - | +| MiniMax M2.5 | $0,30 | $1,20 | $0,06 | - | | MiniMax M2.1 | $0,30 | $1,20 | $0,10 | - | -| GLM 4.7 Free | Gratis | Gratis | Gratis | - | +| GLM 5 | $1,00 | $3,20 | $0,20 | - | | GLM 4.7 | $0,60 | $2.20 | $0,10 | - | | GLM 4.6 | $0,60 | $2.20 | $0,10 | - | -| Kimi K2.5 Free | Gratis | Gratis | Gratis | - | | Kimi K2.5 | $0,60 | $3.00 | $0,08 | - | | Kimi K2 Thinking | $0,40 | $2.50 | - | - | | Kimi K2 | $0,40 | $2.50 | - | - | | Qwen3 Coder 480B | $0,45 | $1,50 | - | - | +| Claude Opus 4.6 (≤ 200.000 tokens) | $5.00 | $25.00 | $0,50 | $6.25 | +| Claude Opus 4.6 (> 200K tokens) | $10.00 | $37,50 | $1.00 | $12,50 | +| Claude Opus 4.5 | $5.00 | $25.00 | $0,50 | $6.25 | +| Claude Opus 4.1 | $15.00 | $75.00 | $1,50 | $18,75 | +| Claude Sonnet 4.6 (≤ 200.000 tokens) | $3.00 | $15.00 | $0,30 | $3,75 | +| Claude Sonnet 4.6 (> 200.000 tokens) | $6.00 | $22,50 | $0,60 | $7.50 | | Claude Sonnet 4.5 (≤ 200.000 tokens) | $3.00 | $15.00 | $0,30 | $3,75 | | Claude Sonnet 4.5 (> 200.000 tokens) | $6.00 | $22,50 | $0,60 | $7.50 | | Claude Sonnet 4 (≤ 200K tokens) | $3.00 | $15.00 | $0,30 | $3,75 | | Claude Sonnet 4 (> 200K tokens) | $6.00 | $22,50 | $0,60 | $7.50 | | Claude Haiku 4.5 | $1.00 | $5.00 | $0,10 | $1,25 | | Claude Haiku 3.5 | $0,80 | $4.00 | $0,08 | $1.00 | -| Claude Opus 4.6 (≤ 200.000 tokens) | $5.00 | $25.00 | $0,50 | $6.25 | -| Claude Opus 4.6 (> 200K tokens) | $10.00 | $37,50 | $1.00 | $12,50 | -| Claude Opus 4.5 | $5.00 | $25.00 | $0,50 | $6.25 | -| Claude Opus 4.1 | $15.00 | $75.00 | $1,50 | $18,75 | +| Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0,20 | - | +| Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0,40 | - | | Gemini 3 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0,20 | - | | Gemini 3 Pro (> 200K tokens) | $4.00 | $18.00 | $0,40 | - | | Gemini 3 Flash | $0,50 | $3.00 | $0,05 | - | +| GPT 5.3 Codex | $1,75 | $14.00 | $0,175 | - | | GPT 5.2 | $1,75 | $14.00 | $0,175 | - | | GPT 5.2 Codex | $1,75 | $14.00 | $0,175 | - | | GPT 5.1 | $1.07 | $8,50 | $0,107 | - | @@ -156,9 +164,7 @@ Las tarifas de las tarjetas de crédito se trasladan al costo (4,4% + 0,30 dóla Los modelos gratuitos: -- GLM 4.7 Free está disponible en OpenCode por tiempo limitado. El equipo está aprovechando este tiempo para recopilar comentarios y mejorar el modelo. -- Kimi K2.5 Free está disponible en OpenCode por tiempo limitado. El equipo está aprovechando este tiempo para recopilar comentarios y mejorar el modelo. -- MiniMax M2.1 Free está disponible en OpenCode por tiempo limitado. El equipo está aprovechando este tiempo para recopilar comentarios y mejorar el modelo. +- MiniMax M2.5 Free está disponible en OpenCode por tiempo limitado. El equipo está aprovechando este tiempo para recopilar comentarios y mejorar el modelo. - Big Pickle es un modelo sigiloso gratuito en OpenCode por tiempo limitado. El equipo está aprovechando este tiempo para recopilar comentarios y mejorar el modelo. Contáctenos si tiene alguna pregunta. @@ -189,9 +195,7 @@ cobrarle más de $20 si su saldo es inferior a $5. Todos nuestros modelos están alojados en los EE. UU. Nuestros proveedores siguen una política de retención cero y no utilizan sus datos para la capacitación de modelos, con las siguientes excepciones: - Big Pickle: Durante su periodo gratuito, los datos recopilados podrán utilizarse para mejorar el modelo. -- GLM 4.7 Gratis: Durante su periodo gratuito, los datos recopilados podrán utilizarse para mejorar el modelo. -- Kimi K2.5 Free: Durante su periodo gratuito, los datos recopilados podrán utilizarse para mejorar el modelo. -- MiniMax M2.1 Free: Durante su período gratuito, los datos recopilados podrán utilizarse para mejorar el modelo. +- MiniMax M2.5 Free: Durante su período gratuito, los datos recopilados podrán utilizarse para mejorar el modelo. - API de OpenAI: las solicitudes se conservan durante 30 días de acuerdo con las [Políticas de datos de OpenAI](https://platform.openai.com/docs/guides/your-data). - API de Anthropic: las solicitudes se conservan durante 30 días de acuerdo con las [Políticas de datos de Anthropic](https://docs.anthropic.com/en/docs/claude-code/data-usage). diff --git a/packages/web/src/content/docs/fr/cli.mdx b/packages/web/src/content/docs/fr/cli.mdx index 8773d941f37..92a473d34b8 100644 --- a/packages/web/src/content/docs/fr/cli.mdx +++ b/packages/web/src/content/docs/fr/cli.mdx @@ -558,6 +558,7 @@ OpenCode peut être configuré à l'aide de variables d'environnement. | `OPENCODE_AUTO_SHARE` | booléen | Partager automatiquement des sessions | | `OPENCODE_GIT_BASH_PATH` | chaîne | Chemin vers l'exécutable Git Bash sur Windows | | `OPENCODE_CONFIG` | chaîne | Chemin d'accès au fichier de configuration | +| `OPENCODE_TUI_CONFIG` | chaîne | Chemin d'accès au fichier de configuration TUI | | `OPENCODE_CONFIG_DIR` | chaîne | Chemin d'accès au répertoire de configuration | | `OPENCODE_CONFIG_CONTENT` | chaîne | Contenu de configuration JSON en ligne | | `OPENCODE_DISABLE_AUTOUPDATE` | booléen | Désactiver les vérifications automatiques des mises à jour | diff --git a/packages/web/src/content/docs/fr/config.mdx b/packages/web/src/content/docs/fr/config.mdx index 8c0d15e183a..c576fe2da11 100644 --- a/packages/web/src/content/docs/fr/config.mdx +++ b/packages/web/src/content/docs/fr/config.mdx @@ -14,10 +14,11 @@ OpenCode prend en charge les formats **JSON** et **JSONC** (JSON avec commentair ```jsonc title="opencode.jsonc" { "$schema": "https://opencode.ai/config.json", - // Theme configuration - "theme": "opencode", "model": "anthropic/claude-sonnet-4-5", "autoupdate": true, + "server": { + "port": 4096, + }, } ``` @@ -34,7 +35,7 @@ Les fichiers de configuration sont **fusionnés**, pas remplacés. Les fichiers de configuration sont fusionnés et non remplacés. Les paramètres des emplacements de configuration suivants sont combinés. Les configurations ultérieures remplacent les précédentes uniquement en cas de clés en conflit. Les paramètres non conflictuels de toutes les configurations sont conservés. -Par exemple, si votre configuration globale définit `theme: "opencode"` et `autoupdate: true` et que la configuration de votre projet définit `model: "anthropic/claude-sonnet-4-5"`, la configuration finale inclura les trois paramètres. +Par exemple, si votre configuration globale définit `autoupdate: true` et que la configuration de votre projet définit `model: "anthropic/claude-sonnet-4-5"`, la configuration finale inclura les deux paramètres. --- @@ -95,7 +96,9 @@ Vous pouvez activer des serveurs spécifiques dans votre configuration locale : ### Globale -Placez votre configuration globale OpenCode dans `~/.config/opencode/opencode.json`. Utilisez la configuration globale pour les préférences de l'utilisateur telles que les thèmes, les fournisseurs ou les raccourcis clavier. +Placez votre configuration globale OpenCode dans `~/.config/opencode/opencode.json`. Utilisez la configuration globale pour les préférences de l'utilisateur telles que les fournisseurs, les modèles et les autorisations. + +Pour les paramètres spécifiques à TUI, utilisez `~/.config/opencode/tui.json`. La configuration globale remplace les paramètres par défaut de l'organisation distante. @@ -105,6 +108,8 @@ La configuration globale remplace les paramètres par défaut de l'organisation Ajoutez `opencode.json` à la racine de votre projet. La configuration du projet a la priorité la plus élevée parmi les fichiers de configuration standard : elle remplace les configurations globales et distantes. +Pour les paramètres TUI spécifiques au projet, ajoutez `tui.json` à côté. + :::tip Placez la configuration spécifique au projet à la racine de votre projet. ::: @@ -145,34 +150,32 @@ Le répertoire personnalisé est chargé après les répertoires de configuratio Le fichier de configuration a un schéma défini dans [**`opencode.ai/config.json`**](https://opencode.ai/config.json). +La configuration TUI utilise [**`opencode.ai/tui.json`**](https://opencode.ai/tui.json). + Votre éditeur doit être capable de valider et de compléter automatiquement en fonction du schéma. --- ### TUI -Vous pouvez configurer les paramètres spécifiques à TUI via l'option `tui`. +Utilisez un fichier dédié `tui.json` (ou `tui.jsonc`) pour les paramètres spécifiques à TUI. -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", - "tui": { - "scroll_speed": 3, - "scroll_acceleration": { - "enabled": true - }, - "diff_style": "auto" - } + "$schema": "https://opencode.ai/tui.json", + "scroll_speed": 3, + "scroll_acceleration": { + "enabled": true + }, + "diff_style": "auto" } ``` -Options disponibles : +Utilisez `OPENCODE_TUI_CONFIG` pour pointer vers un fichier de configuration TUI personnalisé. -- `scroll_acceleration.enabled` - Active l'accélération de défilement de style macOS. **A priorité sur `scroll_speed`.** -- `scroll_speed` - Multiplicateur de vitesse de défilement personnalisé (par défaut : `3`, minimum : `1`). Ignoré si `scroll_acceleration.enabled` est `true`. -- `diff_style` - Contrôle le rendu différentiel. `"auto"` s'adapte à la largeur du terminal, `"stacked"` affiche toujours une seule colonne. +Les anciennes clés `theme`, `keybinds` et `tui` dans `opencode.json` sont obsolètes et migrées automatiquement lorsque cela est possible. -[En savoir plus sur l'utilisation du TUI ici](/docs/tui). +[En savoir plus sur l'utilisation du TUI ici](/docs/tui#configure). --- @@ -298,12 +301,12 @@ Les jetons du porteur (`AWS_BEARER_TOKEN_BEDROCK` ou `/connect`) ont priorité s ### Thèmes -Vous pouvez configurer le thème que vous souhaitez utiliser dans votre configuration OpenCode via l'option `theme`. +Définissez votre thème d'interface utilisateur dans `tui.json`. -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", - "theme": "" + "$schema": "https://opencode.ai/tui.json", + "theme": "tokyonight" } ``` @@ -403,11 +406,11 @@ Vous pouvez également définir des commandes à l'aide de fichiers markdown dan ### Raccourcis clavier -Vous pouvez personnaliser vos raccourcis clavier via l'option `keybinds`. +Personnalisez les raccourcis clavier dans `tui.json`. -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "keybinds": {} } ``` @@ -487,13 +490,15 @@ Vous pouvez contrôler le comportement de compactage du contexte via l'option `c "$schema": "https://opencode.ai/config.json", "compaction": { "auto": true, - "prune": true + "prune": true, + "reserved": 10000 } } ``` - `auto` - Compacte automatiquement la session lorsque le contexte est plein (par défaut : `true`). - `prune` - Supprimez les anciennes sorties de l'outil pour économiser des tokens (par défaut : `true`). +- `reserved` - Tampon de jetons pour le compactage. Laisse suffisamment de marge pour éviter le débordement lors du compactage. --- diff --git a/packages/web/src/content/docs/fr/ecosystem.mdx b/packages/web/src/content/docs/fr/ecosystem.mdx index dd74e1f7bf5..46fcd7bde47 100644 --- a/packages/web/src/content/docs/fr/ecosystem.mdx +++ b/packages/web/src/content/docs/fr/ecosystem.mdx @@ -6,71 +6,72 @@ description: Projets et intégrations construits avec OpenCode. Une collection de projets communautaires construits sur OpenCode. :::note -Vous souhaitez ajouter votre projet lié à OpenCode à cette liste ? Soumettez un PR. +Vous souhaitez ajouter votre projet lié à OpenCode à cette liste ? Soumettez une PR. ::: -Vous pouvez également consulter [awesome-opencode](https://github.com/awesome-opencode/awesome-opencode) et [opencode.cafe](https://opencode.cafe), une communauté qui regroupe l'écosystème OpenCode. +Vous pouvez également consulter [awesome-opencode](https://github.com/awesome-opencode/awesome-opencode) et [opencode.cafe](https://opencode.cafe), une communauté qui regroupe l'écosystème et la communauté. --- ## Extensions -| Nom | Description | -| --------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | -| [opencode-daytona](https://github.com/jamesmurdza/daytona/blob/main/guides/typescript/opencode/README.md) | Exécute automatiquement des sessions OpenCode dans des environnements sandbox Daytona isolés avec synchronisation git et prévisualisations en direct | -| [opencode-helicone-session](https://github.com/H2Shami/opencode-helicone-session) | Injecte automatiquement les en-têtes de session Helicone pour le regroupement des requêtes | -| [opencode-type-inject](https://github.com/nick-vi/opencode-type-inject) | Injecte automatiquement les types TypeScript/Svelte dans les lectures de fichiers avec des outils de recherche | -| [opencode-openai-codex-auth](https://github.com/numman-ali/opencode-openai-codex-auth) | Utilise votre abonnement ChatGPT Plus/Pro au lieu de crédits API | -| [opencode-gemini-auth](https://github.com/jenslys/opencode-gemini-auth) | Utilise votre forfait Gemini existant au lieu de la facturation API | -| [opencode-antigravity-auth](https://github.com/NoeFabris/opencode-antigravity-auth) | Utilise les modèles gratuits d'Antigravity au lieu de la facturation API | -| [opencode-devcontainers](https://github.com/athal7/opencode-devcontainers) | Isolation de conteneur de développement multibranche avec clones superficiels et ports attribués automatiquement | -| [opencode-google-antigravity-auth](https://github.com/shekohex/opencode-google-antigravity-auth) | Plugin Google Antigravity OAuth, avec support de la recherche Google et gestion API plus robuste | -| [opencode-dynamic-context-pruning](https://github.com/Tarquinen/opencode-dynamic-context-pruning) | Optimise l'utilisation des jetons en éliminant les sorties d'outils obsolètes | -| [opencode-websearch-cited](https://github.com/ghoulr/opencode-websearch-cited.git) | Ajoute le support natif de la recherche Web pour les fournisseurs pris en charge avec le style ancré par Google | -| [opencode-pty](https://github.com/shekohex/opencode-pty.git) | Permet aux agents IA d'exécuter des processus en arrière-plan dans un PTY et de leur envoyer des entrées interactives. | -| [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Instructions pour les commandes shell non interactives - empêche les blocages des opérations dépendantes du TTY | -| [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Suit l'utilisation de OpenCode avec Wakatime | -| [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Nettoie les tableaux Markdown produits par les LLM | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | Édition de code 10 fois plus rapide avec Morph Fast Apply API et les marqueurs d'édition différée | -| [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Agents d'arrière-plan, outils LSP/AST/MCP prédéfinis, agents sélectionnés, compatibles Claude Code | -| [opencode-notificator](https://github.com/panta82/opencode-notificator) | Notifications de bureau et alertes sonores pour les sessions OpenCode | -| [opencode-notifier](https://github.com/mohak34/opencode-notifier) | Notifications sur le bureau et alertes sonores pour les événements d'autorisation, d'achèvement et d'erreur | -| [opencode-zellij-namer](https://github.com/24601/opencode-zellij-namer) | Dénomination automatique de session Zellij basée sur l'IA et le contexte OpenCode | -| [opencode-skillful](https://github.com/zenobi-us/opencode-skillful) | Autorise les agents OpenCode à charger paresseusement les prompts à la demande grâce à la découverte et à l'injection de compétences | -| [opencode-supermemory](https://github.com/supermemoryai/opencode-supermemory) | Mémoire persistante entre les sessions utilisant Supermemory | -| [@plannotator/opencode](https://github.com/backnotprop/plannotator/tree/main/apps/opencode-plugin) | Révision interactive du plan avec annotation visuelle et partage privé/hors ligne | -| [@openspoon/subtask2](https://github.com/spoons-and-mirrors/subtask2) | Étend les commandes /commands d'opencode dans un système d'orchestration puissant avec contrôle de flux granulaire | -| [opencode-scheduler](https://github.com/different-ai/opencode-scheduler) | Planifie des tâches récurrentes à l'aide de launchd (Mac) ou systemd (Linux) avec la syntaxe cron | -| [micode](https://github.com/vtemian/micode) | Workflow structuré Brainstorming → Planifier → Mettre en œuvre avec continuité de session | -| [octto](https://github.com/vtemian/octto) | Interface utilisateur de navigateur interactive pour le brainstorming IA avec des formulaires multi-questions | -| [opencode-background-agents](https://github.com/kdcokenny/opencode-background-agents) | Agents d'arrière-plan de style Claude Code avec délégation asynchrone et persistance du contexte | -| [opencode-notify](https://github.com/kdcokenny/opencode-notify) | Notifications natives du système d'exploitation pour OpenCode – savoir quand les tâches sont terminées | -| [opencode-workspace](https://github.com/kdcokenny/opencode-workspace) | Harness d'orchestration multi-agent prêt à l'emploi - 16 composants, une installation | -| [opencode-worktree](https://github.com/kdcokenny/opencode-worktree) | Arbres de travail Git sans friction pour OpenCode | +| Nom | Description | +| -------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | +| [opencode-daytona](https://github.com/daytonaio/daytona/tree/main/libs/opencode-plugin) | Exécute automatiquement des sessions OpenCode dans des sandbox Daytona isolées avec synchronisation git et prévisualisations en direct | +| [opencode-helicone-session](https://github.com/H2Shami/opencode-helicone-session) | Injecte automatiquement les en-têtes de session Helicone pour le regroupement des requêtes | +| [opencode-type-inject](https://github.com/nick-vi/opencode-type-inject) | Injecte automatiquement les types TypeScript/Svelte dans les lectures de fichiers avec des outils de recherche | +| [opencode-openai-codex-auth](https://github.com/numman-ali/opencode-openai-codex-auth) | Utilise votre abonnement ChatGPT Plus/Pro au lieu de crédits API | +| [opencode-gemini-auth](https://github.com/jenslys/opencode-gemini-auth) | Utilise votre forfait Gemini existant au lieu de la facturation API | +| [opencode-antigravity-auth](https://github.com/NoeFabris/opencode-antigravity-auth) | Utilise les modèles gratuits d'Antigravity au lieu de la facturation API | +| [opencode-devcontainers](https://github.com/athal7/opencode-devcontainers) | Isolation de conteneur de développement multi-branches avec clones superficiels et ports attribués automatiquement | +| [opencode-google-antigravity-auth](https://github.com/shekohex/opencode-google-antigravity-auth) | Plugin OAuth Google Antigravity, avec prise en charge de la recherche Google et une gestion d'API plus robuste | +| [opencode-dynamic-context-pruning](https://github.com/Tarquinen/opencode-dynamic-context-pruning) | Optimise l'utilisation des jetons en élaguant les sorties d'outils obsolètes | +| [opencode-vibeguard](https://github.com/inkdust2021/opencode-vibeguard) | Masque les secrets/PII par des espaces réservés de style VibeGuard avant les appels LLM ; restaure localement | +| [opencode-websearch-cited](https://github.com/ghoulr/opencode-websearch-cited.git) | Ajoute le support natif de la recherche web pour les fournisseurs pris en charge avec le style Google Grounding | +| [opencode-pty](https://github.com/shekohex/opencode-pty.git) | Permet aux agents IA d'exécuter des processus en arrière-plan dans un PTY et de leur envoyer des entrées interactives. | +| [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Instructions pour les commandes shell non interactives - empêche les blocages dus aux opérations dépendantes du TTY | +| [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Suit l'utilisation d'OpenCode avec Wakatime | +| [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Nettoie les tableaux Markdown produits par les LLM | +| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | Édition de code 10x plus rapide avec l'API Morph Fast Apply et des marqueurs d'édition différée | +| [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Agents d'arrière-plan, outils LSP/AST/MCP pré-construits, agents sélectionnés, compatible Claude Code | +| [opencode-notificator](https://github.com/panta82/opencode-notificator) | Notifications de bureau et alertes sonores pour les sessions OpenCode | +| [opencode-notifier](https://github.com/mohak34/opencode-notifier) | Notifications de bureau et alertes sonores pour les événements de permission, d'achèvement et d'erreur | +| [opencode-zellij-namer](https://github.com/24601/opencode-zellij-namer) | Nommage automatique de session Zellij alimenté par l'IA basé sur le contexte OpenCode | +| [opencode-skillful](https://github.com/zenobi-us/opencode-skillful) | Permet aux agents OpenCode le chargement différé de prompts à la demande avec découverte et injection de compétences | +| [opencode-supermemory](https://github.com/supermemoryai/opencode-supermemory) | Mémoire persistante entre les sessions utilisant Supermemory | +| [@plannotator/opencode](https://github.com/backnotprop/plannotator/tree/main/apps/opencode-plugin) | Révision de plan interactive avec annotation visuelle et partage privé/hors ligne | +| [@openspoon/subtask2](https://github.com/spoons-and-mirrors/subtask2) | Étend opencode /commands en un système d'orchestration puissant avec contrôle de flux granulaire | +| [opencode-scheduler](https://github.com/different-ai/opencode-scheduler) | Planifie des tâches récurrentes en utilisant launchd (Mac) ou systemd (Linux) avec la syntaxe cron | +| [micode](https://github.com/vtemian/micode) | Flux de travail structuré Brainstorming → Planification → Implémentation avec continuité de session | +| [octto](https://github.com/vtemian/octto) | Interface utilisateur de navigateur interactive pour le brainstorming IA avec des formulaires multi-questions | +| [opencode-background-agents](https://github.com/kdcokenny/opencode-background-agents) | Agents d'arrière-plan de style Claude Code avec délégation asynchrone et persistance du contexte | +| [opencode-notify](https://github.com/kdcokenny/opencode-notify) | Notifications natives de l'OS pour OpenCode – sachez quand les tâches sont terminées | +| [opencode-workspace](https://github.com/kdcokenny/opencode-workspace) | Harnais d'orchestration multi-agents groupé – 16 composants, une installation | +| [opencode-worktree](https://github.com/kdcokenny/opencode-worktree) | Worktrees git sans friction pour OpenCode | --- ## Projets -| Nom | Description | -| ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------ | -| [kimaki](https://github.com/remorses/kimaki) | Bot Discord pour contrôler les sessions OpenCode, construit sur le SDK | -| [opencode.nvim](https://github.com/NickvanDyke/opencode.nvim) | Plugin Neovim pour les prompts compatibles avec l'éditeur, construit sur l'API | -| [portal](https://github.com/hosenur/portal) | Interface utilisateur Web axée sur le mobile pour OpenCode sur Tailscale/VPN | -| [opencode plugin template](https://github.com/zenobi-us/opencode-plugin-template/) | Modèle pour créer des plugins OpenCode | -| [opencode.nvim](https://github.com/sudo-tee/opencode.nvim) | Frontend Neovim pour opencode - un agent de codage d'IA basé sur un terminal | -| [ai-sdk-provider-opencode-sdk](https://github.com/ben-vargas/ai-sdk-provider-opencode-sdk) | Fournisseur Vercel AI SDK pour l'utilisation de OpenCode via @opencode-ai/sdk | -| [OpenChamber](https://github.com/btriapitsyn/openchamber) | Application Web/De bureau et extension VS Code pour OpenCode | -| [OpenCode-Obsidian](https://github.com/mtymek/opencode-obsidian) | Plugin Obsidian qui intègre OpenCode dans l'interface utilisateur d'Obsidian | -| [OpenWork](https://github.com/different-ai/openwork) | Une alternative open source à Claude Cowork, propulsée par OpenCode | -| [ocx](https://github.com/kdcokenny/ocx) | Gestionnaire d'extensions OpenCode avec profils portables et isolés. | -| [CodeNomad](https://github.com/NeuralNomadsAI/CodeNomad) | Application client de bureau, Web, mobile et distante pour OpenCode | +| Nom | Description | +| ------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------- | +| [kimaki](https://github.com/remorses/kimaki) | Bot Discord pour contrôler les sessions OpenCode, construit sur le SDK | +| [opencode.nvim](https://github.com/NickvanDyke/opencode.nvim) | Plugin Neovim pour des prompts conscients de l'éditeur, construit sur l'API | +| [portal](https://github.com/hosenur/portal) | Interface Web mobile-first pour OpenCode via Tailscale/VPN | +| [opencode plugin template](https://github.com/zenobi-us/opencode-plugin-template/) | Modèle pour créer des plugins OpenCode | +| [opencode.nvim](https://github.com/sudo-tee/opencode.nvim) | Frontend Neovim pour opencode - un agent de codage IA basé sur terminal | +| [ai-sdk-provider-opencode-sdk](https://github.com/ben-vargas/ai-sdk-provider-opencode-sdk) | Fournisseur Vercel AI SDK pour utiliser OpenCode via @opencode-ai/sdk | +| [OpenChamber](https://github.com/btriapitsyn/openchamber) | Application Web / Bureau et extension VS Code pour OpenCode | +| [OpenCode-Obsidian](https://github.com/mtymek/opencode-obsidian) | Plugin Obsidian qui intègre OpenCode dans l'interface d'Obsidian | +| [OpenWork](https://github.com/different-ai/openwork) | Une alternative open-source à Claude Cowork, propulsée par OpenCode | +| [ocx](https://github.com/kdcokenny/ocx) | Gestionnaire d'extensions OpenCode avec profils portables et isolés. | +| [CodeNomad](https://github.com/NeuralNomadsAI/CodeNomad) | Application client Bureau, Web, Mobile et Distante pour OpenCode | --- ## Agents -| Nom | Description | -| ----------------------------------------------------------------- | ----------------------------------------------------------------------------- | -| [Agentic](https://github.com/Cluster444/agentic) | Agents et commandes d'IA modulaires pour un développement structuré | -| [opencode-agents](https://github.com/darrenhinde/opencode-agents) | Configurations, prompts, agents et plugins pour des flux de travail améliorés | +| Nom | Description | +| ----------------------------------------------------------------- | ---------------------------------------------------------------------- | +| [Agentic](https://github.com/Cluster444/agentic) | Agents IA modulaires et commandes pour un développement structuré | +| [opencode-agents](https://github.com/darrenhinde/opencode-agents) | Configs, prompts, agents et plugins pour des flux de travail améliorés | diff --git a/packages/web/src/content/docs/fr/go.mdx b/packages/web/src/content/docs/fr/go.mdx new file mode 100644 index 00000000000..fc2b6aa6c65 --- /dev/null +++ b/packages/web/src/content/docs/fr/go.mdx @@ -0,0 +1,145 @@ +--- +title: Go +description: Abonnement à bas coût pour les modèles de code ouverts. +--- + +import config from "../../../../config.mjs" +export const console = config.console +export const email = `mailto:${config.email}` + +OpenCode Go est un abonnement à bas coût de **10 $/mois** qui vous donne un accès fiable aux modèles de code ouverts populaires. + +:::note +OpenCode Go est actuellement en bêta. +::: + +Go fonctionne comme tout autre fournisseur dans OpenCode. Vous vous abonnez à OpenCode Go et obtenez votre clé API. C'est **complètement optionnel** et vous n'avez pas besoin de l'utiliser pour utiliser OpenCode. + +Il est conçu principalement pour les utilisateurs internationaux, avec des modèles hébergés aux États-Unis, en UE et à Singapour pour un accès mondial stable. + +--- + +## Contexte + +Les modèles ouverts sont devenus vraiment bons. Ils atteignent maintenant des performances proches des modèles propriétaires pour les tâches de codage. Et parce que de nombreux fournisseurs peuvent les servir de manière compétitive, ils sont généralement beaucoup moins chers. + +Cependant, obtenir un accès fiable et à faible latence à ces modèles peut être difficile. Les fournisseurs varient en qualité et en disponibilité. + +:::tip +Nous avons testé un groupe sélectionné de modèles et de fournisseurs qui fonctionnent bien avec OpenCode. +::: + +Pour remédier à cela, nous avons fait plusieurs choses : + +1. Nous avons testé un groupe sélectionné de modèles ouverts et discuté avec leurs équipes de la meilleure façon de les exécuter. +2. Nous avons ensuite travaillé avec quelques fournisseurs pour nous assurer qu'ils étaient servis correctement. +3. Enfin, nous avons évalué la combinaison modèle/fournisseur et établi une liste que nous nous sentons à l'aise de recommander. + +OpenCode Go vous donne accès à ces modèles pour **10 $/mois**. + +--- + +## Comment ça marche + +OpenCode Go fonctionne comme tout autre fournisseur dans OpenCode. + +1. Vous vous connectez à **OpenCode Zen**, vous vous abonnez à Go et copiez votre clé API. +2. Vous exécutez la commande `/connect` dans la TUI, sélectionnez `OpenCode Go`, et collez votre clé API. +3. Exécutez `/models` dans la TUI pour voir la liste des modèles disponibles via Go. + +:::note +Un seul membre par espace de travail peut s'abonner à OpenCode Go. +::: + +La liste actuelle des modèles inclut : + +- **GLM-5** +- **Kimi K2.5** +- **MiniMax M2.5** + +La liste des modèles peut changer à mesure que nous testons et ajoutons de nouveaux modèles. + +--- + +## Limites d'utilisation + +OpenCode Go inclut les limites suivantes : + +- **Limite de 5 heures** — 12 $ d'utilisation +- **Limite hebdomadaire** — 30 $ d'utilisation +- **Limite mensuelle** — 60 $ d'utilisation + +Les limites sont définies en valeur monétaire. Cela signifie que votre nombre réel de requêtes dépend du modèle que vous utilisez. Les modèles moins chers comme MiniMax M2.5 permettent plus de requêtes, tandis que les modèles plus coûteux comme GLM-5 en permettent moins. + +Le tableau ci-dessous fournit une estimation du nombre de requêtes basée sur des modèles d'utilisation typiques de Go : + +| | GLM-5 | Kimi K2.5 | MiniMax M2.5 | +| --------------------- | ----- | --------- | ------------ | +| requêtes par 5 heures | 1 150 | 1 850 | 30 000 | +| requêtes par semaine | 2 880 | 4 630 | 75 000 | +| requêtes par mois | 5 750 | 9 250 | 150 000 | + +Les estimations sont basées sur des modèles de requêtes moyens observés : + +- GLM-5 — 700 tokens d'entrée, 52 000 en cache, 150 de sortie par requête +- Kimi K2.5 — 870 tokens d'entrée, 55 000 en cache, 200 de sortie par requête +- MiniMax M2.5 — 300 tokens d'entrée, 55 000 en cache, 125 de sortie par requête + +Vous pouvez suivre votre utilisation actuelle dans la **console**. + +:::tip +Si vous atteignez la limite d'utilisation, vous pouvez continuer à utiliser les modèles gratuits. +::: + +Les limites d'utilisation peuvent changer à mesure que nous apprenons des premiers usages et retours. + +--- + +### Tarification + +OpenCode Go est un plan d'abonnement à **10 $/mois**. Ci-dessous se trouvent les prix **par 1M de tokens**. + +| Modèle | Entrée | Sortie | Lecture en cache | +| ------------ | ------ | ------ | ---------------- | +| GLM-5 | 1,00 $ | 3,20 $ | 0,20 $ | +| Kimi K2.5 | 0,60 $ | 3,00 $ | 0,10 $ | +| MiniMax M2.5 | 0,30 $ | 1,20 $ | 0,03 $ | + +--- + +### Utilisation au-delà des limites + +Si vous avez aussi des crédits sur votre solde Zen, vous pouvez activer l'option **Use balance** dans la console. Lorsqu'elle est activée, Go basculera sur votre solde Zen après que vous ayez atteint vos limites d'utilisation au lieu de bloquer les requêtes. + +--- + +## Endpoints + +Vous pouvez également accéder aux modèles Go via les endpoints API suivants. + +| Modèle | ID du modèle | Endpoint | Package AI SDK | +| ------------ | ------------ | ------------------------------------------------ | --------------------------- | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | + +L'[ID du modèle](/docs/config/#models) dans votre configuration OpenCode utilise le format `opencode-go/`. Par exemple, pour Kimi K2.5, vous utiliseriez `opencode-go/kimi-k2.5` dans votre configuration. + +--- + +## Confidentialité + +Le plan est conçu principalement pour les utilisateurs internationaux, avec des modèles hébergés aux États-Unis, en UE et à Singapour pour un accès mondial stable. + +Contactez-nous si vous avez des questions. + +--- + +## Objectifs + +Nous avons créé OpenCode Go pour : + +1. Rendre le codage par IA **accessible** à plus de personnes avec un abonnement à bas coût. +2. Fournir un accès **fiable** aux meilleurs modèles de code ouverts. +3. Sélectionner des modèles qui sont **testés et évalués** pour l'utilisation d'agents de codage. +4. N'avoir **aucun verrouillage** en vous permettant d'utiliser tout autre fournisseur avec OpenCode également. diff --git a/packages/web/src/content/docs/fr/lsp.mdx b/packages/web/src/content/docs/fr/lsp.mdx index cf2dfb6180d..8a83370da05 100644 --- a/packages/web/src/content/docs/fr/lsp.mdx +++ b/packages/web/src/content/docs/fr/lsp.mdx @@ -27,6 +27,7 @@ OpenCode est livré avec plusieurs serveurs LSP intégrés pour les langages pop | gopls | .go | Commande `go` disponible | | hls | .hs, .lhs | Commande `haskell-language-server-wrapper` disponible | | jdtls | .java | `Java SDK (version 21+)` installé | +| julials | .jl | `julia` et `LanguageServer.jl` installés | | kotlin-ls | .kt, .kts | Installation automatique pour les projets Kotlin | | lua-ls | .lua | Installation automatique pour les projets Lua | | nixd | .nix | Commande `nixd` disponible | diff --git a/packages/web/src/content/docs/fr/mcp-servers.mdx b/packages/web/src/content/docs/fr/mcp-servers.mdx index e1d1f24e7c0..5d012868799 100644 --- a/packages/web/src/content/docs/fr/mcp-servers.mdx +++ b/packages/web/src/content/docs/fr/mcp-servers.mdx @@ -375,9 +375,9 @@ Si vous disposez d'un grand nombre de serveurs MCP, vous souhaiterez peut-être --- -#### Patterns glob +#### Modèles globaux -Le pattern glob utilise des modèles de globbing regex simples : +Le modèle glob utilise des modèles de globbing regex simples : - `*` correspond à zéro ou plusieurs caractères (par exemple, `"my-mcp*"` correspond à `my-mcp_search`, `my-mcp_list`, etc.) - `?` correspond exactement à un caractère @@ -509,473 +509,3 @@ Alternativement, vous pouvez ajouter quelque chose comme ceci à votre [AGENTS.m ```md title="AGENTS.md" If you are unsure how to do something, use `gh_grep` to search code examples from GitHub. ``` - -Vous pouvez également désactiver un serveur en définissant `enabled` sur `false`. Ceci est utile si vous souhaitez désactiver temporairement un serveur sans le supprimer de votre configuration. - ---- - -### Remplacement des valeurs par défaut distantes - -Les organisations peuvent fournir des serveurs MCP par défaut via leur point de terminaison `.well-known/opencode`. Ces serveurs peuvent être désactivés par défaut, permettant aux utilisateurs de choisir ceux dont ils ont besoin. - -Pour activer un serveur spécifique à partir de la configuration distante de votre organisation, ajoutez-le à votre configuration locale avec `enabled: true` : - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "mcp": { - "jira": { - "type": "remote", - "url": "https://jira.example.com/mcp", - "enabled": true - } - } -} -``` - -Vos valeurs de configuration locales remplacent les valeurs par défaut distantes. Voir [config precedence](/docs/config#precedence-order) pour plus de détails. - ---- - -## Local - -Ajoutez des serveurs MCP locaux en utilisant `type` à `"local"` dans l'objet MCP. - -```jsonc title="opencode.jsonc" {15} -{ - "$schema": "https://opencode.ai/config.json", - "mcp": { - "my-local-mcp-server": { - "type": "local", - // Or ["bun", "x", "my-mcp-command"] - "command": ["npx", "-y", "my-mcp-command"], - "enabled": true, - "environment": { - "MY_ENV_VAR": "my_env_var_value", - }, - }, - }, -} -``` - -La commande indique comment le serveur MCP local est démarré. Vous pouvez également transmettre une liste de variables d’environnement. - -Par exemple, voici comment ajouter le serveur de test [`@modelcontextprotocol/server-everything`](https://www.npmjs.com/package/@modelcontextprotocol/server-everything) MCP. - -```jsonc title="opencode.jsonc" -{ - "$schema": "https://opencode.ai/config.json", - "mcp": { - "mcp_everything": { - "type": "local", - "command": ["npx", "-y", "@modelcontextprotocol/server-everything"], - }, - }, -} -``` - -Et pour l'utiliser, je peux ajouter `use the mcp_everything tool` à mes invites. - -```txt "mcp_everything" -use the mcp_everything tool to add the number 3 and 4 -``` - ---- - -#### Options - -Voici toutes les options pour configurer un serveur MCP local. - -| Options | Tapez | Obligatoire | Descriptif | -| ------------- | ------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------- | -| `type` | Chaîne | Oui | Le type de connexion au serveur MCP doit être `"local"`. | -| `command` | Tableau | Oui | Commande et arguments pour exécuter le serveur MCP. | -| `environment` | Objet | | Variables d'environnement à définir lors de l'exécution du serveur. | -| `enabled` | Booléen | | Activez ou désactivez le serveur MCP au démarrage. | -| `timeout` | Numéro | | Délai d'expiration en ms pour la récupération des outils depuis le serveur MCP. La valeur par défaut est 5 000 (5 secondes). | - ---- - -## Remote - -Ajoutez des serveurs MCP distants en définissant `type` sur `"remote"`. - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "mcp": { - "my-remote-mcp": { - "type": "remote", - "url": "https://my-mcp-server.com", - "enabled": true, - "headers": { - "Authorization": "Bearer MY_API_KEY" - } - } - } -} -``` - -Le champ `url` est l'URL du serveur MCP distant et l'option `headers` vous permet de transmettre des en-têtes. - ---- - -#### Options - -| Options | Tapez | Obligatoire | Descriptif | -| --------- | ------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------- | -| `type` | Chaîne | Oui | Le type de connexion au serveur MCP doit être `"remote"`. | -| `url` | Chaîne | Oui | URL du serveur MCP distant. | -| `enabled` | Booléen | | Activez ou désactivez le serveur MCP au démarrage. | -| `headers` | Objet | | En-têtes à envoyer avec la demande. | -| `oauth` | Objet | | Configuration de l'authentification OAuth. Voir la section [OAuth](#oauth) ci-dessous. | -| `timeout` | Numéro | | Délai d'expiration en ms pour la récupération des outils depuis le serveur MCP. La valeur par défaut est 5 000 (5 secondes). | - ---- - -## OAuth - -OpenCode gère automatiquement l'authentification OAuth pour les serveurs MCP distants. Lorsqu'un serveur nécessite une authentification, OpenCode : - -1. Détectez la réponse 401 et lancez le flux OAuth -2. Utilisez **Enregistrement client dynamique (RFC 7591)** s'il est pris en charge par le serveur. -3. Stockez les jetons en toute sécurité pour les demandes futures - ---- - -### Automatique - -Pour la plupart des serveurs MCP compatibles OAuth, aucune configuration particulière n'est nécessaire. Configurez simplement le serveur distant : - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "mcp": { - "my-oauth-server": { - "type": "remote", - "url": "https://mcp.example.com/mcp" - } - } -} -``` - -Si le serveur nécessite une authentification, OpenCode vous demandera de vous authentifier lorsque vous essayez de l'utiliser pour la première fois. Sinon, vous pouvez [déclencher manuellement le flux](#authenticating) avec `opencode mcp auth `. - ---- - -### Pré-inscrit - -Si vous disposez des informations d'identification client du fournisseur de serveur MCP, vous pouvez les configurer : - -```json title="opencode.json" {7-11} -{ - "$schema": "https://opencode.ai/config.json", - "mcp": { - "my-oauth-server": { - "type": "remote", - "url": "https://mcp.example.com/mcp", - "oauth": { - "clientId": "{env:MY_MCP_CLIENT_ID}", - "clientSecret": "{env:MY_MCP_CLIENT_SECRET}", - "scope": "tools:read tools:execute" - } - } - } -} -``` - ---- - -### Authentification - -Vous pouvez déclencher manuellement l'authentification ou gérer les informations d'identification. - -Authentifiez-vous auprès d'un serveur MCP spécifique : - -```bash -opencode mcp auth my-oauth-server -``` - -Répertoriez tous les serveurs MCP et leur statut d'authentification : - -```bash -opencode mcp list -``` - -Supprimez les informations d'identification stockées : - -```bash -opencode mcp logout my-oauth-server -``` - -La commande `mcp auth` ouvrira votre navigateur pour autorisation. Après votre autorisation, OpenCode stockera les jetons en toute sécurité dans `~/.local/share/opencode/mcp-auth.json`. - ---- - -#### Désactivation de OAuth - -Si vous souhaitez désactiver le OAuth automatique pour un serveur (par exemple, pour les serveurs qui utilisent les clés API à la place), définissez `oauth` sur `false` : - -```json title="opencode.json" {7} -{ - "$schema": "https://opencode.ai/config.json", - "mcp": { - "my-api-key-server": { - "type": "remote", - "url": "https://mcp.example.com/mcp", - "oauth": false, - "headers": { - "Authorization": "Bearer {env:MY_API_KEY}" - } - } - } -} -``` - ---- - -#### Options OAuth - -| Options | Tapez | Descriptif | -| -------------- | ------------- | ---------------------------------------------------------------------------------------- | -| `oauth` | Objet \| faux | Objet de configuration OAuth, ou `false` pour désactiver la détection automatique OAuth. | -| `clientId` | Chaîne | ID client OAuth. S’il n’est pas fourni, l’enregistrement dynamique du client sera tenté. | -| `clientSecret` | Chaîne | OAuth secret client, si requis par le serveur d'autorisation. | -| `scope` | Chaîne | OAuth scopes à demander lors de l'autorisation. | - -#### Débogage - -Si un serveur MCP distant ne parvient pas à s'authentifier, vous pouvez diagnostiquer les problèmes avec : - -```bash -# View auth status for all OAuth-capable servers -opencode mcp auth list - -# Debug connection and OAuth flow for a specific server -opencode mcp debug my-oauth-server -``` - -La commande `mcp debug` affiche l'état d'authentification actuel, teste la connectivité HTTP et tente le flux de découverte OAuth. - ---- - -## Gérer - -Vos MCP sont disponibles sous forme d'outils dans OpenCode, aux côtés des outils intégrés. Vous pouvez donc les gérer via la configuration OpenCode comme n'importe quel autre outil. - ---- - -### Global - -Cela signifie que vous pouvez les activer ou les désactiver globalement. - -```json title="opencode.json" {14} -{ - "$schema": "https://opencode.ai/config.json", - "mcp": { - "my-mcp-foo": { - "type": "local", - "command": ["bun", "x", "my-mcp-command-foo"] - }, - "my-mcp-bar": { - "type": "local", - "command": ["bun", "x", "my-mcp-command-bar"] - } - }, - "tools": { - "my-mcp-foo": false - } -} -``` - -Nous pouvons également utiliser un modèle global pour désactiver tous les MCP correspondants. - -```json title="opencode.json" {14} -{ - "$schema": "https://opencode.ai/config.json", - "mcp": { - "my-mcp-foo": { - "type": "local", - "command": ["bun", "x", "my-mcp-command-foo"] - }, - "my-mcp-bar": { - "type": "local", - "command": ["bun", "x", "my-mcp-command-bar"] - } - }, - "tools": { - "my-mcp*": false - } -} -``` - -Ici, nous utilisons le modèle global `my-mcp*` pour désactiver tous les MCP. - ---- - -### Par agent - -Si vous disposez d'un grand nombre de serveurs MCP, vous souhaiterez peut-être les activer uniquement par agent et les désactiver globalement. Pour ce faire : - -1. Désactivez-le en tant qu'outil à l'échelle mondiale. -2. Dans votre [agent config](/docs/agents#tools), activez le serveur MCP en tant qu'outil. - -```json title="opencode.json" {11, 14-18} -{ - "$schema": "https://opencode.ai/config.json", - "mcp": { - "my-mcp": { - "type": "local", - "command": ["bun", "x", "my-mcp-command"], - "enabled": true - } - }, - "tools": { - "my-mcp*": false - }, - "agent": { - "my-agent": { - "tools": { - "my-mcp*": true - } - } - } -} -``` - ---- - -#### Modèles globaux - -Le modèle glob utilise des modèles de globbing regex simples : - -- `*` correspond à zéro ou plusieurs caractères (par exemple, `"my-mcp*"` correspond à `my-mcp_search`, `my-mcp_list`, etc.) -- `?` correspond exactement à un caractère -- Tous les autres caractères correspondent littéralement - -:::note -Les outils serveur MCP sont enregistrés avec le nom du serveur comme préfixe, donc pour désactiver tous les outils d'un serveur, utilisez simplement : - -``` -"mymcpservername_*": false -``` - -::: - ---- - -## Exemples - -Vous trouverez ci-dessous des exemples de serveurs MCP courants. Vous pouvez soumettre un PR si vous souhaitez documenter d'autres serveurs. - ---- - -### Sentry - -Ajoutez le [serveur Sentry MCP](https://mcp.sentry.dev) pour interagir avec vos projets et problèmes Sentry. - -```json title="opencode.json" {4-8} -{ - "$schema": "https://opencode.ai/config.json", - "mcp": { - "sentry": { - "type": "remote", - "url": "https://mcp.sentry.dev/mcp", - "oauth": {} - } - } -} -``` - -Après avoir ajouté la configuration, authentifiez-vous auprès de Sentry : - -```bash -opencode mcp auth sentry -``` - -Cela ouvrira une fenêtre de navigateur pour terminer le flux OAuth et connecter OpenCode à votre compte Sentry. - -Une fois authentifié, vous pouvez utiliser les outils Sentry dans vos invites pour interroger les problèmes, les projets et les données d'erreur. - -```txt "use sentry" -Show me the latest unresolved issues in my project. use sentry -``` - ---- - -### Contexte7 - -Ajoutez le [Context7 MCP server](https://github.com/upstash/context7) pour effectuer une recherche dans les documents. - -```json title="opencode.json" {4-7} -{ - "$schema": "https://opencode.ai/config.json", - "mcp": { - "context7": { - "type": "remote", - "url": "https://mcp.context7.com/mcp" - } - } -} -``` - -Si vous avez créé un compte gratuit, vous pouvez utiliser votre clé API et obtenir des limites de débit plus élevées. - -```json title="opencode.json" {7-9} -{ - "$schema": "https://opencode.ai/config.json", - "mcp": { - "context7": { - "type": "remote", - "url": "https://mcp.context7.com/mcp", - "headers": { - "CONTEXT7_API_KEY": "{env:CONTEXT7_API_KEY}" - } - } - } -} -``` - -Ici, nous supposons que la variable d'environnement `CONTEXT7_API_KEY` est définie. - -Ajoutez `use context7` à vos invites pour utiliser le serveur Context7 MCP. - -```txt "use context7" -Configure a Cloudflare Worker script to cache JSON API responses for five minutes. use context7 -``` - -Alternativement, vous pouvez ajouter quelque chose comme ceci à votre [AGENTS.md](/docs/rules/). - -```md title="AGENTS.md" -When you need to search docs, use `context7` tools. -``` - ---- - -### Grep by Vercel - -Ajoutez le serveur [Grep by Vercel](https://grep.app) MCP pour rechercher des extraits de code sur GitHub. - -```json title="opencode.json" {4-7} -{ - "$schema": "https://opencode.ai/config.json", - "mcp": { - "gh_grep": { - "type": "remote", - "url": "https://mcp.grep.app" - } - } -} -``` - -Puisque nous avons nommé notre serveur MCP `gh_grep`, vous pouvez ajouter `use the gh_grep tool` à vos invites pour que l'agent l'utilise. - -```txt "use the gh_grep tool" -What's the right way to set a custom domain in an SST Astro component? use the gh_grep tool -``` - -Alternativement, vous pouvez ajouter quelque chose comme ceci à votre [AGENTS.md](/docs/rules/). - -```md title="AGENTS.md" -If you are unsure how to do something, use `gh_grep` to search code examples from GitHub. -``` diff --git a/packages/web/src/content/docs/fr/providers.mdx b/packages/web/src/content/docs/fr/providers.mdx index b65e9c00a15..36e1ed2d2bf 100644 --- a/packages/web/src/content/docs/fr/providers.mdx +++ b/packages/web/src/content/docs/fr/providers.mdx @@ -84,6 +84,38 @@ Il fonctionne comme n’importe quel autre fournisseur dans OpenCode et son util --- +## OpenCode Go + +OpenCode Go est un plan d'abonnement à faible coût qui offre un accès fiable aux modèles de codage ouverts populaires fournis par l'équipe OpenCode qui ont été +testé et vérifié pour fonctionner correctement avec OpenCode. + +1. Exécutez la commande `/connect` dans le TUI, sélectionnez `OpenCode Go` et rendez-vous sur [opencode.ai/auth](https://opencode.ai/zen). + + ```txt + /connect + ``` + +2. Connectez-vous, ajoutez vos informations de facturation et copiez votre clé API. + +3. Collez votre clé API. + + ```txt + ┌ API key + │ + │ + └ enter + ``` + +4. Exécutez `/models` dans le TUI pour voir la liste des modèles que nous recommandons. + + ```txt + /models + ``` + +Il fonctionne comme n’importe quel autre fournisseur dans OpenCode et son utilisation est totalement facultative. + +--- + ## Annuaire Examinons certains fournisseurs en détail. Si vous souhaitez ajouter un fournisseur au @@ -1487,6 +1519,39 @@ Ou ajoutez-le à votre profil bash : --- +### STACKIT + +STACKIT AI Model Serving fournit un environnement d'hébergement souverain entièrement géré pour les modèles d'IA, se concentrant sur les LLM comme Llama, Mistral et Qwen, avec une souveraineté maximale des données sur l'infrastructure européenne. + +1. Rendez-vous sur le [portail STACKIT](https://portal.stackit.cloud), accédez à **AI Model Serving** et créez un jeton d'authentification pour votre projet. + + :::tip + Vous avez besoin d'un compte client STACKIT, d'un compte utilisateur et d'un projet avant de créer des jetons d'authentification. + ::: + +2. Exécutez la commande `/connect` et recherchez **STACKIT**. + + ```txt + /connect + ``` + +3. Entrez votre jeton d'authentification STACKIT AI Model Serving. + + ```txt + ┌ API key + │ + │ + └ enter + ``` + +4. Exécutez la commande `/models` pour sélectionner parmi les modèles disponibles tels que _Qwen3-VL 235B_ ou _Llama 3.3 70B_. + + ```txt + /models + ``` + +--- + ### OVHcloud AI Endpoints 1. Rendez-vous sur le [Panneau OVHcloud](https://ovh.com/manager). Accédez à la section `Public Cloud`, `AI & Machine Learning` > `AI Endpoints` et dans l'onglet `API Keys`, cliquez sur **Créer une nouvelle clé API**. diff --git a/packages/web/src/content/docs/fr/sdk.mdx b/packages/web/src/content/docs/fr/sdk.mdx index 0e67aebd00a..ee3a2dc49c2 100644 --- a/packages/web/src/content/docs/fr/sdk.mdx +++ b/packages/web/src/content/docs/fr/sdk.mdx @@ -117,6 +117,78 @@ try { --- +## Sortie structurée + +Vous pouvez demander une sortie JSON structurée au modèle en spécifiant un `format` avec un schéma JSON. Le modèle utilisera un outil `StructuredOutput` pour renvoyer un JSON validé correspondant à votre schéma. + +### Utilisation de base + +```typescript +const result = await client.session.prompt({ + path: { id: sessionId }, + body: { + parts: [{ type: "text", text: "Research Anthropic and provide company info" }], + format: { + type: "json_schema", + schema: { + type: "object", + properties: { + company: { type: "string", description: "Company name" }, + founded: { type: "number", description: "Year founded" }, + products: { + type: "array", + items: { type: "string" }, + description: "Main products", + }, + }, + required: ["company", "founded"], + }, + }, + }, +}) + +// Access the structured output +console.log(result.data.info.structured_output) +// { company: "Anthropic", founded: 2021, products: ["Claude", "Claude API"] } +``` + +### Types de format de sortie + +| Type | Description | +| ------------- | ----------------------------------------------------------------- | +| `text` | Par défaut. Réponse textuelle standard (pas de sortie structurée) | +| `json_schema` | Renvoie un JSON validé correspondant au schéma fourni | + +### Format de schéma JSON + +Lors de l'utilisation de `type: 'json_schema'`, fournissez : + +| Champ | Type | Description | +| ------------ | --------------- | --------------------------------------------------------------- | +| `type` | `'json_schema'` | Requis. Spécifie le mode de schéma JSON | +| `schema` | `object` | Requis. Objet JSON Schema définissant la structure de sortie | +| `retryCount` | `number` | Facultatif. Nombre de tentatives de validation (par défaut : 2) | + +### Gestion des erreurs + +Si le modèle ne parvient pas à produire une sortie structurée valide après toutes les tentatives, la réponse inclura une `StructuredOutputError` : + +```typescript +if (result.data.info.error?.name === "StructuredOutputError") { + console.error("Failed to produce structured output:", result.data.info.error.message) + console.error("Attempts:", result.data.info.error.retries) +} +``` + +### Bonnes pratiques + +1. **Fournissez des descriptions claires** dans les propriétés de votre schéma pour aider le modèle à comprendre quelles données extraire +2. **Utilisez `required`** pour spécifier quels champs doivent être présents +3. **Gardez les schémas ciblés** - les schémas imbriqués complexes peuvent être plus difficiles à remplir correctement pour le modèle +4. **Définissez un `retryCount` approprié** - augmentez-le pour les schémas complexes, diminuez-le pour les simples + +--- + ## APIs Le SDK expose toutes les API du serveur via un client de type sécurisé. @@ -226,27 +298,27 @@ const { providers, default: defaults } = await client.config.providers() ### Séances -| Méthode | Descriptif | Remarques | -| ---------------------------------------------------------- | ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `session.list()` | Liste des séances | Renvoie Session[] | -| `session.get({ path })` | Obtenir une session | Renvoie Session | -| `session.children({ path })` | Liste des sessions enfants | Renvoie Session[] | -| `session.create({ body })` | Créer une séance | Renvoie Session | -| `session.delete({ path })` | Supprimer la séance | Renvoie `boolean` | -| `session.update({ path, body })` | Mettre à jour les propriétés de la session | Renvoie Session | -| `session.init({ path, body })` | Analysez l'application et créez `AGENTS.md` | Renvoie `boolean` | -| `session.abort({ path })` | Abandonner une session en cours | Renvoie `boolean` | -| `session.share({ path })` | Séance de partage | Renvoie Session | -| `session.unshare({ path })` | Annuler le partage de la session | Renvoie Session | -| `session.summarize({ path, body })` | Résumer la séance | Renvoie `boolean` | -| `session.messages({ path })` | Liste des messages dans une session | Renvoie `{ info: `Message`, parts: `Part[]`}[]` | -| `session.message({ path })` | Obtenir les détails du message | Renvoie `{ info: `Message`, parts: `Part[]`}` | -| `session.prompt({ path, body })` | Envoyer un message d'invite | `body.noReply: true` renvoie UserMessage (contexte uniquement). La valeur par défaut renvoie AssistantMessage avec réponse IA | -| `session.command({ path, body })` | Envoyer la commande à la session | Renvoie `{ info: `AssistantMessage`, parts: `Part[]`}` | -| `session.shell({ path, body })` | Exécuter une commande shell | Renvoie AssistantMessage | -| `session.revert({ path, body })` | Rétablir un message | Renvoie Session | -| `session.unrevert({ path })` | Restaurer les messages annulés | Renvoie Session | -| `postSessionByIdPermissionsByPermissionId({ path, body })` | Répondre à une demande d'autorisation | Renvoie `boolean` | +| Méthode | Descriptif | Remarques | +| ---------------------------------------------------------- | ------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `session.list()` | Liste des séances | Renvoie Session[] | +| `session.get({ path })` | Obtenir une session | Renvoie Session | +| `session.children({ path })` | Liste des sessions enfants | Renvoie Session[] | +| `session.create({ body })` | Créer une séance | Renvoie Session | +| `session.delete({ path })` | Supprimer la séance | Renvoie `boolean` | +| `session.update({ path, body })` | Mettre à jour les propriétés de la session | Renvoie Session | +| `session.init({ path, body })` | Analysez l'application et créez `AGENTS.md` | Renvoie `boolean` | +| `session.abort({ path })` | Abandonner une session en cours | Renvoie `boolean` | +| `session.share({ path })` | Séance de partage | Renvoie Session | +| `session.unshare({ path })` | Annuler le partage de la session | Renvoie Session | +| `session.summarize({ path, body })` | Résumer la séance | Renvoie `boolean` | +| `session.messages({ path })` | Liste des messages dans une session | Renvoie `{ info: `Message`, parts: `Part[]`}[]` | +| `session.message({ path })` | Obtenir les détails du message | Renvoie `{ info: `Message`, parts: `Part[]`}` | +| `session.prompt({ path, body })` | Envoyer un message d'invite | `body.noReply: true` renvoie UserMessage (contexte uniquement). La valeur par défaut renvoie AssistantMessage avec réponse IA. Prend en charge `body.outputFormat` pour [sortie structurée](#structured-output) | +| `session.command({ path, body })` | Envoyer la commande à la session | Renvoie `{ info: `AssistantMessage`, parts: `Part[]`}` | +| `session.shell({ path, body })` | Exécuter une commande shell | Renvoie AssistantMessage | +| `session.revert({ path, body })` | Rétablir un message | Renvoie Session | +| `session.unrevert({ path })` | Restaurer les messages annulés | Renvoie Session | +| `postSessionByIdPermissionsByPermissionId({ path, body })` | Répondre à une demande d'autorisation | Renvoie `boolean` | --- diff --git a/packages/web/src/content/docs/fr/themes.mdx b/packages/web/src/content/docs/fr/themes.mdx index d17f2169c71..696e56d59ce 100644 --- a/packages/web/src/content/docs/fr/themes.mdx +++ b/packages/web/src/content/docs/fr/themes.mdx @@ -61,11 +61,11 @@ Le thème système est destiné aux utilisateurs qui : ## Utiliser un thème -Vous pouvez sélectionner un thème en affichant la sélection de thème avec la commande `/theme`. Ou vous pouvez le spécifier dans votre [config](/docs/config). +Vous pouvez sélectionner un thème en affichant la sélection de thème avec la commande `/theme`. Ou vous pouvez le spécifier dans `tui.json`. -```json title="opencode.json" {3} +```json title="tui.json" {3} { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "theme": "tokyonight" } ``` diff --git a/packages/web/src/content/docs/fr/tui.mdx b/packages/web/src/content/docs/fr/tui.mdx index f9078508dd5..8129eb20124 100644 --- a/packages/web/src/content/docs/fr/tui.mdx +++ b/packages/web/src/content/docs/fr/tui.mdx @@ -355,24 +355,34 @@ Certains éditeurs ont besoin d'arguments de ligne de commande pour s'exécuter ## Configurer -Vous pouvez personnaliser le comportement de TUI via votre fichier de configuration OpenCode. +Vous pouvez personnaliser le comportement de TUI via `tui.json` (ou `tui.jsonc`). -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", - "tui": { - "scroll_speed": 3, - "scroll_acceleration": { - "enabled": true - } - } + "$schema": "https://opencode.ai/tui.json", + "theme": "opencode", + "keybinds": { + "leader": "ctrl+x" + }, + "scroll_speed": 3, + "scroll_acceleration": { + "enabled": true + }, + "diff_style": "auto" } ``` +Ceci est séparé de `opencode.json`, qui configure le comportement du serveur/d'exécution. + ### Options -- `scroll_acceleration` - Activez l'accélération de défilement de style macOS pour un défilement fluide et naturel. Lorsqu'elle est activée, la vitesse de défilement augmente avec les gestes de défilement rapides et reste précise pour les mouvements plus lents. **Ce paramètre est prioritaire sur `scroll_speed` et le remplace lorsqu'il est activé.** -- `scroll_speed` - Contrôle la vitesse de défilement du TUI lors de l'utilisation des commandes de défilement (minimum : `1`). La valeur par défaut est `3`. **Remarque : Ceci est ignoré si `scroll_acceleration.enabled` est défini sur `true`.** +- `theme` - Définit votre thème d'interface utilisateur. [En savoir plus](/docs/themes). +- `keybinds` - Personnalise les raccourcis clavier. [En savoir plus](/docs/keybinds). +- `scroll_acceleration.enabled` - Activez l'accélération de défilement de style macOS pour un défilement fluide et naturel. Lorsqu'elle est activée, la vitesse de défilement augmente avec les gestes de défilement rapides et reste précise pour les mouvements plus lents. **Ce paramètre est prioritaire sur `scroll_speed` et le remplace lorsqu'il est activé.** +- `scroll_speed` - Contrôle la vitesse de défilement du TUI lors de l'utilisation des commandes de défilement (minimum : `0.001`, prend en charge les valeurs décimales). La valeur par défaut est `3`. **Remarque : Ceci est ignoré si `scroll_acceleration.enabled` est défini sur `true`.** +- `diff_style` - Contrôle le rendu différentiel. `"auto"` s'adapte à la largeur du terminal, `"stacked"` affiche toujours une seule colonne. + +Utilisez `OPENCODE_TUI_CONFIG` pour charger un chemin de configuration TUI personnalisé. --- diff --git a/packages/web/src/content/docs/fr/zen.mdx b/packages/web/src/content/docs/fr/zen.mdx index c69f2632f65..e40b1be77ea 100644 --- a/packages/web/src/content/docs/fr/zen.mdx +++ b/packages/web/src/content/docs/fr/zen.mdx @@ -13,33 +13,25 @@ OpenCode Zen est une liste de modèles testés et vérifiés fournie par l'équi OpenCode Zen est actuellement en version bêta. ::: -Zen fonctionne comme n'importe quel autre fournisseur dans OpenCode. Vous vous connectez à OpenCode Zen et obtenez -votre clé API. C'est **complètement facultatif** et vous n'avez pas besoin de l'utiliser pour l'utiliser -OpenCode. +Zen fonctionne comme n'importe quel autre fournisseur dans OpenCode. Vous vous connectez à OpenCode Zen et obtenez votre clé API. C'est **complètement facultatif** et vous n'avez pas besoin de l'utiliser pour utiliser OpenCode. --- ## Arrière-plan -Il existe un grand nombre de modèles, mais seulement quelques-uns d'entre eux -ces modèles fonctionnent bien comme agents de codage. De plus, la plupart des fournisseurs sont -configuré très différemment; vous obtenez donc des performances et une qualité très différentes. +Il existe un grand nombre de modèles, mais seulement quelques-uns d'entre eux fonctionnent bien comme agents de codage. De plus, la plupart des fournisseurs sont configurés très différemment; vous obtenez donc des performances et une qualité très différentes. :::tip Nous avons testé un groupe sélectionné de modèles et de fournisseurs qui fonctionnent bien avec OpenCode. ::: -Donc, si vous utilisez un modèle via quelque chose comme OpenRouter, vous ne pourrez jamais être -assurez-vous que vous obtenez la meilleure version du modèle que vous souhaitez. +Donc, si vous utilisez un modèle via quelque chose comme OpenRouter, vous ne pourrez jamais être sûr que vous obtenez la meilleure version du modèle que vous souhaitez. -Pour résoudre ce problème, nous avons effectué plusieurs opérations : +Pour résoudre ce problème, nous avons effectué plusieurs opérations : -1. Nous avons testé un groupe sélectionné de modèles et discuté avec leurs équipes de la manière de - mieux vaut les exécuter. -2. Nous avons ensuite travaillé avec quelques prestataires pour nous assurer qu'ils étaient servis. - correctement. -3. Enfin, nous avons comparé la combinaison modèle/fournisseur et sommes arrivés - avec une liste que nous nous ferons un plaisir de recommander. +1. Nous avons testé un groupe sélectionné de modèles et discuté avec leurs équipes de la manière de mieux les exécuter. +2. Nous avons ensuite travaillé avec quelques prestataires pour nous assurer qu'ils étaient servis correctement. +3. Enfin, nous avons comparé la combinaison modèle/fournisseur et sommes arrivés avec une liste que nous nous ferons un plaisir de recommander. OpenCode Zen est une passerelle IA qui vous donne accès à ces modèles. @@ -49,8 +41,7 @@ OpenCode Zen est une passerelle IA qui vous donne accès à ces modèles. OpenCode Zen fonctionne comme n'importe quel autre fournisseur dans OpenCode. -1. Vous vous connectez à **OpenCode Zen**, ajoutez votre facturation - détails et copiez votre clé API. +1. Vous vous connectez à **OpenCode Zen**, ajoutez vos informations de facturation et copiez votre clé API. 2. Vous exécutez la commande `/connect` dans le TUI, sélectionnez OpenCode Zen et collez votre clé API. 3. Exécutez `/models` dans le TUI pour voir la liste des modèles que nous recommandons. @@ -64,6 +55,7 @@ Vous pouvez également accéder à nos modèles via les points de terminaison AP | Modèle | ID du modèle | Point de terminaison | Package SDK IA | | ------------------ | ------------------ | -------------------------------------------------- | --------------------------- | +| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | @@ -73,36 +65,36 @@ Vous pouvez également accéder à nos modèles via les points de terminaison AP | GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | | Gemini 3 Pro | gemini-3-pro | `https://opencode.ai/zen/v1/models/gemini-3-pro` | `@ai-sdk/google` | | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.1 | minimax-m2.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.1 Free | minimax-m2.1-free | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| GLM 5 | glm-5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 4.7 | glm-4.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM 4.7 Free | glm-4.7-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 4.6 | glm-4.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 Free | kimi-k2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2 Thinking | kimi-k2-thinking | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2 | kimi-k2 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Qwen3 Coder 480B | qwen3-coder | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -Le [id du modèle](/docs/config/#models) dans votre configuration OpenCode -utilise le format `opencode/`. Par exemple, pour GPT 5.2 Codex, vous devez -utilisez `opencode/gpt-5.2-codex` dans votre configuration. +Le [id du modèle](/docs/config/#models) dans votre configuration OpenCode utilise le format `opencode/`. Par exemple, pour GPT 5.2 Codex, vous devez utilisez `opencode/gpt-5.2-codex` dans votre configuration. --- ### Modèles -Vous pouvez récupérer la liste complète des modèles disponibles et leurs métadonnées à partir de : +Vous pouvez récupérer la liste complète des modèles disponibles et leurs métadonnées à partir de : ``` https://opencode.ai/zen/v1/models @@ -117,29 +109,34 @@ Nous soutenons un modèle de paiement à l'utilisation. Vous trouverez ci-dessou | Modèle | Entrée | Sortie | Lecture en cache | Écriture en cache | | --------------------------------- | ------- | ------- | ---------------- | ----------------- | | Big Pickle | Gratuit | Gratuit | Gratuit | - | -| MiniMax M2.1 Free | Gratuit | Gratuit | Gratuit | - | +| MiniMax M2.5 Free | Gratuit | Gratuit | Gratuit | - | +| MiniMax M2.5 | 0,30 $ | 1,20 $ | 0,06 $ | - | | MiniMax M2.1 | 0,30 $ | 1,20 $ | 0,10 $ | - | -| GLM 4.7 Free | Gratuit | Gratuit | Gratuit | - | +| GLM 5 | 1,00 $ | 3,20 $ | 0,20 $ | - | | GLM 4.7 | 0,60 $ | 2,20 $ | 0,10 $ | - | | GLM 4.6 | 0,60 $ | 2,20 $ | 0,10 $ | - | -| Kimi K2.5 Free | Gratuit | Gratuit | Gratuit | - | | Kimi K2.5 | 0,60 $ | 3,00 $ | 0,08 $ | - | | Kimi K2 Thinking | 0,40 $ | 2,50 $ | - | - | | Kimi K2 | 0,40 $ | 2,50 $ | - | - | | Qwen3 Coder 480B | 0,45 $ | 1,50 $ | - | - | +| Claude Opus 4.6 (≤ 200K jetons) | 5,00 $ | 25,00 $ | 0,50 $ | 6,25 $ | +| Claude Opus 4.6 (> 200K jetons) | 10,00 $ | 37,50 $ | 1,00 $ | 12,50 $ | +| Claude Opus 4.5 | 5,00 $ | 25,00 $ | 0,50 $ | 6,25 $ | +| Claude Opus 4.1 | 15,00 $ | 75,00 $ | 1,50 $ | 18,75 $ | +| Claude Sonnet 4.6 (≤ 200K jetons) | 3,00 $ | 15,00 $ | 0,30 $ | 3,75 $ | +| Claude Sonnet 4.6 (> 200K jetons) | 6,00 $ | 22,50 $ | 0,60 $ | 7,50 $ | | Claude Sonnet 4.5 (≤ 200K jetons) | 3,00 $ | 15,00 $ | 0,30 $ | 3,75 $ | | Claude Sonnet 4.5 (> 200K jetons) | 6,00 $ | 22,50 $ | 0,60 $ | 7,50 $ | | Claude Sonnet 4 (≤ 200K jetons) | 3,00 $ | 15,00 $ | 0,30 $ | 3,75 $ | | Claude Sonnet 4 (> 200K jetons) | 6,00 $ | 22,50 $ | 0,60 $ | 7,50 $ | | Claude Haiku 4.5 | 1,00 $ | 5,00 $ | 0,10 $ | 1,25 $ | | Claude Haiku 3.5 | 0,80 $ | 4,00 $ | 0,08 $ | 1,00 $ | -| Claude Opus 4.6 (≤ 200K jetons) | 5,00 $ | 25,00 $ | 0,50 $ | 6,25 $ | -| Claude Opus 4.6 (> 200K jetons) | 10,00 $ | 37,50 $ | 1,00 $ | 12,50 $ | -| Claude Opus 4.5 | 5,00 $ | 25,00 $ | 0,50 $ | 6,25 $ | -| Claude Opus 4.1 | 15,00 $ | 75,00 $ | 1,50 $ | 18,75 $ | -| Gemini 3 Pro (≤ 200 000 jetons) | 2,00 $ | 12,00 $ | 0,20 $ | - | -| Gemini 3 Pro (> 200 000 jetons) | 4,00 $ | 18,00 $ | 0,40 $ | - | +| Gemini 3.1 Pro (≤ 200K jetons) | 2,00 $ | 12,00 $ | 0,20 $ | - | +| Gemini 3.1 Pro (> 200K jetons) | 4,00 $ | 18,00 $ | 0,40 $ | - | +| Gemini 3 Pro (≤ 200K jetons) | 2,00 $ | 12,00 $ | 0,20 $ | - | +| Gemini 3 Pro (> 200K jetons) | 4,00 $ | 18,00 $ | 0,40 $ | - | | Gemini 3 Flash | 0,50 $ | 3,00 $ | 0,05 $ | - | +| GPT 5.3 Codex | 1,75 $ | 14,00 $ | 0,175 $ | - | | GPT 5.2 | 1,75 $ | 14,00 $ | 0,175 $ | - | | GPT 5.2 Codex | 1,75 $ | 14,00 $ | 0,175 $ | - | | GPT 5.1 | 1,07 $ | 8,50 $ | 0,107 $ | - | @@ -158,9 +155,7 @@ Les frais de carte de crédit sont répercutés au prix coûtant (4,4 % + 0,30 $ Les modèles gratuits : -- GLM 4.7 Free est disponible sur OpenCode pour une durée limitée. L’équipe profite de ce temps pour recueillir des commentaires et améliorer le modèle. -- Kimi K2.5 Free est disponible sur OpenCode pour une durée limitée. L’équipe profite de ce temps pour recueillir des commentaires et améliorer le modèle. -- MiniMax M2.1 Free est disponible sur OpenCode pour une durée limitée. L’équipe profite de ce temps pour recueillir des commentaires et améliorer le modèle. +- MiniMax M2.5 Free est disponible sur OpenCode pour une durée limitée. L’équipe profite de ce temps pour recueillir des commentaires et améliorer le modèle. - Big Pickle est un modèle furtif gratuit sur OpenCode pour une durée limitée. L’équipe profite de ce temps pour recueillir des commentaires et améliorer le modèle. Contactez-nous si vous avez des questions. @@ -177,48 +172,41 @@ Vous pouvez modifier le montant du rechargement automatique. Vous pouvez égalem ### Limites mensuelles -Vous pouvez également définir une limite d'utilisation mensuelle pour l'ensemble de l'espace de travail et pour chaque -membre de votre équipe. +Vous pouvez également définir une limite d'utilisation mensuelle pour l'ensemble de l'espace de travail et pour chaque membre de votre équipe. -Par exemple, disons que vous définissez une limite d'utilisation mensuelle à 20 $, Zen n'utilisera pas -plus de 20 $ par mois. Mais si le rechargement automatique est activé, Zen pourrait finir par -vous facturant plus de 20 $ si votre solde descend en dessous de 5 $. +Par exemple, disons que vous définissez une limite d'utilisation mensuelle à 20 $, Zen n'utilisera pas plus de 20 $ par mois. Mais si le rechargement automatique est activé, Zen pourrait finir par vous facturant plus de 20 $ si votre solde descend en dessous de 5 $. --- ## Confidentialité -Tous nos modèles sont hébergés aux États-Unis. Nos fournisseurs suivent une politique de rétention zéro et n'utilisent pas vos données pour la formation de modèles, avec les exceptions suivantes : +Tous nos modèles sont hébergés aux États-Unis. Nos fournisseurs suivent une politique de rétention zéro et n'utilisent pas vos données pour la formation de modèles, avec les exceptions suivantes : - Big Pickle : Pendant sa période gratuite, les données collectées peuvent être utilisées pour améliorer le modèle. -- GLM 4.7 Gratuit : Pendant sa période gratuite, les données collectées peuvent être utilisées pour améliorer le modèle. -- Kimi K2.5 Gratuit : Pendant sa période gratuite, les données collectées peuvent être utilisées pour améliorer le modèle. -- MiniMax M2.1 Gratuit : Pendant sa période gratuite, les données collectées peuvent être utilisées pour améliorer le modèle. -- API OpenAI : les demandes sont conservées pendant 30 jours conformément aux politiques de données de [OpenAI](https://platform.openai.com/docs/guides/your-data). -- API Anthropic : les demandes sont conservées pendant 30 jours conformément aux [Politiques de données d'Anthropic](https://docs.anthropic.com/en/docs/claude-code/data-usage). +- MiniMax M2.5 Free : Pendant sa période gratuite, les données collectées peuvent être utilisées pour améliorer le modèle. +- API OpenAI : Les demandes sont conservées pendant 30 jours conformément aux politiques de données de [OpenAI](https://platform.openai.com/docs/guides/your-data). +- API Anthropic : Les demandes sont conservées pendant 30 jours conformément aux [Politiques de données d'Anthropic](https://docs.anthropic.com/en/docs/claude-code/data-usage). --- ## Pour les équipes -Zen fonctionne également très bien pour les équipes. Vous pouvez inviter des coéquipiers, attribuer des rôles, organiser -les modèles utilisés par votre équipe, et bien plus encore. +Zen fonctionne également très bien pour les équipes. Vous pouvez inviter des coéquipiers, attribuer des rôles, organiser les modèles utilisés par votre équipe, et bien plus encore. :::note Les espaces de travail sont actuellement gratuits pour les équipes dans le cadre de la version bêta. ::: -La gestion de votre espace de travail est actuellement gratuite pour les équipes dans le cadre de la version bêta. Nous serons -partagera bientôt plus de détails sur les prix. +La gestion de votre espace de travail est actuellement gratuite pour les équipes dans le cadre de la version bêta. Nous partagerons bientôt plus de détails sur les prix. --- ### Rôles -Vous pouvez inviter des coéquipiers dans votre espace de travail et attribuer des rôles : +Vous pouvez inviter des coéquipiers dans votre espace de travail et attribuer des rôles : -- **Administrateur** : gérez les modèles, les membres, les clés API et la facturation. -- **Membre** : gérer uniquement ses propres clés API +- **Administrateur** : gérez les modèles, les membres, les clés API et la facturation. +- **Membre** : gérer uniquement ses propres clés API Les administrateurs peuvent également définir des limites de dépenses mensuelles pour chaque membre afin de garder les coûts sous contrôle. @@ -228,8 +216,7 @@ Les administrateurs peuvent également définir des limites de dépenses mensuel Les administrateurs peuvent activer ou désactiver des modèles spécifiques pour l'espace de travail. Les requêtes adressées à un modèle désactivé renverront une erreur. -Ceci est utile dans les cas où vous souhaitez désactiver l'utilisation d'un modèle qui -collecte des données. +Ceci est utile dans les cas où vous souhaitez désactiver l'utilisation d'un modèle qui collecte des données. --- @@ -239,8 +226,7 @@ Vous pouvez utiliser vos propres clés OpenAI ou Anthropic API tout en accédant Lorsque vous utilisez vos propres clés, les tokens sont facturés directement par le fournisseur et non par Zen. -Par exemple, votre organisation dispose peut-être déjà d'une clé pour OpenAI ou Anthropic -et vous souhaitez l'utiliser à la place de celui fourni par Zen. +Par exemple, votre organisation dispose peut-être déjà d'une clé pour OpenAI ou Anthropic et vous souhaitez l'utiliser à la place de celui fourni par Zen. --- @@ -250,5 +236,5 @@ Nous avons créé OpenCode Zen pour : 1. **Benchmark** les meilleurs modèles/fournisseurs d'agents de codage. 2. Ayez accès aux options de **la plus haute qualité** et ne dégradez pas les performances ni ne vous dirigez vers des fournisseurs moins chers. -3. Répercutez toute **baisse de prix** en vendant au prix coûtant ; la seule majoration est donc pour couvrir nos frais de traitement. +3. Répercutez toute **baisse de prix** en vendant au prix coûtant ; la seule majoration est donc pour couvrir nos frais de traitement. 4. N'ayez **aucun verrouillage** en vous permettant de l'utiliser avec n'importe quel autre agent de codage. Et laissez-vous toujours utiliser n'importe quel autre fournisseur avec OpenCode également. diff --git a/packages/web/src/content/docs/go.mdx b/packages/web/src/content/docs/go.mdx index 796e67c89c1..1e04704fd58 100644 --- a/packages/web/src/content/docs/go.mdx +++ b/packages/web/src/content/docs/go.mdx @@ -63,8 +63,8 @@ Only one member per workspace can subscribe to OpenCode Go. The current list of models includes: -- **Kimi K2.5** - **GLM-5** +- **Kimi K2.5** - **MiniMax M2.5** The list of models may change as we test and add new ones. @@ -75,17 +75,27 @@ The list of models may change as we test and add new ones. OpenCode Go includes the following limits: -- **5 hour limit** — $4 of usage -- **Weekly limit** — $10 of usage -- **Monthly limit** — $20 of usage +- **5 hour included allowance** — up to $12 of usage +- **Weekly included allowance** — up to $30 of usage +- **Monthly included allowance** — up to $60 of usage + +Limits are defined in dollar value. This means your actual request count depends on the model you use. Cheaper models like MiniMax M2.5 allow for more requests, while higher-cost models like GLM-5 allow for fewer. + +The table below provides an estimated request count based on typical Go usage patterns: + +| | GLM-5 | Kimi K2.5 | MiniMax M2.5 | +| ------------------- | ----- | --------- | ------------ | +| requests per 5 hour | 1,150 | 1,850 | 20,000 | +| requests per week | 2,880 | 4,630 | 50,000 | +| requests per month | 5,750 | 9,250 | 100,000 | -In terms of tokens, $20 of usage is roughly equivalent to: +Estimates are based on observed average request patterns: -- 69 million GLM-5 tokens -- 121 million Kimi K2.5 tokens -- 328 million MiniMax M2.5 tokens +- GLM-5 — 700 input, 52,000 cached, 150 output tokens per request +- Kimi K2.5 — 870 input, 55,000 cached, 200 output tokens per request +- MiniMax M2.5 — 300 input, 55,000 cached, 125 output tokens per request -You can view your current usage in the **console**. +You can track your current usage in the **console**. :::tip If you reach the usage limit, you can continue using the free models. @@ -95,18 +105,6 @@ Usage limits may change as we learn from early usage and feedback. --- -### Pricing - -OpenCode Go is a **$10/month** subscription plan. Below are the prices **per 1M tokens**. - -| Model | Input | Output | Cached Read | -| ------------ | ----- | ------ | ----------- | -| GLM-5 | $1.00 | $3.20 | $0.20 | -| Kimi K2.5 | $0.60 | $3.00 | $0.10 | -| MiniMax M2.5 | $0.30 | $1.20 | $0.03 | - ---- - ### Usage beyond limits If you also have credits on your Zen balance, you can enable the **Use balance** diff --git a/packages/web/src/content/docs/it/cli.mdx b/packages/web/src/content/docs/it/cli.mdx index a97bbde1c96..b35292a0a51 100644 --- a/packages/web/src/content/docs/it/cli.mdx +++ b/packages/web/src/content/docs/it/cli.mdx @@ -558,6 +558,7 @@ OpenCode può essere configurato tramite variabili d'ambiente. | `OPENCODE_AUTO_SHARE` | boolean | Condivide automaticamente le sessioni | | `OPENCODE_GIT_BASH_PATH` | string | Percorso all'eseguibile Git Bash su Windows | | `OPENCODE_CONFIG` | string | Percorso al file di configurazione | +| `OPENCODE_TUI_CONFIG` | string | Percorso al file di configurazione TUI | | `OPENCODE_CONFIG_DIR` | string | Percorso alla directory di configurazione | | `OPENCODE_CONFIG_CONTENT` | string | Contenuto JSON di config inline | | `OPENCODE_DISABLE_AUTOUPDATE` | boolean | Disabilita i controlli automatici di aggiornamento | diff --git a/packages/web/src/content/docs/it/config.mdx b/packages/web/src/content/docs/it/config.mdx index c94cc59a9b3..05741e172ed 100644 --- a/packages/web/src/content/docs/it/config.mdx +++ b/packages/web/src/content/docs/it/config.mdx @@ -14,10 +14,11 @@ OpenCode supporta sia **JSON** sia **JSONC** (JSON con commenti). ```jsonc title="opencode.jsonc" { "$schema": "https://opencode.ai/config.json", - // Theme configuration - "theme": "opencode", "model": "anthropic/claude-sonnet-4-5", "autoupdate": true, + "server": { + "port": 4096, + }, } ``` @@ -33,7 +34,7 @@ I file di configurazione vengono **uniti (merge)**, non sostituiti. I file di configurazione vengono uniti (merge), non sostituiti. Le impostazioni provenienti dalle posizioni qui sotto vengono combinate. Le configurazioni caricate dopo sovrascrivono quelle precedenti solo per le chiavi in conflitto. Le impostazioni non in conflitto vengono preservate. -Per esempio, se la tua configurazione globale imposta `theme: "opencode"` e `autoupdate: true`, e la configurazione del progetto imposta `model: "anthropic/claude-sonnet-4-5"`, la configurazione finale includera tutte e tre le impostazioni. +Per esempio, se la tua configurazione globale imposta `autoupdate: true` e la configurazione del progetto imposta `model: "anthropic/claude-sonnet-4-5"`, la configurazione finale includera entrambe le impostazioni. --- @@ -94,7 +95,9 @@ Puoi abilitare server specifici nella tua configurazione locale: ### Configurazione globale -Metti la configurazione globale di OpenCode in `~/.config/opencode/opencode.json`. Usa la configurazione globale per preferenze valide per l'utente (ad es. temi, provider o keybind). +Metti la configurazione globale di OpenCode in `~/.config/opencode/opencode.json`. Usa la configurazione globale per preferenze server/runtime valide per l'utente come provider, modelli e permessi. + +Per le impostazioni specifiche della TUI, usa `~/.config/opencode/tui.json`. La configurazione globale sovrascrive i default remoti dell'organizzazione. @@ -104,6 +107,8 @@ La configurazione globale sovrascrive i default remoti dell'organizzazione. Aggiungi `opencode.json` nella root del progetto. La configurazione di progetto ha la precedenza piu alta tra i file standard: sovrascrive sia la configurazione globale sia quella remota. +Per le impostazioni TUI specifiche del progetto, aggiungi `tui.json` nella stessa posizione. + :::tip Metti la configurazione specifica del progetto nella root del progetto. ::: @@ -144,34 +149,32 @@ La directory personalizzata viene caricata dopo la configurazione globale e le d Il file di configurazione ha uno schema definito in [**`opencode.ai/config.json`**](https://opencode.ai/config.json). +La configurazione TUI usa [**`opencode.ai/tui.json`**](https://opencode.ai/tui.json). + Il tuo editor dovrebbe poter validare e suggerire l'autocompletamento in base allo schema. --- ### TUI -Puoi configurare impostazioni specifiche della TUI tramite l'opzione `tui`. +Usa un file dedicato `tui.json` (o `tui.jsonc`) per le impostazioni specifiche della TUI. -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", - "tui": { - "scroll_speed": 3, - "scroll_acceleration": { - "enabled": true - }, - "diff_style": "auto" - } + "$schema": "https://opencode.ai/tui.json", + "scroll_speed": 3, + "scroll_acceleration": { + "enabled": true + }, + "diff_style": "auto" } ``` -Opzioni disponibili: +Usa `OPENCODE_TUI_CONFIG` per puntare a un file di configurazione TUI personalizzato. -- `scroll_acceleration.enabled` - Abilita l'accelerazione di scorrimento in stile macOS. **Ha precedenza su `scroll_speed`.** -- `scroll_speed` - Moltiplicatore personalizzato della velocita di scorrimento (predefinito: `3`, minimo: `1`). Ignorato se `scroll_acceleration.enabled` e `true`. -- `diff_style` - Controlla la resa delle diff. `"auto"` si adatta alla larghezza del terminale, `"stacked"` mostra sempre una singola colonna. +Le chiavi legacy `theme`, `keybinds` e `tui` in `opencode.json` sono deprecate e vengono migrate automaticamente quando possibile. -[Scopri di piu sull'uso della TUI](/docs/tui). +[Scopri di piu sulla configurazione TUI](/docs/tui#configure). --- @@ -297,12 +300,12 @@ I bearer token (`AWS_BEARER_TOKEN_BEDROCK` o `/connect`) hanno precedenza sull'a ### Temi -Puoi configurare il tema da usare in OpenCode tramite l'opzione `theme`. +Imposta il tuo tema UI in `tui.json`. -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", - "theme": "" + "$schema": "https://opencode.ai/tui.json", + "theme": "tokyonight" } ``` @@ -402,11 +405,11 @@ Puoi anche definire comandi usando file markdown in `~/.config/opencode/commands ### Scorciatoie -Puoi personalizzare i keybind tramite l'opzione `keybinds`. +Personalizza i keybind in `tui.json`. -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "keybinds": {} } ``` @@ -486,13 +489,15 @@ Puoi controllare il comportamento di compattazione del contesto tramite l'opzion "$schema": "https://opencode.ai/config.json", "compaction": { "auto": true, - "prune": true + "prune": true, + "reserved": 10000 } } ``` - `auto` - Compatta automaticamente la sessione quando il contesto e pieno (predefinito: `true`). - `prune` - Rimuove output vecchi degli strumenti per risparmiare token (predefinito: `true`). +- `reserved` - Token buffer per la compattazione. Lascia abbastanza margine per evitare overflow durante la compattazione --- diff --git a/packages/web/src/content/docs/it/custom-tools.mdx b/packages/web/src/content/docs/it/custom-tools.mdx index 43f69c43c6c..a0ef1c46c25 100644 --- a/packages/web/src/content/docs/it/custom-tools.mdx +++ b/packages/web/src/content/docs/it/custom-tools.mdx @@ -79,6 +79,32 @@ Questo crea due strumenti: `math_add` e `math_multiply`. --- +#### Collisioni di nomi con strumenti integrati + +Gli strumenti personalizzati sono indicizzati per nome. Se uno strumento personalizzato usa lo stesso nome di uno strumento integrato, quello personalizzato ha la precedenza. + +Per esempio, questo file sostituisce lo strumento `bash` integrato: + +```ts title=".opencode/tools/bash.ts" +import { tool } from "@opencode-ai/plugin" + +export default tool({ + description: "Restricted bash wrapper", + args: { + command: tool.schema.string(), + }, + async execute(args) { + return `blocked: ${args.command}` + }, +}) +``` + +:::note +Preferisci nomi univoci a meno che tu non voglia intenzionalmente sostituire uno strumento integrato. Se vuoi disabilitare uno strumento integrato ma non sovrascriverlo, usa i [permessi](/docs/permissions). +::: + +--- + ### Argomenti Puoi usare `tool.schema`, che e semplicemente [Zod](https://zod.dev), per definire i tipi degli argomenti. diff --git a/packages/web/src/content/docs/it/ecosystem.mdx b/packages/web/src/content/docs/it/ecosystem.mdx index 54fcdb8dbd9..11ce7193f9d 100644 --- a/packages/web/src/content/docs/it/ecosystem.mdx +++ b/packages/web/src/content/docs/it/ecosystem.mdx @@ -3,50 +3,51 @@ title: Ecosistema description: Progetti e integrazioni costruiti con OpenCode. --- -Una raccolta di progetti della comunita costruiti su OpenCode. +Una raccolta di progetti della comunità costruiti su OpenCode. :::note Vuoi aggiungere il tuo progetto legato a OpenCode a questa lista? Apri una PR. ::: -Puoi anche dare un'occhiata a [awesome-opencode](https://github.com/awesome-opencode/awesome-opencode) e [opencode.cafe](https://opencode.cafe), una comunita che aggrega ecosistema e community. +Puoi anche dare un'occhiata a [awesome-opencode](https://github.com/awesome-opencode/awesome-opencode) e [opencode.cafe](https://opencode.cafe), una comunità che aggrega ecosistema e community. --- ## Plugin -| Nome | Descrizione | -| --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | -| [opencode-daytona](https://github.com/jamesmurdza/daytona/blob/main/guides/typescript/opencode/README.md) | Esegue automaticamente sessioni OpenCode in sandbox Daytona isolate con sync git e anteprime live | -| [opencode-helicone-session](https://github.com/H2Shami/opencode-helicone-session) | Inietta automaticamente gli header di sessione Helicone per raggruppare le richieste | -| [opencode-type-inject](https://github.com/nick-vi/opencode-type-inject) | Inietta automaticamente tipi TypeScript/Svelte nelle letture dei file con tool di lookup | -| [opencode-openai-codex-auth](https://github.com/numman-ali/opencode-openai-codex-auth) | Usa il tuo abbonamento ChatGPT Plus/Pro invece dei crediti API | -| [opencode-gemini-auth](https://github.com/jenslys/opencode-gemini-auth) | Usa il tuo piano Gemini esistente invece della fatturazione API | -| [opencode-antigravity-auth](https://github.com/NoeFabris/opencode-antigravity-auth) | Usa i modelli gratuiti di Antigravity invece della fatturazione API | -| [opencode-devcontainers](https://github.com/athal7/opencode-devcontainers) | Isolamento devcontainer multi-branch con shallow clone e porte assegnate automaticamente | -| [opencode-google-antigravity-auth](https://github.com/shekohex/opencode-google-antigravity-auth) | Plugin OAuth Google Antigravity, con supporto a Google Search e gestione API piu robusta | -| [opencode-dynamic-context-pruning](https://github.com/Tarquinen/opencode-dynamic-context-pruning) | Ottimizza l'uso dei token eliminando output obsoleti degli strumenti | -| [opencode-websearch-cited](https://github.com/ghoulr/opencode-websearch-cited.git) | Aggiunge supporto websearch nativo per provider supportati con stile grounded di Google | -| [opencode-pty](https://github.com/shekohex/opencode-pty.git) | Permette agli agenti AI di eseguire processi in background in una PTY e inviare input interattivo | -| [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Istruzioni per comandi shell non interattivi: evita blocchi dovuti a operazioni dipendenti da TTY | -| [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Traccia l'uso di OpenCode con Wakatime | -| [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Ripulisce le tabelle markdown prodotte dai LLM | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | Editing del codice 10x piu veloce con Morph Fast Apply API e marker lazy edit | -| [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Agenti in background, tool LSP/AST/MCP predefiniti, agenti curati, compatibile con Claude Code | -| [opencode-notificator](https://github.com/panta82/opencode-notificator) | Notifiche desktop e avvisi sonori per le sessioni OpenCode | -| [opencode-notifier](https://github.com/mohak34/opencode-notifier) | Notifiche desktop e avvisi sonori per eventi di permesso, completamento ed errore | -| [opencode-zellij-namer](https://github.com/24601/opencode-zellij-namer) | Naming automatico delle sessioni Zellij basato sul contesto OpenCode | -| [opencode-skillful](https://github.com/zenobi-us/opencode-skillful) | Permette agli agenti OpenCode di caricare prompt al bisogno con discovery e injection di skill | -| [opencode-supermemory](https://github.com/supermemoryai/opencode-supermemory) | Memoria persistente tra sessioni usando Supermemory | -| [@plannotator/opencode](https://github.com/backnotprop/plannotator/tree/main/apps/opencode-plugin) | Revisione interattiva dei piani con annotazione visiva e condivisione privata/offline | -| [@openspoon/subtask2](https://github.com/spoons-and-mirrors/subtask2) | Estende opencode /commands in un sistema di orchestrazione con controllo di flusso granulare | -| [opencode-scheduler](https://github.com/different-ai/opencode-scheduler) | Pianifica job ricorrenti con launchd (Mac) o systemd (Linux) usando sintassi cron | -| [micode](https://github.com/vtemian/micode) | Workflow strutturato Brainstorm → Plan → Implement con continuita di sessione | -| [octto](https://github.com/vtemian/octto) | UI browser interattiva per brainstorming AI con moduli multi-domanda | -| [opencode-background-agents](https://github.com/kdcokenny/opencode-background-agents) | Agenti in background stile Claude Code con delega async e persistenza del contesto | -| [opencode-notify](https://github.com/kdcokenny/opencode-notify) | Notifiche native del sistema per OpenCode: sai quando i task finiscono | -| [opencode-workspace](https://github.com/kdcokenny/opencode-workspace) | Harness di orchestrazione multi-agente bundle: 16 componenti, una installazione | -| [opencode-worktree](https://github.com/kdcokenny/opencode-worktree) | Git worktree senza attriti per OpenCode | +| Nome | Descrizione | +| -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | +| [opencode-daytona](https://github.com/daytonaio/daytona/tree/main/libs/opencode-plugin) | Esegue automaticamente sessioni OpenCode in sandbox Daytona isolate con sync git e anteprime live | +| [opencode-helicone-session](https://github.com/H2Shami/opencode-helicone-session) | Inietta automaticamente gli header di sessione Helicone per raggruppare le richieste | +| [opencode-type-inject](https://github.com/nick-vi/opencode-type-inject) | Inietta automaticamente tipi TypeScript/Svelte nelle letture dei file con tool di lookup | +| [opencode-openai-codex-auth](https://github.com/numman-ali/opencode-openai-codex-auth) | Usa il tuo abbonamento ChatGPT Plus/Pro invece dei crediti API | +| [opencode-gemini-auth](https://github.com/jenslys/opencode-gemini-auth) | Usa il tuo piano Gemini esistente invece della fatturazione API | +| [opencode-antigravity-auth](https://github.com/NoeFabris/opencode-antigravity-auth) | Usa i modelli gratuiti di Antigravity invece della fatturazione API | +| [opencode-devcontainers](https://github.com/athal7/opencode-devcontainers) | Isolamento devcontainer multi-branch con shallow clone e porte assegnate automaticamente | +| [opencode-google-antigravity-auth](https://github.com/shekohex/opencode-google-antigravity-auth) | Plugin OAuth Google Antigravity, con supporto a Google Search e gestione API più robusta | +| [opencode-dynamic-context-pruning](https://github.com/Tarquinen/opencode-dynamic-context-pruning) | Ottimizza l'uso dei token eliminando output obsoleti degli strumenti | +| [opencode-vibeguard](https://github.com/inkdust2021/opencode-vibeguard) | Oscura segreti/PII in placeholder stile VibeGuard prima delle chiamate LLM; ripristina localmente | +| [opencode-websearch-cited](https://github.com/ghoulr/opencode-websearch-cited.git) | Aggiunge supporto websearch nativo per provider supportati con stile grounded di Google | +| [opencode-pty](https://github.com/shekohex/opencode-pty.git) | Permette agli agenti AI di eseguire processi in background in una PTY e inviare input interattivo | +| [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Istruzioni per comandi shell non interattivi: evita blocchi dovuti a operazioni dipendenti da TTY | +| [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Traccia l'uso di OpenCode con Wakatime | +| [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Ripulisce le tabelle markdown prodotte dai LLM | +| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | Editing del codice 10x più veloce con Morph Fast Apply API e marker lazy edit | +| [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Agenti in background, tool LSP/AST/MCP predefiniti, agenti curati, compatibile con Claude Code | +| [opencode-notificator](https://github.com/panta82/opencode-notificator) | Notifiche desktop e avvisi sonori per le sessioni OpenCode | +| [opencode-notifier](https://github.com/mohak34/opencode-notifier) | Notifiche desktop e avvisi sonori per eventi di permesso, completamento ed errore | +| [opencode-zellij-namer](https://github.com/24601/opencode-zellij-namer) | Naming automatico delle sessioni Zellij basato sul contesto OpenCode | +| [opencode-skillful](https://github.com/zenobi-us/opencode-skillful) | Permette agli agenti OpenCode di caricare prompt al bisogno con discovery e injection di skill | +| [opencode-supermemory](https://github.com/supermemoryai/opencode-supermemory) | Memoria persistente tra sessioni usando Supermemory | +| [@plannotator/opencode](https://github.com/backnotprop/plannotator/tree/main/apps/opencode-plugin) | Revisione interattiva dei piani con annotazione visiva e condivisione privata/offline | +| [@openspoon/subtask2](https://github.com/spoons-and-mirrors/subtask2) | Estende opencode /commands in un sistema di orchestrazione con controllo di flusso granulare | +| [opencode-scheduler](https://github.com/different-ai/opencode-scheduler) | Pianifica job ricorrenti con launchd (Mac) o systemd (Linux) usando sintassi cron | +| [micode](https://github.com/vtemian/micode) | Workflow strutturato Brainstorm → Plan → Implement con continuità di sessione | +| [octto](https://github.com/vtemian/octto) | UI browser interattiva per brainstorming AI con moduli multi-domanda | +| [opencode-background-agents](https://github.com/kdcokenny/opencode-background-agents) | Agenti in background stile Claude Code con delega async e persistenza del contesto | +| [opencode-notify](https://github.com/kdcokenny/opencode-notify) | Notifiche native del sistema per OpenCode: sai quando i task finiscono | +| [opencode-workspace](https://github.com/kdcokenny/opencode-workspace) | Harness di orchestrazione multi-agente bundle: 16 componenti, una installazione | +| [opencode-worktree](https://github.com/kdcokenny/opencode-worktree) | Git worktree senza attriti per OpenCode | --- diff --git a/packages/web/src/content/docs/it/formatters.mdx b/packages/web/src/content/docs/it/formatters.mdx index c264da3f4b7..58e4c60ed03 100644 --- a/packages/web/src/content/docs/it/formatters.mdx +++ b/packages/web/src/content/docs/it/formatters.mdx @@ -11,33 +11,34 @@ OpenCode formatta automaticamente i file dopo che vengono scritti o modificati u OpenCode include diversi formattatori integrati per linguaggi e framework popolari. Qui sotto trovi la lista dei formattatori, delle estensioni supportate e dei comandi o opzioni di config richiesti. -| Formattatore | Estensioni | Requisiti | -| -------------------- | -------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | -| gofmt | .go | `gofmt` command available | -| mix | .ex, .exs, .eex, .heex, .leex, .neex, .sface | `mix` command available | -| prettier | .js, .jsx, .ts, .tsx, .html, .css, .md, .json, .yaml, and [more](https://prettier.io/docs/en/index.html) | `prettier` dependency in `package.json` | -| biome | .js, .jsx, .ts, .tsx, .html, .css, .md, .json, .yaml, and [more](https://biomejs.dev/) | `biome.json(c)` config file | -| zig | .zig, .zon | `zig` command available | -| clang-format | .c, .cpp, .h, .hpp, .ino, and [more](https://clang.llvm.org/docs/ClangFormat.html) | `.clang-format` config file | -| ktlint | .kt, .kts | `ktlint` command available | -| ruff | .py, .pyi | `ruff` command available with config | -| rustfmt | .rs | `rustfmt` command available | -| cargofmt | .rs | `cargo fmt` command available | -| uv | .py, .pyi | `uv` command available | -| rubocop | .rb, .rake, .gemspec, .ru | `rubocop` command available | -| standardrb | .rb, .rake, .gemspec, .ru | `standardrb` command available | -| htmlbeautifier | .erb, .html.erb | `htmlbeautifier` command available | -| air | .R | `air` command available | -| dart | .dart | `dart` command available | -| dfmt | .d | `dfmt` command available | -| ocamlformat | .ml, .mli | `ocamlformat` command available and `.ocamlformat` config file | -| terraform | .tf, .tfvars | `terraform` command available | -| gleam | .gleam | `gleam` command available | -| nixfmt | .nix | `nixfmt` command available | -| shfmt | .sh, .bash | `shfmt` command available | -| pint | .php | `laravel/pint` dependency in `composer.json` | -| oxfmt (Experimental) | .js, .jsx, .ts, .tsx | `oxfmt` dependency in `package.json` and an [experimental env variable flag](/docs/cli/#experimental) | -| ormolu | .hs | `ormolu` command available | +| Formattatore | Estensioni | Requisiti | +| -------------------- | -------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | +| air | .R | comando `air` disponibile | +| biome | .js, .jsx, .ts, .tsx, .html, .css, .md, .json, .yaml, and [more](https://biomejs.dev/) | file di configurazione `biome.json(c)` | +| cargofmt | .rs | comando `cargo fmt` disponibile | +| clang-format | .c, .cpp, .h, .hpp, .ino, and [more](https://clang.llvm.org/docs/ClangFormat.html) | file di configurazione `.clang-format` | +| cljfmt | .clj, .cljs, .cljc, .edn | comando `cljfmt` disponibile | +| dart | .dart | comando `dart` disponibile | +| dfmt | .d | comando `dfmt` disponibile | +| gleam | .gleam | comando `gleam` disponibile | +| gofmt | .go | comando `gofmt` disponibile | +| htmlbeautifier | .erb, .html.erb | comando `htmlbeautifier` disponibile | +| ktlint | .kt, .kts | comando `ktlint` disponibile | +| mix | .ex, .exs, .eex, .heex, .leex, .neex, .sface | comando `mix` disponibile | +| nixfmt | .nix | comando `nixfmt` disponibile | +| ocamlformat | .ml, .mli | comando `ocamlformat` disponibile e file di configurazione `.ocamlformat` | +| ormolu | .hs | comando `ormolu` disponibile | +| oxfmt (Experimental) | .js, .jsx, .ts, .tsx | dipendenza `oxfmt` in `package.json` e una [flag variabile d'ambiente sperimentale](/docs/cli/#experimental) | +| pint | .php | dipendenza `laravel/pint` in `composer.json` | +| prettier | .js, .jsx, .ts, .tsx, .html, .css, .md, .json, .yaml, and [more](https://prettier.io/docs/en/index.html) | dipendenza `prettier` in `package.json` | +| rubocop | .rb, .rake, .gemspec, .ru | comando `rubocop` disponibile | +| ruff | .py, .pyi | comando `ruff` disponibile con config | +| rustfmt | .rs | comando `rustfmt` disponibile | +| shfmt | .sh, .bash | comando `shfmt` disponibile | +| standardrb | .rb, .rake, .gemspec, .ru | comando `standardrb` disponibile | +| terraform | .tf, .tfvars | comando `terraform` disponibile | +| uv | .py, .pyi | comando `uv` disponibile | +| zig | .zig, .zon | comando `zig` disponibile | Quindi, se il progetto ha `prettier` in `package.json`, OpenCode lo usera automaticamente. diff --git a/packages/web/src/content/docs/it/go.mdx b/packages/web/src/content/docs/it/go.mdx new file mode 100644 index 00000000000..912cd29004c --- /dev/null +++ b/packages/web/src/content/docs/it/go.mdx @@ -0,0 +1,145 @@ +--- +title: Go +description: Abbonamento a basso costo per modelli di coding open source. +--- + +import config from "../../../../config.mjs" +export const console = config.console +export const email = `mailto:${config.email}` + +OpenCode Go è un abbonamento a basso costo di **$10/mese** che ti offre un accesso affidabile ai modelli di coding open source più popolari. + +:::note +OpenCode Go è attualmente in beta. +::: + +Go funziona come qualsiasi altro provider in OpenCode. Ti abboni a OpenCode Go e ottieni la tua chiave API. È **completamente opzionale** e non è necessario utilizzarlo per usare OpenCode. + +È progettato principalmente per utenti internazionali, con modelli ospitati negli Stati Uniti, UE e Singapore per un accesso globale stabile. + +--- + +## Contesto + +I modelli open source sono diventati davvero validi. Ora raggiungono prestazioni vicine ai modelli proprietari per le attività di coding. E poiché molti provider possono servirli in modo competitivo, sono solitamente molto più economici. + +Tuttavia, ottenere un accesso affidabile e a bassa latenza può essere difficile. I provider variano in termini di qualità e disponibilità. + +:::tip +Abbiamo testato un gruppo selezionato di modelli e provider che funzionano bene con OpenCode. +::: + +Per risolvere questo problema, abbiamo fatto un paio di cose: + +1. Abbiamo testato un gruppo selezionato di modelli open source e parlato con i loro team su come eseguirli al meglio. +2. Abbiamo poi lavorato con alcuni provider per assicurarci che questi venissero serviti correttamente. +3. Infine, abbiamo effettuato benchmark sulla combinazione modello/provider e abbiamo stilato un elenco che ci sentiamo di raccomandare. + +OpenCode Go ti dà accesso a questi modelli per **$10/mese**. + +--- + +## Come funziona + +OpenCode Go funziona come qualsiasi altro provider in OpenCode. + +1. Accedi a **OpenCode Zen**, abbonati a Go e copia la tua chiave API. +2. Esegui il comando `/connect` nella TUI, seleziona `OpenCode Go` e incolla la tua chiave API. +3. Esegui `/models` nella TUI per vedere l'elenco dei modelli disponibili tramite Go. + +:::note +Solo un membro per workspace può abbonarsi a OpenCode Go. +::: + +L'elenco attuale dei modelli include: + +- **GLM-5** +- **Kimi K2.5** +- **MiniMax M2.5** + +L'elenco dei modelli potrebbe cambiare man mano che ne testiamo e aggiungiamo di nuovi. + +--- + +## Limiti di utilizzo + +OpenCode Go include i seguenti limiti: + +- **Limite di 5 ore** — $12 di utilizzo +- **Limite settimanale** — $30 di utilizzo +- **Limite mensile** — $60 di utilizzo + +I limiti sono definiti in valore monetario. Ciò significa che il conteggio effettivo delle richieste dipende dal modello utilizzato. Modelli più economici come MiniMax M2.5 consentono più richieste, mentre modelli più costosi come GLM-5 ne consentono meno. + +La tabella seguente fornisce una stima del conteggio delle richieste basata su tipici modelli di utilizzo di Go: + +| | GLM-5 | Kimi K2.5 | MiniMax M2.5 | +| --------------------- | ----- | --------- | ------------ | +| richieste ogni 5 ore | 1.150 | 1.850 | 30.000 | +| richieste a settimana | 2.880 | 4.630 | 75.000 | +| richieste al mese | 5.750 | 9.250 | 150.000 | + +Le stime si basano sui modelli di richiesta medi osservati: + +- GLM-5 — 700 input, 52.000 cached, 150 output tokens per richiesta +- Kimi K2.5 — 870 input, 55.000 cached, 200 output tokens per richiesta +- MiniMax M2.5 — 300 input, 55.000 cached, 125 output tokens per richiesta + +Puoi monitorare il tuo utilizzo attuale nella **console**. + +:::tip +Se raggiungi il limite di utilizzo, puoi continuare a utilizzare i modelli gratuiti. +::: + +I limiti di utilizzo potrebbero cambiare man mano che impariamo dall'utilizzo iniziale e dai feedback. + +--- + +### Prezzi + +OpenCode Go è un piano di abbonamento da **$10/mese**. Di seguito sono riportati i prezzi **per 1M di token**. + +| Modello | Input | Output | Lettura Cached | +| ------------ | ----- | ------ | -------------- | +| GLM-5 | $1.00 | $3.20 | $0.20 | +| Kimi K2.5 | $0.60 | $3.00 | $0.10 | +| MiniMax M2.5 | $0.30 | $1.20 | $0.03 | + +--- + +### Utilizzo oltre i limiti + +Se hai anche crediti sul tuo saldo Zen, puoi abilitare l'opzione **Use balance** nella console. Quando abilitata, Go utilizzerà il tuo saldo Zen dopo aver raggiunto i limiti di utilizzo invece di bloccare le richieste. + +--- + +## Endpoint + +Puoi anche accedere ai modelli Go tramite i seguenti endpoint API. + +| Modello | ID Modello | Endpoint | Pacchetto AI SDK | +| ------------ | ------------ | ------------------------------------------------ | --------------------------- | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | + +Il [model id](/docs/config/#models) nella tua configurazione OpenCode utilizza il formato `opencode-go/`. Ad esempio, per Kimi K2.5, useresti `opencode-go/kimi-k2.5` nella tua configurazione. + +--- + +## Privacy + +Il piano è progettato principalmente per utenti internazionali, con modelli ospitati negli Stati Uniti, UE e Singapore per un accesso globale stabile. + +Contattaci se hai domande. + +--- + +## Obiettivi + +Abbiamo creato OpenCode Go per: + +1. Rendere l'AI per il coding **accessibile** a più persone con un abbonamento a basso costo. +2. Fornire un accesso **affidabile** ai migliori modelli di coding open source. +3. Curare modelli che sono **testati e benchmarked** per l'uso con agenti di coding. +4. Non avere **alcun lock-in** permettendoti di utilizzare qualsiasi altro provider con OpenCode. diff --git a/packages/web/src/content/docs/it/keybinds.mdx b/packages/web/src/content/docs/it/keybinds.mdx index eb08c2c286a..548805e6bae 100644 --- a/packages/web/src/content/docs/it/keybinds.mdx +++ b/packages/web/src/content/docs/it/keybinds.mdx @@ -3,11 +3,11 @@ title: Scorciatoie description: Personalizza le scorciatoie da tastiera. --- -OpenCode ha una lista di scorciatoie che puoi personalizzare tramite la configurazione di OpenCode. +OpenCode ha una lista di scorciatoie che puoi personalizzare tramite `tui.json`. -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "keybinds": { "leader": "ctrl+x", "app_exit": "ctrl+c,ctrl+d,q", @@ -117,11 +117,11 @@ Non sei obbligato a usare un tasto leader per le scorciatoie, ma lo consigliamo. ## Disabilitare una scorciatoia -Puoi disabilitare una scorciatoia aggiungendo la chiave nella configurazione con valore "none". +Puoi disabilitare una scorciatoia aggiungendo la chiave in `tui.json` con valore "none". -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "keybinds": { "session_compact": "none" } diff --git a/packages/web/src/content/docs/it/lsp.mdx b/packages/web/src/content/docs/it/lsp.mdx index 765475dba07..a21133b141d 100644 --- a/packages/web/src/content/docs/it/lsp.mdx +++ b/packages/web/src/content/docs/it/lsp.mdx @@ -27,6 +27,7 @@ OpenCode include diversi server LSP integrati per linguaggi popolari: | gopls | .go | comando `go` disponibile | | hls | .hs, .lhs | comando `haskell-language-server-wrapper` disponibile | | jdtls | .java | `Java SDK (version 21+)` installato | +| julials | .jl | `julia` e `LanguageServer.jl` installati | | kotlin-ls | .kt, .kts | Installazione automatica per progetti Kotlin | | lua-ls | .lua | Installazione automatica per progetti Lua | | nixd | .nix | comando `nixd` disponibile | diff --git a/packages/web/src/content/docs/it/plugins.mdx b/packages/web/src/content/docs/it/plugins.mdx index ea73f40ffdd..3fc22a78c56 100644 --- a/packages/web/src/content/docs/it/plugins.mdx +++ b/packages/web/src/content/docs/it/plugins.mdx @@ -3,7 +3,7 @@ title: Plugin description: Scrivi plugin per estendere OpenCode. --- -I plugin ti permettono di estendere OpenCode agganciandoti a vari eventi e personalizzando il comportamento. Puoi creare plugin per aggiungere nuove funzionalita', integrare servizi esterni o modificare il comportamento predefinito di OpenCode. +I plugin ti permettono di estendere OpenCode agganciandoti a vari eventi e personalizzando il comportamento. Puoi creare plugin per aggiungere nuove funzionalità, integrare servizi esterni o modificare il comportamento predefinito di OpenCode. Per esempi, dai un'occhiata ai [plugin](/docs/ecosystem#plugins) creati dalla community. @@ -53,7 +53,7 @@ I **plugin locali** vengono caricati direttamente dalla directory dei plugin. Pe ### Ordine di caricamento -I plugin vengono caricati da tutte le sorgenti e tutti gli hook vengono eseguiti in sequenza. L'ordine di caricamento e': +I plugin vengono caricati da tutte le sorgenti e tutti gli hook vengono eseguiti in sequenza. L'ordine di caricamento è: 1. Config globale (`~/.config/opencode/opencode.json`) 2. Config di progetto (`opencode.json`) @@ -66,7 +66,7 @@ I pacchetti npm duplicati con lo stesso nome e versione vengono caricati una sol ## Creare un plugin -Un plugin e' un **modulo JavaScript/TypeScript** che esporta una o piu' funzioni di plugin. Ogni funzione riceve un oggetto di contesto e restituisce un oggetto di hook. +Un plugin è un **modulo JavaScript/TypeScript** che esporta una o più funzioni di plugin. Ogni funzione riceve un oggetto di contesto e restituisce un oggetto di hook. --- @@ -234,7 +234,7 @@ export const NotificationPlugin = async ({ project, client, $, directory, worktr Stiamo usando `osascript` per eseguire AppleScript su macOS. Qui lo usiamo per inviare notifiche. :::note -Se usi l'app desktop di OpenCode, puo' inviare automaticamente notifiche di sistema quando una risposta e' pronta o quando una sessione va in errore. +Se usi l'app desktop di OpenCode, può inviare automaticamente notifiche di sistema quando una risposta è pronta o quando una sessione va in errore. ::: --- @@ -299,7 +299,7 @@ export const CustomToolsPlugin: Plugin = async (ctx) => { } ``` -L'helper `tool` crea uno strumento personalizzato che opencode puo' chiamare. Accetta una funzione di schema Zod e restituisce una definizione di tool con: +L'helper `tool` crea uno strumento personalizzato che opencode può chiamare. Accetta una funzione di schema Zod e restituisce una definizione di tool con: - `description`: cosa fa lo strumento - `args`: schema Zod per gli argomenti dello strumento @@ -307,6 +307,10 @@ L'helper `tool` crea uno strumento personalizzato che opencode puo' chiamare. Ac I tuoi strumenti personalizzati saranno disponibili in opencode insieme agli strumenti integrati. +:::note +Se uno strumento di un plugin usa lo stesso nome di uno strumento integrato, lo strumento del plugin ha la precedenza. +::: + --- ### Logging @@ -381,4 +385,4 @@ Format as a structured prompt that a new agent can use to resume work. } ``` -Quando `output.prompt` e' impostato, sostituisce completamente il prompt di compaction predefinito. In questo caso l'array `output.context` viene ignorato. +Quando `output.prompt` è impostato, sostituisce completamente il prompt di compaction predefinito. In questo caso l'array `output.context` viene ignorato. diff --git a/packages/web/src/content/docs/it/providers.mdx b/packages/web/src/content/docs/it/providers.mdx index 9b4c07b665d..c0c5489d080 100644 --- a/packages/web/src/content/docs/it/providers.mdx +++ b/packages/web/src/content/docs/it/providers.mdx @@ -818,7 +818,7 @@ Alcuni modelli devono essere abilitati manualmente nelle tue [impostazioni GitHu │ │ Enter code: 8F43-6FCF │ - └ Waiting for authorization... + │ └ Waiting for authorization... ``` 3. Ora esegui il comando `/models` per selezionare il modello che vuoi. @@ -1488,6 +1488,39 @@ SAP AI Core fornisce accesso a oltre 40 modelli di OpenAI, Anthropic, Google, Am --- +### STACKIT + +STACKIT AI Model Serving fornisce un ambiente di hosting completamente gestito e sovrano per modelli AI, concentrandosi su LLM come Llama, Mistral e Qwen, con la massima sovranità dei dati su infrastruttura europea. + +1. Vai al [Portale STACKIT](https://portal.stackit.cloud), naviga in **AI Model Serving** e crea un token di autenticazione per il tuo progetto. + + :::tip + Devi avere un account cliente STACKIT, un account utente e un progetto prima di creare token di autenticazione. + ::: + +2. Esegui il comando `/connect` e cerca **STACKIT**. + + ```txt + /connect + ``` + +3. Inserisci il tuo token di autenticazione STACKIT AI Model Serving. + + ```txt + ┌ API key + │ + │ + └ enter + ``` + +4. Esegui il comando `/models` per selezionare tra i modelli disponibili come _Qwen3-VL 235B_ o _Llama 3.3 70B_. + + ```txt + /models + ``` + +--- + ### OVHcloud AI Endpoints 1. Vai al [pannello OVHcloud](https://ovh.com/manager). Naviga nella sezione `Public Cloud`, `AI & Machine Learning` > `AI Endpoints` e nella scheda `API Keys`, clicca **Create a new API key**. diff --git a/packages/web/src/content/docs/it/sdk.mdx b/packages/web/src/content/docs/it/sdk.mdx index 9cf7e4eae52..2941bfb24cf 100644 --- a/packages/web/src/content/docs/it/sdk.mdx +++ b/packages/web/src/content/docs/it/sdk.mdx @@ -117,6 +117,78 @@ try { --- +## Output Strutturato + +Puoi richiedere un output JSON strutturato dal modello specificando un `format` con uno schema JSON. Il modello usera un tool `StructuredOutput` per restituire JSON validato che corrisponde al tuo schema. + +### Uso Base + +```typescript +const result = await client.session.prompt({ + path: { id: sessionId }, + body: { + parts: [{ type: "text", text: "Research Anthropic and provide company info" }], + format: { + type: "json_schema", + schema: { + type: "object", + properties: { + company: { type: "string", description: "Company name" }, + founded: { type: "number", description: "Year founded" }, + products: { + type: "array", + items: { type: "string" }, + description: "Main products", + }, + }, + required: ["company", "founded"], + }, + }, + }, +}) + +// Access the structured output +console.log(result.data.info.structured_output) +// { company: "Anthropic", founded: 2021, products: ["Claude", "Claude API"] } +``` + +### Tipi di Formato Output + +| Tipo | Descrizione | +| ------------- | ------------------------------------------------------------------- | +| `text` | Predefinito. Risposta di testo standard (nessun output strutturato) | +| `json_schema` | Restituisce JSON validato che corrisponde allo schema fornito | + +### Formato Schema JSON + +Quando usi `type: 'json_schema'`, fornisci: + +| Campo | Tipo | Descrizione | +| ------------ | --------------- | --------------------------------------------------------------------- | +| `type` | `'json_schema'` | Richiesto. Specifica la modalita schema JSON | +| `schema` | `object` | Richiesto. Oggetto JSON Schema che definisce la struttura dell'output | +| `retryCount` | `number` | Opzionale. Numero di tentativi di validazione (default: 2) | + +### Gestione Errori + +Se il modello non riesce a produrre un output strutturato valido dopo tutti i tentativi, la risposta includera un `StructuredOutputError`: + +```typescript +if (result.data.info.error?.name === "StructuredOutputError") { + console.error("Failed to produce structured output:", result.data.info.error.message) + console.error("Attempts:", result.data.info.error.retries) +} +``` + +### Best Practices + +1. **Fornisci descrizioni chiare** nelle proprieta del tuo schema per aiutare il modello a capire quali dati estrarre +2. **Usa `required`** per specificare quali campi devono essere presenti +3. **Mantieni gli schemi focalizzati** - schemi annidati complessi potrebbero essere piu difficili da compilare correttamente per il modello +4. **Imposta un `retryCount` appropriato** - aumenta per schemi complessi, diminuisci per quelli semplici + +--- + ## API L'SDK espone tutte le API del server tramite un client type-safe. @@ -226,27 +298,27 @@ const { providers, default: defaults } = await client.config.providers() ### Sessioni -| Metodo | Descrizione | Note | -| ---------------------------------------------------------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | -| `session.list()` | Elenca le sessioni | Returns Session[] | -| `session.get({ path })` | Ottieni una sessione | Returns Session | -| `session.children({ path })` | Elenca sessioni figlie | Returns Session[] | -| `session.create({ body })` | Crea una sessione | Returns Session | -| `session.delete({ path })` | Elimina una sessione | Returns `boolean` | -| `session.update({ path, body })` | Aggiorna proprieta della sessione | Returns Session | -| `session.init({ path, body })` | Analizza app e crea `AGENTS.md` | Returns `boolean` | -| `session.abort({ path })` | Interrompe una sessione in corso | Returns `boolean` | -| `session.share({ path })` | Condivide la sessione | Returns Session | -| `session.unshare({ path })` | Rimuove la condivisione | Returns Session | -| `session.summarize({ path, body })` | Riassume la sessione | Returns `boolean` | -| `session.messages({ path })` | Elenca i messaggi della sessione | Returns `{ info: `Message`, parts: `Part[]`}[]` | -| `session.message({ path })` | Ottieni dettagli di un messaggio | Returns `{ info: `Message`, parts: `Part[]`}` | -| `session.prompt({ path, body })` | Invia un prompt | `body.noReply: true` returns UserMessage (solo contesto). Di default ritorna AssistantMessage con risposta AI | -| `session.command({ path, body })` | Invia un comando alla sessione | Returns `{ info: `AssistantMessage`, parts: `Part[]`}` | -| `session.shell({ path, body })` | Esegue un comando shell | Returns AssistantMessage | -| `session.revert({ path, body })` | Ripristina un messaggio | Returns Session | -| `session.unrevert({ path })` | Ripristina messaggi revertiti | Returns Session | -| `postSessionByIdPermissionsByPermissionId({ path, body })` | Risponde a una richiesta permessi | Returns `boolean` | +| Metodo | Descrizione | Note | +| ---------------------------------------------------------- | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `session.list()` | Elenca le sessioni | Returns Session[] | +| `session.get({ path })` | Ottieni una sessione | Returns Session | +| `session.children({ path })` | Elenca sessioni figlie | Returns Session[] | +| `session.create({ body })` | Crea una sessione | Returns Session | +| `session.delete({ path })` | Elimina una sessione | Returns `boolean` | +| `session.update({ path, body })` | Aggiorna proprieta della sessione | Returns Session | +| `session.init({ path, body })` | Analizza app e crea `AGENTS.md` | Returns `boolean` | +| `session.abort({ path })` | Interrompe una sessione in corso | Returns `boolean` | +| `session.share({ path })` | Condivide la sessione | Returns Session | +| `session.unshare({ path })` | Rimuove la condivisione | Returns Session | +| `session.summarize({ path, body })` | Riassume la sessione | Returns `boolean` | +| `session.messages({ path })` | Elenca i messaggi della sessione | Returns `{ info: `Message`, parts: `Part[]`}[]` | +| `session.message({ path })` | Ottieni dettagli di un messaggio | Returns `{ info: `Message`, parts: `Part[]`}` | +| `session.prompt({ path, body })` | Invia un prompt | `body.noReply: true` returns UserMessage (solo contesto). Di default ritorna AssistantMessage con risposta AI. Supporta `body.outputFormat` per [output strutturato](#output-strutturato) | +| `session.command({ path, body })` | Invia un comando alla sessione | Returns `{ info: `AssistantMessage`, parts: `Part[]`}` | +| `session.shell({ path, body })` | Esegue un comando shell | Returns AssistantMessage | +| `session.revert({ path, body })` | Ripristina un messaggio | Returns Session | +| `session.unrevert({ path })` | Ripristina messaggi revertiti | Returns Session | +| `postSessionByIdPermissionsByPermissionId({ path, body })` | Risponde a una richiesta permessi | Returns `boolean` | --- diff --git a/packages/web/src/content/docs/it/themes.mdx b/packages/web/src/content/docs/it/themes.mdx index 6752470e0f1..055e108727b 100644 --- a/packages/web/src/content/docs/it/themes.mdx +++ b/packages/web/src/content/docs/it/themes.mdx @@ -45,13 +45,13 @@ E altri ancora: aggiungiamo costantemente nuovi temi. ## Tema di sistema -Il tema `system` e progettato per adattarsi automaticamente allo schema colori del tuo terminale. A differenza dei temi tradizionali con colori fissi, il tema _system_: +Il tema `system` è progettato per adattarsi automaticamente allo schema colori del tuo terminale. A differenza dei temi tradizionali con colori fissi, il tema _system_: - **Genera una scala di grigi**: crea una scala di grigi personalizzata in base al colore di sfondo del terminale, garantendo un contrasto ottimale. - **Usa colori ANSI**: sfrutta i colori ANSI standard (0-15) per evidenziazione della sintassi ed elementi UI, rispettando la palette del terminale. - **Preserva i default del terminale**: usa `none` per testo e sfondo per mantenere l'aspetto nativo del terminale. -Il tema di sistema e pensato per chi: +Il tema di sistema è pensato per chi: - Vuole che OpenCode corrisponda all'aspetto del terminale - Usa schemi colori personalizzati del terminale @@ -61,11 +61,11 @@ Il tema di sistema e pensato per chi: ## Usare un tema -Puoi selezionare un tema aprendo la selezione temi con il comando `/theme`. In alternativa, puoi specificarlo nella tua [configurazione](/docs/config). +Puoi selezionare un tema aprendo la selezione temi con il comando `/theme`. In alternativa, puoi specificarlo in `tui.json`. -```json title="opencode.json" {3} +```json title="tui.json" {3} { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "theme": "tokyonight" } ``` @@ -80,14 +80,14 @@ OpenCode supporta un sistema di temi flessibile basato su JSON che permette di c ### Gerarchia -I temi vengono caricati da piu directory nel seguente ordine, dove le directory successive sovrascrivono le precedenti: +I temi vengono caricati da più directory nel seguente ordine, dove le directory successive sovrascrivono le precedenti: 1. **Temi integrati** - incorporati nel binario 2. **Directory di configurazione utente** - in `~/.config/opencode/themes/*.json` o `$XDG_CONFIG_HOME/opencode/themes/*.json` 3. **Directory root del progetto** - in `/.opencode/themes/*.json` 4. **Directory di lavoro corrente** - in `./.opencode/themes/*.json` -Se piu directory contengono un tema con lo stesso nome, verra usato il tema della directory con priorita piu alta. +Se più directory contengono un tema con lo stesso nome, verrà usato il tema della directory con priorità più alta. --- @@ -125,13 +125,13 @@ I temi usano un formato JSON flessibile che supporta: ### Definizioni dei colori -La sezione `defs` e opzionale e ti permette di definire colori riutilizzabili che possono essere referenziati nel tema. +La sezione `defs` è opzionale e ti permette di definire colori riutilizzabili che possono essere referenziati nel tema. --- ### Valori predefiniti del terminale -Il valore speciale `"none"` puo essere usato per qualunque colore per ereditare il colore predefinito del terminale. E particolarmente utile per creare temi che si fondono con lo schema colori del terminale: +Il valore speciale `"none"` può essere usato per qualunque colore per ereditare il colore predefinito del terminale. È particolarmente utile per creare temi che si fondono con lo schema colori del terminale: - `"text": "none"` - usa il colore del testo predefinito del terminale - `"background": "none"` - usa il colore di sfondo predefinito del terminale diff --git a/packages/web/src/content/docs/it/troubleshooting.mdx b/packages/web/src/content/docs/it/troubleshooting.mdx index 51405715072..7c168979daf 100644 --- a/packages/web/src/content/docs/it/troubleshooting.mdx +++ b/packages/web/src/content/docs/it/troubleshooting.mdx @@ -57,8 +57,8 @@ Se l'app desktop va in crash all'avvio, si blocca o si comporta in modo strano, Apri il tuo file di configurazione globale e cerca la chiave `plugin`. -- **macOS/Linux**: `~/.config/opencode/opencode.jsonc` (or `~/.config/opencode/opencode.json`) -- **macOS/Linux** (older installs): `~/.local/share/opencode/opencode.jsonc` +- **macOS/Linux**: `~/.config/opencode/opencode.jsonc` (o `~/.config/opencode/opencode.json`) +- **macOS/Linux** (installazioni vecchie): `~/.local/share/opencode/opencode.jsonc` - **Windows**: premi `WIN+R` e incolla `%USERPROFILE%\.config\opencode\opencode.jsonc` Se hai plugin configurati, disabilitali temporaneamente rimuovendo la chiave o impostandola a un array vuoto: @@ -88,14 +88,14 @@ Se l'app ricomincia a funzionare, riabilita i plugin uno alla volta per capire q Se disabilitare i plugin non aiuta (o l'installazione di un plugin e bloccata), svuota la cache in modo che OpenCode possa ricostruirla. -1. Quit OpenCode Desktop completely. +1. Chiudi completamente OpenCode Desktop. 2. Elimina la directory della cache: - **macOS**: Finder -> `Cmd+Shift+G` -> paste `~/.cache/opencode` - **Linux**: elimina `~/.cache/opencode` (oppure esegui `rm -rf ~/.cache/opencode`) - **Windows**: premi `WIN+R` e incolla `%USERPROFILE%\.cache\opencode` -3. Restart OpenCode Desktop. +3. Riavvia OpenCode Desktop. --- @@ -155,7 +155,7 @@ OpenCode Desktop mostra le notifiche di sistema solo quando: Se l'app non si avvia e non riesci a ripulire le impostazioni dall'interfaccia, reimposta lo stato salvato dell'app desktop. -1. Quit OpenCode Desktop. +1. Chiudi OpenCode Desktop. 2. Trova ed elimina questi file (si trovano nella directory dati dell'app OpenCode Desktop): - `opencode.settings.dat` (desktop default server URL) diff --git a/packages/web/src/content/docs/it/tui.mdx b/packages/web/src/content/docs/it/tui.mdx index 8a09c97a3ee..2e4efa26080 100644 --- a/packages/web/src/content/docs/it/tui.mdx +++ b/packages/web/src/content/docs/it/tui.mdx @@ -63,7 +63,7 @@ Quando usi la TUI di OpenCode, puoi digitare `/` seguito dal nome di un comando /help ``` -Molti comandi hanno anche una scorciatoia da tastiera che usa `ctrl+x` come tasto leader (predefinito). [Scopri di piu](/docs/keybinds). +Molti comandi hanno anche una scorciatoia da tastiera che usa `ctrl+x` come tasto leader (predefinito). [Scopri di più](/docs/keybinds). Ecco tutti i comandi slash disponibili: @@ -105,7 +105,7 @@ Attiva/disattiva i dettagli di esecuzione degli strumenti. ### editor -Apre un editor esterno per comporre messaggi. Usa l'editor impostato nella variabile d'ambiente `EDITOR`. [Scopri di piu](#editor-setup). +Apre un editor esterno per comporre messaggi. Usa l'editor impostato nella variabile d'ambiente `EDITOR`. [Scopri di più](#editor-setup). ```bash frame="none" /editor @@ -129,7 +129,7 @@ Esce da OpenCode. _Alias_: `/quit`, `/q` ### esporta -Esporta la conversazione corrente in Markdown e la apre nell'editor predefinito. Usa l'editor impostato nella variabile d'ambiente `EDITOR`. [Scopri di piu](#editor-setup). +Esporta la conversazione corrente in Markdown e la apre nell'editor predefinito. Usa l'editor impostato nella variabile d'ambiente `EDITOR`. [Scopri di più](#editor-setup). ```bash frame="none" /export @@ -153,7 +153,7 @@ Mostra la finestra di aiuto. ### inizializza -Crea o aggiorna il file `AGENTS.md`. [Scopri di piu](/docs/rules). +Crea o aggiorna il file `AGENTS.md`. [Scopri di più](/docs/rules). ```bash frame="none" /init @@ -219,7 +219,7 @@ Elenca e passa tra le sessioni. _Alias_: `/resume`, `/continue` ### condividi -Condivide la sessione corrente. [Scopri di piu](/docs/share). +Condivide la sessione corrente. [Scopri di più](/docs/share). ```bash frame="none" /share @@ -234,7 +234,7 @@ Condivide la sessione corrente. [Scopri di piu](/docs/share). Elenca i temi disponibili. ```bash frame="none" -/theme +/themes ``` **Scorciatoia:** `ctrl+x t` @@ -243,10 +243,10 @@ Elenca i temi disponibili. ### ragionamento -Attiva/disattiva la visibilita dei blocchi thinking/reasoning nella conversazione. Quando abilitato, puoi vedere il ragionamento del modello per i modelli che supportano extended thinking. +Attiva/disattiva la visibilità dei blocchi thinking/reasoning nella conversazione. Quando abilitato, puoi vedere il ragionamento del modello per i modelli che supportano extended thinking. :::note -Questo comando controlla solo se i blocchi di thinking vengono **mostrati**: non abilita o disabilita le capacita di ragionamento del modello. Per cambiare le capacita di ragionamento effettive, usa `ctrl+t` per ciclare tra le varianti del modello. +Questo comando controlla solo se i blocchi di thinking vengono **mostrati**: non abilita o disabilita le capacità di ragionamento del modello. Per cambiare le capacità di ragionamento effettive, usa `ctrl+t` per ciclare tra le varianti del modello. ::: ```bash frame="none" @@ -275,7 +275,7 @@ Internamente usa Git per gestire le modifiche ai file. Quindi il progetto **deve ### annulla condivisione -Annulla la condivisione della sessione corrente. [Scopri di piu](/docs/share#un-sharing). +Annulla la condivisione della sessione corrente. [Scopri di più](/docs/share#un-sharing). ```bash frame="none" /unshare @@ -346,30 +346,40 @@ Opzioni comuni per l'editor includono: Alcuni editor come VS Code devono essere avviati con il flag `--wait`. ::: -Alcuni editor richiedono argomenti da riga di comando per funzionare in modalita bloccante. Il flag `--wait` fa si che il processo dell'editor resti in attesa finche non viene chiuso. +Alcuni editor richiedono argomenti da riga di comando per funzionare in modalità bloccante. Il flag `--wait` fa sì che il processo dell'editor resti in attesa finché non viene chiuso. --- ## Configurazione -Puoi personalizzare il comportamento della TUI tramite il file di config di OpenCode. +Puoi personalizzare il comportamento della TUI tramite `tui.json` (o `tui.jsonc`). -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", - "tui": { - "scroll_speed": 3, - "scroll_acceleration": { - "enabled": true - } - } + "$schema": "https://opencode.ai/tui.json", + "theme": "opencode", + "keybinds": { + "leader": "ctrl+x" + }, + "scroll_speed": 3, + "scroll_acceleration": { + "enabled": true + }, + "diff_style": "auto" } ``` +Questo è separato da `opencode.json`, che configura il comportamento di server/runtime. + ### Opzioni -- `scroll_acceleration` - Abilita l'accelerazione di scroll in stile macOS per uno scorrimento fluido e naturale. Quando abilitata, la velocita aumenta con gesture rapide e resta precisa con movimenti lenti. **Questa impostazione ha precedenza su `scroll_speed` e lo sovrascrive quando attiva.** -- `scroll_speed` - Controlla la velocita di scorrimento della TUI quando usi i comandi di scroll (minimo: `1`). Default: `3`. **Nota: viene ignorata se `scroll_acceleration.enabled` e impostato a `true`.** +- `theme` - Imposta il tema della UI. [Scopri di più](/docs/themes). +- `keybinds` - Personalizza le scorciatoie da tastiera. [Scopri di più](/docs/keybinds). +- `scroll_acceleration.enabled` - Abilita l'accelerazione di scroll in stile macOS per uno scorrimento fluido e naturale. Quando abilitata, la velocità aumenta con gesture rapide e resta precisa con movimenti lenti. **Questa impostazione ha precedenza su `scroll_speed` e lo sovrascrive quando attiva.** +- `scroll_speed` - Controlla la velocità di scorrimento della TUI quando usi i comandi di scroll (minimo: `0.001`, supporta valori decimali). Default: `3`. **Nota: viene ignorata se `scroll_acceleration.enabled` è impostato a `true`.** +- `diff_style` - Controlla il rendering delle diff. `"auto"` si adatta alla larghezza del terminale, `"stacked"` mostra sempre un layout a colonna singola. + +Usa `OPENCODE_TUI_CONFIG` per caricare un path di configurazione TUI personalizzato. --- diff --git a/packages/web/src/content/docs/it/zen.mdx b/packages/web/src/content/docs/it/zen.mdx index 8ea628aee2a..db0434db502 100644 --- a/packages/web/src/content/docs/it/zen.mdx +++ b/packages/web/src/content/docs/it/zen.mdx @@ -55,6 +55,7 @@ Puoi anche accedere ai nostri modelli tramite i seguenti endpoint API. | Modello | ID modello | Endpoint | Pacchetto AI SDK | | ------------------ | ------------------ | -------------------------------------------------- | --------------------------- | +| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | @@ -64,22 +65,24 @@ Puoi anche accedere ai nostri modelli tramite i seguenti endpoint API. | GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | | Gemini 3 Pro | gemini-3-pro | `https://opencode.ai/zen/v1/models/gemini-3-pro` | `@ai-sdk/google` | | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.1 | minimax-m2.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.1 Free | minimax-m2.1-free | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| GLM 5 | glm-5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 4.7 | glm-4.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM 4.7 Free | glm-4.7-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 4.6 | glm-4.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 Free | kimi-k2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2 Thinking | kimi-k2-thinking | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2 | kimi-k2 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Qwen3 Coder 480B | qwen3-coder | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -106,29 +109,34 @@ Supportiamo un modello pay-as-you-go. Qui sotto trovi i prezzi **per 1M token**. | Modello | Input | Output | Lettura in cache | Scrittura in cache | | --------------------------------- | ------ | ------ | ---------------- | ------------------ | | Big Pickle | Gratis | Gratis | Gratis | - | -| MiniMax M2.1 Free | Gratis | Gratis | Gratis | - | +| MiniMax M2.5 Free | Gratis | Gratis | Gratis | - | +| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | - | | MiniMax M2.1 | $0.30 | $1.20 | $0.10 | - | -| GLM 4.7 Free | Gratis | Gratis | Gratis | - | +| GLM 5 | $1.00 | $3.20 | $0.20 | - | | GLM 4.7 | $0.60 | $2.20 | $0.10 | - | | GLM 4.6 | $0.60 | $2.20 | $0.10 | - | -| Kimi K2.5 Free | Gratis | Gratis | Gratis | - | | Kimi K2.5 | $0.60 | $3.00 | $0.08 | - | | Kimi K2 Thinking | $0.40 | $2.50 | - | - | | Kimi K2 | $0.40 | $2.50 | - | - | | Qwen3 Coder 480B | $0.45 | $1.50 | - | - | +| Claude Opus 4.6 (≤ 200K tokens) | $5.00 | $25.00 | $0.50 | $6.25 | +| Claude Opus 4.6 (> 200K tokens) | $10.00 | $37.50 | $1.00 | $12.50 | +| Claude Opus 4.5 | $5.00 | $25.00 | $0.50 | $6.25 | +| Claude Opus 4.1 | $15.00 | $75.00 | $1.50 | $18.75 | +| Claude Sonnet 4.6 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 | +| Claude Sonnet 4.6 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 | | Claude Sonnet 4.5 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 | | Claude Sonnet 4.5 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 | | Claude Sonnet 4 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 | | Claude Sonnet 4 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 | | Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 | | Claude Haiku 3.5 | $0.80 | $4.00 | $0.08 | $1.00 | -| Claude Opus 4.6 (≤ 200K tokens) | $5.00 | $25.00 | $0.50 | $6.25 | -| Claude Opus 4.6 (> 200K tokens) | $10.00 | $37.50 | $1.00 | $12.50 | -| Claude Opus 4.5 | $5.00 | $25.00 | $0.50 | $6.25 | -| Claude Opus 4.1 | $15.00 | $75.00 | $1.50 | $18.75 | +| Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | +| Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | +| GPT 5.3 Codex | $1.75 | $14.00 | $0.175 | - | | GPT 5.2 | $1.75 | $14.00 | $0.175 | - | | GPT 5.2 Codex | $1.75 | $14.00 | $0.175 | - | | GPT 5.1 | $1.07 | $8.50 | $0.107 | - | @@ -147,9 +155,7 @@ Le commissioni della carta di credito vengono ribaltate al costo (4.4% + $0.30 p I modelli gratuiti: -- GLM 4.7 Free e disponibile su OpenCode per un periodo limitato. Il team usa questo tempo per raccogliere feedback e migliorare il modello. -- Kimi K2.5 Free e disponibile su OpenCode per un periodo limitato. Il team usa questo tempo per raccogliere feedback e migliorare il modello. -- MiniMax M2.1 Free e disponibile su OpenCode per un periodo limitato. Il team usa questo tempo per raccogliere feedback e migliorare il modello. +- MiniMax M2.5 Free e disponibile su OpenCode per un periodo limitato. Il team usa questo tempo per raccogliere feedback e migliorare il modello. - Big Pickle e un modello stealth gratuito su OpenCode per un periodo limitato. Il team usa questo tempo per raccogliere feedback e migliorare il modello. Contattaci se hai domande. @@ -177,9 +183,7 @@ Per esempio, se imposti un limite mensile a $20, Zen non usera piu di $20 in un Tutti i nostri modelli sono ospitati negli US. I nostri provider seguono una policy di zero-retention e non usano i tuoi dati per training dei modelli, con le seguenti eccezioni: - Big Pickle: durante il periodo gratuito, i dati raccolti potrebbero essere usati per migliorare il modello. -- GLM 4.7 Free: durante il periodo gratuito, i dati raccolti potrebbero essere usati per migliorare il modello. -- Kimi K2.5 Free: durante il periodo gratuito, i dati raccolti potrebbero essere usati per migliorare il modello. -- MiniMax M2.1 Free: durante il periodo gratuito, i dati raccolti potrebbero essere usati per migliorare il modello. +- MiniMax M2.5 Free: durante il periodo gratuito, i dati raccolti potrebbero essere usati per migliorare il modello. - OpenAI APIs: le richieste vengono conservate per 30 giorni in conformita alle [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). - Anthropic APIs: le richieste vengono conservate per 30 giorni in conformita alle [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage). diff --git a/packages/web/src/content/docs/ja/cli.mdx b/packages/web/src/content/docs/ja/cli.mdx index aeae74731f2..dcb411c8331 100644 --- a/packages/web/src/content/docs/ja/cli.mdx +++ b/packages/web/src/content/docs/ja/cli.mdx @@ -335,20 +335,20 @@ opencode run --attach http://localhost:4096 "Explain async/await in JavaScript" #### フラグ -| フラグ | ショート | 説明 | -| ------------ | ----------- | ----------------------------------------------------------------------------------------- | -| `--command` | | 実行するコマンド。引数には message を使用します。 | -| `--continue` | `-c` | 最後のセッションを続行 | -| `--session` | | 続行時にセッションをフォーク (`-s` または `--fork` と併用) | -| `--continue` | `--session` | 続行するセッション ID | -| `--share` | | セッションを共有する | -| `--model` | `-m` | プロバイダー/モデルの形式で使用するモデル | -| `--agent` | | 使用するエージェント | -| `--file` | `-f` | メッセージに添付するファイル | -| `--format` | | 形式: デフォルト (フォーマット済み) または json (生の JSON イベント) | -| `--title` | | セッションのタイトル (値が指定されていない場合は、切り詰められたプロンプトが使用されます) | -| `--attach` | | 実行中の opencode サーバー (http://localhost:4096 など) に接続します。 | -| `--port` | | ローカルサーバーのポート (デフォルトはランダムポート) | +| フラグ | ショート | 説明 | +| ------------ | -------- | ----------------------------------------------------------------------------------------- | +| `--command` | | 実行するコマンド。引数には message を使用します。 | +| `--continue` | `-c` | 最後のセッションを続行 | +| `--session` | `-s` | 続行するセッション ID | +| `--fork` | | 続行時にセッションをフォーク (`--continue` または `--session` と併用) | +| `--share` | | セッションを共有する | +| `--model` | `-m` | プロバイダー/モデルの形式で使用するモデル | +| `--agent` | | 使用するエージェント | +| `--file` | `-f` | メッセージに添付するファイル | +| `--format` | | 形式: デフォルト (フォーマット済み) または json (生の JSON イベント) | +| `--title` | | セッションのタイトル (値が指定されていない場合は、切り詰められたプロンプトが使用されます) | +| `--attach` | | 実行中の opencode サーバー (http://localhost:4096 など) に接続します。 | +| `--port` | | ローカルサーバーのポート (デフォルトはランダムポート) | --- diff --git a/packages/web/src/content/docs/ja/config.mdx b/packages/web/src/content/docs/ja/config.mdx index 114336d43c9..20e29190dae 100644 --- a/packages/web/src/content/docs/ja/config.mdx +++ b/packages/web/src/content/docs/ja/config.mdx @@ -14,10 +14,11 @@ OpenCode は、**JSON** と **JSONC** (コメント付きの JSON) 形式の両 ```jsonc title="opencode.jsonc" { "$schema": "https://opencode.ai/config.json", - // Theme configuration - "theme": "opencode", "model": "anthropic/claude-sonnet-4-5", "autoupdate": true, + "server": { + "port": 4096, + }, } ``` @@ -25,14 +26,15 @@ OpenCode は、**JSON** と **JSONC** (コメント付きの JSON) 形式の両 ## ファイルの場所 -設定をいくつかの異なる場所に配置できます。 -優先順位が異なります。 +設定をいくつかの異なる場所に配置できます。それらは異なる優先順位を持ちます。 :::note -設定ファイルは置き換えられるのではなく、**マージ**されます。設定は、次の構成場所から結合されます。後続の設定は、競合するキーに対してのみ以前の設定をオーバーライドします。すべての設定の競合しない設定は保持されます。 +設定ファイルは置き換えられるのではなく、**マージ**されます。 ::: -たとえば、グローバル設定で `theme: "opencode"` と `autoupdate: true` が設定され、プロジェクト設定で `model: "anthropic/claude-sonnet-4-5"` が設定されている場合、最終的な設定には 3 つの設定がすべて含まれます。 +設定ファイルは結合され、置き換えられません。次の構成場所からの設定が結合されます。競合するキーに対してのみ、後の設定が前の設定を上書きします。すべての構成からの競合しない設定は保持されます。 + +たとえば、グローバル設定で `autoupdate: true` を設定し、プロジェクト設定で `model: "anthropic/claude-sonnet-4-5"` を設定した場合、最終的な構成には両方の設定が含まれます。 --- @@ -93,7 +95,9 @@ OpenCode は、**JSON** と **JSONC** (コメント付きの JSON) 形式の両 ### グローバル -グローバル OpenCode 設定を `~/.config/opencode/opencode.json` に配置します。テーマ、プロバイダー、キーバインドなどのユーザー全体の設定にはグローバル設定を使用します。 +グローバル OpenCode 設定を `~/.config/opencode/opencode.json` に配置します。プロバイダー、モデル、権限などのユーザー全体の設定にはグローバル設定を使用します。 + +TUI 固有の設定には、`~/.config/opencode/tui.json` を使用します。 グローバル設定はリモート組織のデフォルトをオーバーライドします。 @@ -103,6 +107,8 @@ OpenCode は、**JSON** と **JSONC** (コメント付きの JSON) 形式の両 プロジェクトのルートに `opencode.json` を追加します。プロジェクト設定は、標準設定ファイルの中で最も高い優先順位を持ち、グローバル設定とリモート設定の両方をオーバーライドします。 +プロジェクト固有の TUI 設定については、その横に `tui.json` を追加します。 + :::tip プロジェクト固有の設定をプロジェクトのルートに配置します。 ::: @@ -144,7 +150,9 @@ opencode run "Hello world" ## スキーマ -設定ファイルには、[**`opencode.ai/config.json`**](https://opencode.ai/config.json) を使用します。 +サーバー/ランタイム構成スキーマは [**`opencode.ai/config.json`**](https://opencode.ai/config.json) で定義されています。 + +TUI 設定は [**`opencode.ai/tui.json`**](https://opencode.ai/tui.json) を使用します。 エディターはスキーマに基づいて検証し、オートコンプリートできる必要があります。 @@ -152,28 +160,24 @@ opencode run "Hello world" ### TUI -`tui` オプションを使用して TUI 固有の設定を構成できます。 +TUI 固有の設定には、専用の `tui.json` (または `tui.jsonc`) ファイルを使用します。 -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", - "tui": { - "scroll_speed": 3, - "scroll_acceleration": { - "enabled": true - }, - "diff_style": "auto" - } + "$schema": "https://opencode.ai/tui.json", + "scroll_speed": 3, + "scroll_acceleration": { + "enabled": true + }, + "diff_style": "auto" } ``` -利用可能なオプション: +カスタム TUI 設定ファイルを指定するには、`OPENCODE_TUI_CONFIG` を使用します。 -- `scroll_acceleration.enabled` - macOS スタイルのスクロールアクセラレーションを有効にします。 **`scroll_speed` よりも優先されます。** -- `scroll_speed` - カスタムのスクロール速度乗数 (デフォルト: `3`、最小: `1`)。 `scroll_acceleration.enabled` が `true` の場合は無視されます。 -- `diff_style` - 差分レンダリングを制御します。 `"auto"` はターミナルの幅に適応し、`"stacked"` は常に 1 列を表示します。 +`opencode.json` 内の従来の `theme`、`keybinds`、および `tui` キーは非推奨となり、可能であれば自動的に移行されます。 -[TUI の使用方法の詳細については、こちら](/docs/tui) をご覧ください。 +[TUI の設定の詳細については、こちら](/docs/tui#configure) をご覧ください。 --- @@ -237,7 +241,7 @@ LLM が使用できるツールは、`tools` オプションを通じて管理 } ``` -`small_model` オプションは、タイトル生成などの軽量タスク用に別のモデルを構成します。デフォルトでは、OpenCode は、プロバイダーから安価なモデルが入手可能な場合は、より安価なモデルを使用しようとします。そうでない場合は、メインモデルにフォールバックします。 +`small_model` オプションは、タイトルの生成などの軽量タスク用に個別のモデルを構成します。デフォルトでは、OpenCode は、プロバイダーから安価なモデルが入手可能な場合は、より安価なモデルを使用しようとします。そうでない場合は、メインモデルにフォールバックします。 プロバイダーオプションには、`timeout` および `setCacheKey` を含めることができます。 @@ -258,7 +262,7 @@ LLM が使用できるツールは、`tools` オプションを通じて管理 - `timeout` - リクエストのタイムアウト (ミリ秒単位) (デフォルト: 300000)。無効にするには、`false` に設定します。 - `setCacheKey` - 指定されたプロバイダーに対してキャッシュキーが常に設定されていることを確認します。 -[ローカルモデル](/docs/models#local). [詳細はこちら](/docs/models)。 +[ローカルモデル](/docs/models#local) も設定できます。[詳細はこちら](/docs/models)。 --- @@ -290,21 +294,21 @@ Amazon Bedrock は、AWS 固有の設定をサポートしています。 - `endpoint` - VPC エンドポイントのカスタムエンドポイント URL。これは、AWS 固有の用語を使用した汎用 `baseURL` オプションのエイリアスです。両方を指定した場合は、`endpoint` が優先されます。 :::note -ベアラー トークン (`AWS_BEARER_TOKEN_BEDROCK` または `/connect`) は、プロファイルベースの認証より優先されます。詳細については、「認証優先順位](/docs/providers#authentication-precedence)」を参照してください。 +ベアラー トークン (`AWS_BEARER_TOKEN_BEDROCK` または `/connect`) は、プロファイルベースの認証より優先されます。詳細については、「[認証優先順位](/docs/providers#authentication-precedence)」を参照してください。 ::: -[Amazon Bedrock 設定 ](/docs/providers#amazon-bedrock) の詳細をご覧ください。 +[Amazon Bedrock 設定](/docs/providers#amazon-bedrock) の詳細をご覧ください。 --- ### テーマ -`theme` オプションを使用して、OpenCode 設定で使用するテーマを構成できます。 +UI テーマを `tui.json` で設定します。 -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", - "theme": "" + "$schema": "https://opencode.ai/tui.json", + "theme": "tokyonight" } ``` @@ -404,11 +408,11 @@ Amazon Bedrock は、AWS 固有の設定をサポートしています。 ### キーバインド -`keybinds` オプションを使用してキーバインドをカスタマイズできます。 +`tui.json` でキーバインドをカスタマイズします。 -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "keybinds": {} } ``` diff --git a/packages/web/src/content/docs/ja/custom-tools.mdx b/packages/web/src/content/docs/ja/custom-tools.mdx index badf50c627d..2377fc77308 100644 --- a/packages/web/src/content/docs/ja/custom-tools.mdx +++ b/packages/web/src/content/docs/ja/custom-tools.mdx @@ -79,6 +79,32 @@ export const multiply = tool({ --- +#### 組み込みツールとの名前の衝突 + +カスタムツールはツール名でキー設定されます。カスタムツールが組み込みツールと同じ名前を使用する場合、カスタムツールが優先されます。 + +たとえば、このファイルは組み込みの `bash` ツールを置き換えます。 + +```ts title=".opencode/tools/bash.ts" +import { tool } from "@opencode-ai/plugin" + +export default tool({ + description: "Restricted bash wrapper", + args: { + command: tool.schema.string(), + }, + async execute(args) { + return `blocked: ${args.command}` + }, +}) +``` + +:::note +組み込みツールを意図的に置き換えたい場合を除き、一意の名前を使用することをお勧めします。組み込みツールを無効にしたいがオーバーライドしたくない場合は、[権限](/docs/permissions) を使用してください。 +::: + +--- + ### 引数 引数の型を定義するには、`tool.schema` (つまり [Zod](https://zod.dev)) を使用できます。 diff --git a/packages/web/src/content/docs/ja/ecosystem.mdx b/packages/web/src/content/docs/ja/ecosystem.mdx index 479fdfbdc34..711b31d4824 100644 --- a/packages/web/src/content/docs/ja/ecosystem.mdx +++ b/packages/web/src/content/docs/ja/ecosystem.mdx @@ -8,44 +8,46 @@ OpenCode に基づいて構築されたコミュニティプロジェクトの :::note OpenCode 関連プロジェクトをこのリストに追加したいですか? PR を送信してください。 ::: + [awesome-opencode](https://github.com/awesome-opencode/awesome-opencode) および [opencode.cafe](https://opencode.cafe) もチェックしてください。 --- ## プラグイン -| 名前 | 説明 | -| --------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | -| [opencode-daytona](https://github.com/jamesmurdza/daytona/blob/main/guides/typescript/opencode/README.md) | git sync とライブプレビューを使用して、隔離された Daytona サンドボックスで OpenCode セッションを自動的に実行します。 | -| [opencode-helicone-session](https://github.com/H2Shami/opencode-helicone-session) | リクエストのグループ化のために Helicone セッションヘッダーを自動的に挿入する | -| [opencode-type-inject](https://github.com/nick-vi/opencode-type-inject) | ルックアップツールを使用して TypeScript/Svelte 型をファイル読み取りに自動挿入する | -| [opencode-openai-codex-auth](https://github.com/numman-ali/opencode-openai-codex-auth) | API クレジットの代わりに ChatGPT Plus/Pro サブスクリプションを使用する | -| [opencode-gemini-auth](https://github.com/jenslys/opencode-gemini-auth) | API 課金の代わりに既存の Gemini プランを使用する | -| [opencode-antigravity-auth](https://github.com/NoeFabris/opencode-antigravity-auth) | API 課金の代わりに Antigravity の無料モデルを使用する | -| [opencode-devcontainers](https://github.com/athal7/opencode-devcontainers) | 浅いクローンと自動割り当てポートを使用したマルチブランチ devcontainer の分離 | -| [opencode-google-antigravity-auth](https://github.com/shekohex/opencode-google-antigravity-auth) | Google Antigravity OAuth プラグイン、Google 検索のサポート、およびより堅牢な API 処理 | -| [opencode-dynamic-context-pruning](https://github.com/Tarquinen/opencode-dynamic-context-pruning) | 古いツールの出力を削除してトークンの使用を最適化する | -| [opencode-websearch-cited](https://github.com/ghoulr/opencode-websearch-cited.git) | Google ベースのスタイルでサポートされているプロバイダーにネイティブ Web 検索サポートを追加 | -| [opencode-pty](https://github.com/shekohex/opencode-pty.git) | AI エージェントが PTY でバックグラウンドプロセスを実行し、インタラクティブな入力を送信できるようにします。 | -| [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | 非対話型シェルコマンドの手順 - TTY に依存する操作によるハングの防止 | -| [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | wakatime で OpenCode の使用状況を追跡する | -| [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | LLM によって生成された Markdown テーブルをクリーンアップする | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | Morph Fast apply API と遅延編集マーカーにより 10 倍高速なコード編集 | -| [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | バックグラウンドエージェント、事前構築された LSP/AST/MCP ツール、厳選されたエージェント、Claude Code 互換 | -| [opencode-notificator](https://github.com/panta82/opencode-notificator) | OpenCode セッションのデスクトップ通知とサウンドアラート | -| [opencode-notifier](https://github.com/mohak34/opencode-notifier) | 許可、完了、エラーイベントのデスクトップ通知とサウンドアラート | -| [opencode-zellij-namer](https://github.com/24601/opencode-zellij-namer) | OpenCode コンテキストに基づいた AI による自動 Zellij セッション命名 | -| [opencode-skillful](https://github.com/zenobi-us/opencode-skillful) | OpenCode エージェントがスキルの検出と挿入を使用してオンデマンドでプロンプトを遅延ロードできるようにする | -| [opencode-supermemory](https://github.com/supermemoryai/opencode-supermemory) | スーパーメモリを使用したセッション間での永続メモリ | -| [@plannotator/opencode](https://github.com/backnotprop/plannotator/tree/main/apps/opencode-plugin) | 視覚的な注釈とプライベート/オフライン共有による対話型の計画レビュー | -| [@openspoon/subtask2](https://github.com/spoons-and-mirrors/subtask2) | OpenCode/コマンドをきめ細かいフロー制御を備えた強力なオーケストレーションシステムに拡張 | -| [opencode-scheduler](https://github.com/different-ai/opencode-scheduler) | launchd (Mac) または systemd (Linux) を cron 構文で使用して、定期的なジョブをスケジュールする | -| [micode](https://github.com/vtemian/micode) | 構造化されたブレインストーミング → 計画 → セッション継続性のあるワークフローの実装 | -| [octto](https://github.com/vtemian/octto) | 複数の質問フォームを使用した AI ブレインストーミング用のインタラクティブなブラウザ UI | -| [opencode-background-agents](https://github.com/kdcokenny/opencode-background-agents) | 非同期委任とコンテキスト永続性を備えた Claude Code スタイルのバックグラウンドエージェント | -| [opencode-notify](https://github.com/kdcokenny/opencode-notify) | OpenCode のネイティブ OS 通知 – タスクがいつ完了したかを知る | -| [opencode-workspace](https://github.com/kdcokenny/opencode-workspace) | バンドルされたマルチエージェントオーケストレーションハーネス – 16 コンポーネント、1 回のインストール | -| [opencode-worktree](https://github.com/kdcokenny/opencode-worktree) | OpenCode 用のゼロフリクション Git ワークツリー | +| 名前 | 説明 | +| -------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | +| [opencode-daytona](https://github.com/daytonaio/daytona/tree/main/libs/opencode-plugin) | git sync とライブプレビューを使用して、隔離された Daytona サンドボックスで OpenCode セッションを自動的に実行します。 | +| [opencode-helicone-session](https://github.com/H2Shami/opencode-helicone-session) | リクエストのグループ化のために Helicone セッションヘッダーを自動的に挿入する | +| [opencode-type-inject](https://github.com/nick-vi/opencode-type-inject) | ルックアップツールを使用して TypeScript/Svelte 型をファイル読み取りに自動挿入する | +| [opencode-openai-codex-auth](https://github.com/numman-ali/opencode-openai-codex-auth) | API クレジットの代わりに ChatGPT Plus/Pro サブスクリプションを使用する | +| [opencode-gemini-auth](https://github.com/jenslys/opencode-gemini-auth) | API 課金の代わりに既存の Gemini プランを使用する | +| [opencode-antigravity-auth](https://github.com/NoeFabris/opencode-antigravity-auth) | API 課金の代わりに Antigravity の無料モデルを使用する | +| [opencode-devcontainers](https://github.com/athal7/opencode-devcontainers) | 浅いクローンと自動割り当てポートを使用したマルチブランチ devcontainer の分離 | +| [opencode-google-antigravity-auth](https://github.com/shekohex/opencode-google-antigravity-auth) | Google Antigravity OAuth プラグイン、Google 検索のサポート、およびより堅牢な API 処理 | +| [opencode-dynamic-context-pruning](https://github.com/Tarquinen/opencode-dynamic-context-pruning) | 古いツールの出力を削除してトークンの使用を最適化する | +| [opencode-vibeguard](https://github.com/inkdust2021/opencode-vibeguard) | LLM 呼び出しの前にシークレット/PII を VibeGuard スタイルのプレースホルダーに編集し、ローカルで復元する | +| [opencode-websearch-cited](https://github.com/ghoulr/opencode-websearch-cited.git) | Google ベースのスタイルでサポートされているプロバイダーにネイティブ Web 検索サポートを追加 | +| [opencode-pty](https://github.com/shekohex/opencode-pty.git) | AI エージェントが PTY でバックグラウンドプロセスを実行し、インタラクティブな入力を送信できるようにします。 | +| [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | 非対話型シェルコマンドの手順 - TTY に依存する操作によるハングの防止 | +| [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | wakatime で OpenCode の使用状況を追跡する | +| [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | LLM によって生成された Markdown テーブルをクリーンアップする | +| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | Morph Fast apply API と遅延編集マーカーにより 10 倍高速なコード編集 | +| [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | バックグラウンドエージェント、事前構築された LSP/AST/MCP ツール、厳選されたエージェント、Claude Code 互換 | +| [opencode-notificator](https://github.com/panta82/opencode-notificator) | OpenCode セッションのデスクトップ通知とサウンドアラート | +| [opencode-notifier](https://github.com/mohak34/opencode-notifier) | 許可、完了、エラーイベントのデスクトップ通知とサウンドアラート | +| [opencode-zellij-namer](https://github.com/24601/opencode-zellij-namer) | OpenCode コンテキストに基づいた AI による自動 Zellij セッション命名 | +| [opencode-skillful](https://github.com/zenobi-us/opencode-skillful) | OpenCode エージェントがスキルの検出と挿入を使用してオンデマンドでプロンプトを遅延ロードできるようにする | +| [opencode-supermemory](https://github.com/supermemoryai/opencode-supermemory) | スーパーメモリを使用したセッション間での永続メモリ | +| [@plannotator/opencode](https://github.com/backnotprop/plannotator/tree/main/apps/opencode-plugin) | 視覚的な注釈とプライベート/オフライン共有による対話型の計画レビュー | +| [@openspoon/subtask2](https://github.com/spoons-and-mirrors/subtask2) | OpenCode/コマンドをきめ細かいフロー制御を備えた強力なオーケストレーションシステムに拡張 | +| [opencode-scheduler](https://github.com/different-ai/opencode-scheduler) | launchd (Mac) または systemd (Linux) を cron 構文で使用して、定期的なジョブをスケジュールする | +| [micode](https://github.com/vtemian/micode) | 構造化されたブレインストーミング → 計画 → セッション継続性のあるワークフローの実装 | +| [octto](https://github.com/vtemian/octto) | 複数の質問フォームを使用した AI ブレインストーミング用のインタラクティブなブラウザ UI | +| [opencode-background-agents](https://github.com/kdcokenny/opencode-background-agents) | 非同期委任とコンテキスト永続性を備えた Claude Code スタイルのバックグラウンドエージェント | +| [opencode-notify](https://github.com/kdcokenny/opencode-notify) | OpenCode のネイティブ OS 通知 – タスクがいつ完了したかを知る | +| [opencode-workspace](https://github.com/kdcokenny/opencode-workspace) | バンドルされたマルチエージェントオーケストレーションハーネス – 16 コンポーネント、1 回のインストール | +| [opencode-worktree](https://github.com/kdcokenny/opencode-worktree) | OpenCode 用のゼロフリクション Git ワークツリー | --- diff --git a/packages/web/src/content/docs/ja/formatters.mdx b/packages/web/src/content/docs/ja/formatters.mdx index 26bcbb5de5c..8769b9760d1 100644 --- a/packages/web/src/content/docs/ja/formatters.mdx +++ b/packages/web/src/content/docs/ja/formatters.mdx @@ -13,31 +13,32 @@ OpenCode には、一般的な言語およびフレームワーク用のいく | Formatter | Extensions | Requirements | | -------------------- | -------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | -| gofmt | .go | `gofmt` command available | -| mix | .ex, .exs, .eex, .heex, .leex, .neex, .sface | `mix` command available | -| prettier | .js, .jsx, .ts, .tsx, .html, .css, .md, .json, .yaml, and [more](https://prettier.io/docs/en/index.html) | `prettier` dependency in `package.json` | +| air | .R | `air` command available | | biome | .js, .jsx, .ts, .tsx, .html, .css, .md, .json, .yaml, and [more](https://biomejs.dev/) | `biome.json(c)` config file | -| zig | .zig, .zon | `zig` command available | -| clang-format | .c, .cpp, .h, .hpp, .ino, and [more](https://clang.llvm.org/docs/ClangFormat.html) | `.clang-format` config file | -| ktlint | .kt, .kts | `ktlint` command available | -| ruff | .py, .pyi | `ruff` command available with config | -| rustfmt | .rs | `rustfmt` command available | | cargofmt | .rs | `cargo fmt` command available | -| uv | .py, .pyi | `uv` command available | -| rubocop | .rb, .rake, .gemspec, .ru | `rubocop` command available | -| standardrb | .rb, .rake, .gemspec, .ru | `standardrb` command available | -| htmlbeautifier | .erb, .html.erb | `htmlbeautifier` command available | -| air | .R | `air` command available | +| clang-format | .c, .cpp, .h, .hpp, .ino, and [more](https://clang.llvm.org/docs/ClangFormat.html) | `.clang-format` config file | +| cljfmt | .clj, .cljs, .cljc, .edn | `cljfmt` command available | | dart | .dart | `dart` command available | | dfmt | .d | `dfmt` command available | -| ocamlformat | .ml, .mli | `ocamlformat` command available and `.ocamlformat` config file | -| terraform | .tf, .tfvars | `terraform` command available | | gleam | .gleam | `gleam` command available | +| gofmt | .go | `gofmt` command available | +| htmlbeautifier | .erb, .html.erb | `htmlbeautifier` command available | +| ktlint | .kt, .kts | `ktlint` command available | +| mix | .ex, .exs, .eex, .heex, .leex, .neex, .sface | `mix` command available | | nixfmt | .nix | `nixfmt` command available | -| shfmt | .sh, .bash | `shfmt` command available | -| pint | .php | `laravel/pint` dependency in `composer.json` | -| oxfmt (Experimental) | .js, .jsx, .ts, .tsx | `oxfmt` dependency in `package.json` and an [experimental env variable flag](/docs/cli/#experimental) | +| ocamlformat | .ml, .mli | `ocamlformat` command available and `.ocamlformat` config file | | ormolu | .hs | `ormolu` command available | +| oxfmt (Experimental) | .js, .jsx, .ts, .tsx | `oxfmt` dependency in `package.json` and an [experimental env variable flag](/docs/cli/#experimental) | +| pint | .php | `laravel/pint` dependency in `composer.json` | +| prettier | .js, .jsx, .ts, .tsx, .html, .css, .md, .json, .yaml, and [more](https://prettier.io/docs/en/index.html) | `prettier` dependency in `package.json` | +| rubocop | .rb, .rake, .gemspec, .ru | `rubocop` command available | +| ruff | .py, .pyi | `ruff` command available with config | +| rustfmt | .rs | `rustfmt` command available | +| shfmt | .sh, .bash | `shfmt` command available | +| standardrb | .rb, .rake, .gemspec, .ru | `standardrb` command available | +| terraform | .tf, .tfvars | `terraform` command available | +| uv | .py, .pyi | `uv` command available | +| zig | .zig, .zon | `zig` command available | したがって、プロジェクトの `prettier` に `package.json` が含まれている場合、OpenCode は自動的にそれを使用します。 diff --git a/packages/web/src/content/docs/ja/go.mdx b/packages/web/src/content/docs/ja/go.mdx new file mode 100644 index 00000000000..17ec4acb939 --- /dev/null +++ b/packages/web/src/content/docs/ja/go.mdx @@ -0,0 +1,145 @@ +--- +title: Go +description: オープンコーディングモデル向けの低価格サブスクリプション。 +--- + +import config from "../../../../config.mjs" +export const console = config.console +export const email = `mailto:${config.email}` + +OpenCode Goは、人気のあるオープンコーディングモデルへの信頼性の高いアクセスを提供する、低価格な**月額10ドル**のサブスクリプションです。 + +:::note +OpenCode Goは現在ベータ版です。 +::: + +GoはOpenCodeの他のプロバイダーと同様に機能します。OpenCode Goに登録してAPIキーを取得します。これは**完全にオプション**であり、OpenCodeを使用するために必須ではありません。 + +主に海外ユーザー向けに設計されており、安定したグローバルアクセスのためにモデルは米国、EU、シンガポールでホストされています。 + +--- + +## 背景 + +オープンモデルは非常に高性能になりました。現在では、コーディングタスクにおいてプロプライエタリモデルに近いパフォーマンスを発揮します。また、多くのプロバイダーが競争力のある価格で提供できるため、通常はずっと安価です。 + +しかし、信頼性が高く低遅延なアクセスを得ることは難しい場合があります。プロバイダーによって品質や可用性が異なるためです。 + +:::tip +OpenCodeとうまく連携する厳選されたモデルとプロバイダーをテストしました。 +::: + +これを解決するために、私たちはいくつかのことを行いました。 + +1. 厳選されたオープンモデルをテストし、それらを最適に実行する方法についてチームと話し合いました。 +2. 次に、いくつかのプロバイダーと協力して、これらが正しく提供されていることを確認しました。 +3. 最後に、モデルとプロバイダーの組み合わせをベンチマークし、自信を持って推奨できるリストを作成しました。 + +OpenCode Goでは、これらのモデルに**月額10ドル**でアクセスできます。 + +--- + +## 仕組み + +OpenCode GoはOpenCodeの他のプロバイダーと同様に機能します。 + +1. **OpenCode Zen**にサインインし、Goに登録してAPIキーをコピーします。 +2. TUIで`/connect`コマンドを実行し、`OpenCode Go`を選択してAPIキーを貼り付けます。 +3. TUIで`/models`を実行して、Go経由で利用可能なモデルのリストを確認します。 + +:::note +ワークスペースごとに1人のメンバーのみがOpenCode Goに登録できます。 +::: + +現在のモデルリストには以下が含まれます: + +- **GLM-5** +- **Kimi K2.5** +- **MiniMax M2.5** + +モデルのリストは、テストや新しいモデルの追加に伴い変更される可能性があります。 + +--- + +## 利用制限 + +OpenCode Goには以下の制限が含まれます: + +- **5時間制限** — 12ドル分の利用 +- **週間制限** — 30ドル分の利用 +- **月間制限** — 60ドル分の利用 + +制限は金額で定義されています。つまり、実際のリクエスト数は使用するモデルによって異なります。MiniMax M2.5のような安価なモデルではより多くのリクエストが可能ですが、GLM-5のような高価なモデルでは少なくなります。 + +下の表は、典型的なGoの使用パターンに基づいた推定リクエスト数を示しています: + +| | GLM-5 | Kimi K2.5 | MiniMax M2.5 | +| ------------------------- | ----- | --------- | ------------ | +| 5時間あたりのリクエスト数 | 1,150 | 1,850 | 30,000 | +| 週間リクエスト数 | 2,880 | 4,630 | 75,000 | +| 月間リクエスト数 | 5,750 | 9,250 | 150,000 | + +推定値は、観測された平均的なリクエストパターンに基づいています: + +- GLM-5 — 1リクエストあたり入力700、キャッシュ52,000、出力150トークン +- Kimi K2.5 — 1リクエストあたり入力870、キャッシュ55,000、出力200トークン +- MiniMax M2.5 — 1リクエストあたり入力300、キャッシュ55,000、出力125トークン + +現在の使用状況は**コンソール**で確認できます。 + +:::tip +利用制限に達した場合でも、無料モデルを引き続き使用できます。 +::: + +利用制限は、初期の使用状況やフィードバックに基づいて変更される可能性があります。 + +--- + +### 価格 + +OpenCode Goは**月額10ドル**のサブスクリプションプランです。以下は**100万トークンあたり**の価格です。 + +| モデル | 入力 | 出力 | キャッシュ読み込み | +| ------------ | ----- | ----- | ------------------ | +| GLM-5 | $1.00 | $3.20 | $0.20 | +| Kimi K2.5 | $0.60 | $3.00 | $0.10 | +| MiniMax M2.5 | $0.30 | $1.20 | $0.03 | + +--- + +### 制限を超えた利用 + +Zen残高にクレジットがある場合、コンソールで**残高を使用 (Use balance)**オプションを有効にできます。有効にすると、利用制限に達した後、リクエストをブロックする代わりにZen残高が使用されます。 + +--- + +## エンドポイント + +以下のAPIエンドポイントを通じてGoモデルにアクセスすることもできます。 + +| モデル | モデルID | エンドポイント | AI SDKパッケージ | +| ------------ | ------------ | ------------------------------------------------ | --------------------------- | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | + +OpenCode設定の[モデルID](/docs/config/#models)は、`opencode-go/`という形式を使用します。たとえば、Kimi K2.5の場合、設定で`opencode-go/kimi-k2.5`を使用します。 + +--- + +## プライバシー + +このプランは主に海外ユーザー向けに設計されており、安定したグローバルアクセスのためにモデルは米国、EU、シンガポールでホストされています。 + +ご質問がある場合はお問い合わせください。 + +--- + +## 目標 + +OpenCode Goを作成した目的は以下の通りです: + +1. 低価格のサブスクリプションで、より多くの人々がAIコーディングに**アクセス**できるようにすること。 +2. 最高のオープンコーディングモデルへの**信頼性の高い**アクセスを提供すること。 +3. コーディングエージェントでの使用向けに**テストおよびベンチマーク**されたモデルを厳選すること。 +4. OpenCodeで他のプロバイダーも使用できるようにすることで、**ロックインを排除**すること。 diff --git a/packages/web/src/content/docs/ja/keybinds.mdx b/packages/web/src/content/docs/ja/keybinds.mdx index 66466a7f72d..09a3b0d7cba 100644 --- a/packages/web/src/content/docs/ja/keybinds.mdx +++ b/packages/web/src/content/docs/ja/keybinds.mdx @@ -3,11 +3,11 @@ title: キーバインド description: キーバインドをカスタマイズします。 --- -OpenCode には、OpenCode 設定を通じてカスタマイズできるキーバインドのリストがあります。 +OpenCode には、`tui.json` を通じてカスタマイズできるキーバインドのリストがあります。 -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "keybinds": { "leader": "ctrl+x", "app_exit": "ctrl+c,ctrl+d,q", @@ -117,11 +117,11 @@ OpenCode は、ほとんどのキーバインドに `leader` キーを使用し ## キーバインドを無効にする -キーバインドを無効にするには、値「none」を指定してキーを構成に追加します。 +キーバインドを無効にするには、値「none」を指定してキーを `tui.json` に追加します。 -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "keybinds": { "session_compact": "none" } diff --git a/packages/web/src/content/docs/ja/lsp.mdx b/packages/web/src/content/docs/ja/lsp.mdx index 7b42c373327..948e743503f 100644 --- a/packages/web/src/content/docs/ja/lsp.mdx +++ b/packages/web/src/content/docs/ja/lsp.mdx @@ -27,6 +27,7 @@ OpenCode には、一般的な言語用のいくつかの組み込み LSP サー | gopls | .go | `go` command available | | hls | .hs, .lhs | `haskell-language-server-wrapper` command available | | jdtls | .java | `Java SDK (version 21+)` installed | +| julials | .jl | `julia` and `LanguageServer.jl` installed | | kotlin-ls | .kt, .kts | Auto-installs for Kotlin projects | | lua-ls | .lua | Auto-installs for Lua projects | | nixd | .nix | `nixd` command available | diff --git a/packages/web/src/content/docs/ja/mcp-servers.mdx b/packages/web/src/content/docs/ja/mcp-servers.mdx index a4b2485337e..3ac9edc811b 100644 --- a/packages/web/src/content/docs/ja/mcp-servers.mdx +++ b/packages/web/src/content/docs/ja/mcp-servers.mdx @@ -16,7 +16,8 @@ MCP サーバーを使用すると、コンテキストが追加されます。 :::tip MCP サーバーはコンテキストに追加されるため、どのサーバーを有効にするかには注意してください。 ::: -特定の MCP サーバーは、大量のトークンを追加する傾向があり、コンテキスト制限を簡単に超える可能性があります。 + +特定の MCP サーバー (GitHub MCP サーバーなど) は、大量のトークンを追加する傾向があり、コンテキスト制限を簡単に超える可能性があります。 --- @@ -157,7 +158,7 @@ use the mcp_everything tool to add the number 3 and 4 | `url` | 文字列 | Y | リモート MCP サーバーの URL。 | | `enabled` | ブール値 | | 起動時に MCP サーバーを有効または無効にします。 | | `headers` | オブジェクト | | リクエストとともに送信するヘッダー。 | -| `oauth` | オブジェクト | | OAuth 認証設定。以下の「OAuth](#oauth)」セクションを参照してください。 | +| `oauth` | オブジェクト | | OAuth 認証設定。以下の「[OAuth](#oauth)」セクションを参照してください。 | | `timeout` | 数値 | | MCP サーバーからツールを取得する際のタイムアウト (ミリ秒)。デフォルトは 5000 (5 秒) です。 | --- @@ -391,6 +392,8 @@ MCP サーバーツールはサーバー名をプレフィックスとして登 ::: +--- + ## 例 以下に、一般的な MCP サーバーの例をいくつか示します。他のサーバーを文書化したい場合は、PR を送信できます。 @@ -506,471 +509,3 @@ What's the right way to set a custom domain in an SST Astro component? use the g ```md title="AGENTS.md" If you are unsure how to do something, use `gh_grep` to search code examples from GitHub. ``` - -`enabled` を `false` に設定してサーバーを無効にすることもできます。これは、サーバーを構成から削除せずに一時的に無効にする場合に便利です。 - ---- - -### リモートのデフォルトを上書きする - -組織は、`.well-known/opencode` エンドポイント経由でデフォルトの MCP サーバーを提供できます。これらのサーバーはデフォルトで無効になっている場合があり、ユーザーは必要なサーバーにオプトインできます。 - -組織のリモート構成から特定のサーバーを有効にするには、`enabled: true` を使用してローカル構成に追加します。 - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "mcp": { - "jira": { - "type": "remote", - "url": "https://jira.example.com/mcp", - "enabled": true - } - } -} -``` - -ローカルの設定値はリモートのデフォルト値をオーバーライドします。詳細については、「config precedence](/docs/config#precedence-order)」を参照してください。 - ---- - -## ローカル - -MCP オブジェクト内の `type` から `"local"` を使用してローカル MCP サーバーを追加します。 - -```jsonc title="opencode.jsonc" {15} -{ - "$schema": "https://opencode.ai/config.json", - "mcp": { - "my-local-mcp-server": { - "type": "local", - // Or ["bun", "x", "my-mcp-command"] - "command": ["npx", "-y", "my-mcp-command"], - "enabled": true, - "environment": { - "MY_ENV_VAR": "my_env_var_value", - }, - }, - }, -} -``` - -このコマンドは、ローカル MCP サーバーの起動方法を示します。環境変数のリストを渡すこともできます。 - -たとえば、テスト [`@modelcontextprotocol/server-everything`](https://www.npmjs.com/package/@modelcontextprotocol/server-everything) MCP サーバー] を追加する方法は次のとおりです。 - -```jsonc title="opencode.jsonc" -{ - "$schema": "https://opencode.ai/config.json", - "mcp": { - "mcp_everything": { - "type": "local", - "command": ["npx", "-y", "@modelcontextprotocol/server-everything"], - }, - }, -} -``` - -これを使用するには、プロンプトに `use the mcp_everything tool` を追加します。 - -```txt "mcp_everything" -use the mcp_everything tool to add the number 3 and 4 -``` - ---- - -#### オプション - -ここでは、ローカル MCP サーバーを構成するためのすべてのオプションを示します。 - -| オプション | タイプ | 必須 | 説明 | -| ------------- | ------------ | ---- | ------------------------------------------------------------------------------------------ | -| `type` | 文字列 | Y | MCP サーバー接続のタイプは、`"local"` である必要があります。 | -| `command` | 配列 | Y | MCP サーバーを実行するためのコマンドと引数。 | -| `environment` | オブジェクト | | サーバーの実行時に設定する環境変数。 | -| `enabled` | ブール値 | | 起動時に MCP サーバーを有効または無効にします。 | -| `timeout` | 番号 | | MCP サーバーからツールを取得する際のタイムアウト (ミリ秒)。デフォルトは 5000 (5 秒) です。 | - ---- - -## リモート - -`type` を `"remote"` に設定して、リモート MCP サーバーを追加します。 - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "mcp": { - "my-remote-mcp": { - "type": "remote", - "url": "https://my-mcp-server.com", - "enabled": true, - "headers": { - "Authorization": "Bearer MY_API_KEY" - } - } - } -} -``` - -`url` はリモート MCP サーバーの URL で、`headers` オプションを使用するとヘッダーのリストを渡すことができます。 - ---- - -#### オプション - -| オプション | タイプ | 必須 | 説明 | -| ---------- | ------------ | ---- | ------------------------------------------------------------------------------------------ | -| `type` | 文字列 | Y | MCP サーバー接続のタイプは、`"remote"` である必要があります。 | -| `url` | 文字列 | Y | リモート MCP サーバーの URL。 | -| `enabled` | ブール値 | | 起動時に MCP サーバーを有効または無効にします。 | -| `headers` | オブジェクト | | リクエストとともに送信するヘッダー。 | -| `oauth` | オブジェクト | | OAuth認証構成。以下の「OAuth](#oauth)」セクションを参照してください。 | -| `timeout` | 番号 | | MCP サーバーからツールを取得する際のタイムアウト (ミリ秒)。デフォルトは 5000 (5 秒) です。 | - ---- - -## OAuth - -OpenCode は、リモート MCP サーバーの OAuth 認証を自動的に処理します。サーバーが認証を必要とする場合、OpenCode は次のことを行います。 - -1. 401 応答を検出し、OAuth フローを開始します。 -2. サーバーでサポートされている場合は **動的クライアント登録 (RFC 7591)** を使用します -3. 今後のリクエストに備えてトークンを安全に保管する - ---- - -### 自動 - -ほとんどの OAuth 対応 MCP サーバーでは、特別な構成は必要ありません。リモートサーバーを設定するだけです。 - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "mcp": { - "my-oauth-server": { - "type": "remote", - "url": "https://mcp.example.com/mcp" - } - } -} -``` - -サーバーが認証を必要とする場合、OpenCode を初めて使用しようとすると、認証を求めるプロンプトが表示されます。そうでない場合は、`opencode mcp auth ` を使用して flow](#authenticating) を手動でトリガーできます。 - ---- - -### 事前登録済み - -MCP サーバープロバイダーからクライアント認証情報を取得している場合は、それらを構成できます。 - -```json title="opencode.json" {7-11} -{ - "$schema": "https://opencode.ai/config.json", - "mcp": { - "my-oauth-server": { - "type": "remote", - "url": "https://mcp.example.com/mcp", - "oauth": { - "clientId": "{env:MY_MCP_CLIENT_ID}", - "clientSecret": "{env:MY_MCP_CLIENT_SECRET}", - "scope": "tools:read tools:execute" - } - } - } -} -``` - ---- - -### 認証中 - -手動で認証をトリガーしたり、資格情報を管理したりできます。 - -特定の MCP サーバーで認証します。 - -```bash -opencode mcp auth my-oauth-server -``` - -すべての MCP サーバーとその認証ステータスをリストします。 - -```bash -opencode mcp list -``` - -保存されている認証情報を削除します。 - -```bash -opencode mcp logout my-oauth-server -``` - -`mcp auth` コマンドは、認証のためにブラウザを開きます。承認後、OpenCode はトークンを `~/.local/share/opencode/mcp-auth.json` に安全に保存します。 - ---- - -#### OAuthの無効化 - -サーバー (代わりに API キーを使用するサーバーなど) の自動 OAuth を無効にする場合は、`oauth` を `false` に設定します。 - -```json title="opencode.json" {7} -{ - "$schema": "https://opencode.ai/config.json", - "mcp": { - "my-api-key-server": { - "type": "remote", - "url": "https://mcp.example.com/mcp", - "oauth": false, - "headers": { - "Authorization": "Bearer {env:MY_API_KEY}" - } - } - } -} -``` - ---- - -#### OAuth オプション - -| オプション | タイプ | 説明 | -| -------------- | --------------- | -------------------------------------------------------------------------------- | -| `oauth` | Object \| false | OAuth config object, or `false` to disable OAuth auto-detection. | -| `clientId` | String | OAuth client ID. If not provided, dynamic client registration will be attempted. | -| `clientSecret` | String | OAuth client secret, if required by the authorization server. | -| `scope` | String | OAuth scopes to request during authorization. | - -#### デバッグ - -リモート MCP サーバーが認証に失敗した場合は、次の方法で問題を診断できます。 - -```bash -# View auth status for all OAuth-capable servers -opencode mcp auth list - -# Debug connection and OAuth flow for a specific server -opencode mcp debug my-oauth-server -``` - -`mcp debug` コマンドは、現在の認証ステータスを表示し、HTTP 接続をテストし、OAuth 検出フローを試行します。 - ---- - -## 管理 - -MCP は、組み込みツールと並んで、OpenCode のツールとして利用できます。したがって、他のツールと同様に、OpenCode config を通じてそれらを管理できます。 - ---- - -### グローバル - -これは、それらをグローバルに有効または無効にできることを意味します。 - -```json title="opencode.json" {14} -{ - "$schema": "https://opencode.ai/config.json", - "mcp": { - "my-mcp-foo": { - "type": "local", - "command": ["bun", "x", "my-mcp-command-foo"] - }, - "my-mcp-bar": { - "type": "local", - "command": ["bun", "x", "my-mcp-command-bar"] - } - }, - "tools": { - "my-mcp-foo": false - } -} -``` - -グロブ パターンを使用して、一致するすべての MCP を無効にすることもできます。 - -```json title="opencode.json" {14} -{ - "$schema": "https://opencode.ai/config.json", - "mcp": { - "my-mcp-foo": { - "type": "local", - "command": ["bun", "x", "my-mcp-command-foo"] - }, - "my-mcp-bar": { - "type": "local", - "command": ["bun", "x", "my-mcp-command-bar"] - } - }, - "tools": { - "my-mcp*": false - } -} -``` - -ここでは、グロブ パターン `my-mcp*` を使用して、すべての MCP を無効にしています。 - ---- - -### エージェントごと - -多数の MCP サーバーがある場合は、エージェントごとにのみ有効にし、グローバルに無効にすることができます。これを行うには: - -1. ツールとしてグローバルに無効にします。 -2. [エージェント config](/docs/agents#tools) で、MCP サーバーをツールとして有効にします。 - -```json title="opencode.json" {11, 14-18} -{ - "$schema": "https://opencode.ai/config.json", - "mcp": { - "my-mcp": { - "type": "local", - "command": ["bun", "x", "my-mcp-command"], - "enabled": true - } - }, - "tools": { - "my-mcp*": false - }, - "agent": { - "my-agent": { - "tools": { - "my-mcp*": true - } - } - } -} -``` - ---- - -#### グロブパターン - -グロブ パターンでは、単純な正規表現のグロブ パターンを使用します。 - -- `*` は 0 個以上の任意の文字に一致します (例: `"my-mcp*"` は `my-mcp_search`、`my-mcp_list` などに一致します)。 -- `?` は 1 つの文字に正確に一致します -- 他のすべての文字は文字通り一致します - -:::note -MCP サーバー ツールはサーバー名をプレフィックスとして登録されているため、サーバーのすべてのツールを無効にするには、次のコマンドを使用するだけです。 - -``` -"mymcpservername_*": false -``` - -::: - -## 例 - -以下に、一般的な MCP サーバーの例をいくつか示します。他のサーバーを文書化したい場合は、PR を送信できます。 - ---- - -### Sentry - -[Sentry MCP サーバー ](https://mcp.sentry.dev) を追加して、Sentry プロジェクトや問題と対話します。 - -```json title="opencode.json" {4-8} -{ - "$schema": "https://opencode.ai/config.json", - "mcp": { - "sentry": { - "type": "remote", - "url": "https://mcp.sentry.dev/mcp", - "oauth": {} - } - } -} -``` - -構成を追加した後、Sentry で認証します。 - -```bash -opencode mcp auth sentry -``` - -これにより、ブラウザ ウィンドウが開き、OAuth フローが完了し、OpenCode が Sentry アカウントに接続されます。 - -認証が完了すると、プロンプトで Sentry ツールを使用して、問題、プロジェクト、エラー データをクエリできるようになります。 - -```txt "use sentry" -Show me the latest unresolved issues in my project. use sentry -``` - ---- - -### Context7 - -ドキュメントを検索するために [Context7 MCP server](https://github.com/upstash/context7) を追加します。 - -```json title="opencode.json" {4-7} -{ - "$schema": "https://opencode.ai/config.json", - "mcp": { - "context7": { - "type": "remote", - "url": "https://mcp.context7.com/mcp" - } - } -} -``` - -無料アカウントにサインアップしている場合は、API キーを使用して、より高いレート制限を取得できます。 - -```json title="opencode.json" {7-9} -{ - "$schema": "https://opencode.ai/config.json", - "mcp": { - "context7": { - "type": "remote", - "url": "https://mcp.context7.com/mcp", - "headers": { - "CONTEXT7_API_KEY": "{env:CONTEXT7_API_KEY}" - } - } - } -} -``` - -ここでは、`CONTEXT7_API_KEY` 環境変数が設定されていることを前提としています。 - -Context7 MCP サーバーを使用するには、プロンプトに `use context7` を追加します。 - -```txt "use context7" -Configure a Cloudflare Worker script to cache JSON API responses for five minutes. use context7 -``` - -あるいは、次のようなものを [AGENTS.md](/docs/rules/). - -```md title="AGENTS.md" -When you need to search docs, use `context7` tools. -``` - ---- - -### Grep by Vercel - -GitHub 上のコード スニペットを検索するには、[Grep by Vercel](https://grep.app) MCP サーバーを追加します。 - -```json title="opencode.json" {4-7} -{ - "$schema": "https://opencode.ai/config.json", - "mcp": { - "gh_grep": { - "type": "remote", - "url": "https://mcp.grep.app" - } - } -} -``` - -MCP サーバーに `gh_grep` という名前を付けたので、プロンプトに `use the gh_grep tool` を追加して、エージェントにそれを使用させることができます。 - -```txt "use the gh_grep tool" -What's the right way to set a custom domain in an SST Astro component? use the gh_grep tool -``` - -あるいは、次のようなものを [AGENTS.md](/docs/rules/). - -```md title="AGENTS.md" -If you are unsure how to do something, use `gh_grep` to search code examples from GitHub. -``` diff --git a/packages/web/src/content/docs/ja/plugins.mdx b/packages/web/src/content/docs/ja/plugins.mdx index 39fc1dda217..06a0dca9adf 100644 --- a/packages/web/src/content/docs/ja/plugins.mdx +++ b/packages/web/src/content/docs/ja/plugins.mdx @@ -235,11 +235,87 @@ export const NotificationPlugin = async ({ project, client, $, directory, worktr macOS 上で AppleScript を実行するために `osascript` を使用しています。ここでは通知を送信するために使用しています。 :::note -OpenCode デスクトップアプリを使用している場合は、応答の準備ができたとき、またはセッションエラーが発生したときにシステム通知を自動的に送信できます。 +組み込みツールと同じ名前のプラグインツールを使用すると、プラグインツールが優先されます。 ::: --- +### ロギング + +構造化ログには `console.log` の代わりに `client.app.log()` を使用します。 + +```ts title=".opencode/plugins/my-plugin.ts" +export const MyPlugin = async ({ client }) => { + await client.app.log({ + body: { + service: "my-plugin", + level: "info", + message: "Plugin initialized", + extra: { foo: "bar" }, + }, + }) +} +``` + +レベル: `debug`、`info`、`warn`、`error`。詳細については、[SDK ドキュメント](https://opencode.ai/docs/sdk) を参照してください。 + +--- + +### 圧縮フック + +セッションが圧縮されたときに含まれるコンテキストをカスタマイズします。 + +```ts title=".opencode/plugins/compaction.ts" +import type { Plugin } from "@opencode-ai/plugin" + +export const CompactionPlugin: Plugin = async (ctx) => { + return { + "experimental.session.compacting": async (input, output) => { + // Inject additional context into the compaction prompt + output.context.push(` +## Custom Context + +Include any state that should persist across compaction: +- Current task status +- Important decisions made +- Files being actively worked on +`) + }, + } +} +``` + +`experimental.session.compacting` フックは、LLM が継続概要を生成する前に起動します。これを使用して、デフォルトの圧縮プロンプトでは見逃されるドメイン固有のコンテキストを挿入します。 + +`output.prompt` を設定することで、圧縮プロンプトを完全に置き換えることもできます。 + +```ts title=".opencode/plugins/custom-compaction.ts" +import type { Plugin } from "@opencode-ai/plugin" + +export const CustomCompactionPlugin: Plugin = async (ctx) => { + return { + "experimental.session.compacting": async (input, output) => { + // Replace the entire compaction prompt + output.prompt = ` +You are generating a continuation prompt for a multi-agent swarm session. + +Summarize: +1. The current task and its status +2. Which files are being modified and by whom +3. Any blockers or dependencies between agents +4. The next steps to complete the work + +Format as a structured prompt that a new agent can use to resume work. +` + }, + } +} +``` + +`output.prompt` を設定すると、デフォルトの圧縮プロンプトが完全に置き換えられます。この場合、`output.context` 配列は無視されます。 + +--- + ### .env の保護 OpenCode が `.env` ファイルを読み取らないようにします。 diff --git a/packages/web/src/content/docs/ja/providers.mdx b/packages/web/src/content/docs/ja/providers.mdx index 2602f8ef22a..388dc8e41dc 100644 --- a/packages/web/src/content/docs/ja/providers.mdx +++ b/packages/web/src/content/docs/ja/providers.mdx @@ -57,7 +57,38 @@ OpenCode で適切に動作することがテストおよび検証されてい 初めての方は、OpenCode Zen から始めることをお勧めします。 ::: -1. TUI で `/connect` コマンドを実行し、opencode を選択して、[opencode.ai/auth](https://opencode.ai/auth) で認証します。 +1. TUI で `/connect` コマンドを実行し、`OpenCode Zen` を選択して、[opencode.ai/zen](https://opencode.ai/zen) にアクセスします。 + + ```txt + /connect + ``` + +2. サインインし、お支払いの詳細を追加し、API キーをコピーします。 + +3. API キーを貼り付けます。 + + ```txt + ┌ API key + │ + │ + └ enter + ``` + +4. TUI で `/models` を実行すると、推奨されるモデルのリストが表示されます。 + + ```txt + /models + ``` + +これは OpenCode の他のプロバイダーと同様に機能し、使用は完全にオプションです。 + +--- + +## OpenCode Go + +OpenCode Go は、OpenCode チームによって提供される、人気のあるオープンコーディングモデルへの信頼性の高いアクセスを提供する低コストのサブスクリプションプランです。これらは OpenCode でうまく機能することがテストおよび検証されています。 + +1. TUI で `/connect` コマンドを実行し、`OpenCode Go` を選択して、[opencode.ai/zen](https://opencode.ai/zen) にアクセスします。 ```txt /connect @@ -126,105 +157,111 @@ OpenCode で適切に動作することがテストおよび検証されてい OpenCode で Amazon Bedrock を使用するには: -1. Amazon Bedrock コンソールの **モデルカタログ** に移動してリクエストします。 - 必要なモデルにアクセスします。 +1. Amazon Bedrock コンソールの **モデルカタログ** に移動し、必要なモデルへのアクセスをリクエストします。 -:::tip -Amazon Bedrock で必要なモデルにアクセスできる必要があります。 -::: + :::tip + Amazon Bedrock で必要なモデルにアクセスできる必要があります。 + ::: -2. **次のいずれかの方法を使用して認証を構成します**。 +2. **次のいずれかの方法を使用して認証を構成します**: + + *** #### 環境変数 (クイックスタート) -opencode の実行中に次の環境変数のいずれかを設定します。 + opencode の実行中に次の環境変数のいずれかを設定します。 -```bash - # Option 1: Using AWS access keys + ```bash + # オプション 1: AWS アクセスキーの使用 AWS_ACCESS_KEY_ID=XXX AWS_SECRET_ACCESS_KEY=YYY opencode - # Option 2: Using named AWS profile + # オプション 2: 名前付き AWS プロファイルの使用 AWS_PROFILE=my-profile opencode - # Option 3: Using Bedrock bearer token + # オプション 3: Bedrock ベアラートークンの使用 AWS_BEARER_TOKEN_BEDROCK=XXX opencode -``` + ``` -または、それらを bash プロファイルに追加します。 + または、それらを bash プロファイルに追加します。 -```bash title="~/.bash_profile" + ```bash title="~/.bash_profile" export AWS_PROFILE=my-dev-profile export AWS_REGION=us-east-1 -``` + ``` -#### 設定ファイル (推奨) + *** -プロジェクト固有の設定または永続的な設定の場合は、`opencode.json` を使用します。 + #### 設定ファイル (推奨) -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "provider": { - "amazon-bedrock": { - "options": { - "region": "us-east-1", - "profile": "my-aws-profile" - } - } - } -} -``` + プロジェクト固有の設定または永続的な設定の場合は、`opencode.json` を使用します。 -**利用可能なオプション:** + ```json title="opencode.json" + { + "$schema": "https://opencode.ai/config.json", + "provider": { + "amazon-bedrock": { + "options": { + "region": "us-east-1", + "profile": "my-aws-profile" + } + } + } + } + ``` -- `region` - AWS リージョン (例: `us-east-1`、`eu-west-1`) -- `profile` - `~/.aws/credentials` からの AWS 名前付きプロファイル -- `endpoint` - VPC エンドポイントのカスタムエンドポイント URL (汎用 `baseURL` オプションのエイリアス) + **利用可能なオプション:** + - `region` - AWS リージョン (例: `us-east-1`、`eu-west-1`) + - `profile` - `~/.aws/credentials` からの AWS 名前付きプロファイル + - `endpoint` - VPC エンドポイントのカスタムエンドポイント URL (汎用 `baseURL` オプションのエイリアス) -:::tip -設定ファイルのオプションは環境変数より優先されます。 -::: + :::tip + 設定ファイルのオプションは環境変数より優先されます。 + ::: -#### 上級: VPC エンドポイント + *** -Bedrock の VPC エンドポイントを使用している場合: + #### 上級: VPC エンドポイント -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "provider": { - "amazon-bedrock": { - "options": { - "region": "us-east-1", - "profile": "production", - "endpoint": "https://bedrock-runtime.us-east-1.vpce-xxxxx.amazonaws.com" - } - } - } -} -``` + Bedrock の VPC エンドポイントを使用している場合: -:::note -`endpoint` オプションは、AWS 固有の用語を使用した汎用の `baseURL` オプションのエイリアスです。 `endpoint` と `baseURL` の両方が指定された場合は、`endpoint` が優先されます。 -::: + ```json title="opencode.json" + { + "$schema": "https://opencode.ai/config.json", + "provider": { + "amazon-bedrock": { + "options": { + "region": "us-east-1", + "profile": "production", + "endpoint": "https://bedrock-runtime.us-east-1.vpce-xxxxx.amazonaws.com" + } + } + } + } + ``` -#### 認証方法 + :::note + `endpoint` オプションは、AWS 固有の用語を使用した汎用の `baseURL` オプションのエイリアスです。 `endpoint` と `baseURL` の両方が指定された場合は、`endpoint` が優先されます。 + ::: -- **`AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY`**: IAM ユーザーを作成し、AWS コンソールでアクセスキーを生成します。 -- **`AWS_PROFILE`**: `~/.aws/credentials` の名前付きプロファイルを使用します。最初に `aws configure --profile my-profile` または `aws sso login` を設定します -- **`AWS_BEARER_TOKEN_BEDROCK`**: Amazon Bedrock コンソールから長期 API キーを生成します -- **`AWS_WEB_IDENTITY_TOKEN_FILE` / `AWS_ROLE_ARN`**: EKS IRSA (サービスアカウントの IAM ロール) または OIDC フェデレーションを備えた他の Kubernetes 環境の場合。これらの環境変数は、サービスアカウントアノテーションを使用するときに Kubernetes によって自動的に挿入されます。 + *** -#### 認証の優先順位 + #### 認証方法 + - **`AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY`**: IAM ユーザーを作成し、AWS コンソールでアクセスキーを生成します + - **`AWS_PROFILE`**: `~/.aws/credentials` の名前付きプロファイルを使用します。最初に `aws configure --profile my-profile` または `aws sso login` を設定します + - **`AWS_BEARER_TOKEN_BEDROCK`**: Amazon Bedrock コンソールから長期 API キーを生成します + - **`AWS_WEB_IDENTITY_TOKEN_FILE` / `AWS_ROLE_ARN`**: EKS IRSA (サービスアカウントの IAM ロール) または OIDC フェデレーションを備えた他の Kubernetes 環境の場合。これらの環境変数は、サービスアカウントアノテーションを使用するときに Kubernetes によって自動的に挿入されます。 -Amazon Bedrock は次の認証優先度を使用します。 + *** -1. **ベアラー トークン** - `AWS_BEARER_TOKEN_BEDROCK` 環境変数または `/connect` コマンドからのトークン -2. **AWS 認証情報チェーン** - プロファイル、アクセスキー、共有認証情報、IAM ロール、Web ID トークン (EKS IRSA)、インスタンスメタデータ + #### 認証の優先順位 -:::note -ベアラー トークンが (`/connect` または `AWS_BEARER_TOKEN_BEDROCK` 経由で) 設定されると、設定されたプロファイルを含むすべての AWS 認証情報方法よりも優先されます。 -::: + Amazon Bedrock は次の認証優先度を使用します。 + 1. **ベアラー トークン** - `AWS_BEARER_TOKEN_BEDROCK` 環境変数または `/connect` コマンドからのトークン + 2. **AWS 認証情報チェーン** - プロファイル、アクセスキー、共有認証情報、IAM ロール、Web ID トークン (EKS IRSA)、インスタンスメタデータ + + :::note + ベアラー トークンが (`/connect` または `AWS_BEARER_TOKEN_BEDROCK` 経由で) 設定されると、設定されたプロファイルを含むすべての AWS 認証情報方法よりも優先されます。 + ::: 3. `/models` コマンドを実行して、必要なモデルを選択します。 @@ -234,6 +271,7 @@ Amazon Bedrock は次の認証優先度を使用します。 :::note カスタム推論プロファイルの場合、キーでモデルとプロバイダー名を使用し、`id` プロパティを arn に設定します。これにより、正しいキャッシュが保証されます。 +::: ```json title="opencode.json" { @@ -251,8 +289,6 @@ Amazon Bedrock は次の認証優先度を使用します。 } ``` -::: - --- ### Anthropic @@ -672,9 +708,42 @@ GitLab Duo は、GitLab の Anthropic プロキシを介したネイティブツ 6. `/models` コマンドを実行して、利用可能なモデルを確認します。 - ```txt - /models - ``` +```txt +/models +``` + +--- + +### STACKIT + +STACKIT AI Model Serving は、Llama、Mistral、Qwen などの LLM に焦点を当て、ヨーロッパのインフラストラクチャでの最大限のデータ主権を備えた、AI モデル用の完全に管理された主権ホスティング環境を提供します。 + +1. [STACKIT Portal](https://portal.stackit.cloud) に移動し、**AI Model Serving** に移動して、プロジェクトの認証トークンを作成します。 + + :::tip + 認証トークンを作成する前に、STACKIT 顧客アカウント、ユーザーアカウント、およびプロジェクトが必要です。 + ::: + +2. `/connect` コマンドを実行し、**STACKIT** を検索します。 + + ```txt + /connect + ``` + +3. STACKIT AI Model Serving 認証トークンを入力します。 + + ```txt + ┌ API key + │ + │ + └ enter + ``` + +4. `/models` コマンドを実行して、_Qwen3-VL 235B_ や _Llama 3.3 70B_ などの利用可能なモデルから選択します。 + + ```txt + /models + ``` 3 つの Claude ベースのモデルが利用可能です。 diff --git a/packages/web/src/content/docs/ja/sdk.mdx b/packages/web/src/content/docs/ja/sdk.mdx index ee5fd3645ac..5afaeb4a130 100644 --- a/packages/web/src/content/docs/ja/sdk.mdx +++ b/packages/web/src/content/docs/ja/sdk.mdx @@ -117,6 +117,78 @@ try { --- +## 構造化出力 + +`format` を JSON スキーマで指定することで、モデルから構造化された JSON 出力をリクエストできます。モデルは `StructuredOutput` ツールを使用して、スキーマに一致する検証済み JSON を返します。 + +### 基本的な使用法 + +```typescript +const result = await client.session.prompt({ + path: { id: sessionId }, + body: { + parts: [{ type: "text", text: "Research Anthropic and provide company info" }], + format: { + type: "json_schema", + schema: { + type: "object", + properties: { + company: { type: "string", description: "Company name" }, + founded: { type: "number", description: "Year founded" }, + products: { + type: "array", + items: { type: "string" }, + description: "Main products", + }, + }, + required: ["company", "founded"], + }, + }, + }, +}) + +// Access the structured output +console.log(result.data.info.structured_output) +// { company: "Anthropic", founded: 2021, products: ["Claude", "Claude API"] } +``` + +### 出力フォーマットの種類 + +| タイプ | 説明 | +| ------------- | ---------------------------------------------------- | +| `text` | デフォルト。標準テキスト応答 (構造化出力なし) | +| `json_schema` | 提供されたスキーマに一致する検証済み JSON を返します | + +### JSON スキーマフォーマット + +`type: 'json_schema'` を使用する場合は、以下を指定します: + +| フィールド | タイプ | 説明 | +| ------------ | --------------- | -------------------------------------------------- | +| `type` | `'json_schema'` | 必須。JSON スキーマモードを指定します | +| `schema` | `object` | 必須。出力構造を定義する JSON スキーマオブジェクト | +| `retryCount` | `number` | オプション。検証の再試行回数 (デフォルト: 2) | + +### エラー処理 + +すべての再試行後にモデルが有効な構造化出力を生成できない場合、応答には `StructuredOutputError` が含まれます: + +```typescript +if (result.data.info.error?.name === "StructuredOutputError") { + console.error("Failed to produce structured output:", result.data.info.error.message) + console.error("Attempts:", result.data.info.error.retries) +} +``` + +### ベストプラクティス + +1. **明確な説明を提供する**: モデルが抽出するデータを理解できるように、スキーマプロパティに明確な説明を提供します +2. **`required` を使用する**: 存在する必要があるフィールドを指定します +3. **スキーマを焦点を絞ったものにする**: 複雑なネストされたスキーマは、モデルが正しく入力するのが難しい場合があります +4. **適切な `retryCount` を設定する**: 複雑なスキーマの場合は増やし、単純なスキーマの場合は減らします + +--- + ## API SDK は、型安全なクライアントを通じてすべてのサーバー API を公開します。 @@ -226,27 +298,27 @@ const { providers, default: defaults } = await client.config.providers() ### Sessions -| メソッド | 説明 | 詳細 | -| ---------------------------------------------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `session.list()` | セッションをリストする | 戻り値 Session[] | -| `session.get({ path })` | セッションを取得 | 戻り値 Session | -| `session.children({ path })` | 子セッションをリストする | 戻り値 Session[] | -| `session.create({ body })` | セッションの作成 | 戻り値 Session | -| `session.delete({ path })` | セッションを削除 | 戻り値 `boolean` | -| `session.update({ path, body })` | セッションのプロパティを更新する | 戻り値 Session | -| `session.init({ path, body })` | アプリを分析して `AGENTS.md` を作成する | 戻り値 `boolean` | -| `session.abort({ path })` | 実行中のセッションを中止する | 戻り値 `boolean` | -| `session.share({ path })` | セッションを共有する | 戻り値 Session | -| `session.unshare({ path })` | セッションの共有を解除 | 戻り値 Session | -| `session.summarize({ path, body })` | セッションを要約する | 戻り値 `boolean` | -| `session.messages({ path })` | セッション内のメッセージをリストする | 戻り値 `{ info: `Message`, parts: `Part[]`}[]` | -| `session.message({ path })` | メッセージの詳細を取得する | 戻り値 `{ info: `Message`, parts: `Part[]`}` | -| `session.prompt({ path, body })` | プロンプトメッセージを送信する | `body.noReply: true` は UserMessage (コンテキストのみ) を返します。デフォルトでは、AI 応答を含む AssistantMessage を返します。 | -| `session.command({ path, body })` | コマンドをセッションに送信 | 戻り値 `{ info: `AssistantMessage`, parts: `Part[]`}` | -| `session.shell({ path, body })` | シェルコマンドを実行する | 戻り値 AssistantMessage | -| `session.revert({ path, body })` | メッセージを元に戻す | 戻り値 Session | -| `session.unrevert({ path })` | 元に戻したメッセージを復元する | 戻り値 Session | -| `postSessionByIdPermissionsByPermissionId({ path, body })` | 許可リクエストに応答する | 戻り値 `boolean` | +| メソッド | 説明 | 詳細 | +| ---------------------------------------------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `session.list()` | セッションをリストする | 戻り値 Session[] | +| `session.get({ path })` | セッションを取得 | 戻り値 Session | +| `session.children({ path })` | 子セッションをリストする | 戻り値 Session[] | +| `session.create({ body })` | セッションの作成 | 戻り値 Session | +| `session.delete({ path })` | セッションを削除 | 戻り値 `boolean` | +| `session.update({ path, body })` | セッションのプロパティを更新する | 戻り値 Session | +| `session.init({ path, body })` | アプリを分析して `AGENTS.md` を作成する | 戻り値 `boolean` | +| `session.abort({ path })` | 実行中のセッションを中止する | 戻り値 `boolean` | +| `session.share({ path })` | セッションを共有する | 戻り値 Session | +| `session.unshare({ path })` | セッションの共有を解除 | 戻り値 Session | +| `session.summarize({ path, body })` | セッションを要約する | 戻り値 `boolean` | +| `session.messages({ path })` | セッション内のメッセージをリストする | 戻り値 `{ info: `Message`, parts: `Part[]`}[]` | +| `session.message({ path })` | メッセージの詳細を取得する | 戻り値 `{ info: `Message`, parts: `Part[]`}` | +| `session.prompt({ path, body })` | プロンプトメッセージを送信する | `body.noReply: true` は UserMessage (コンテキストのみ) を返します。デフォルトでは、AI 応答を含む AssistantMessage を返します。[構造化出力](#構造化出力) のための `body.outputFormat` をサポートします。 | +| `session.command({ path, body })` | コマンドをセッションに送信 | 戻り値 `{ info: `AssistantMessage`, parts: `Part[]`}` | +| `session.shell({ path, body })` | シェルコマンドを実行する | 戻り値 AssistantMessage | +| `session.revert({ path, body })` | メッセージを元に戻す | 戻り値 Session | +| `session.unrevert({ path })` | 元に戻したメッセージを復元する | 戻り値 Session | +| `postSessionByIdPermissionsByPermissionId({ path, body })` | 許可リクエストに応答する | 戻り値 `boolean` | --- diff --git a/packages/web/src/content/docs/ja/themes.mdx b/packages/web/src/content/docs/ja/themes.mdx index dd76651d6a5..1e4bf421805 100644 --- a/packages/web/src/content/docs/ja/themes.mdx +++ b/packages/web/src/content/docs/ja/themes.mdx @@ -61,11 +61,11 @@ OpenCode にはいくつかの組み込みテーマが付属しています。 ## テーマの使用 -テーマを選択するには、`/theme` コマンドでテーマ選択を表示します。または、[config](/docs/config) で設定します。 +テーマを選択するには、`/theme` コマンドでテーマ選択を表示します。または、`tui.json` で指定することもできます。 -```json title="opencode.json" {3} +```json title="tui.json" {3} { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "theme": "tokyonight" } ``` diff --git a/packages/web/src/content/docs/ja/tui.mdx b/packages/web/src/content/docs/ja/tui.mdx index 36c94fffcf7..0049c7b0147 100644 --- a/packages/web/src/content/docs/ja/tui.mdx +++ b/packages/web/src/content/docs/ja/tui.mdx @@ -349,24 +349,34 @@ VS Code などの一部のエディターは、`--wait` フラグを使用して ## 設定 -OpenCode 設定ファイルを通じて TUI の動作をカスタマイズできます。 +`tui.json` (または `tui.jsonc`) ファイルを通じて TUI の動作をカスタマイズできます。 -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", - "tui": { - "scroll_speed": 3, - "scroll_acceleration": { - "enabled": true - } - } + "$schema": "https://opencode.ai/tui.json", + "theme": "opencode", + "keybinds": { + "leader": "ctrl+x" + }, + "scroll_speed": 3, + "scroll_acceleration": { + "enabled": true + }, + "diff_style": "auto" } ``` +これは、サーバー/ランタイムの動作を構成する `opencode.json` とは別です。 + ### オプション -- `scroll_acceleration` - macOS スタイルのスクロールアクセラレーションを有効にして、スムーズで自然なスクロールを実現します。有効にすると、高速スクロールジェスチャではスクロール速度が向上し、ゆっくりとした動きでは正確なままになります。 **この設定は `scroll_speed` よりも優先され、有効になっている場合は上書きされます。** -- `scroll_speed` - スクロールコマンドを使用するときに TUI がスクロールする速度を制御します (最小: `1`)。デフォルトは `3` です。 **注: `scroll_acceleration.enabled` が `true` に設定されている場合、これは無視されます。** +- `theme` - UI テーマを設定します。[詳細はこちら](/docs/themes)。 +- `keybinds` - キーボードショートカットをカスタマイズします。[詳細はこちら](/docs/keybinds)。 +- `scroll_acceleration.enabled` - macOS スタイルのスクロールアクセラレーションを有効にして、スムーズで自然なスクロールを実現します。有効にすると、高速スクロールジェスチャではスクロール速度が向上し、ゆっくりとした動きでは正確なままになります。 **この設定は `scroll_speed` よりも優先され、有効になっている場合は上書きされます。** +- `scroll_speed` - スクロールコマンドを使用するときに TUI がスクロールする速度を制御します (最小: `0.001`、小数をサポート)。デフォルトは `3` です。 **注: `scroll_acceleration.enabled` が `true` に設定されている場合、これは無視されます。** +- `diff_style` - 差分レンダリングを制御します。 `"auto"` はターミナルの幅に適応し、`"stacked"` は常に 1 列のレイアウトを表示します。 + +カスタム TUI 設定パスをロードするには、`OPENCODE_TUI_CONFIG` を使用します。 --- diff --git a/packages/web/src/content/docs/ja/zen.mdx b/packages/web/src/content/docs/ja/zen.mdx index 751ca45e45d..c7121fb3b79 100644 --- a/packages/web/src/content/docs/ja/zen.mdx +++ b/packages/web/src/content/docs/ja/zen.mdx @@ -18,23 +18,19 @@ Zen は OpenCode の他のプロバイダーと同様に機能します。 OpenC ## 背景 -モデルはたくさんありますが、そのうちのほんの一部です -これらのモデルはコーディングエージェントとしてうまく機能します。さらに、ほとんどのプロバイダーは、 -構成が大きく異なります。したがって、まったく異なるパフォーマンスと品質が得られます。 +世の中には多数のモデルがありますが、コーディングエージェントとしてうまく機能するのはごく一部です。さらに、ほとんどのプロバイダーは構成が大きく異なるため、パフォーマンスと品質も大きく異なります。 :::tip -私たちは、OpenCode で適切に動作するモデルとプロバイダーの選択されたグループをテストしました。 +OpenCode で適切に動作する、厳選されたモデルとプロバイダーのグループをテストしました。 ::: -OpenRouter などを通じてモデルを使用している場合は、決してそうすることはできません。 -必要なモデルの最高のバージョンを入手しているかどうかを確認してください。 + +OpenRouter などを通じてモデルを使用している場合、必要なモデルの最高のバージョンを取得できているか確信が持てません。 これを修正するために、いくつかのことを行いました。 -1. 私たちは選択したモデルのグループをテストし、その方法についてチームと話し合いました。 - それらを実行するのが最善です。 +1. 選抜したモデルグループをテストし、それらを最適に実行する方法についてチームと話し合いました。 2. その後、いくつかのプロバイダーと協力して、これらが確実に提供されるようにしました。 -3. 最後に、モデルとプロバイダーの組み合わせをベンチマークし、次の結果を導き出しました。 - 私たちが自信を持ってお勧めするリストをご紹介します。 +3. 最後に、モデルとプロバイダーの組み合わせをベンチマークし、自信を持ってお勧めできるリストを作成しました。 OpenCode Zen は、これらのモデルへのアクセスを可能にする AI ゲートウェイです。 @@ -44,8 +40,7 @@ OpenCode Zen は、これらのモデルへのアクセスを可能にする AI OpenCode Zen は、OpenCode の他のプロバイダーと同様に機能します。 -1. **OpenCode Zen** にログインし、請求内容を追加します - 詳細を確認し、API キーをコピーします。 +1. **OpenCode Zen** にサインインし、請求情報を追加して、API キーをコピーします。 2. TUI で `/connect` コマンドを実行し、OpenCode Zen を選択して API キーを貼り付けます。 3. TUI で `/models` を実行すると、推奨されるモデルのリストが表示されます。 @@ -59,6 +54,7 @@ OpenCode Zen は、OpenCode の他のプロバイダーと同様に機能しま | Model | Model ID | Endpoint | AI SDK Package | | ------------------ | ------------------ | -------------------------------------------------- | --------------------------- | +| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | @@ -68,22 +64,24 @@ OpenCode Zen は、OpenCode の他のプロバイダーと同様に機能しま | GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | | Gemini 3 Pro | gemini-3-pro | `https://opencode.ai/zen/v1/models/gemini-3-pro` | `@ai-sdk/google` | | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.1 | minimax-m2.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.1 Free | minimax-m2.1-free | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| GLM 5 | glm-5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 4.7 | glm-4.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM 4.7 Free | glm-4.7-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 4.6 | glm-4.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 Free | kimi-k2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2 Thinking | kimi-k2-thinking | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2 | kimi-k2 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Qwen3 Coder 480B | qwen3-coder | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -112,29 +110,34 @@ https://opencode.ai/zen/v1/models | Model | Input | Output | Cached Read | Cached Write | | --------------------------------- | ------ | ------ | ----------- | ------------ | | Big Pickle | Free | Free | Free | - | -| MiniMax M2.1 Free | Free | Free | Free | - | +| MiniMax M2.5 Free | Free | Free | Free | - | +| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | - | | MiniMax M2.1 | $0.30 | $1.20 | $0.10 | - | -| GLM 4.7 Free | Free | Free | Free | - | +| GLM 5 | $1.00 | $3.20 | $0.20 | - | | GLM 4.7 | $0.60 | $2.20 | $0.10 | - | | GLM 4.6 | $0.60 | $2.20 | $0.10 | - | -| Kimi K2.5 Free | Free | Free | Free | - | | Kimi K2.5 | $0.60 | $3.00 | $0.08 | - | | Kimi K2 Thinking | $0.40 | $2.50 | - | - | | Kimi K2 | $0.40 | $2.50 | - | - | | Qwen3 Coder 480B | $0.45 | $1.50 | - | - | +| Claude Opus 4.6 (≤ 200K tokens) | $5.00 | $25.00 | $0.50 | $6.25 | +| Claude Opus 4.6 (> 200K tokens) | $10.00 | $37.50 | $1.00 | $12.50 | +| Claude Opus 4.5 | $5.00 | $25.00 | $0.50 | $6.25 | +| Claude Opus 4.1 | $15.00 | $75.00 | $1.50 | $18.75 | +| Claude Sonnet 4.6 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 | +| Claude Sonnet 4.6 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 | | Claude Sonnet 4.5 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 | | Claude Sonnet 4.5 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 | | Claude Sonnet 4 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 | | Claude Sonnet 4 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 | | Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 | | Claude Haiku 3.5 | $0.80 | $4.00 | $0.08 | $1.00 | -| Claude Opus 4.6 (≤ 200K tokens) | $5.00 | $25.00 | $0.50 | $6.25 | -| Claude Opus 4.6 (> 200K tokens) | $10.00 | $37.50 | $1.00 | $12.50 | -| Claude Opus 4.5 | $5.00 | $25.00 | $0.50 | $6.25 | -| Claude Opus 4.1 | $15.00 | $75.00 | $1.50 | $18.75 | +| Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | +| Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | +| GPT 5.3 Codex | $1.75 | $14.00 | $0.175 | - | | GPT 5.2 | $1.75 | $14.00 | $0.175 | - | | GPT 5.2 Codex | $1.75 | $14.00 | $0.175 | - | | GPT 5.1 | $1.07 | $8.50 | $0.107 | - | @@ -151,10 +154,10 @@ https://opencode.ai/zen/v1/models クレジットカード手数料は実費で引き継がれます (4.4% + 取引ごとに 0.30 ドル)。それ以上の料金はかかりません。 ::: -- GLM 4.7 Free は期間限定で OpenCode で入手できます。チームはこの時間を利用してフィードバックを収集し、モデルを改善します。 -- Kim K2.5 Free は OpenCode で期間限定で利用可能です。チームはこの時間を利用してフィードバックを収集し、モデルを改善します。 -- MiniMax M2.1 Free は期間限定で OpenCode で入手できます。チームはこの時間を利用してフィードバックを収集し、モデルを改善します。 -- Big Pickle は、期間限定で OpenCode で無料で利用できるステルスモデルです。チームはこの時間を利用してフィードバックを収集し、モデルを改善します。 +無料のモデル: + +- MiniMax M2.5 Free は期間限定で OpenCode で利用可能です。チームはこの期間を利用してフィードバックを収集し、モデルを改善します。 +- Big Pickle は、期間限定で OpenCode で無料で利用できるステルスモデルです。チームはこの期間を利用してフィードバックを収集し、モデルを改善します。 ご質問がございましたら、お問い合わせください。 @@ -170,12 +173,9 @@ https://opencode.ai/zen/v1/models ### 月ごとの制限 -ワークスペース全体およびワークスペースごとに月ごとの使用制限を設定することもできます。 -あなたのチームのメンバー。 +ワークスペース全体およびチームの各メンバーの月ごとの使用制限を設定することもできます。 -たとえば、毎月の使用制限を 20 ドルに設定したとします。Zen は使用しません。 -月に20ドル以上。ただし、自動リロードを有効にしている場合、Zen が終了する可能性があります。 -残高が 5 ドルを下回ると、20 ドル以上の請求が行われます。 +たとえば、毎月の使用制限を 20 ドルに設定したとします。Zen は月に 20 ドル以上を使用しません。ただし、自動リロードを有効にしている場合、残高が 5 ドルを下回ると、Zen が 20 ドル以上の請求を行う可能性があります。 --- @@ -183,24 +183,22 @@ https://opencode.ai/zen/v1/models すべてのモデルは米国でホストされています。当社のプロバイダーはゼロ保持ポリシーに従い、次の例外を除いて、モデルのトレーニングにデータを使用しません。 -- Big Pickle: 無料期間中に、収集されたデータはモデルの改善に使用される場合があります。 -- GLM 4.7 無料: 無料期間中、収集されたデータはモデルを改善するために使用される場合があります。 -- Kimi K2.5 Free: 無料期間中、収集されたデータはモデルの改善に使用される場合があります。 -- MiniMax M2.1 無料: 無料期間中、収集されたデータはモデルを改善するために使用される場合があります。 -- OpenAI API: リクエストは [OpenAI のデータポリシー](https://platform.openai.com/docs/guides/your-data)に従います。 -- Anthropic API: リクエストは、[Anthropic のデータポリシー](https://docs.anthropic.com/en/docs/claude-code/data-usage)に従います。 +- Big Pickle: 無料期間中、収集されたデータはモデルの改善に使用される場合があります。 +- MiniMax M2.5 Free: 無料期間中、収集されたデータはモデルの改善に使用される場合があります。 +- OpenAI API: リクエストは [OpenAI のデータポリシー](https://platform.openai.com/docs/guides/your-data) に従い、30 日間保持されます。 +- Anthropic API: リクエストは [Anthropic のデータポリシー](https://docs.anthropic.com/en/docs/claude-code/data-usage) に従い、30 日間保持されます。 --- ## チーム向け -Zen はチームにも効果的です。チームメイトを招待し、役割を割り当て、キュレートすることができます -チームが使用するモデルなど。 +Zen はチームにも効果的です。チームメイトを招待し、役割を割り当て、チームが使用するモデルをキュレートすることなどができます。 :::note ワークスペースは現在、ベータ版の一部としてチームに無料で提供されています。 ::: -価格の詳細については近日中にお知らせします。 + +ワークスペースの管理は現在、ベータ版の一部としてチームに無料で提供されています。価格の詳細については近日中にお知らせします。 --- diff --git a/packages/web/src/content/docs/ko/config.mdx b/packages/web/src/content/docs/ko/config.mdx index e906eaf47b2..2f08824d699 100644 --- a/packages/web/src/content/docs/ko/config.mdx +++ b/packages/web/src/content/docs/ko/config.mdx @@ -14,10 +14,11 @@ OpenCode는 **JSON**과 **JSONC**(주석이 포함된 JSON) 형식을 모두 지 ```jsonc title="opencode.jsonc" { "$schema": "https://opencode.ai/config.json", - // Theme configuration - "theme": "opencode", "model": "anthropic/claude-sonnet-4-5", "autoupdate": true, + "server": { + "port": 4096, + }, } ``` @@ -33,7 +34,7 @@ config 파일은 **교체되지 않고 병합**됩니다. config 파일은 서로 대체되는 방식이 아니라 병합됩니다. 아래 config 위치의 설정이 결합되며, 충돌하는 key에 대해서만 나중에 로드된 config가 앞선 값을 override합니다. 충돌하지 않는 설정은 모두 유지됩니다. -예를 들어, 전역 config에 `theme: "opencode"`와 `autoupdate: true`가 있고 프로젝트 config에 `model: "anthropic/claude-sonnet-4-5"`가 있으면 최종 config에는 이 세 설정이 모두 포함됩니다. +예를 들어, 전역 config에 `autoupdate: true`가 있고 프로젝트 config에 `model: "anthropic/claude-sonnet-4-5"`가 있으면 최종 config에는 이 설정이 모두 포함됩니다. --- @@ -94,7 +95,9 @@ Remote config는 가장 먼저 로드되어 기본 레이어 역할을 합니다 ### Global -전역 OpenCode config는 `~/.config/opencode/opencode.json`에 두세요. theme, provider, keybind 같은 사용자 전체 기본 설정은 전역 config로 관리하세요. +전역 OpenCode config는 `~/.config/opencode/opencode.json`에 두세요. provider, model, permissions 같은 사용자 전체 기본 설정은 전역 config로 관리하세요. + +TUI 관련 설정은 `~/.config/opencode/tui.json`을 사용하세요. 전역 config는 조직의 Remote 기본값을 override합니다. @@ -104,6 +107,8 @@ Remote config는 가장 먼저 로드되어 기본 레이어 역할을 합니다 프로젝트 루트에 `opencode.json`을 추가하세요. 프로젝트 config는 표준 config 파일 중 우선순위가 가장 높아 전역 및 Remote config를 모두 override합니다. +프로젝트별 TUI 설정은 `tui.json`을 함께 추가하세요. + :::tip 프로젝트별 config는 프로젝트 루트에 두세요. ::: @@ -142,7 +147,9 @@ custom 디렉토리는 전역 config와 `.opencode` 디렉토리 뒤에 로드 ## Schema -config 파일의 schema는 [**`opencode.ai/config.json`**](https://opencode.ai/config.json)에 정의되어 있습니다. +server/runtime config schema는 [**`opencode.ai/config.json`**](https://opencode.ai/config.json)에 정의되어 있습니다. + +TUI config는 [**`opencode.ai/tui.json`**](https://opencode.ai/tui.json)을 사용합니다. 편집기에서 이 schema를 기반으로 validation과 autocomplete를 사용할 수 있습니다. @@ -150,28 +157,24 @@ config 파일의 schema는 [**`opencode.ai/config.json`**](https://opencode.ai/c ### TUI -`tui` 옵션으로 TUI 관련 설정을 구성할 수 있습니다. +TUI 관련 설정에는 전용 `tui.json` (또는 `tui.jsonc`) 파일을 사용하세요. -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", - "tui": { - "scroll_speed": 3, - "scroll_acceleration": { - "enabled": true - }, - "diff_style": "auto" - } + "$schema": "https://opencode.ai/tui.json", + "scroll_speed": 3, + "scroll_acceleration": { + "enabled": true + }, + "diff_style": "auto" } ``` -사용 가능한 옵션: +`OPENCODE_TUI_CONFIG`를 사용하여 사용자 지정 TUI 설정 파일을 가리킬 수 있습니다. -- `scroll_acceleration.enabled` - macOS 스타일 스크롤 가속을 활성화합니다. **`scroll_speed`보다 우선합니다.** -- `scroll_speed` - 사용자 정의 스크롤 속도 배수(기본: `3`, 최소: `1`). `scroll_acceleration.enabled`가 `true`이면 무시됩니다. -- `diff_style` - diff 렌더링 방식을 제어합니다. `"auto"`는 터미널 너비에 맞춰 조정되고, `"stacked"`는 항상 단일 컬럼으로 표시합니다. +`opencode.json`의 기존 `theme`, `keybinds`, `tui` 키는 더 이상 사용되지 않으며(deprecated) 가능한 경우 자동으로 마이그레이션됩니다. -[TUI에 대해 더 알아보기](/docs/tui). +[TUI 구성에 대해 더 알아보기](/docs/tui#configure). --- @@ -297,12 +300,12 @@ Bearer token(`AWS_BEARER_TOKEN_BEDROCK` 또는 `/connect`)은 profile 기반 인 ### Themes -`theme` 옵션으로 OpenCode config에서 사용할 theme를 설정할 수 있습니다. +`tui.json`에서 UI 테마를 설정하세요. -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", - "theme": "" + "$schema": "https://opencode.ai/tui.json", + "theme": "tokyonight" } ``` @@ -402,11 +405,11 @@ Bearer token(`AWS_BEARER_TOKEN_BEDROCK` 또는 `/connect`)은 profile 기반 인 ### Keybinds -`keybinds` 옵션으로 keybind를 커스터마이즈할 수 있습니다. +`tui.json`에서 단축키를 사용자 지정하세요. -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "keybinds": {} } ``` diff --git a/packages/web/src/content/docs/ko/custom-tools.mdx b/packages/web/src/content/docs/ko/custom-tools.mdx index 77310557faa..c90db16f1d7 100644 --- a/packages/web/src/content/docs/ko/custom-tools.mdx +++ b/packages/web/src/content/docs/ko/custom-tools.mdx @@ -79,6 +79,32 @@ export const multiply = tool({ --- +#### 기본 도구와 이름 충돌 + +커스텀 도구는 도구 이름으로 식별됩니다. 커스텀 도구가 기본 도구와 같은 이름을 사용하면 커스텀 도구가 우선순위를 갖습니다. + +예를 들어, 이 파일은 기본 `bash` 도구를 대체합니다: + +```ts title=".opencode/tools/bash.ts" +import { tool } from "@opencode-ai/plugin" + +export default tool({ + description: "Restricted bash wrapper", + args: { + command: tool.schema.string(), + }, + async execute(args) { + return `blocked: ${args.command}` + }, +}) +``` + +:::note +의도적으로 기본 도구를 대체하려는 경우가 아니라면 고유한 이름을 사용하는 것이 좋습니다. 도구를 오버라이드하지 않고 비활성화만 하려면 [permissions](/docs/permissions)를 사용하세요. +::: + +--- + ### 인자 인자 타입은 `tool.schema`로 정의할 수 있습니다. `tool.schema`는 [Zod](https://zod.dev) 기반입니다. diff --git a/packages/web/src/content/docs/ko/ecosystem.mdx b/packages/web/src/content/docs/ko/ecosystem.mdx index 9f6a8f9bcaa..c0f542da95a 100644 --- a/packages/web/src/content/docs/ko/ecosystem.mdx +++ b/packages/web/src/content/docs/ko/ecosystem.mdx @@ -15,38 +15,39 @@ OpenCode를 기반으로 만들어진 커뮤니티 프로젝트 모음입니다. ## 플러그인 -| 이름 | 설명 | -| --------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | -| [opencode-daytona](https://github.com/jamesmurdza/daytona/blob/main/guides/typescript/opencode/README.md) | git sync와 live preview를 지원하는 격리된 Daytona sandbox에서 OpenCode 세션을 자동 실행합니다. | -| [opencode-helicone-session](https://github.com/H2Shami/opencode-helicone-session) | 요청을 그룹화할 수 있도록 Helicone session header를 자동으로 주입합니다. | -| [opencode-type-inject](https://github.com/nick-vi/opencode-type-inject) | 조회 tool과 함께 TypeScript/Svelte 타입 정보를 파일 읽기에 자동 주입합니다. | -| [opencode-openai-codex-auth](https://github.com/numman-ali/opencode-openai-codex-auth) | API 크레딧 대신 ChatGPT Plus/Pro 구독을 사용할 수 있습니다. | -| [opencode-gemini-auth](https://github.com/jenslys/opencode-gemini-auth) | API 과금 대신 기존 Gemini 플랜을 사용할 수 있습니다. | -| [opencode-antigravity-auth](https://github.com/NoeFabris/opencode-antigravity-auth) | API 과금 대신 Antigravity의 무료 model을 사용할 수 있습니다. | -| [opencode-devcontainers](https://github.com/athal7/opencode-devcontainers) | shallow clone과 자동 포트 할당을 기반으로 multi-branch devcontainer 격리를 제공합니다. | -| [opencode-google-antigravity-auth](https://github.com/shekohex/opencode-google-antigravity-auth) | Google Search 지원과 견고한 API 처리를 제공하는 Google Antigravity OAuth Plugin입니다. | -| [opencode-dynamic-context-pruning](https://github.com/Tarquinen/opencode-dynamic-context-pruning) | 오래된 tool output을 정리해 token 사용량을 최적화합니다. | -| [opencode-websearch-cited](https://github.com/ghoulr/opencode-websearch-cited.git) | 지원 provider에서 Google grounded 스타일의 네이티브 websearch를 추가합니다. | -| [opencode-pty](https://github.com/shekohex/opencode-pty.git) | AI agent가 PTY에서 백그라운드 프로세스를 실행하고 대화형 입력을 보낼 수 있게 합니다. | -| [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | 비대화형 shell 명령 실행 지침을 제공해 TTY 의존 작업으로 인한 멈춤을 방지합니다. | -| [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Wakatime으로 OpenCode 사용량을 추적합니다. | -| [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | LLM이 생성한 markdown 표를 정리합니다. | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | Morph Fast Apply API와 lazy edit marker를 활용해 코드 편집 속도를 크게 높입니다. | -| [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | background agent, 사전 구성된 LSP/AST/MCP tool, curated agent, Claude Code 호환성을 제공합니다. | -| [opencode-notificator](https://github.com/panta82/opencode-notificator) | OpenCode 세션에 데스크톱 알림과 사운드 알림을 제공합니다. | -| [opencode-notifier](https://github.com/mohak34/opencode-notifier) | permission, 완료, 오류 이벤트에 대한 데스크톱 알림과 사운드 알림을 제공합니다. | -| [opencode-zellij-namer](https://github.com/24601/opencode-zellij-namer) | OpenCode 맥락을 기반으로 Zellij session 이름을 AI로 자동 지정합니다. | -| [opencode-skillful](https://github.com/zenobi-us/opencode-skillful) | skill 탐색과 주입을 통해 OpenCode agent가 필요 시 prompt를 lazy load하도록 합니다. | -| [opencode-supermemory](https://github.com/supermemoryai/opencode-supermemory) | Supermemory를 사용해 세션 간 persistent memory를 제공합니다. | -| [@plannotator/opencode](https://github.com/backnotprop/plannotator/tree/main/apps/opencode-plugin) | 시각 주석과 private/offline 공유를 포함한 인터랙티브 계획 리뷰를 제공합니다. | -| [@openspoon/subtask2](https://github.com/spoons-and-mirrors/subtask2) | 세밀한 flow control로 opencode /commands를 강력한 orchestration 시스템으로 확장합니다. | -| [opencode-scheduler](https://github.com/different-ai/opencode-scheduler) | cron 문법을 사용해 launchd(Mac) 또는 systemd(Linux) 기반 반복 작업을 예약합니다. | -| [micode](https://github.com/vtemian/micode) | Structured Brainstorm → Plan → Implement 워크플로를 session continuity와 함께 제공합니다. | -| [octto](https://github.com/vtemian/octto) | 다중 질문 폼 기반의 AI 브레인스토밍용 인터랙티브 브라우저 UI를 제공합니다. | -| [opencode-background-agents](https://github.com/kdcokenny/opencode-background-agents) | Claude Code 스타일의 background agent를 async delegation과 context persistence로 제공합니다. | -| [opencode-notify](https://github.com/kdcokenny/opencode-notify) | OpenCode 작업 완료 시점을 native OS 알림으로 알려줍니다. | -| [opencode-workspace](https://github.com/kdcokenny/opencode-workspace) | 16개 구성요소를 한 번에 설치하는 bundled multi-agent orchestration harness를 제공합니다. | -| [opencode-worktree](https://github.com/kdcokenny/opencode-worktree) | OpenCode용 git worktree를 손쉽게 사용할 수 있도록 돕습니다. | +| 이름 | 설명 | +| -------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | +| [opencode-daytona](https://github.com/daytonaio/daytona/tree/main/libs/opencode-plugin) | git sync와 live preview를 지원하는 격리된 Daytona sandbox에서 OpenCode 세션을 자동 실행합니다. | +| [opencode-helicone-session](https://github.com/H2Shami/opencode-helicone-session) | 요청을 그룹화할 수 있도록 Helicone session header를 자동으로 주입합니다. | +| [opencode-type-inject](https://github.com/nick-vi/opencode-type-inject) | 조회 tool과 함께 TypeScript/Svelte 타입 정보를 파일 읽기에 자동 주입합니다. | +| [opencode-openai-codex-auth](https://github.com/numman-ali/opencode-openai-codex-auth) | API 크레딧 대신 ChatGPT Plus/Pro 구독을 사용할 수 있습니다. | +| [opencode-gemini-auth](https://github.com/jenslys/opencode-gemini-auth) | API 과금 대신 기존 Gemini 플랜을 사용할 수 있습니다. | +| [opencode-antigravity-auth](https://github.com/NoeFabris/opencode-antigravity-auth) | API 과금 대신 Antigravity의 무료 model을 사용할 수 있습니다. | +| [opencode-devcontainers](https://github.com/athal7/opencode-devcontainers) | shallow clone과 자동 포트 할당을 기반으로 multi-branch devcontainer 격리를 제공합니다. | +| [opencode-google-antigravity-auth](https://github.com/shekohex/opencode-google-antigravity-auth) | Google Search 지원과 견고한 API 처리를 제공하는 Google Antigravity OAuth Plugin입니다. | +| [opencode-dynamic-context-pruning](https://github.com/Tarquinen/opencode-dynamic-context-pruning) | 오래된 tool output을 정리해 token 사용량을 최적화합니다. | +| [opencode-vibeguard](https://github.com/inkdust2021/opencode-vibeguard) | LLM 호출 전에 secrets/PII를 VibeGuard 스타일 placeholder로 가리고, 로컬에서 복원합니다. | +| [opencode-websearch-cited](https://github.com/ghoulr/opencode-websearch-cited.git) | 지원 provider에서 Google grounded 스타일의 네이티브 websearch를 추가합니다. | +| [opencode-pty](https://github.com/shekohex/opencode-pty.git) | AI agent가 PTY에서 백그라운드 프로세스를 실행하고 대화형 입력을 보낼 수 있게 합니다. | +| [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | 비대화형 shell 명령 실행 지침을 제공해 TTY 의존 작업으로 인한 멈춤을 방지합니다. | +| [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Wakatime으로 OpenCode 사용량을 추적합니다. | +| [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | LLM이 생성한 markdown 표를 정리합니다. | +| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | Morph Fast Apply API와 lazy edit marker를 활용해 코드 편집 속도를 크게 높입니다. | +| [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | background agent, 사전 구성된 LSP/AST/MCP tool, curated agent, Claude Code 호환성을 제공합니다. | +| [opencode-notificator](https://github.com/panta82/opencode-notificator) | OpenCode 세션에 데스크톱 알림과 사운드 알림을 제공합니다. | +| [opencode-notifier](https://github.com/mohak34/opencode-notifier) | permission, 완료, 오류 이벤트에 대한 데스크톱 알림과 사운드 알림을 제공합니다. | +| [opencode-zellij-namer](https://github.com/24601/opencode-zellij-namer) | OpenCode 맥락을 기반으로 Zellij session 이름을 AI로 자동 지정합니다. | +| [opencode-skillful](https://github.com/zenobi-us/opencode-skillful) | skill 탐색과 주입을 통해 OpenCode agent가 필요 시 prompt를 lazy load하도록 합니다. | +| [opencode-supermemory](https://github.com/supermemoryai/opencode-supermemory) | Supermemory를 사용해 세션 간 persistent memory를 제공합니다. | +| [@plannotator/opencode](https://github.com/backnotprop/plannotator/tree/main/apps/opencode-plugin) | 시각 주석과 private/offline 공유를 포함한 인터랙티브 계획 리뷰를 제공합니다. | +| [@openspoon/subtask2](https://github.com/spoons-and-mirrors/subtask2) | 세밀한 flow control로 opencode /commands를 강력한 orchestration 시스템으로 확장합니다. | +| [opencode-scheduler](https://github.com/different-ai/opencode-scheduler) | cron 문법을 사용해 launchd(Mac) 또는 systemd(Linux) 기반 반복 작업을 예약합니다. | +| [micode](https://github.com/vtemian/micode) | Structured Brainstorm → Plan → Implement 워크플로를 session continuity와 함께 제공합니다. | +| [octto](https://github.com/vtemian/octto) | 다중 질문 폼 기반의 AI 브레인스토밍용 인터랙티브 브라우저 UI를 제공합니다. | +| [opencode-background-agents](https://github.com/kdcokenny/opencode-background-agents) | Claude Code 스타일의 background agent를 async delegation과 context persistence로 제공합니다. | +| [opencode-notify](https://github.com/kdcokenny/opencode-notify) | OpenCode 작업 완료 시점을 native OS 알림으로 알려줍니다. | +| [opencode-workspace](https://github.com/kdcokenny/opencode-workspace) | 16개 구성요소를 한 번에 설치하는 bundled multi-agent orchestration harness를 제공합니다. | +| [opencode-worktree](https://github.com/kdcokenny/opencode-worktree) | OpenCode용 git worktree를 손쉽게 사용할 수 있도록 돕습니다. | --- diff --git a/packages/web/src/content/docs/ko/go.mdx b/packages/web/src/content/docs/ko/go.mdx new file mode 100644 index 00000000000..5909a01c9f9 --- /dev/null +++ b/packages/web/src/content/docs/ko/go.mdx @@ -0,0 +1,145 @@ +--- +title: Go +description: 오픈 코딩 모델을 위한 저렴한 구독 서비스입니다. +--- + +import config from "../../../../config.mjs" +export const console = config.console +export const email = `mailto:${config.email}` + +OpenCode Go는 인기 있는 오픈 코딩 모델에 안정적으로 액세스할 수 있는 저렴한 **월 $10** 구독 서비스입니다. + +:::note +OpenCode Go는 현재 베타 버전입니다. +::: + +Go는 OpenCode의 다른 제공자처럼 작동합니다. OpenCode Go를 구독하고 API 키를 받으세요. 이는 **완전히 선택 사항**이며 OpenCode를 사용하기 위해 반드시 사용할 필요는 없습니다. + +주로 해외 사용자를 위해 설계되었으며, 안정적인 글로벌 액세스를 위해 미국, EU, 싱가포르에서 모델이 호스팅됩니다. + +--- + +## 배경 + +오픈 모델은 정말 좋아졌습니다. 이제 코딩 작업에서 독점 모델에 가까운 성능을 발휘합니다. 그리고 많은 제공자가 경쟁적으로 서비스할 수 있기 때문에 일반적으로 훨씬 저렴합니다. + +하지만 안정적이고 지연 시간이 짧은 액세스를 얻기는 어려울 수 있습니다. 제공자마다 품질과 가용성이 다릅니다. + +:::tip +OpenCode와 잘 작동하는 엄선된 모델 및 제공자 그룹을 테스트했습니다. +::: + +이를 해결하기 위해 몇 가지 작업을 수행했습니다. + +1. 엄선된 오픈 모델 그룹을 테스트하고 해당 팀과 최적의 실행 방법에 대해 논의했습니다. +2. 그런 다음 몇몇 제공자와 협력하여 이것들이 올바르게 서비스되고 있는지 확인했습니다. +3. 마지막으로 모델/제공자 조합을 벤치마킹하여 추천할 만한 목록을 만들었습니다. + +OpenCode Go를 사용하면 **월 $10**에 이러한 모델에 액세스할 수 있습니다. + +--- + +## 작동 방식 + +OpenCode Go는 OpenCode의 다른 제공자처럼 작동합니다. + +1. **OpenCode Zen**에 로그인하고 Go를 구독한 다음 API 키를 복사합니다. +2. TUI에서 `/connect` 명령을 실행하고 `OpenCode Go`를 선택한 다음 API 키를 붙여넣습니다. +3. TUI에서 `/models`를 실행하여 Go를 통해 사용할 수 있는 모델 목록을 확인합니다. + +:::note +워크스페이스당 한 명의 멤버만 OpenCode Go를 구독할 수 있습니다. +::: + +현재 모델 목록은 다음과 같습니다. + +- **GLM-5** +- **Kimi K2.5** +- **MiniMax M2.5** + +모델 목록은 테스트하고 새로운 모델을 추가함에 따라 변경될 수 있습니다. + +--- + +## 사용 한도 + +OpenCode Go에는 다음과 같은 한도가 포함됩니다. + +- **5시간 한도** — $12 사용량 +- **주간 한도** — $30 사용량 +- **월간 한도** — $60 사용량 + +한도는 달러 가치로 정의됩니다. 즉, 실제 요청 수는 사용하는 모델에 따라 다릅니다. MiniMax M2.5와 같은 저렴한 모델은 더 많은 요청을 허용하는 반면, GLM-5와 같은 고비용 모델은 더 적은 요청을 허용합니다. + +아래 표는 일반적인 Go 사용 패턴을 기반으로 한 예상 요청 수를 제공합니다. + +| | GLM-5 | Kimi K2.5 | MiniMax M2.5 | +| --------------- | ----- | --------- | ------------ | +| 5시간당 요청 수 | 1,150 | 1,850 | 30,000 | +| 주당 요청 수 | 2,880 | 4,630 | 75,000 | +| 월당 요청 수 | 5,750 | 9,250 | 150,000 | + +추정치는 관찰된 평균 요청 패턴을 기반으로 합니다. + +- GLM-5 — 요청당 입력 700, 캐시 52,000, 출력 150 토큰 +- Kimi K2.5 — 요청당 입력 870, 캐시 55,000, 출력 200 토큰 +- MiniMax M2.5 — 요청당 입력 300, 캐시 55,000, 출력 125 토큰 + +**콘솔**에서 현재 사용량을 추적할 수 있습니다. + +:::tip +사용 한도에 도달하면 무료 모델을 계속 사용할 수 있습니다. +::: + +사용 한도는 초기 사용 및 피드백을 통해 학습함에 따라 변경될 수 있습니다. + +--- + +### 가격 + +OpenCode Go는 **월 $10** 구독 요금제입니다. 아래는 **100만 토큰당** 가격입니다. + +| Model | Input | Output | Cached Read | +| ------------ | ----- | ------ | ----------- | +| GLM-5 | $1.00 | $3.20 | $0.20 | +| Kimi K2.5 | $0.60 | $3.00 | $0.10 | +| MiniMax M2.5 | $0.30 | $1.20 | $0.03 | + +--- + +### 한도 초과 사용 + +Zen 잔액에 크레딧이 있는 경우 콘솔에서 **잔액 사용(Use balance)** 옵션을 활성화할 수 있습니다. 활성화하면 사용 한도에 도달했을 때 요청을 차단하는 대신 Zen 잔액을 사용하게 됩니다. + +--- + +## 엔드포인트 + +다음 API 엔드포인트를 통해 Go 모델에 액세스할 수도 있습니다. + +| Model | Model ID | Endpoint | AI SDK Package | +| ------------ | ------------ | ------------------------------------------------ | --------------------------- | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | + +OpenCode 설정의 [모델 ID](/docs/config/#models)는 `opencode-go/` 형식을 사용합니다. 예를 들어 Kimi K2.5의 경우 설정에서 `opencode-go/kimi-k2.5`를 사용합니다. + +--- + +## 개인정보 보호 + +이 플랜은 주로 해외 사용자를 위해 설계되었으며, 안정적인 글로벌 액세스를 위해 미국, EU, 싱가포르에서 모델이 호스팅됩니다. + +질문이 있으시면 문의해 주세요. + +--- + +## 목표 + +우리는 다음을 위해 OpenCode Go를 만들었습니다. + +1. 저렴한 구독으로 더 많은 사람들이 AI 코딩에 **접근할 수 있도록** 합니다. +2. 최고의 오픈 코딩 모델에 **안정적으로** 액세스할 수 있도록 합니다. +3. 코딩 에이전트 사용을 위해 **테스트 및 벤치마킹된** 모델을 큐레이팅합니다. +4. OpenCode와 함께 다른 제공자도 사용할 수 있도록 하여 **락인(lock-in)이 없도록** 합니다. diff --git a/packages/web/src/content/docs/ko/keybinds.mdx b/packages/web/src/content/docs/ko/keybinds.mdx index aef7ae357a3..2920a223574 100644 --- a/packages/web/src/content/docs/ko/keybinds.mdx +++ b/packages/web/src/content/docs/ko/keybinds.mdx @@ -3,11 +3,11 @@ title: 키바인드 description: 키바인드를 커스터마이즈하세요. --- -OpenCode에는 OpenCode config를 통해 커스터마이즈할 수 있는 keybinds 목록이 있습니다. +OpenCode에는 `tui.json`을 통해 커스터마이즈할 수 있는 키바인드 목록이 있습니다. -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "keybinds": { "leader": "ctrl+x", "app_exit": "ctrl+c,ctrl+d,q", @@ -117,11 +117,11 @@ keybinds에 리더 키를 꼭 사용할 필요는 없지만, 사용하는 것을 ## 키바인드 비활성화 -config에 해당 키를 값 `"none"`으로 추가하면 keybind를 비활성화할 수 있습니다. +`tui.json`에 해당 키를 값 `"none"`으로 추가하면 키바인드를 비활성화할 수 있습니다. -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "keybinds": { "session_compact": "none" } diff --git a/packages/web/src/content/docs/ko/plugins.mdx b/packages/web/src/content/docs/ko/plugins.mdx index 7214f326587..f20eb90c46c 100644 --- a/packages/web/src/content/docs/ko/plugins.mdx +++ b/packages/web/src/content/docs/ko/plugins.mdx @@ -3,7 +3,7 @@ title: 플러그인 description: OpenCode를 확장하기 위해 자신만의 플러그인을 작성하세요. --- -플러그인은 다양한 이벤트와 사용자 정의 행동으로 후킹하여 opencode를 확장 할 수 있습니다. 플러그인을 만들 수 있습니다 새로운 기능을 추가, 외부 서비스와 통합, 또는 opencode의 기본 동작을 수정. +플러그인은 다양한 이벤트와 사용자 정의 행동으로 후킹하여 opencode를 확장 할 수 있습니다. 플러그인을 사용하여 새로운 기능을 추가하거나, 외부 서비스와 통합하거나, opencode의 기본 동작을 수정할 수 있습니다. 예를 들어, 커뮤니티에 의해 생성 된 [plugins](/docs/ecosystem#plugins)를 확인하십시오. @@ -11,18 +11,18 @@ description: OpenCode를 확장하기 위해 자신만의 플러그인을 작성 ## 플러그인 사용 -플러그인을로드하는 두 가지 방법이 있습니다. +플러그인을 로드하는 두 가지 방법이 있습니다. --- -## 로컬 파일에서 +### 로컬 파일에서 플러그인 디렉토리에 JavaScript 또는 TypeScript 파일을 배치합니다. - `.opencode/plugins/` - 프로젝트 레벨 플러그인 - `~/.config/opencode/plugins/` - 글로벌 플러그인 -이 디렉토리의 파일은 자동으로 시작에로드됩니다. +이 디렉토리의 파일은 시작 시 자동으로 로드됩니다. --- @@ -37,43 +37,42 @@ config 파일에 npm 패키지를 지정합니다. } ``` -일반 및 범위의 npm 패키지 모두 지원됩니다. +일반 패키지 및 스코프 npm 패키지 모두 지원됩니다. [ecosystem](/docs/ecosystem#plugins)에서 사용할 수 있는 플러그인을 찾아보세요. --- -## 플러그인이 설치되는 방법 +### 플러그인이 설치되는 방법 -**npm 플러그인**은 시작시 Bun을 사용하여 자동으로 설치됩니다. 패키지와 그들의 의존성은 `~/.cache/opencode/node_modules/`에서 캐시됩니다. +**npm 플러그인**은 시작시 Bun을 사용하여 자동으로 설치됩니다. 패키지와 그 의존성은 `~/.cache/opencode/node_modules/`에 캐시됩니다. -**로컬 플러그인**은 플러그인 디렉토리에서 직접로드됩니다. 외부 패키지를 사용하려면 구성 디렉토리 내 `package.json`를 작성해야 합니다 ([Dependencies](#dependencies)), 또는 플러그인을 npm에 게시하고 [config에 추가](/docs/config#plugins). +**로컬 플러그인**은 플러그인 디렉토리에서 직접 로드됩니다. 외부 패키지를 사용하려면 구성 디렉토리 내에 `package.json`을 작성해야 하거나([의존성](#의존성) 참조), 플러그인을 npm에 게시하고 [config에 추가](/docs/config#plugins)해야 합니다. --- -## 로드 순서 +### 로드 순서 -플러그인은 모든 소스에서로드되며 모든 후크는 순서대로 실행됩니다. 로드 순서는 다음과 같습니다: +플러그인은 모든 소스에서 로드되며 모든 후크는 순서대로 실행됩니다. 로드 순서는 다음과 같습니다: 1. 글로벌 구성 (`~/.config/opencode/opencode.json`) 2. 프로젝트 구성 (`opencode.json`) 3. 글로벌 플러그인 디렉토리 (`~/.config/opencode/plugins/`) 4. 프로젝트 플러그인 디렉토리 (`.opencode/plugins/`) -중복 npm 패키지는 한 번만 로드됩니다. 하지만, 로컬 플러그인과 같은 이름과 npm 플러그인은 모두 별도로로드됩니다. +중복된 이름과 버전의 npm 패키지는 한 번만 로드됩니다. 하지만 로컬 플러그인과 npm 플러그인의 이름이 비슷하더라도 둘 다 별도로 로드됩니다. --- ## 플러그인 만들기 -플러그인은 **JavaScript/TypeScript 모듈**입니다. -기능. 각 함수는 context 객체를 수신하고 Hooks 객체를 반환합니다. +플러그인은 하나 이상의 플러그인 함수를 내보내는 **JavaScript/TypeScript 모듈**입니다. 각 함수는 context 객체를 수신하고 hooks 객체를 반환합니다. --- ### 의존성 -로컬 플러그인 및 사용자 정의 도구는 외부 npm 패키지를 사용할 수 있습니다. `package.json`를 config 디렉토리에 추가하면 필요한 의존도가 있습니다. +로컬 플러그인 및 사용자 정의 도구는 외부 npm 패키지를 사용할 수 있습니다. config 디렉토리에 `package.json`을 추가하고 필요한 의존성을 명시하십시오. ```json title=".opencode/package.json" { @@ -83,7 +82,7 @@ config 파일에 npm 패키지를 지정합니다. } ``` -opencode는 `bun install`를 시작합니다. 플러그인 및 도구가 가져올 수 있습니다. +opencode는 시작 시 `bun install`을 실행하여 이를 설치합니다. 이후 플러그인 및 도구에서 가져올 수 있습니다. ```ts title=".opencode/plugins/my-plugin.ts" import { escape } from "shescape" @@ -113,19 +112,19 @@ export const MyPlugin = async ({ project, client, $, directory, worktree }) => { } ``` -플러그인 기능 수신: +플러그인 함수가 받는 인자: - `project`: 현재 프로젝트 정보. - `directory`: 현재 작업 디렉토리. - `worktree`: git worktree 경로. - `client`: AI와 상호 작용을 위한 opencode SDK 클라이언트. -- `$`: Bun's [shell API](https://bun.com/docs/runtime/shell) 명령어를 실행합니다. +- `$`: 명령어를 실행하기 위한 Bun의 [shell API](https://bun.com/docs/runtime/shell). --- ### TypeScript 지원 -TypeScript 플러그인의 경우 플러그인 패키지에서 유형을 가져올 수 있습니다. +TypeScript 플러그인의 경우 플러그인 패키지에서 타입을 가져올 수 있습니다. ```ts title="my-plugin.ts" {1} import type { Plugin } from "@opencode-ai/plugin" @@ -139,29 +138,29 @@ export const MyPlugin: Plugin = async ({ project, client, $, directory, worktree --- -## 이벤트 +### 이벤트 -플러그인은 예제 섹션에서 아래에서 볼 때 이벤트에 가입 할 수 있습니다. 여기에 다른 이벤트의 목록입니다. +플러그인은 아래 예제 섹션에서 볼 수 있듯이 이벤트를 구독할 수 있습니다. 사용 가능한 이벤트 목록은 다음과 같습니다. -### 명령어 이벤트 +#### 명령어 이벤트 - `command.executed` -### 파일 이벤트 +#### 파일 이벤트 - `file.edited` - `file.watcher.updated` -### 설치 이벤트 +#### 설치 이벤트 - `installation.updated` -### LSP 이벤트 +#### LSP 이벤트 - `lsp.client.diagnostics` - `lsp.updated` -### 메시지 이벤트 +#### 메시지 이벤트 - `message.part.removed` - `message.part.updated` @@ -173,11 +172,11 @@ export const MyPlugin: Plugin = async ({ project, client, $, directory, worktree - `permission.asked` - `permission.replied` -### 서버 이벤트 +#### 서버 이벤트 - `server.connected` -### 세션 이벤트 +#### 세션 이벤트 - `session.created` - `session.compacted` @@ -188,7 +187,7 @@ export const MyPlugin: Plugin = async ({ project, client, $, directory, worktree - `session.status` - `session.updated` -### Todo 이벤트 +#### Todo 이벤트 - `todo.updated` @@ -196,7 +195,7 @@ export const MyPlugin: Plugin = async ({ project, client, $, directory, worktree - `shell.env` -##### 도구 이벤트 +#### 도구 이벤트 - `tool.execute.after` - `tool.execute.before` @@ -211,11 +210,11 @@ export const MyPlugin: Plugin = async ({ project, client, $, directory, worktree ## 예제 -opencode를 확장하기 위해 사용할 수있는 플러그인의 몇 가지 예입니다. +opencode를 확장하기 위해 사용할 수 있는 플러그인 예제입니다. --- -## 알림 보내기 +### 알림 보내기 특정 이벤트가 발생할 때 알림을 전송: @@ -232,7 +231,7 @@ export const NotificationPlugin = async ({ project, client, $, directory, worktr } ``` -macOS에서 AppleScript를 실행하려면 `osascript`를 사용하고 있습니다. 여기에 우리는 그것을 사용하여 알림을 보낼 수 있습니다. +macOS에서 AppleScript를 실행하기 위해 `osascript`를 사용하고 있습니다. 여기서는 알림을 보내는 데 사용합니다. :::note opencode 데스크톱 앱을 사용하는 경우 응답이 준비되어 있거나 세션 오류가 있을 때 시스템 알림을 자동으로 보낼 수 있습니다. @@ -242,7 +241,7 @@ opencode 데스크톱 앱을 사용하는 경우 응답이 준비되어 있거 ### .env 보호 -읽기 `.env` 파일에서 opencode를 방지하십시오: +opencode가 `.env` 파일을 읽지 못하도록 방지: ```javascript title=".opencode/plugins/env-protection.js" export const EnvProtection = async ({ project, client, $, directory, worktree }) => { @@ -260,7 +259,7 @@ export const EnvProtection = async ({ project, client, $, directory, worktree }) ### Inject 환경 변수 -환경 변수를 모든 shell 실행 (AI 도구 및 사용자 terminal)로 주사하십시오. +모든 shell 실행(AI 도구 및 사용자 terminal)에 환경 변수 주입: ```javascript title=".opencode/plugins/inject-env.js" export const InjectEnvPlugin = async () => { @@ -277,7 +276,7 @@ export const InjectEnvPlugin = async () => { ### 사용자 정의 도구 -플러그인은 opencode에 사용자 정의 도구를 추가 할 수 있습니다 : +플러그인은 opencode에 사용자 정의 도구를 추가할 수 있습니다: ```ts title=".opencode/plugins/custom-tools.ts" import { type Plugin, tool } from "@opencode-ai/plugin" @@ -300,13 +299,17 @@ export const CustomToolsPlugin: Plugin = async (ctx) => { } ``` -`tool` helper는 opencode가 호출 할 수있는 사용자 정의 도구를 만듭니다. Zod schema 기능을 가지고 도구 정의를 반환: +`tool` helper는 opencode가 호출할 수 있는 사용자 정의 도구를 만듭니다. Zod 스키마 함수를 받고 다음을 포함하는 도구 정의를 반환합니다: -- `description`: 도구는 무엇을 +- `description`: 도구가 하는 일 - `args`: 도구의 인수에 대한 Zod 스키마 -- `execute`: 도구가 호출될 때 실행되는 기능 +- `execute`: 도구가 호출될 때 실행되는 함수 -사용자 정의 도구는 내장 도구와 함께 opencode를 사용할 수 있습니다. +사용자 정의 도구는 내장 도구와 함께 opencode에서 사용할 수 있습니다. + +:::note +플러그인 도구가 내장 도구와 같은 이름을 사용하면 플러그인 도구가 우선순위를 갖습니다. +::: --- @@ -331,9 +334,9 @@ export const MyPlugin = async ({ client }) => { --- -## Compaction 훅 +### Compaction 훅 -세션이 압축 될 때 포함 된 컨텍스트를 사용자 지정: +세션이 압축될 때 포함되는 컨텍스트를 사용자 지정할 수 있습니다: ```ts title=".opencode/plugins/compaction.ts" import type { Plugin } from "@opencode-ai/plugin" @@ -355,9 +358,9 @@ Include any state that should persist across compaction: } ``` -LLM이 압축 요약을 생성하기 전에 `experimental.session.compacting` 훅이 실행됩니다. 기본 압축 프롬프트를 대체할 수 있도록 도메인 별 컨텍스트를 주입합니다. +`experimental.session.compacting` 훅은 LLM이 연속 요약을 생성하기 전에 실행됩니다. 기본 압축 프롬프트가 놓칠 수 있는 도메인별 컨텍스트를 주입하는 데 사용하세요. -당신은 또한 `output.prompt`를 조정해서 조밀함을 전적으로 대체할 수 있습니다: +또한 `output.prompt`를 설정하여 압축 프롬프트를 완전히 대체할 수도 있습니다: ```ts title=".opencode/plugins/custom-compaction.ts" import type { Plugin } from "@opencode-ai/plugin" @@ -382,4 +385,4 @@ Format as a structured prompt that a new agent can use to resume work. } ``` -`output.prompt`가 설정되면 완전히 기본 압축 프롬프트를 대체합니다. `output.context` 배열은 이 경우에 무시됩니다. +`output.prompt`가 설정되면 기본 압축 프롬프트를 완전히 대체합니다. 이 경우 `output.context` 배열은 무시됩니다. diff --git a/packages/web/src/content/docs/ko/providers.mdx b/packages/web/src/content/docs/ko/providers.mdx index ea48dbfb0a9..c543c719dd2 100644 --- a/packages/web/src/content/docs/ko/providers.mdx +++ b/packages/web/src/content/docs/ko/providers.mdx @@ -22,13 +22,13 @@ OpenCode는 [AI SDK](https://ai-sdk.dev/) 및 [Models.dev](https://models.dev) --- -#### 구성 +### 구성 OpenCode 설정의 `provider` 섹션을 통해 공급자를 사용자 정의할 수 있습니다. --- -### 기본 URL +#### 기본 URL `baseURL` 옵션을 설정하여 모든 공급자를 위한 기본 URL을 사용자 정의할 수 있습니다. 프록시 서비스 또는 사용자 정의 엔드포인트를 사용할 때 유용합니다. @@ -55,7 +55,38 @@ OpenCode Zen은 OpenCode 팀이 OpenCode와 잘 작동하도록 테스트하고 처음이라면 OpenCode Zen으로 시작하는 것이 좋습니다. ::: -1. TUI에서 `/connect` 명령을 실행하고 `opencode`를 선택한 뒤, [opencode.ai/auth](https://opencode.ai/auth)로 이동합니다. +1. TUI에서 `/connect` 명령을 실행하고 `OpenCode Zen`을 선택한 뒤, [opencode.ai/auth](https://opencode.ai/zen)로 이동합니다. + + ```txt + /connect + ``` + +2. 로그인하고 결제 정보를 입력한 후 API 키를 복사하십시오. + +3. API 키를 붙여넣습니다. + + ```txt + ┌ API key + │ + │ + └ enter + ``` + +4. TUI에서 `/models`를 실행하여 추천 모델 목록을 볼 수 있습니다. + + ```txt + /models + ``` + +OpenCode의 다른 공급자처럼 작동하며 사용은 완전히 선택 사항입니다. + +--- + +## OpenCode Go + +OpenCode Go는 OpenCode 팀이 테스트하고 검증하여 OpenCode와 잘 작동하는 인기 있는 오픈 코딩 모델에 안정적으로 액세스할 수 있는 저렴한 구독 요금제입니다. + +1. TUI에서 `/connect` 명령을 실행하고 `OpenCode Go`를 선택한 뒤 [opencode.ai/auth](https://opencode.ai/zen)로 이동하십시오. ```txt /connect @@ -129,15 +160,15 @@ OpenCode로 Amazon Bedrock을 사용하려면: Amazon Bedrock에서 원하는 모델에 대한 액세스 권한이 있어야 합니다. ::: -2. 다음 방법 중 하나를 사용하여 **설정**합니다: +2. 다음 방법 중 하나를 사용하여 **인증을 구성**합니다: ---- + *** -### 환경 변수 (빠른 시작) + #### 환경 변수 (빠른 시작) -OpenCode를 실행하는 동안 다음 환경 변수 중 하나를 설정합니다: + OpenCode를 실행하는 동안 다음 환경 변수 중 하나를 설정합니다: -```bash + ```bash # Option 1: Using AWS access keys AWS_ACCESS_KEY_ID=XXX AWS_SECRET_ACCESS_KEY=YYY opencode @@ -146,87 +177,84 @@ OpenCode를 실행하는 동안 다음 환경 변수 중 하나를 설정합니 # Option 3: Using Bedrock bearer token AWS_BEARER_TOKEN_BEDROCK=XXX opencode -``` + ``` -또는 bash 프로필에 추가합니다: + 또는 bash 프로필에 추가합니다: -```bash title="~/.bash_profile" + ```bash title="~/.bash_profile" export AWS_PROFILE=my-dev-profile export AWS_REGION=us-east-1 -``` - ---- - -#### 설정 파일 (권장) - -프로젝트별 또는 영구 구성을 위해 `opencode.json`을 사용하십시오. + ``` -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "provider": { - "amazon-bedrock": { - "options": { - "region": "us-east-1", - "profile": "my-aws-profile" - } - } - } -} -``` + *** -**유효한 옵션:** + #### 구성 파일 (권장) -- `region` - AWS 리전 (예: `us-east-1`, `eu-west-1`) -- `profile` - `~/.aws/credentials`의 AWS 프로필 이름 -- `endpoint` - VPC 엔드포인트 등을 위한 사용자 정의 엔드포인트 URL (일반 `baseURL` 옵션의 별칭) + 프로젝트별 또는 영구 구성을 위해 `opencode.json`을 사용하십시오: - :::tip - 구성 파일 옵션은 환경 변수보다 우선 순위가 높습니다. - ::: + ```json title="opencode.json" + { + "$schema": "https://opencode.ai/config.json", + "provider": { + "amazon-bedrock": { + "options": { + "region": "us-east-1", + "profile": "my-aws-profile" + } + } + } + } + ``` ---- + **유효한 옵션:** + - `region` - AWS 리전 (예: `us-east-1`, `eu-west-1`) + - `profile` - `~/.aws/credentials`의 AWS 프로필 이름 + - `endpoint` - VPC 엔드포인트 등을 위한 사용자 정의 엔드포인트 URL (일반 `baseURL` 옵션의 별칭) -#### 고급: VPC 엔드포인트 + :::tip + 구성 파일 옵션은 환경 변수보다 우선 순위가 높습니다. + ::: -Bedrock의 VPC 엔드포인트를 사용하는 경우: + *** -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "provider": { - "amazon-bedrock": { - "options": { - "region": "us-east-1", - "profile": "production", - "endpoint": "https://bedrock-runtime.us-east-1.vpce-xxxxx.amazonaws.com" - } - } - } -} -``` + #### 고급: VPC 엔드포인트 -:::note -`endpoint` 옵션은 일반적인 `baseURL` 옵션의 별칭입니다. `endpoint`와 `baseURL` 둘 다 지정된 경우 `endpoint`가 우선합니다. -::: + Bedrock의 VPC 엔드포인트를 사용하는 경우: ---- + ```json title="opencode.json" + { + "$schema": "https://opencode.ai/config.json", + "provider": { + "amazon-bedrock": { + "options": { + "region": "us-east-1", + "profile": "production", + "endpoint": "https://bedrock-runtime.us-east-1.vpce-xxxxx.amazonaws.com" + } + } + } + } + ``` -#### 인증 방법 + :::note + `endpoint` 옵션은 일반적인 `baseURL` 옵션의 별칭입니다. `endpoint`와 `baseURL` 둘 다 지정된 경우 `endpoint`가 우선합니다. + ::: -- **`AWS_ACCESS_KEY_ID`/`AWS_SECRET_ACCESS_KEY`**: IAM 사용자 및 AWS 콘솔에서 액세스 키 생성 -- **`AWS_PROFILE`**: `~/.aws/credentials`의 프로필 이름을 사용합니다. `aws configure --profile my-profile` 또는 `aws sso login`으로 먼저 구성하십시오. -- **`AWS_BEARER_TOKEN_BEDROCK`**: Amazon Bedrock 콘솔에서 임시 API 키 생성 -- **`AWS_WEB_IDENTITY_TOKEN_FILE` / `AWS_ROLE_ARN`**: EKS IRSA (서비스 계정용 IAM 역할) 또는 다른 Kubernetes 환경의 OIDC 연동. 이 환경 변수는 서비스 계정을 사용할 때 Kubernetes에 의해 자동으로 주입됩니다. + *** ---- + #### 인증 방법 + - **`AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY`**: IAM 사용자 및 AWS 콘솔에서 액세스 키 생성 + - **`AWS_PROFILE`**: `~/.aws/credentials`의 프로필 이름을 사용합니다. `aws configure --profile my-profile` 또는 `aws sso login`으로 먼저 구성하십시오. + - **`AWS_BEARER_TOKEN_BEDROCK`**: Amazon Bedrock 콘솔에서 임시 API 키 생성 + - **`AWS_WEB_IDENTITY_TOKEN_FILE` / `AWS_ROLE_ARN`**: EKS IRSA (서비스 계정용 IAM 역할) 또는 다른 Kubernetes 환경의 OIDC 연동. 이 환경 변수는 서비스 계정을 사용할 때 Kubernetes에 의해 자동으로 주입됩니다. -#### 인증 우선 순위 + *** -Amazon Bedrock은 다음과 같은 인증 우선 순위를 사용합니다. + #### 인증 우선 순위 -1. **Bearer Token** - `AWS_BEARER_TOKEN_BEDROCK` 환경 변수 또는 `/connect` 명령의 토큰 -2. **AWS Credential Chain** - 프로필, 액세스 키, 공유 자격 증명, IAM 역할, 웹 ID 토큰 (EKS IRSA), 인스턴스 메타데이터 + Amazon Bedrock은 다음과 같은 인증 우선 순위를 사용합니다. + 1. **Bearer Token** - `AWS_BEARER_TOKEN_BEDROCK` 환경 변수 또는 `/connect` 명령의 토큰 + 2. **AWS Credential Chain** - 프로필, 액세스 키, 공유 자격 증명, IAM 역할, 웹 ID 토큰 (EKS IRSA), 인스턴스 메타데이터 :::note Bearer 토큰을 설정할 때 (`/connect` 또는 `AWS_BEARER_TOKEN_BEDROCK`를 통해), 구성된 프로필을 포함한 모든 AWS 자격 증명 방법보다 우선 순위가 높습니다. @@ -260,7 +288,7 @@ Amazon Bedrock은 다음과 같은 인증 우선 순위를 사용합니다. --- -#### Anthropic +### Anthropic 1. 가입 후 `/connect` 명령을 실행하고 **Anthropic**을 선택합니다. @@ -268,7 +296,7 @@ Amazon Bedrock은 다음과 같은 인증 우선 순위를 사용합니다. /connect ``` -2. **Claude Pro/Max** 옵션을 선택하면 브라우저가 열립니다. +2. **Claude Pro/Max** 옵션을 선택하면 브라우저가 열리고 인증을 요청합니다. ```txt ┌ Select auth method @@ -300,7 +328,7 @@ Pro/Max 구독이 없는 경우 **Create an API Key**를 선택할 수 있습니 ### Azure OpenAI :::note -"I'm sorry, but I can't support that request" 오류가 발생하면, Azure 리소스의 콘텐츠 필터를 **DefaultV2**에서 **Default**로 변경해 보세요. +"I'm sorry, but I cannot assist with that request" 오류가 발생하면, Azure 리소스의 콘텐츠 필터를 **DefaultV2**에서 **Default**로 변경해 보세요. ::: 1. [Azure 포털](https://portal.azure.com/)로 이동하여 **Azure OpenAI** 리소스를 만듭니다. 다음이 필요합니다: @@ -395,7 +423,7 @@ Pro/Max 구독이 없는 경우 **Create an API Key**를 선택할 수 있습니 --- -#### Baseten +### Baseten 1. [Baseten](https://app.baseten.co/)으로 이동하여 계정을 만들고 API 키를 생성합니다. @@ -422,7 +450,7 @@ Pro/Max 구독이 없는 경우 **Create an API Key**를 선택할 수 있습니 --- -#### Cerebras +### Cerebras 1. [Cerebras 콘솔](https://inference.cerebras.ai/)로 이동하여 계정을 만들고 API 키를 생성합니다. @@ -441,7 +469,7 @@ Pro/Max 구독이 없는 경우 **Create an API Key**를 선택할 수 있습니 └ enter ``` -4. `/models` 명령을 실행하여 모델(예: Qwen 3 Coder 480B)을 선택하십시오. +4. `/models` 명령을 실행하여 모델(예: _Qwen 3 Coder 480B_)을 선택하십시오. ```txt /models @@ -449,7 +477,7 @@ Pro/Max 구독이 없는 경우 **Create an API Key**를 선택할 수 있습니 --- -## Cloudflare AI Gateway +### Cloudflare AI Gateway Cloudflare AI Gateway는 OpenAI, Anthropic, Workers AI 등의 모델에 액세스할 수 있으며, 통합된 엔드포인트를 통해 더 많은 기능을 제공합니다. [Unified Billing](https://developers.cloudflare.com/ai-gateway/features/unified-billing/)을 사용하면 각 공급자의 별도 API 키가 필요하지 않습니다. @@ -507,7 +535,7 @@ Cloudflare AI Gateway는 OpenAI, Anthropic, Workers AI 등의 모델에 액세 --- -#### Cortecs +### Cortecs 1. [Cortecs 콘솔](https://cortecs.ai/)로 이동하여 계정을 만들고 API 키를 생성합니다. @@ -526,7 +554,7 @@ Cloudflare AI Gateway는 OpenAI, Anthropic, Workers AI 등의 모델에 액세 └ enter ``` -4. `/models` 명령을 실행하여 모델(예: Kimi K2 Instruct)을 선택하십시오. +4. `/models` 명령을 실행하여 모델(예: _Kimi K2 Instruct_)을 선택하십시오. ```txt /models @@ -534,7 +562,7 @@ Cloudflare AI Gateway는 OpenAI, Anthropic, Workers AI 등의 모델에 액세 --- -## DeepSeek +### DeepSeek 1. [DeepSeek 콘솔](https://platform.deepseek.com/)로 이동하여 계정을 만들고 **API Keys**를 클릭하여 키를 생성합니다. @@ -553,7 +581,7 @@ Cloudflare AI Gateway는 OpenAI, Anthropic, Workers AI 등의 모델에 액세 └ enter ``` -4. `/models` 명령을 실행하여 DeepSeek 모델(예: DeepSeek Reasoner)을 선택하십시오. +4. `/models` 명령을 실행하여 DeepSeek 모델(예: _DeepSeek Reasoner_)을 선택하십시오. ```txt /models @@ -561,7 +589,7 @@ Cloudflare AI Gateway는 OpenAI, Anthropic, Workers AI 등의 모델에 액세 --- -## Deep Infra +### Deep Infra 1. [Deep Infra 대시보드](https://deepinfra.com/dash)로 이동하여 계정을 만들고 API 키를 생성합니다. @@ -588,7 +616,7 @@ Cloudflare AI Gateway는 OpenAI, Anthropic, Workers AI 등의 모델에 액세 --- -## Firmware +### Firmware 1. [Firmware 대시보드](https://app.firmware.ai/signup)로 이동하여 계정을 만들고 API 키를 생성합니다. @@ -615,7 +643,7 @@ Cloudflare AI Gateway는 OpenAI, Anthropic, Workers AI 등의 모델에 액세 --- -## Fireworks AI +### Fireworks AI 1. [Fireworks AI 콘솔](https://app.fireworks.ai/)로 이동하여 계정을 만들고 **API Keys**를 클릭합니다. @@ -634,7 +662,7 @@ Cloudflare AI Gateway는 OpenAI, Anthropic, Workers AI 등의 모델에 액세 └ enter ``` -4. `/models` 명령을 실행하여 모델(예: Kimi K2 Instruct)을 선택하십시오. +4. `/models` 명령을 실행하여 모델(예: _Kimi K2 Instruct_)을 선택하십시오. ```txt /models @@ -662,38 +690,33 @@ GitLab Duo는 GitLab의 Anthropic 프록시를 통해 기본 도구 호출 기 └ ``` -#### OAuth 사용 (권장) - -**OAuth**를 선택하면 브라우저에서 권한 부여를 요청합니다. - ---- + #### OAuth 사용 (권장) -### 개인 액세스 토큰 사용 + **OAuth**를 선택하면 브라우저가 열리고 인증을 요청합니다. -1. [GitLab User Settings > Access Tokens](https://gitlab.com/-/user_settings/personal_access_tokens)로 이동 -2. 새 토큰 추가 -3. 이름: `OpenCode`, 범위: `api` -4. 토큰 복사 (`glpat-`로 시작) -5. 터미널에 입력하십시오. + #### 개인 액세스 토큰 사용 + 1. [GitLab User Settings > Access Tokens](https://gitlab.com/-/user_settings/personal_access_tokens)로 이동 + 2. **Add new token** 클릭 + 3. 이름: `OpenCode`, 범위: `api` + 4. 토큰 복사 (`glpat-`로 시작) + 5. 터미널에 입력 -6. 사용 가능한 모델을 보려면 `/models` 명령을 실행하십시오. +3. `/models` 명령을 실행하여 사용 가능한 모델을 확인하십시오. ```txt /models ``` -세 가지 Claude 기반 모델을 사용할 수 있습니다: - -- **duo-chat-haiku-4-5** (기본값) - 빠른 작업을 위한 빠른 응답 -- **duo-chat-sonnet-4-5** - 대부분의 워크플로우에 균형 잡힌 성능 -- **duo-chat-opus-4-5** - 복잡한 분석 가능 + 세 가지 Claude 기반 모델을 사용할 수 있습니다: + - **duo-chat-haiku-4-5** (기본값) - 빠른 작업을 위한 빠른 응답 + - **duo-chat-sonnet-4-5** - 대부분의 워크플로우에 균형 잡힌 성능 + - **duo-chat-opus-4-5** - 복잡한 분석에 적합 :::note -`GITLAB_TOKEN` 환경 변수를 지정할 수도 있습니다. -OpenCode는 인증 저장소에 토큰을 저장합니다. +`GITLAB_TOKEN` 환경 변수를 지정하여 토큰을 저장하지 않고 사용할 수도 있습니다. ::: -#### 셀프 호스팅 GitLab (Self-Hosted) +#### 셀프 호스팅 GitLab :::note[규정 준수 참고 사항] OpenCode는 세션 제목 생성과 같은 일부 AI 작업을 위해 작은 모델을 사용합니다. @@ -711,7 +734,7 @@ OpenCode를 자체 호스팅 GitLab 인스턴스만 사용하도록 제한하려 ::: -자체 호스팅 GitLab 인스턴스: +자체 호스팅 GitLab 인스턴스의 경우: ```bash export GITLAB_INSTANCE_URL=https://gitlab.company.com @@ -721,7 +744,7 @@ export GITLAB_TOKEN=glpat-... 인스턴스가 사용자 정의 AI Gateway를 실행하는 경우: ```bash -export GITLAB_AI_GATEWAY_URL=https://ai-gateway.company.com +GITLAB_AI_GATEWAY_URL=https://ai-gateway.company.com ``` 또는 bash 프로필에 추가: @@ -737,19 +760,18 @@ GitLab 관리자는 다음을 활성화해야 합니다: 1. [Duo Agent Platform](https://docs.gitlab.com/user/gitlab_duo/turn_on_off/) (사용자, 그룹 또는 인스턴스) 2. 기능 플래그 (Rails 콘솔을 통해): - -- `agent_platform_claude_code` -- `third_party_agents_enabled` - ::: + - `agent_platform_claude_code` + - `third_party_agents_enabled` + ::: #### 셀프 호스팅 인스턴스용 OAuth 자체 호스팅 인스턴스에 대해 OAuth를 작동시키려면 새로운 애플리케이션(Settings → Applications)을 만들어야 합니다. 콜백 URL `http://127.0.0.1:8080/callback` 및 다음 범위가 필요합니다: -- `api` (사용자 대신 API 액세스) -- `read_user` (개인 정보 읽기) -- `read_repository` (리포지토리 읽기 전용 액세스) +- api (사용자 대신 API 액세스) +- read_user (개인 정보 읽기) +- read_repository (리포지토리 읽기 전용 액세스) 그런 다음 애플리케이션 ID를 환경 변수로 노출하십시오: @@ -759,7 +781,7 @@ export GITLAB_OAUTH_CLIENT_ID=your_application_id_here [opencode-gitlab-auth](https://www.npmjs.com/package/@gitlab/opencode-gitlab-auth) 홈페이지에 추가 문서가 있습니다. -##### 구성 +#### 구성 `opencode.json`을 통해 사용자 정의: @@ -780,7 +802,7 @@ export GITLAB_OAUTH_CLIENT_ID=your_application_id_here } ``` -##### GitLab API 도구 (선택 사항이지만 강력 권장) +#### GitLab API 도구 (선택 사항이지만 강력 권장) GitLab 도구(병합 요청, 이슈, 파이프라인, CI/CD 등)에 액세스하려면: @@ -797,13 +819,10 @@ GitLab 도구(병합 요청, 이슈, 파이프라인, CI/CD 등)에 액세스하 ### GitHub Copilot -GitHub Copilot 구독을 사용하여 opencode: +OpenCode에서 GitHub Copilot 구독을 사용하려면: :::note -몇몇 모형은 [Pro+를 필요로 할지도 모릅니다 -구독](https://github.com/features/copilot/plans) 사용. - -일부 모델은 수동으로 활성화해야합니다 [GitHub Copilot 설정](https://docs.github.com/en/copilot/how-tos/use-ai-models/configure-access-to-ai-models#setup-for-individual-use). +일부 모델은 [Pro+ 구독](https://github.com/features/copilot/plans)이 필요할 수 있습니다. ::: 1. `/connect` 명령을 실행하고 GitHub Copilot을 검색하십시오. @@ -821,7 +840,8 @@ GitHub Copilot 구독을 사용하여 opencode: │ │ Enter code: 8F43-6FCF │ - └ Waiting for authorization... + │ Waiting for authorization... + └ ``` 3. 이제 원하는 모델을 선택하기 위해 `/models` 명령을 실행합니다. @@ -832,44 +852,42 @@ GitHub Copilot 구독을 사용하여 opencode: --- -### 구글 Vertex AI +### Google Vertex AI -opencode로 Google Vertex AI를 사용하려면: +OpenCode로 Google Vertex AI를 사용하려면: -1. Google Cloud Console에서 ** Model Garden**을 통해 헤드를 확인하고 확인하십시오. - 당신의 지역에서 유효한 모형. +1. Google Cloud Console의 **Model Garden**으로 이동하여 해당 리전에서 사용 가능한 모델을 확인하십시오. :::note - Vertex AI API를 사용하여 Google Cloud 프로젝트를 수행해야합니다. + Vertex AI API가 활성화된 Google Cloud 프로젝트가 있어야 합니다. ::: 2. 필요한 환경 변수를 설정: + - `GOOGLE_CLOUD_PROJECT`: 구글 클라우드 프로젝트 ID + - `VERTEX_LOCATION` (선택): Vertex AI 리전 (기본값: `global`) + - 인증 (하나 선택): + - `GOOGLE_APPLICATION_CREDENTIALS`: 서비스 계정 JSON 키 파일 경로 + - gcloud CLI 사용: `gcloud auth application-default login` -- `GOOGLE_CLOUD_PROJECT`: 당신의 구글 클라우드 프로젝트 ID -- `VERTEX_LOCATION` (선택): Vertex AI를 위한 지구 (`global`에 기본) -- 인증(초당): -- `GOOGLE_APPLICATION_CREDENTIALS`: 서비스 계정 JSON 키 파일 경로 -- gcloud CLI를 사용하여 인증 : `gcloud auth application-default login` + OpenCode를 실행할 때 설정: -opencode를 실행하면서 설정한다. - -```bash + ```bash GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json GOOGLE_CLOUD_PROJECT=your-project-id opencode -``` + ``` -또는 bash 프로파일에 추가하십시오. + 또는 bash 프로필에 추가: -```bash title="~/.bash_profile" + ```bash title="~/.bash_profile" export GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json export GOOGLE_CLOUD_PROJECT=your-project-id export VERTEX_LOCATION=global -``` + ``` :::tip -`global` 지구는 가용성을 개량하고 추가 비용 없이 과실을 감소시킵니다. 데이터 거주 요건에 대한 지역 엔드포인트(e.g., `us-central1`)를 사용하십시오. [더 알아보기](https://cloud.google.com/vertex-ai/generative-ai/docs/partner-models/use-partner-models#regional and global endpoints) +`global` 리전은 가용성을 높이고 오류를 줄이며 추가 비용이 없습니다. 데이터 거주 요건이 있는 경우 지역 엔드포인트(예: `us-central1`)를 사용하십시오. [더 알아보기](https://cloud.google.com/vertex-ai/generative-ai/docs/partner-models/use-partner-models#regional_and_global_endpoints) ::: -3. 당신이 원하는 모형을 선정하기 위하여 `/models` 명령을 실행하십시오. +3. `/models` 명령을 실행하여 원하는 모델을 선택하십시오. ```txt /models @@ -877,17 +895,17 @@ opencode를 실행하면서 설정한다. --- -##### Groq +### Groq -1. [Groq 콘솔](https://console.groq.com/)에 머리, click **Create API Key**, 키 복사. +1. [Groq 콘솔](https://console.groq.com/)로 이동하여 **Create API Key**를 클릭하고 키를 복사합니다. -2. `/connect` 명령을 실행하고 Groq에 대한 검색. +2. `/connect` 명령을 실행하고 Groq를 검색하십시오. ```txt /connect ``` -3. 공급자를 위한 API 열쇠를 입력하십시오. +3. API 키를 입력하십시오. ```txt ┌ API key @@ -896,7 +914,7 @@ opencode를 실행하면서 설정한다. └ enter ``` -4. `/models` 명령을 실행하여 원하는 것을 선택합니다. +4. `/models` 명령을 실행하여 원하는 모델을 선택합니다. ```txt /models @@ -906,9 +924,9 @@ opencode를 실행하면서 설정한다. ### Hugging Face -[Hugging Face Inference Provider](https://huggingface.co/docs/inference-providers)는 17+ 공급자가 지원하는 오픈 모델에 대한 액세스를 제공합니다. +[Hugging Face Inference Providers](https://huggingface.co/docs/inference-providers)는 17개 이상의 공급자가 지원하는 오픈 모델에 대한 액세스를 제공합니다. -1. [Hugging Face settings](https://huggingface.co/settings/tokens/new?ownUserPermissions=inference.serverless.write&tokenType=fineGrained)를 통해 토큰을 Inference Provider에게 호출할 권한을 부여합니다. +1. [Hugging Face settings](https://huggingface.co/settings/tokens/new?ownUserPermissions=inference.serverless.write&tokenType=fineGrained)로 이동하여 Inference Providers에 호출할 권한이 있는 토큰을 생성합니다. 2. `/connect` 명령을 실행하고 **Hugging Face**를 검색하십시오. @@ -925,7 +943,7 @@ opencode를 실행하면서 설정한다. └ enter ``` -4. `/models` 명령을 실행하여 같은 모델을 선택하십시오 Kimi-K2-Instruct 또는 GLM-4.6 . +4. `/models` 명령을 실행하여 모델(예: _Kimi-K2-Instruct_ 또는 _GLM-4.6_)을 선택하십시오. ```txt /models @@ -935,17 +953,17 @@ opencode를 실행하면서 설정한다. ### Helicone -[Helicone](https://helicone.ai)는 AI 애플리케이션에 대한 로깅, 모니터링 및 분석 기능을 제공하는 LLM Observability 플랫폼입니다. Helicone AI Gateway는 모델을 기반으로 적절한 공급자에게 요청을 자동으로 전달합니다. +[Helicone](https://helicone.ai)는 AI 애플리케이션을 위한 로깅, 모니터링 및 분석 기능을 제공하는 LLM 관찰 가능성(Observability) 플랫폼입니다. Helicone AI Gateway는 모델을 기반으로 적절한 공급자에게 요청을 자동으로 라우팅합니다. -1. [Helicone](https://helicone.ai)에 머리, 계정을 만들고, 대시보드에서 API 키를 생성합니다. +1. [Helicone](https://helicone.ai)로 이동하여 계정을 만들고 대시보드에서 API 키를 생성합니다. -2. `/connect` 명령을 실행하고 ** Helicone**를 검색하십시오. +2. `/connect` 명령을 실행하고 **Helicone**를 검색하십시오. ```txt /connect ``` -3. Helicone API 열쇠를 입력하십시오. +3. Helicone API 키를 입력하십시오. ```txt ┌ API key @@ -954,19 +972,19 @@ opencode를 실행하면서 설정한다. └ enter ``` -4. 모델을 선택하려면 `/models` 명령을 실행하십시오. +4. `/models` 명령을 실행하여 모델을 선택하십시오. ```txt /models ``` -캐싱 및 속도 제한과 같은 더 많은 공급자와 고급 기능을 위해 [Helicone 문서](https://docs.helicone.ai)를 확인하십시오. +캐싱 및 속도 제한과 같은 더 많은 공급자와 고급 기능은 [Helicone 문서](https://docs.helicone.ai)를 확인하십시오. -#### 선택 사항 +#### 선택적 구성 -이벤트에서 opencode를 통해 자동으로 구성되지 않는 Helicone의 기능 또는 모델을 볼 수 있습니다. +OpenCode를 통해 자동으로 구성되지 않는 Helicone의 기능이나 모델이 있는 경우 직접 구성할 수 있습니다. -여기에 [Helicone의 모델 디렉토리](https://helicone.ai/models), 당신은 당신이 추가 할 모델의 ID를 잡아이 필요. +[Helicone의 모델 디렉토리](https://helicone.ai/models)에서 추가하려는 모델의 ID를 확인하십시오. ```jsonc title="~/.config/opencode/opencode.jsonc" { @@ -992,7 +1010,7 @@ opencode를 실행하면서 설정한다. } ``` -##### 사용자 정의 헤더 +#### 사용자 정의 헤더 Helicone는 캐싱, 사용자 추적 및 세션 관리와 같은 기능을 위한 사용자 정의 헤더를 지원합니다. `options.headers`를 사용하여 공급자 구성에 추가하십시오: @@ -1017,7 +1035,7 @@ Helicone는 캐싱, 사용자 추적 및 세션 관리와 같은 기능을 위 ##### 세션 추적 -Helicone's [Sessions](https://docs.helicone.ai/features/sessions) 기능으로 그룹 관련 LLM 요청이 가능합니다. [opencode-helicone-session](https://github.com/H2Shami/opencode-helicone-session) 플러그인을 사용하여 각 opencode 대화를 Helicone 세션으로 자동 로그인하십시오. +Helicone의 [Sessions](https://docs.helicone.ai/features/sessions) 기능을 사용하면 관련 LLM 요청을 그룹화할 수 있습니다. [opencode-helicone-session](https://github.com/H2Shami/opencode-helicone-session) 플러그인을 사용하여 각 OpenCode 대화를 Helicone 세션으로 자동 기록하십시오. ```bash npm install -g opencode-helicone-session @@ -1031,24 +1049,24 @@ npm install -g opencode-helicone-session } ``` -플러그인은 `Helicone-Session-Id` 및 `Helicone-Session-Name` 헤더를 귀하의 요청에 주사합니다. Helicone의 세션 페이지에서는 별도의 세션으로 나열된 각 opencode 대화를 볼 수 있습니다. +이 플러그인은 `Helicone-Session-Id` 및 `Helicone-Session-Name` 헤더를 요청에 주입합니다. Helicone의 세션 페이지에서 각 OpenCode 대화가 별도의 세션으로 나열되는 것을 볼 수 있습니다. -###### 공통 Helicone 헤더 +##### 공통 Helicone 헤더 -| 헤드러 | Description | +| 헤더 | 설명 | | -------------------------- | ---------------------------------------------------------- | -| `Helicone-Cache-Enabled` | 대응 캐싱 (`true`/`false`) | -| `Helicone-User-Id` | 사용자별 추적 가능 | +| `Helicone-Cache-Enabled` | 응답 캐싱 활성화 (`true`/`false`) | +| `Helicone-User-Id` | 사용자별 지표 추적 | | `Helicone-Property-[Name]` | 사용자 정의 속성 추가(예: `Helicone-Property-Environment`) | -| `Helicone-Prompt-Id` | prompt 대응 | +| `Helicone-Prompt-Id` | 요청을 프롬프트 버전과 연관 | -모든 사용 가능한 헤더에 대한 [Helicone Header Directory](https://docs.helicone.ai/helicone-headers/header-directory)를 참조하십시오. +사용 가능한 모든 헤더는 [Helicone Header Directory](https://docs.helicone.ai/helicone-headers/header-directory)를 참조하십시오. --- -#### llama.cpp +### llama.cpp -[llama.cpp's](https://github.com/ggml-org/llama.cpp) llama-server 유틸리티를 통해 로컬 모델을 사용할 수 있습니다. +[llama.cpp](https://github.com/ggml-org/llama.cpp)의 llama-server 유틸리티를 통해 로컬 모델을 사용하도록 구성할 수 있습니다. ```json title="opencode.json" "llama.cpp" {5, 6, 8, 10-15} { @@ -1076,21 +1094,21 @@ npm install -g opencode-helicone-session 이 예제에서: -- `llama.cpp`는 주문 공급자 ID입니다. 원하는 문자열이 될 수 있습니다. -- `npm`는 이 공급자를 위해 사용할 포장을 지정합니다. 여기, `@ai-sdk/openai-compatible`는 OpenAI 호환 API에 사용됩니다. -- `name`는 UI에 있는 공급자를 위한 전시 이름입니다. -- `options.baseURL`는 로컬 서버의 엔드포인트입니다. -- `models`는 모델 ID를 구성하는 맵입니다. 모델 이름은 모델 선택 목록에 표시됩니다. +- `llama.cpp`는 사용자 정의 공급자 ID입니다. 원하는 문자열로 지정할 수 있습니다. +- `npm`은 이 공급자에 사용할 패키지를 지정합니다. 여기서는 OpenAI 호환 API를 위해 `@ai-sdk/openai-compatible`을 사용합니다. +- `name`은 UI에 표시될 공급자 이름입니다. +- `options.baseURL`은 로컬 서버의 엔드포인트입니다. +- `models`는 모델 ID와 해당 구성을 매핑합니다. 모델 이름은 모델 선택 목록에 표시됩니다. --- -###### IO.NET +### IO.NET IO.NET은 다양한 사용 사례에 최적화된 17개의 모델을 제공합니다: -1. [IO.NET 콘솔](https://ai.io.net/)에 머리, 계정을 만들고 API 키를 생성합니다. +1. [IO.NET 콘솔](https://ai.io.net/)로 이동하여 계정을 만들고 API 키를 생성합니다. -2. `/connect` 명령을 실행하고 **IO.NET**를 검색하십시오. +2. `/connect` 명령을 실행하고 **IO.NET**을 검색하십시오. ```txt /connect @@ -1115,7 +1133,7 @@ IO.NET은 다양한 사용 사례에 최적화된 17개의 모델을 제공합 ### LM Studio -LM Studio를 통해 로컬 모델을 사용할 수 있습니다. +LM Studio를 통해 로컬 모델을 사용하도록 구성할 수 있습니다. ```json title="opencode.json" "lmstudio" {5, 6, 8, 10-14} { @@ -1139,19 +1157,19 @@ LM Studio를 통해 로컬 모델을 사용할 수 있습니다. 이 예제에서: -- `lmstudio`는 주문 공급자 ID입니다. 원하는 문자열이 될 수 있습니다. -- `npm`는 이 공급자를 위해 사용할 포장을 지정합니다. 여기, `@ai-sdk/openai-compatible`는 OpenAI 호환 API에 사용됩니다. -- `name`는 UI에 있는 공급자를 위한 전시 이름입니다. -- `options.baseURL`는 로컬 서버의 엔드포인트입니다. -- `models`는 모델 ID를 구성하는 맵입니다. 모델 이름은 모델 선택 목록에 표시됩니다. +- `lmstudio`는 사용자 정의 공급자 ID입니다. 원하는 문자열로 지정할 수 있습니다. +- `npm`은 이 공급자에 사용할 패키지를 지정합니다. 여기서는 OpenAI 호환 API를 위해 `@ai-sdk/openai-compatible`을 사용합니다. +- `name`은 UI에 표시될 공급자 이름입니다. +- `options.baseURL`은 로컬 서버의 엔드포인트입니다. +- `models`는 모델 ID와 해당 구성을 매핑합니다. 모델 이름은 모델 선택 목록에 표시됩니다. --- -## Moonshot AI +### Moonshot AI -Moonshot AI에서 Kimi K2 사용 : +Moonshot AI에서 Kimi K2를 사용하려면: -1. [Moonshot AI 콘솔](https://platform.moonshot.ai/console)에 머리, 계정을 만들고, ** API 키**를 클릭합니다. +1. [Moonshot AI 콘솔](https://platform.moonshot.ai/console)로 이동하여 계정을 만들고 **Create API key**를 클릭합니다. 2. `/connect` 명령을 실행하고 **Moonshot AI**를 검색하십시오. @@ -1168,7 +1186,7 @@ Moonshot AI에서 Kimi K2 사용 : └ enter ``` -4. `/models` 명령을 실행하여 Kimi K2 . +4. `/models` 명령을 실행하여 *Kimi K2*를 선택하십시오. ```txt /models @@ -1176,9 +1194,9 @@ Moonshot AI에서 Kimi K2 사용 : --- -## MiniMax +### MiniMax -1. [MiniMax API 콘솔](https://platform.minimax.io/login)에 머리, 계정을 만들고 API 키를 생성합니다. +1. [MiniMax API 콘솔](https://platform.minimax.io/login)로 이동하여 계정을 만들고 API 키를 생성합니다. 2. `/connect` 명령을 실행하고 **MiniMax**를 검색하십시오. @@ -1195,7 +1213,7 @@ Moonshot AI에서 Kimi K2 사용 : └ enter ``` -4. `/models` 명령을 실행하여 같은 모델을 선택하십시오 M2.1 . +4. `/models` 명령을 실행하여 모델(예: _M2.1_)을 선택하십시오. ```txt /models @@ -1203,9 +1221,9 @@ Moonshot AI에서 Kimi K2 사용 : --- -## Nebius Token Factory +### Nebius Token Factory -1. [Nebius Token Factory 콘솔](https://tokenfactory.nebius.com/)에 머리, 계정을 만들고, ** 키 추가 **. +1. [Nebius Token Factory 콘솔](https://tokenfactory.nebius.com/)로 이동하여 계정을 만들고 **Add Key**를 클릭합니다. 2. `/connect` 명령을 실행하고 **Nebius Token Factory**를 검색하십시오. @@ -1213,7 +1231,7 @@ Moonshot AI에서 Kimi K2 사용 : /connect ``` -3. Nebius 토큰 공장 API 키를 입력하십시오. +3. Nebius Token Factory API 키를 입력하십시오. ```txt ┌ API key @@ -1222,7 +1240,7 @@ Moonshot AI에서 Kimi K2 사용 : └ enter ``` -4. `/models` 명령을 실행하여 같은 모델을 선택하십시오 Kimi K2 Instruct . +4. `/models` 명령을 실행하여 모델(예: _Kimi K2 Instruct_)을 선택하십시오. ```txt /models @@ -1230,12 +1248,12 @@ Moonshot AI에서 Kimi K2 사용 : --- -#### Ollama +### Ollama -Ollama를 통해 로컬 모델을 사용할 수 있습니다. +Ollama를 통해 로컬 모델을 사용하도록 구성할 수 있습니다. :::tip -Ollama는 opencode를 자동으로 구성할 수 있습니다. 자세한 내용은 [Ollama 통합 문서](https://docs.ollama.com/integrations/opencode)를 참조하십시오. +Ollama는 OpenCode에 대해 자동으로 구성될 수 있습니다. 자세한 내용은 [Ollama 통합 문서](https://docs.ollama.com/integrations/opencode)를 참조하십시오. ::: ```json title="opencode.json" "ollama" {5, 6, 8, 10-14} @@ -1260,35 +1278,35 @@ Ollama는 opencode를 자동으로 구성할 수 있습니다. 자세한 내용 이 예제에서: -- `ollama`는 주문 공급자 ID입니다. 원하는 문자열이 될 수 있습니다. -- `npm`는 이 공급자를 위해 사용할 포장을 지정합니다. 여기, `@ai-sdk/openai-compatible`는 OpenAI 호환 API에 사용됩니다. -- `name`는 UI에 있는 공급자를 위한 전시 이름입니다. -- `options.baseURL`는 로컬 서버의 엔드포인트입니다. -- `models`는 모델 ID를 구성하는 맵입니다. 모델 이름은 모델 선택 목록에 표시됩니다. +- `ollama`는 사용자 정의 공급자 ID입니다. 원하는 문자열로 지정할 수 있습니다. +- `npm`은 이 공급자에 사용할 패키지를 지정합니다. 여기서는 OpenAI 호환 API를 위해 `@ai-sdk/openai-compatible`을 사용합니다. +- `name`은 UI에 표시될 공급자 이름입니다. +- `options.baseURL`은 로컬 서버의 엔드포인트입니다. +- `models`는 모델 ID와 해당 구성을 매핑합니다. 모델 이름은 모델 선택 목록에 표시됩니다. :::tip -도구 호출이 작동하지 않는 경우, Ollama에서 `num_ctx` 증가. 주위 시작 16k - 32k. +도구 호출이 작동하지 않는 경우, Ollama에서 `num_ctx`를 늘려보십시오. 16k - 32k 정도에서 시작하십시오. ::: --- -## Ollama Cloud +### Ollama Cloud -opencode로 Ollama Cloud를 사용하려면: +OpenCode로 Ollama Cloud를 사용하려면: -1. [https://ollama.com/](https://ollama.com/) 이상 머리와 로그인하거나 계정을 만들 수 있습니다. +1. [https://ollama.com/](https://ollama.com/)으로 이동하여 로그인하거나 계정을 만듭니다. -2. Navigate to**Settings** > **Keys** 및 click **API Key**를 추가하여 새로운 API 키 생성. +2. **Settings** > **Keys**로 이동하여 **Add API Key**를 클릭해 새 API 키를 생성합니다. -3. opencode에서 사용을 위한 API 열쇠를 복사하십시오. +3. OpenCode에서 사용할 API 키를 복사합니다. -4. `/connect` 명령을 실행하고 ** Ollama Cloud**를 검색하십시오. +4. `/connect` 명령을 실행하고 **Ollama Cloud**를 검색하십시오. ```txt /connect ``` -5. Ollama Cloud API 키 입력. +5. Ollama Cloud API 키를 입력하십시오. ```txt ┌ API key @@ -1297,7 +1315,7 @@ opencode로 Ollama Cloud를 사용하려면: └ enter ``` -6. ** 중요 **: opencode의 클라우드 모델을 사용하기 전에, 로컬 모델 정보를 끌어야 합니다: +6. **중요**: OpenCode에서 클라우드 모델을 사용하기 전에, 로컬에서 모델 정보를 가져와야 합니다: ```bash ollama pull gpt-oss:20b-cloud @@ -1315,14 +1333,13 @@ opencode로 Ollama Cloud를 사용하려면: [ChatGPT Plus 또는 Pro](https://chatgpt.com/pricing)에 가입하는 것이 좋습니다. -1. 가입하면 `/connect` 명령을 실행하고 OpenAI를 선택하십시오. +1. 가입 후 `/connect` 명령을 실행하고 OpenAI를 선택하십시오. ```txt /connect ``` -2. **ChatGPT Plus/Pro** 옵션을 선택하고 브라우저를 열 수 있습니다. - 자주 묻는 질문 +2. **ChatGPT Plus/Pro** 옵션을 선택하면 브라우저가 열리고 인증을 요청합니다. ```txt ┌ Select auth method @@ -1332,7 +1349,7 @@ opencode로 Ollama Cloud를 사용하려면: └ ``` -3. 이제 모든 OpenAI 모델은 `/models` 명령을 사용할 때 사용할 수 있어야합니다. +3. 이제 `/models` 명령을 사용할 때 모든 OpenAI 모델을 사용할 수 있습니다. ```txt /models @@ -1340,23 +1357,23 @@ opencode로 Ollama Cloud를 사용하려면: ##### API 키 사용 -API 키가 이미 있다면 ** 수동으로 API 키**를 입력하고 terminal에서 붙여넣을 수 있습니다. +이미 API 키가 있다면 **Manually enter API Key**를 선택하고 터미널에 붙여넣을 수 있습니다. --- -## OpenCode Zen +### OpenCode Zen -OpenCode Zen은 opencode 팀에서 제공하는 테스트 및 검증된 모델 목록입니다. [더 알아보기](/docs/zen). +OpenCode Zen은 OpenCode 팀에서 제공하는 테스트 및 검증된 모델 목록입니다. [더 알아보기](/docs/zen). -1. 로그인 **OpenCode Zen** and click**Create API Key**. +1. **OpenCode Zen**에 로그인하고 **Create API Key**를 클릭합니다. -2. `/connect` 명령을 실행하고 **OpenCode Zen**를 검색하십시오. +2. `/connect` 명령을 실행하고 **OpenCode Zen**을 검색하십시오. ```txt /connect ``` -3. opencode API 키를 입력하십시오. +3. OpenCode API 키를 입력하십시오. ```txt ┌ API key @@ -1365,7 +1382,7 @@ OpenCode Zen은 opencode 팀에서 제공하는 테스트 및 검증된 모델 └ enter ``` -4. `/models` 명령을 실행하여 같은 모델을 선택하십시오 Qwen 3 Coder 480B . +4. `/models` 명령을 실행하여 모델(예: _Qwen 3 Coder 480B_)을 선택하십시오. ```txt /models @@ -1373,9 +1390,9 @@ OpenCode Zen은 opencode 팀에서 제공하는 테스트 및 검증된 모델 --- -## OpenRouter +### OpenRouter -1. [OpenRouter 대시보드](https://openrouter.ai/settings/keys)에 머리, click ** API Key**를 클릭하고 키를 복사합니다. +1. [OpenRouter 대시보드](https://openrouter.ai/settings/keys)로 이동하여 **Create API Key**를 클릭하고 키를 복사합니다. 2. `/connect` 명령을 실행하고 OpenRouter를 검색하십시오. @@ -1383,7 +1400,7 @@ OpenCode Zen은 opencode 팀에서 제공하는 테스트 및 검증된 모델 /connect ``` -3. 공급자를 위한 API 열쇠를 입력하십시오. +3. API 키를 입력하십시오. ```txt ┌ API key @@ -1392,28 +1409,28 @@ OpenCode Zen은 opencode 팀에서 제공하는 테스트 및 검증된 모델 └ enter ``` -4. 많은 OpenRouter 모델은 기본적으로 `/models` 명령을 실행하여 원하는 것을 선택합니다. +4. 많은 OpenRouter 모델은 기본적으로 미리 로드되어 있으므로 `/models` 명령을 실행하여 원하는 것을 선택하십시오. ```txt /models ``` -opencode config를 통해 추가 모델을 추가할 수 있습니다. + OpenCode 구성을 통해 모델을 추가할 수도 있습니다. -```json title="opencode.json" {6} -{ - "$schema": "https://opencode.ai/config.json", - "provider": { - "openrouter": { - "models": { - "somecoolnewmodel": {} - } - } - } -} -``` + ```json title="opencode.json" {6} + { + "$schema": "https://opencode.ai/config.json", + "provider": { + "openrouter": { + "models": { + "somecoolnewmodel": {} + } + } + } + } + ``` -5. 당신은 또한 당신의 opencode config를 통해 그들을 주문을 받아서 만들 수 있습니다. 공급자 지정의 예입니다. +5. 또한 OpenCode 구성을 통해 사용자 정의할 수도 있습니다. 다음은 공급자 순서를 지정하는 예입니다. ```json title="opencode.json" { @@ -1439,12 +1456,12 @@ opencode config를 통해 추가 모델을 추가할 수 있습니다. ### SAP AI Core -SAP AI Core는 OpenAI, Anthropic, Google, Amazon, Meta, Mistral 및 AI21의 40+ 모델에 대한 액세스를 제공합니다. +SAP AI Core는 OpenAI, Anthropic, Google, Amazon, Meta, Mistral 및 AI21의 40개 이상의 모델에 대한 액세스를 제공합니다. -1. [SAP BTP Cockpit](https://account.hana.ondemand.com/)로 이동하여 SAP AI Core 서비스 인스턴스로 이동하고 서비스 키를 만듭니다. +1. [SAP BTP Cockpit](https://account.hana.ondemand.com/)으로 이동하여 SAP AI Core 서비스 인스턴스로 이동하고 서비스 키를 만듭니다. :::tip - 서비스 키는 `clientid`, `clientsecret`, `url` 및 `serviceurls.AI_API_URL`를 포함하는 JSON 객체입니다. **Services** > **Instances 및 Subscriptions** 아래 AI Core 인스턴스를 찾을 수 있습니다. + 서비스 키는 `clientid`, `clientsecret`, `url` 및 `serviceurls.AI_API_URL`을 포함하는 JSON 객체입니다. **Services** > **Instances and Subscriptions** 아래에서 AI Core 인스턴스를 찾을 수 있습니다. ::: 2. `/connect` 명령을 실행하고 **SAP AI Core**를 검색하십시오. @@ -1462,29 +1479,29 @@ SAP AI Core는 OpenAI, Anthropic, Google, Amazon, Meta, Mistral 및 AI21의 40+ └ enter ``` -또는 `AICORE_SERVICE_KEY` 환경 변수를 설정: + 또는 `AICORE_SERVICE_KEY` 환경 변수를 설정합니다: -```bash + ```bash AICORE_SERVICE_KEY='{"clientid":"...","clientsecret":"...","url":"...","serviceurls":{"AI_API_URL":"..."}}' opencode -``` + ``` -또는 bash 프로파일에 추가: + 또는 bash 프로필에 추가합니다: -```bash title="~/.bash_profile" + ```bash title="~/.bash_profile" export AICORE_SERVICE_KEY='{"clientid":"...","clientsecret":"...","url":"...","serviceurls":{"AI_API_URL":"..."}}' -``` + ``` -4. 선택적으로 배치 ID 및 자원 그룹: +4. 선택적으로 배포 ID 및 리소스 그룹을 설정합니다: ```bash AICORE_DEPLOYMENT_ID=your-deployment-id AICORE_RESOURCE_GROUP=your-resource-group opencode ``` :::note - 이 설정은 선택 사항이며 SAP AI Core 설정에 따라 구성해야합니다. + 이 설정은 선택 사항이며 SAP AI Core 설정에 따라 구성해야 합니다. ::: -5. `/models` 명령을 실행하여 40+ 유효한 모형에서 선택하십시오. +5. `/models` 명령을 실행하여 40개 이상의 사용 가능한 모델 중에서 선택하십시오. ```txt /models @@ -1492,11 +1509,44 @@ SAP AI Core는 OpenAI, Anthropic, Google, Amazon, Meta, Mistral 및 AI21의 40+ --- -### OVHcloud AI 엔드포인트 +### STACKIT -1. [OVHcloud 패널](https://ovh.com/manager)에 머리. `Public Cloud` 섹션으로 이동, `AI & Machine Learning` > `AI Endpoints` 및 `API Keys` 탭에서, ** 새로운 API 키 활성화 **. +STACKIT AI Model Serving은 Llama, Mistral, Qwen과 같은 LLM에 초점을 맞추고 유럽 인프라에서 데이터 주권을 최대한 보장하는 완전 관리형 AI 모델 호스팅 환경을 제공합니다. -2. `/connect` 명령을 실행하고 ** OVHcloud AI Endpoints**를 검색하십시오. +1. [STACKIT Portal](https://portal.stackit.cloud)로 이동하여 **AI Model Serving**으로 이동한 다음 프로젝트의 인증 토큰을 만듭니다. + + :::tip + 인증 토큰을 만들기 전에 STACKIT 고객 계정, 사용자 계정 및 프로젝트가 필요합니다. + ::: + +2. `/connect` 명령을 실행하고 **STACKIT**을 검색하십시오. + + ```txt + /connect + ``` + +3. STACKIT AI Model Serving 인증 토큰을 입력하십시오. + + ```txt + ┌ API key + │ + │ + └ enter + ``` + +4. `/models` 명령을 실행하여 _Qwen3-VL 235B_ 또는 *Llama 3.3 70B*와 같은 사용 가능한 모델을 선택하십시오. + + ```txt + /models + ``` + +--- + +### OVHcloud AI Endpoints + +1. [OVHcloud 패널](https://ovh.com/manager)로 이동합니다. `Public Cloud` 섹션으로 이동하여 `AI & Machine Learning` > `AI Endpoints`로 간 뒤 `API Keys` 탭에서 **Create a new API key**를 클릭합니다. + +2. `/connect` 명령을 실행하고 **OVHcloud AI Endpoints**를 검색하십시오. ```txt /connect @@ -1511,7 +1561,7 @@ SAP AI Core는 OpenAI, Anthropic, Google, Amazon, Meta, Mistral 및 AI21의 40+ └ enter ``` -4. `/models` 명령을 실행하여 같은 모델을 선택하십시오 gpt-oss-120b . +4. `/models` 명령을 실행하여 모델(예: _gpt-oss-120b_)을 선택하십시오. ```txt /models @@ -1521,9 +1571,9 @@ SAP AI Core는 OpenAI, Anthropic, Google, Amazon, Meta, Mistral 및 AI21의 40+ ### Scaleway -opencode를 사용하여 [Scaleway Generative APIs](https://www.scaleway.com/en/docs/generative-apis/)를 사용하려면: +OpenCode로 [Scaleway Generative APIs](https://www.scaleway.com/en/docs/generative-apis/)를 사용하려면: -1. [Scaleway 콘솔 IAM 설정](https://console.scaleway.com/iam/api-keys)를 통해 새로운 API 키 생성. +1. [Scaleway Console IAM 설정](https://console.scaleway.com/iam/api-keys)에서 새 API 키를 생성합니다. 2. `/connect` 명령을 실행하고 **Scaleway**를 검색하십시오. @@ -1540,7 +1590,7 @@ opencode를 사용하여 [Scaleway Generative APIs](https://www.scaleway.com/en/ └ enter ``` -4. `/models` 명령을 실행하여 같은 모델을 선택하십시오. devstral-2-123b-instruct-2512 또는 gpt-oss-120b . +4. `/models` 명령을 실행하여 모델(예: _devstral-2-123b-instruct-2512_ 또는 _gpt-oss-120b_)을 선택하십시오. ```txt /models @@ -1548,9 +1598,9 @@ opencode를 사용하여 [Scaleway Generative APIs](https://www.scaleway.com/en/ --- -## Together AI +### Together AI -1. [Together AI 콘솔](https://api.together.ai)에 머리, 계정을 만들고 ** 키 추가 **를 클릭합니다. +1. [Together AI 콘솔](https://api.together.ai)로 이동하여 계정을 만들고 **Add Key**를 클릭합니다. 2. `/connect` 명령을 실행하고 **Together AI**를 검색하십시오. @@ -1558,7 +1608,7 @@ opencode를 사용하여 [Scaleway Generative APIs](https://www.scaleway.com/en/ /connect ``` -3. 함께 AI API 키를 입력하십시오. +3. Together AI API 키를 입력하십시오. ```txt ┌ API key @@ -1567,7 +1617,7 @@ opencode를 사용하여 [Scaleway Generative APIs](https://www.scaleway.com/en/ └ enter ``` -4. `/models` 명령을 실행하여 같은 모델을 선택하십시오 Kimi K2 Instruct . +4. `/models` 명령을 실행하여 모델(예: _Kimi K2 Instruct_)을 선택하십시오. ```txt /models @@ -1575,17 +1625,17 @@ opencode를 사용하여 [Scaleway Generative APIs](https://www.scaleway.com/en/ --- -## Venice AI +### Venice AI -1. [Venice AI 콘솔](https://venice.ai)에 머리, 계정을 만들고 API 키를 생성합니다. +1. [Venice AI 콘솔](https://venice.ai)로 이동하여 계정을 만들고 API 키를 생성합니다. -2. `/connect` 명령을 실행하고 **Venice AI **를 검색하십시오. +2. `/connect` 명령을 실행하고 **Venice AI**를 검색하십시오. ```txt /connect ``` -3. 베니스 AI API 열쇠를 입력하십시오. +3. Venice AI API 키를 입력하십시오. ```txt ┌ API key @@ -1594,7 +1644,7 @@ opencode를 사용하여 [Scaleway Generative APIs](https://www.scaleway.com/en/ └ enter ``` -4. `/models` 명령을 실행하여 같은 모델을 선택하십시오 Llama 3.3 70B . +4. `/models` 명령을 실행하여 모델(예: _Llama 3.3 70B_)을 선택하십시오. ```txt /models @@ -1602,11 +1652,11 @@ opencode를 사용하여 [Scaleway Generative APIs](https://www.scaleway.com/en/ --- -## Vercel AI 게이트웨이 +### Vercel AI Gateway -Vercel AI를 게이트웨이는 OpenAI, Anthropic, Google, xAI 등에서 모델에 액세스할 수 있습니다. 모델은 Markup없이 목록 가격에서 제공됩니다. +Vercel AI Gateway는 OpenAI, Anthropic, Google, xAI 등의 모델에 액세스할 수 있으며, 통합된 엔드포인트를 통해 더 많은 기능을 제공합니다. 모델은 마크업 없이 정가로 제공됩니다. -1. [Vercel 대시보드](https://vercel.com/)에 머리, **AI Gateway** 탭으로 이동하고, **API 키**를 클릭하여 새로운 API 키 생성. +1. [Vercel 대시보드](https://vercel.com/)로 이동하여 **AI Gateway** 탭으로 간 뒤, **API keys**를 클릭하여 새 API 키를 생성합니다. 2. `/connect` 명령을 실행하고 **Vercel AI Gateway**를 검색하십시오. @@ -1614,7 +1664,7 @@ Vercel AI를 게이트웨이는 OpenAI, Anthropic, Google, xAI 등에서 모델 /connect ``` -3. Vercel AI Gateway API 키 입력. +3. Vercel AI Gateway API 키를 입력하십시오. ```txt ┌ API key @@ -1623,13 +1673,13 @@ Vercel AI를 게이트웨이는 OpenAI, Anthropic, Google, xAI 등에서 모델 └ enter ``` -4. 모델을 선택하려면 `/models` 명령을 실행하십시오. +4. `/models` 명령을 실행하여 모델을 선택하십시오. ```txt /models ``` -opencode config를 통해 모델을 사용자 정의 할 수 있습니다. 공급자 routing 순서를 지정하는 예입니다. +OpenCode 구성을 통해 모델을 사용자 정의할 수도 있습니다. 다음은 공급자 라우팅 순서를 지정하는 예입니다. ```json title="opencode.json" { @@ -1648,19 +1698,19 @@ opencode config를 통해 모델을 사용자 정의 할 수 있습니다. 공 } ``` -몇몇 유용한 여정 선택권: +몇 가지 유용한 라우팅 옵션: | 옵션 | 설명 | | ------------------- | --------------------------------- | -| `order` | 공급자의 순서 | -| `only` | 특정 공급자 제한 | -| `zeroDataRetention` | 제로 데이터 보유 정책만 이용 가능 | +| `order` | 시도할 공급자 순서 | +| `only` | 특정 공급자로 제한 | +| `zeroDataRetention` | 데이터 보유 정책이 없는 곳만 사용 | --- ### xAI -1. [xAI 콘솔](https://console.x.ai/)에 머리, 계정을 만들고 API 키를 생성합니다. +1. [xAI 콘솔](https://console.x.ai/)로 이동하여 계정을 만들고 API 키를 생성합니다. 2. `/connect` 명령을 실행하고 **xAI**를 검색하십시오. @@ -1677,7 +1727,7 @@ opencode config를 통해 모델을 사용자 정의 할 수 있습니다. 공 └ enter ``` -4. `/models` 명령을 실행하여 같은 모델을 선택하십시오 Grok Beta . +4. `/models` 명령을 실행하여 모델(예: _Grok Beta_)을 선택하십시오. ```txt /models @@ -1687,15 +1737,15 @@ opencode config를 통해 모델을 사용자 정의 할 수 있습니다. 공 ### Z.AI -1. [Z.AI API 콘솔](https://z.ai/manage-apikey/apikey-list)에 머리, 계정을 만들고, **새로운 API 키**를 클릭합니다. +1. [Z.AI API 콘솔](https://z.ai/manage-apikey/apikey-list)로 이동하여 계정을 만들고 **Create a new API key**를 클릭합니다. -2. `/connect` 명령을 실행하고 ** Z.AI**를 검색하십시오. +2. `/connect` 명령을 실행하고 **Z.AI**를 검색하십시오. ```txt /connect ``` -**GLM 코딩 플랜**에 가입하면 **Z.AI 코딩 플랜**을 선택하십시오. + **GLM Coding Plan**에 가입했다면 **Z.AI Coding Plan**을 선택하십시오. 3. Z.AI API 키를 입력하십시오. @@ -1706,7 +1756,7 @@ opencode config를 통해 모델을 사용자 정의 할 수 있습니다. 공 └ enter ``` -4. `/models` 명령을 실행하여 같은 모델을 선택하십시오 GLM-4.7 . +4. `/models` 명령을 실행하여 모델(예: _GLM-4.7_)을 선택하십시오. ```txt /models @@ -1714,9 +1764,9 @@ opencode config를 통해 모델을 사용자 정의 할 수 있습니다. 공 --- -## ZenMux +### ZenMux -1. [ZenMux 대쉬보드](https://zenmux.ai/settings/keys)에 머리, click **Create API Key**, 키 복사. +1. [ZenMux 대시보드](https://zenmux.ai/settings/keys)로 이동하여 **Create API Key**를 클릭하고 키를 복사합니다. 2. `/connect` 명령을 실행하고 ZenMux를 검색하십시오. @@ -1724,7 +1774,7 @@ opencode config를 통해 모델을 사용자 정의 할 수 있습니다. 공 /connect ``` -3. 공급자를 위한 API 열쇠를 입력하십시오. +3. API 키를 입력하십시오. ```txt ┌ API key @@ -1733,38 +1783,38 @@ opencode config를 통해 모델을 사용자 정의 할 수 있습니다. 공 └ enter ``` -4. 많은 ZenMux 모델은 기본적으로 사전 로드되며 `/models` 명령을 실행하여 원하는 것을 선택합니다. +4. 많은 ZenMux 모델은 기본적으로 미리 로드되어 있으므로 `/models` 명령을 실행하여 원하는 것을 선택하십시오. ```txt /models ``` -opencode config를 통해 추가 모델을 추가할 수 있습니다. + OpenCode 구성을 통해 모델을 추가할 수도 있습니다. -```json title="opencode.json" {6} -{ - "$schema": "https://opencode.ai/config.json", - "provider": { - "zenmux": { - "models": { - "somecoolnewmodel": {} - } - } - } -} -``` + ```json title="opencode.json" {6} + { + "$schema": "https://opencode.ai/config.json", + "provider": { + "zenmux": { + "models": { + "somecoolnewmodel": {} + } + } + } + } + ``` --- ## 사용자 정의 공급자 -`/connect` 명령에 나열되지 않은 **OpenAI-compatible** 공급자를 추가하려면: +`/connect` 명령에 나열되지 않은 **OpenAI 호환** 공급자를 추가하려면: :::tip -opencode를 사용하여 OpenAI 호환 공급자를 사용할 수 있습니다. 가장 현대적인 AI 제공 업체는 OpenAI 호환 API를 제공합니다. +OpenCode에서 모든 OpenAI 호환 공급자를 사용할 수 있습니다. 대부분의 최신 AI 공급자는 OpenAI 호환 API를 제공합니다. ::: -1. `/connect` 명령을 실행하고 ** 다른**로 스크롤하십시오. +1. `/connect` 명령을 실행하고 **Other**로 스크롤하십시오. ```bash $ /connect @@ -1777,7 +1827,7 @@ opencode를 사용하여 OpenAI 호환 공급자를 사용할 수 있습니다. └ ``` -2. 공급자를 위한 유일한 ID를 입력하십시오. +2. 공급자를 위한 고유한 ID를 입력하십시오. ```bash $ /connect @@ -1790,10 +1840,10 @@ opencode를 사용하여 OpenAI 호환 공급자를 사용할 수 있습니다. ``` :::note - 기억에 남는 ID를 선택하면 구성 파일에서 이것을 사용할 수 있습니다. + 기억하기 쉬운 ID를 선택하십시오. 구성 파일에서 이 ID를 사용하게 됩니다. ::: -3. 공급자를 위한 당신의 API 열쇠를 입력하십시오. +3. 공급자의 API 키를 입력하십시오. ```bash $ /connect @@ -1829,23 +1879,23 @@ opencode를 사용하여 OpenAI 호환 공급자를 사용할 수 있습니다. } ``` -여기에 구성 옵션: + 구성 옵션은 다음과 같습니다: + - **npm**: 사용할 AI SDK 패키지. OpenAI 호환 공급자의 경우 `@ai-sdk/openai-compatible` + - **name**: UI에 표시될 이름 + - **models**: 사용 가능한 모델 + - **options.baseURL**: API 엔드포인트 URL + - **options.apiKey**: 인증을 사용하지 않는 경우 선택적으로 API 키 설정 + - **options.headers**: 선택적으로 사용자 정의 헤더 설정 -- **npm**: AI SDK 패키지, OpenAI 호환 공급자 `@ai-sdk/openai-compatible` -**name**: UI의 표시 이름. -- ** 모델**: 유효한 모델. -- **options.baseURL**: API 엔드포인트 URL. -- **options.apiKey**: 선택적으로 auth를 사용하지 않는 경우 API 키 설정. -- **options.headers**: 선택적으로 사용자 정의 헤더를 설정합니다. + 고급 옵션에 대한 자세한 내용은 아래 예제를 참조하십시오. -아래 예에서 고급 옵션에 더. - -5. `/models` 명령을 실행하고 사용자 정의 공급자와 모델은 선택 목록에서 나타납니다. +5. `/models` 명령을 실행하면 사용자 정의 공급자와 모델이 선택 목록에 나타납니다. --- ##### 예제 -다음은 `apiKey`, `headers` 및 모델 `limit` 옵션 설정 예입니다. +다음은 `apiKey`, `headers` 및 모델 `limit` 옵션을 설정하는 예입니다. ```json title="opencode.json" {9,11,17-20} { @@ -1877,11 +1927,12 @@ opencode를 사용하여 OpenAI 호환 공급자를 사용할 수 있습니다. 구성 세부 사항: -- **apiKey**: `env` 변수 구문을 사용하여 설정, [learn more](/docs/config#env-vars). -**headers**: 각 요청으로 전송된 사용자 정의 헤더. -- **limit.context**: 모델이 허용하는 최대 Input Tokens. -- **limit.output**: 모델이 생성할 수 있는 최대 Output Tokens. +- **apiKey**: `env` 변수 구문을 사용하여 설정, [더 알아보기](/docs/config#env-vars). +- **headers**: 각 요청과 함께 전송되는 사용자 정의 헤더. +- **limit.context**: 모델이 허용하는 최대 입력 토큰. +- **limit.output**: 모델이 생성할 수 있는 최대 출력 토큰. -`limit` 필드를 사용하면 opencode가 얼마나 많은 컨텍스트를 이해 할 수 있습니다. 표준 공급자는 model.dev에서 자동적으로 당깁니다. +`limit` 필드를 사용하면 OpenCode가 남은 컨텍스트 양을 파악할 수 있습니다. 표준 공급자는 models.dev에서 자동으로 이를 가져옵니다. --- @@ -1889,13 +1940,11 @@ opencode를 사용하여 OpenAI 호환 공급자를 사용할 수 있습니다. 공급자 구성에 문제가 있는 경우 다음을 확인하십시오. -1. **주의 설정 확인 **: `opencode auth list`를 실행하여 자격 증명을 볼 수 있습니다. - 공급자는 config에 추가됩니다. - -이것은 Amazon Bedrock과 같은 공급자에 적용되지 않습니다. 환경 변수에 의존합니다. +1. **인증 설정 확인**: `opencode auth list`를 실행하여 공급자의 자격 증명이 구성에 추가되었는지 확인하십시오. -2. 주문 공급자를 위해, opencode config를 검사하고: + Amazon Bedrock과 같이 인증을 위해 환경 변수에 의존하는 공급자에는 적용되지 않습니다. -- `/connect` 명령에 사용되는 공급자 ID가 opencode config에서 ID를 일치시킵니다. -- 오른쪽 npm 패키지는 공급자에 사용됩니다. 예를 들어 Cerebras의 `@ai-sdk/cerebras`를 사용합니다. 그리고 다른 모든 OpenAI 호환 공급자를 위해, 사용 `@ai-sdk/openai-compatible`. -- 올바른 API 엔드포인트는 `options.baseURL` 필드에 사용됩니다. +2. 사용자 정의 공급자의 경우, OpenCode 구성을 확인하고 다음을 수행하십시오: + - `/connect` 명령에 사용된 공급자 ID가 OpenCode 구성의 ID와 일치하는지 확인하십시오. + - 공급자에 올바른 npm 패키지가 사용되었는지 확인하십시오. 예를 들어 Cerebras에는 `@ai-sdk/cerebras`를 사용하고, 다른 모든 OpenAI 호환 공급자에는 `@ai-sdk/openai-compatible`을 사용하십시오. + - `options.baseURL` 필드에 올바른 API 엔드포인트가 사용되었는지 확인하십시오. diff --git a/packages/web/src/content/docs/ko/sdk.mdx b/packages/web/src/content/docs/ko/sdk.mdx index f6d43c136d6..a5d12d1ab05 100644 --- a/packages/web/src/content/docs/ko/sdk.mdx +++ b/packages/web/src/content/docs/ko/sdk.mdx @@ -117,13 +117,85 @@ try { --- +## 구조화된 출력 + +JSON 스키마와 함께 `format`을 지정하여 모델에서 구조화된 JSON 출력을 요청할 수 있습니다. 모델은 `StructuredOutput` 도구를 사용하여 스키마와 일치하는 검증된 JSON을 반환합니다. + +### 기본 사용법 + +```typescript +const result = await client.session.prompt({ + path: { id: sessionId }, + body: { + parts: [{ type: "text", text: "Research Anthropic and provide company info" }], + format: { + type: "json_schema", + schema: { + type: "object", + properties: { + company: { type: "string", description: "Company name" }, + founded: { type: "number", description: "Year founded" }, + products: { + type: "array", + items: { type: "string" }, + description: "Main products", + }, + }, + required: ["company", "founded"], + }, + }, + }, +}) + +// Access the structured output +console.log(result.data.info.structured_output) +// { company: "Anthropic", founded: 2021, products: ["Claude", "Claude API"] } +``` + +### 출력 형식 유형 + +| 유형 | 설명 | +| ------------- | ------------------------------------------------- | +| `text` | 기본값. 표준 텍스트 응답 (구조화된 출력 없음) | +| `json_schema` | 제공된 스키마와 일치하는 검증된 JSON을 반환합니다 | + +### JSON 스키마 형식 + +`type: 'json_schema'`를 사용할 때 다음을 제공하십시오: + +| 필드 | 유형 | 설명 | +| ------------ | --------------- | ------------------------------------------- | +| `type` | `'json_schema'` | 필수. JSON 스키마 모드를 지정합니다 | +| `schema` | `object` | 필수. 출력 구조를 정의하는 JSON 스키마 객체 | +| `retryCount` | `number` | 선택 사항. 검증 재시도 횟수 (기본값: 2) | + +### 오류 처리 + +모델이 모든 재시도 후에도 유효한 구조화된 출력을 생성하지 못하면 응답에 `StructuredOutputError`가 포함됩니다: + +```typescript +if (result.data.info.error?.name === "StructuredOutputError") { + console.error("Failed to produce structured output:", result.data.info.error.message) + console.error("Attempts:", result.data.info.error.retries) +} +``` + +### 모범 사례 + +1. **명확한 설명 제공**: 모델이 추출할 데이터를 이해하는 데 도움이 되도록 스키마 속성에 명확한 설명을 제공하십시오. +2. **`required` 사용**: 필수 필드를 지정하려면 `required`를 사용하십시오. +3. **스키마를 집중적으로 유지**: 복잡한 중첩 스키마는 모델이 올바르게 채우기 더 어려울 수 있습니다. +4. **적절한 `retryCount` 설정**: 복잡한 스키마의 경우 늘리고 단순한 스키마의 경우 줄이십시오. + +--- + ## API SDK는 type-safe 클라이언트를 통해 모든 서버 API를 노출합니다. --- -## 글로벌 +### 글로벌 | 메서드 | 설명 | 응답 | | ----------------- | ---------------------- | ------------------------------------ | @@ -142,10 +214,10 @@ console.log(health.data.version) ### 앱 -| 방법 | 설명 | 응답 | -| -------------- | ------------------------- | ----------------------------------------------- | -| `app.log()` | 로그 항목 작성 | `boolean` | -| `app.agents()` | 이용 가능한 모든 에이전트 | 에이전트[] | +| 방법 | 설명 | 응답 | +| -------------- | ------------------------- | ------------------------------------------- | +| `app.log()` | 로그 항목 작성 | `boolean` | +| `app.agents()` | 이용 가능한 모든 에이전트 | Agent[] | --- @@ -167,7 +239,7 @@ const agents = await client.app.agents() --- -## 프로젝트 +### 프로젝트 | 방법 | 설명 | 응답 | | ------------------- | ----------------------- | --------------------------------------------- | @@ -205,7 +277,7 @@ const pathInfo = await client.path.get() --- -#### 구성 +### 구성 | 방법 | 설명 | 응답 | | -------------------- | -------------------------- | ----------------------------------------------------------------------------------------------------- | @@ -224,29 +296,29 @@ const { providers, default: defaults } = await client.config.providers() --- -## 세션 - -| 메서드 | 설명 | 비고 | -| ---------------------------------------------------------- | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | -| `session.list()` | 세션 일람 | Session[] | -| `session.get({ path })` | 세션 가져 오기 | Session | -| `session.children({ path })` | 하위 세션 목록 | 반품 Session | -| `session.create({ body })` | 세션 만들기 | 리턴 Session | -| `session.delete({ path })` | 세션 삭제 | `boolean` 반품 | -| `session.update({ path, body })` | 업데이트 세션 속성 | 반품 Session | -| `session.init({ path, body })` | 앱 초기화 및 `AGENTS.md` 분석 | `boolean`를 반환 | -| `session.abort({ path })` | 운영 중인 세션 | 반품 `boolean` | -| `session.share({ path })` | 공유 세션 | 반품 Session | -| `session.unshare({ path })` | 공유 세션 | 반품 Session | -| `session.summarize({ path, body })` | 세션 요약 | 반품 `boolean` | -| `session.messages({ path })` | 세션의 메시지 목록 | `{ info: `Message`, parts: `Part`}[]` | -| `session.message({ path })` | 메시지 상세정보 | 반품 `{ info: `Message`, parts: `Part[]`}` | -| `session.prompt({ path, body })` | prompt 메시지 보내기 | `body.noReply: true` 반환 UserMessage (콘텍스트 전용). 기본 반환 AssistantMessage 에 AI 응답 | -| `session.command({ path, body })` | 세션으로 명령을 전송 | `{ info: `AssistantMessage`, parts: `Part[]`}` | -| `session.shell({ path, body })` | shell 명령을 실행 | AssistantMessage | -| `session.revert({ path, body })` | 메시지 다시 변환 | Session | -| `session.unrevert({ path })` | 메시지 되돌리기 취소 | 반품 Session | -| `postSessionByIdPermissionsByPermissionId({ path, body })` | 허가 요청 대응 | 반품 `boolean` | +### 세션 + +| 메서드 | 설명 | 비고 | +| ---------------------------------------------------------- | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `session.list()` | 세션 일람 | Session[] | +| `session.get({ path })` | 세션 가져 오기 | Session | +| `session.children({ path })` | 하위 세션 목록 | 반품 Session | +| `session.create({ body })` | 세션 만들기 | 리턴 Session | +| `session.delete({ path })` | 세션 삭제 | `boolean` 반품 | +| `session.update({ path, body })` | 업데이트 세션 속성 | 반품 Session | +| `session.init({ path, body })` | 앱 초기화 및 `AGENTS.md` 분석 | `boolean`를 반환 | +| `session.abort({ path })` | 운영 중인 세션 | 반품 `boolean` | +| `session.share({ path })` | 공유 세션 | 반품 Session | +| `session.unshare({ path })` | 공유 세션 취소 | 반품 Session | +| `session.summarize({ path, body })` | 세션 요약 | 반품 `boolean` | +| `session.messages({ path })` | 세션의 메시지 목록 | `{ info: `Message`, parts: `Part`}[]` | +| `session.message({ path })` | 메시지 상세정보 | 반품 `{ info: `Message`, parts: `Part[]`}` | +| `session.prompt({ path, body })` | prompt 메시지 보내기 | `body.noReply: true`는 UserMessage(컨텍스트 전용)를 반환합니다. 기본값은 AI 응답과 함께 AssistantMessage를 반환합니다. [구조화된 출력](#구조화된-출력)을 위한 `body.outputFormat`을 지원합니다 | +| `session.command({ path, body })` | 세션으로 명령을 전송 | `{ info: `AssistantMessage`, parts: `Part[]`}` | +| `session.shell({ path, body })` | shell 명령을 실행 | AssistantMessage | +| `session.revert({ path, body })` | 메시지 다시 변환 | Session | +| `session.unrevert({ path })` | 메시지 되돌리기 취소 | 반품 Session | +| `postSessionByIdPermissionsByPermissionId({ path, body })` | 허가 요청 대응 | 반품 `boolean` | --- @@ -281,7 +353,7 @@ await client.session.prompt({ --- -## 파일 +### 파일 | 방법 | 설명 | 응답 | | ------------------------- | ---------------------------- | -------------------------------------------------------------------------------------- | @@ -322,7 +394,7 @@ const content = await client.file.read({ --- -#### TUI +### TUI | 방법 | 설명 | 응답 | | ------------------------------ | ------------------------ | --------- | @@ -353,7 +425,7 @@ await client.tui.showToast({ --- -##### 인증 +### 인증 | 방법 | 설명 | 응답 | | ------------------- | -------------- | --------- | @@ -372,7 +444,7 @@ await client.auth.set({ --- -## 이벤트 +### 이벤트 | 방법 | 설명 | 응답 | | ------------------- | ----------------------- | ----------------------- | diff --git a/packages/web/src/content/docs/ko/themes.mdx b/packages/web/src/content/docs/ko/themes.mdx index b83b6201702..af148eda10a 100644 --- a/packages/web/src/content/docs/ko/themes.mdx +++ b/packages/web/src/content/docs/ko/themes.mdx @@ -3,28 +3,27 @@ title: 테마 description: 내장 테마를 선택하거나 자신만의 테마를 정의하세요. --- -opencode를 사용하면 여러 내장 테마 중 하나에서 선택할 수 있으며 terminal 테마에 적응하는 테마를 사용하거나 사용자 정의 테마를 정의 할 수 있습니다. +OpenCode를 사용하면 여러 내장 테마 중 하나에서 선택할 수 있으며 terminal 테마에 적응하는 테마를 사용하거나 사용자 정의 테마를 정의 할 수 있습니다. -기본적으로 opencode는 자체 `opencode` 테마를 사용합니다. +기본적으로 OpenCode는 자체 `opencode` 테마를 사용합니다. --- ## 터미널 요구 사항 -자신의 풀 컬러 팔레트로 올바르게 표시하려면 terminal을 지원해야합니다 ** truecolor** (24 비트 색상). 대부분의 현대 terminal은 기본적으로 이것을 지원합니다, 그러나 당신은 그것을 가능하게 할 필요가 있을지도 모릅니다: +테마가 전체 색상 팔레트로 올바르게 표시되려면 터미널이 **truecolor** (24비트 색상)를 지원해야 합니다. 대부분의 최신 터미널은 기본적으로 이를 지원하지만, 활성화해야 할 수도 있습니다: --**체크 지원**: `echo $COLORTERM` - 그것은 `truecolor` 또는 `24bit`를 출력해야 합니다 +- **지원 확인**: `echo $COLORTERM` 실행 - `truecolor` 또는 `24bit`가 출력되어야 합니다. +- **truecolor 활성화**: 셸 프로필에서 환경 변수 `COLORTERM=truecolor`를 설정하십시오. +- **터미널 호환성**: 터미널 에뮬레이터가 24비트 색상을 지원하는지 확인하십시오 (iTerm2, Alacritty, Kitty, Windows Terminal 및 최신 버전의 GNOME Terminal 등 대부분의 최신 터미널이 지원함). -- ** truecolor 사용 가능**: shell 프로파일에서 환경 변수 `COLORTERM=truecolor`를 설정 -- **Terminal 호환성 **: terminal 에뮬레이터 지원 24 비트 색상 (iTerm2, Alacritty, Kitty, Windows Terminal 및 GNOME Terminal의 최신 버전) - -truecolor 지원 없이, 테마는 감소된 색깔 정확도로 나타날지도 모릅니다 또는 가장 가까운 256 색깔 대류로 뒤떨어질지도 모릅니다. +truecolor 지원이 없으면 테마가 감소된 색상 정확도로 표시되거나 가장 가까운 256색 근사치로 대체될 수 있습니다. --- ## 내장 테마 -opencode는 여러 내장 테마와 함께 제공됩니다. +OpenCode는 여러 내장 테마와 함께 제공됩니다. | 이름 | 설명 | | ---------------------- | ------------------------------------------------------------------- | @@ -46,27 +45,27 @@ opencode는 여러 내장 테마와 함께 제공됩니다. ## 시스템 테마 -`system` 테마는 terminal의 색깔 계획에 자동적으로 적응시키기 위하여 디자인됩니다. 고정 색상을 사용하는 전통적인 테마와 달리, system 테마: +`system` 테마는 터미널의 색상 스키마에 자동으로 적응하도록 설계되었습니다. 고정 색상을 사용하는 기존 테마와 달리, system 테마는: -- **그레이스케일**: terminal의 배경 색상을 기반으로 사용자 정의 회색 가늠자를 만들고 최적의 대조를 보장합니다. -- ** ANSI 색상 사용 ** : terminal의 색상 팔레트를 존중하는 구문 강조 및 UI 요소에 대한 표준 ANSI 색상 (0-15). -- ** terminal 기본 사항**: `none` 텍스트 및 배경 색상을 사용하여 terminal의 네이티브 외관을 유지합니다. +- **그레이스케일 생성**: 터미널의 배경 색상을 기반으로 사용자 정의 그레이스케일을 생성하여 최적의 대비를 보장합니다. +- **ANSI 색상 사용**: 구문 강조 및 UI 요소에 표준 ANSI 색상(0-15)을 활용하여 터미널의 색상 팔레트를 존중합니다. +- **터미널 기본값 유지**: 텍스트 및 배경 색상에 `none`을 사용하여 터미널의 기본 모양을 유지합니다. -시스템 테마는 사용자를위한 것입니다 : +시스템 테마는 다음과 같은 사용자에게 적합합니다: -- opencode가 terminal의 외관과 일치해야 합니다. -- 사용자 정의 terminal 색상 구성 -- 모든 terminal 응용 분야의 일관된 모습 +- OpenCode가 터미널의 모양과 일치하기를 원하는 경우 +- 사용자 정의 터미널 색상 스키마를 사용하는 경우 +- 모든 터미널 애플리케이션에서 일관된 모양을 선호하는 경우 --- ## 테마 사용 -테마를 `/theme` 명령어로 선택하여 테마를 선택할 수 있습니다. 또는 [config](/docs/config)에서 지정할 수 있습니다. +`/theme` 명령어로 테마 선택기를 불러와 테마를 선택할 수 있습니다. 또는 `tui.json`에서 지정할 수 있습니다. -```json title="opencode.json" {3} +```json title="tui.json" {3} { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "theme": "tokyonight" } ``` @@ -75,32 +74,35 @@ opencode는 여러 내장 테마와 함께 제공됩니다. ## 사용자 정의 테마 -opencode는 사용자가 쉽게 테마를 만들 수 있도록 유연한 JSON 기반 테마 시스템을 지원합니다. +OpenCode는 사용자가 쉽게 테마를 만들고 사용자 정의할 수 있도록 유연한 JSON 기반 테마 시스템을 지원합니다. --- -##### 계층 구조 +### 계층 구조 -테마는 다음과 같은 순서에서 여러 디렉토리에서로드됩니다. 나중에 감독은 이전 것을 무시합니다. +테마는 다음 순서대로 여러 디렉토리에서 로드되며, 나중 디렉토리가 이전 디렉토리를 덮어씁니다: -1.**Built-in themes** - 이것은 바이너리에 내장되어 있습니다. 2. **사용자 설정 디렉토리 ** - `~/.config/opencode/themes/*.json` 또는 `$XDG_CONFIG_HOME/opencode/themes/*.json`에서 정의 3. ** 루트 디렉토리 ** - `/.opencode/themes/*.json`에서 정의 4. **현재 작업 디렉토리 ** - `./.opencode/themes/*.json`에서 정의 +1. **내장 테마 (Built-in themes)** - 바이너리에 내장되어 있습니다. +2. **사용자 설정 디렉토리 (User config directory)** - `~/.config/opencode/themes/*.json` 또는 `$XDG_CONFIG_HOME/opencode/themes/*.json`에 정의됩니다. +3. **프로젝트 루트 디렉토리 (Project root directory)** - `/.opencode/themes/*.json`에 정의됩니다. +4. **현재 작업 디렉토리 (Current working directory)** - `./.opencode/themes/*.json`에 정의됩니다. -여러 디렉토리가 같은 이름을 가진 테마를 포함한다면, 더 높은 우선 순위를 가진 디렉토리의 테마가 사용됩니다. +여러 디렉토리에 같은 이름의 테마가 있는 경우, 더 높은 우선 순위를 가진 디렉토리의 테마가 사용됩니다. --- ### 테마 만들기 -사용자 정의 테마를 만들려면 테마 디렉토리 중 하나에서 JSON 파일을 만듭니다. +사용자 정의 테마를 만들려면 테마 디렉토리 중 하나에 JSON 파일을 만듭니다. -사용자 넓은 테마: +사용자 전역 테마: ```bash no-frame mkdir -p ~/.config/opencode/themes vim ~/.config/opencode/themes/my-theme.json ``` -프로젝트 별 테마. +프로젝트별 테마: ```bash no-frame mkdir -p .opencode/themes @@ -111,35 +113,34 @@ vim .opencode/themes/my-theme.json ### JSON 형식 -테마는 유연한 JSON 형식을 사용하여 지원: - --**Hex 색상**: `"#ffffff"` +테마는 다음을 지원하는 유연한 JSON 형식을 사용합니다: -- ** ANSI 색상**: `3` (0-255) -- ** 색상 참조 ** : `"primary"` 또는 사용자 정의 정의 -- ** 어두운 / 조명 변형 ** : `{"dark": "#000", "light": "#fff"}` -- ** 색상 없음 ** : `"none"` - terminal의 기본 색상 또는 투명 사용 +- **Hex 색상**: `"#ffffff"` +- **ANSI 색상**: `3` (0-255) +- **색상 참조**: `"primary"` 또는 사용자 정의 정의 +- **다크/라이트 변형**: `{"dark": "#000", "light": "#fff"}` +- **색상 없음**: `"none"` - 터미널의 기본 색상 또는 투명 사용 --- ### 색상 정의 -`defs` 단면도는 선택적이고 당신은 주제에서 참조될 수 있는 재사용할 수 있는 색깔을 정의할 수 있습니다. +`defs` 섹션은 선택 사항이며 테마 내에서 참조할 수 있는 재사용 가능한 색상을 정의할 수 있습니다. --- -## 터미널 기본값 +### 터미널 기본값 -특별한 가치 `"none"`는 terminal의 기본 색깔을 상속하기 위하여 어떤 색깔든지를 위해 사용될 수 있습니다. 이것은 특히 당신의 terminal의 색깔 계획과 이음새가 없는 혼합 테마 창조를 위해 유용합니다: +`"none"`이라는 특별한 값은 모든 색상에 대해 터미널의 기본 색상을 상속하는 데 사용할 수 있습니다. 이는 특히 터미널의 색상 스키마와 매끄럽게 어우러지는 테마를 만들 때 유용합니다: -- `"text": "none"` - terminal의 기본 전경 색상 사용 -- `"background": "none"` - terminal의 기본 배경 색상 사용 +- `"text": "none"` - 터미널의 기본 전경색 사용 +- `"background": "none"` - 터미널의 기본 배경색 사용 --- ### 예제 -사용자 정의 테마의 예입니다 : +사용자 정의 테마의 예입니다: ```json title="my-theme.json" { diff --git a/packages/web/src/content/docs/ko/tui.mdx b/packages/web/src/content/docs/ko/tui.mdx index 8717cc785f8..669d899d0ce 100644 --- a/packages/web/src/content/docs/ko/tui.mdx +++ b/packages/web/src/content/docs/ko/tui.mdx @@ -5,9 +5,9 @@ description: OpenCode 터미널 사용자 인터페이스 사용. import { Tabs, TabItem } from "@astrojs/starlight/components" -opencode는 LLM과 함께 프로젝트를 위해 대화형 terminal 인터페이스 또는 TUI를 제공합니다. +OpenCode는 LLM과 함께 프로젝트 작업을 하기 위한 대화형 터미널 인터페이스(TUI)를 제공합니다. -opencode는 현재 디렉토리에 TUI를 시작합니다. +OpenCode를 실행하면 현재 디렉토리에서 TUI가 시작됩니다. ```bash opencode @@ -19,7 +19,7 @@ opencode opencode /path/to/project ``` -TUI에 있다면 메시지가 표시됩니다. +TUI에 들어가면 메시지를 입력하여 프롬프트할 수 있습니다. ```text Give me a quick summary of the codebase. @@ -29,10 +29,10 @@ Give me a quick summary of the codebase. ## 파일 참조 -`@`를 사용하여 메시지에 파일을 참조 할 수 있습니다. 이것은 현재 작업 디렉토리에서 fuzzy 파일 검색입니다. +`@`를 사용하여 메시지에서 파일을 참조할 수 있습니다. 이것은 현재 작업 디렉토리에서 퍼지(fuzzy) 파일 검색을 수행합니다. :::tip -`@`를 사용하여 메시지의 참조 파일을 사용할 수 있습니다. +`@`를 사용하여 메시지에서 파일을 참조할 수 있습니다. ::: ```text "@packages/functions/src/api/index.ts" @@ -45,7 +45,7 @@ How is auth handled in @packages/functions/src/api/index.ts? ## Bash 명령 -`!`를 사용하여 shell 명령을 실행합니다. +`!`로 메시지를 시작하여 셸 명령을 실행합니다. ```bash frame="none" !ls -la @@ -57,21 +57,21 @@ How is auth handled in @packages/functions/src/api/index.ts? ## 명령 -opencode TUI를 사용할 때, `/`를 입력하여 명령 이름을 따라 작업을 신속하게 실행할 수 있습니다. 예를 들면: +OpenCode TUI를 사용할 때 `/` 뒤에 명령 이름을 입력하여 작업을 빠르게 실행할 수 있습니다. 예를 들어: ```bash frame="none" /help ``` -대부분의 명령은 `ctrl+x`를 `ctrl+x`가 기본 리더 키입니다. [더 알아보기](/docs/keybinds). +대부분의 명령에는 기본 리더 키인 `ctrl+x`를 사용하는 키바인드도 있습니다. [더 알아보기](/docs/keybinds). -여기에 모든 가능한 슬래시 명령이 있습니다. +사용 가능한 모든 슬래시 명령은 다음과 같습니다: --- -### /connect +### connect -opencode에 대한 공급자를 추가합니다. 사용 가능한 공급자에서 선택하고 API 키를 추가 할 수 있습니다. +OpenCode에 공급자를 추가합니다. 사용 가능한 공급자 중에서 선택하고 API 키를 추가할 수 있습니다. ```bash frame="none" /connect @@ -79,67 +79,67 @@ opencode에 대한 공급자를 추가합니다. 사용 가능한 공급자에 --- -### /compact +### compact -현재 세션을 압축합니다. 앨리스 : `/summarize` +현재 세션을 압축합니다. _별칭_: `/summarize` ```bash frame="none" /compact ``` -** Keybind:** `ctrl+x c` +**키바인드:** `ctrl+x c` --- -### /details +### details -토글 툴 실행 세부 사항. +도구 실행 세부 정보 토글. ```bash frame="none" /details ``` -** Keybind:** `ctrl+x d` +**키바인드:** `ctrl+x d` --- -### /editor +### editor -메시지를 작성하기 위한 외부 편집기를 엽니다. `EDITOR` 환경에서 설정된 편집기를 사용합니다. [더 알아보기](#editor-setup). +메시지 작성을 위한 외부 편집기를 엽니다. `EDITOR` 환경 변수에 설정된 편집기를 사용합니다. [더 알아보기](#editor-setup). ```bash frame="none" /editor ``` -** Keybind:** `ctrl+x e` +**키바인드:** `ctrl+x e` --- -### /exit +### exit -opencode를 종료합니다. Aliases : `/quit`, `/q` +OpenCode를 종료합니다. _별칭_: `/quit`, `/q` ```bash frame="none" /exit ``` -** Keybind:** `ctrl+x q` +**키바인드:** `ctrl+x q` --- -### /export +### export -Markdown에 대한 현재 대화를 내보내고 기본 편집기에서 열립니다. `EDITOR` 환경에서 설정된 편집기를 사용합니다. [더 알아보기](#editor-setup). +현재 대화를 Markdown으로 내보내고 기본 편집기에서 엽니다. `EDITOR` 환경 변수에 설정된 편집기를 사용합니다. [더 알아보기](#editor-setup). ```bash frame="none" /export ``` -** Keybind:** `ctrl+x x` +**키바인드:** `ctrl+x x` --- -### /help +### help 도움말 대화 상자를 표시합니다. @@ -147,107 +147,106 @@ Markdown에 대한 현재 대화를 내보내고 기본 편집기에서 열립 /help ``` -** Keybind:** `ctrl+x h` +**키바인드:** `ctrl+x h` --- -###### /init +### init -`AGENTS.md` 파일을 만들거나 업데이트하십시오. [더 알아보기](/docs/rules). +`AGENTS.md` 파일을 생성하거나 업데이트합니다. [더 알아보기](/docs/rules). ```bash frame="none" /init ``` -** Keybind:** `ctrl+x i` +**키바인드:** `ctrl+x i` --- -## /models +### models -사용 가능한 모델 목록. +사용 가능한 모델 목록을 표시합니다. ```bash frame="none" /models ``` -** Keybind:** `ctrl+x m` +**키바인드:** `ctrl+x m` --- -## /new +### new -새로운 세션을 시작합니다. 앨리스 : `/clear` +새 세션을 시작합니다. _별칭_: `/clear` ```bash frame="none" /new ``` -** Keybind:** `ctrl+x n` +**키바인드:** `ctrl+x n` --- -##### /redo +### redo -이전 undone 메시지 Redo. `/undo`를 사용하는 후에만 유효한. +이전에 실행 취소한 메시지를 다시 실행합니다. `/undo`를 사용한 후에만 사용할 수 있습니다. :::tip -모든 파일 변경도 복원됩니다. +모든 파일 변경 사항도 복원됩니다. ::: -내부적으로 Git을 사용하여 파일 변경을 관리합니다. 그래서 프로젝트 ** -Git 저장소**입니다. +내부적으로 Git을 사용하여 파일 변경 사항을 관리합니다. 따라서 프로젝트가 **Git 저장소**여야 합니다. ```bash frame="none" /redo ``` -** Keybind:** `ctrl+x r` +**키바인드:** `ctrl+x r` --- -## /sessions +### sessions -세션 간 목록 및 전환. Aliases : `/resume`, `/continue` +세션 목록을 표시하고 세션 간을 전환합니다. _별칭_: `/resume`, `/continue` ```bash frame="none" /sessions ``` -** Keybind:** `ctrl+x l` +**키바인드:** `ctrl+x l` --- -## 공유 +### share -현재 세션 공유. [더 알아보기](/docs/share). +현재 세션을 공유합니다. [더 알아보기](/docs/share). ```bash frame="none" /share ``` -** Keybind:** `ctrl+x s` +**키바인드:** `ctrl+x s` --- -## /theme +### themes -사용할 수 있는 테마 목록. +사용 가능한 테마 목록을 표시합니다. ```bash frame="none" -/theme +/themes ``` -** Keybind:** `ctrl+x t` +**키바인드:** `ctrl+x t` --- -### /thinking +### thinking -대화의 사고/거주 블록의 가시성을 토합니다. 사용할 때, 확장 된 생각을 지원하는 모델의 이유 프로세스를 볼 수 있습니다. +대화에서 생각/추론 블록의 가시성을 토글합니다. 활성화하면 확장된 사고를 지원하는 모델의 추론 과정을 볼 수 있습니다. :::note -이 명령은 생각 블록이 ** 표시되었는지 여부 만 제어 ** - 모델의 소싱 기능을 활성화하거나 비활성화하지 않습니다. toggle 실제적인 reasoning 기능에, 모형 변종을 통해서 주기 위하여 `ctrl+t`를 이용합니다. +이 명령은 생각 블록이 **표시되는지 여부만 제어**하며 모델의 추론 기능을 활성화하거나 비활성화하지 않습니다. 실제 추론 기능을 토글하려면 `ctrl+t`를 사용하여 모델 변형을 순환하십시오. ::: ```bash frame="none" @@ -256,28 +255,27 @@ Git 저장소**입니다. --- -##### /undo +### undo -대화에서 마지막 메시지. 가장 최근의 사용자 메시지, 모든 후속 응답 및 모든 파일 변경 제거. +대화의 마지막 메시지를 실행 취소합니다. 가장 최근의 사용자 메시지, 모든 후속 응답 및 모든 파일 변경 사항을 제거합니다. :::tip -어떤 파일 변경도 복제됩니다. +모든 파일 변경 사항도 되돌려집니다. ::: -내부적으로 Git을 사용하여 파일 변경을 관리합니다. 그래서 프로젝트 ** -Git 저장소**입니다. +내부적으로 Git을 사용하여 파일 변경 사항을 관리합니다. 따라서 프로젝트가 **Git 저장소**여야 합니다. ```bash frame="none" /undo ``` -** Keybind:** `ctrl+x u` +**키바인드:** `ctrl+x u` --- -#### /unshare +### unshare -Unshare 현재 세션. [더 알아보기](/docs/share#un-sharing). +현재 세션 공유를 취소합니다. [더 알아보기](/docs/share#un-sharing). ```bash frame="none" /unshare @@ -285,9 +283,9 @@ Unshare 현재 세션. [더 알아보기](/docs/share#un-sharing). --- -## 편집기 설정 +## Editor setup -`/editor`와 `/export` 명령 모두는 `EDITOR` 환경변수에서 지정된 편집기를 사용합니다. +`/editor`와 `/export` 명령 모두 `EDITOR` 환경 변수에 지정된 편집기를 사용합니다. @@ -301,8 +299,7 @@ Unshare 현재 세션. [더 알아보기](/docs/share#un-sharing). export EDITOR="code --wait" ``` -영원한 만들기 위하여, 당신의 shell 프로파일에 이것을 추가하십시오; -`~/.bashrc`, `~/.zshrc`, 등. + 영구적으로 설정하려면 셸 프로필(`~/.bashrc`, `~/.zshrc` 등)에 추가하십시오. @@ -315,8 +312,7 @@ Unshare 현재 세션. [더 알아보기](/docs/share#un-sharing). set EDITOR=code --wait ``` -영구적으로, use **System Properties** > ** 환경 -변수**. + 영구적으로 설정하려면 **시스템 속성** > **환경 변수**를 사용하십시오. @@ -329,62 +325,72 @@ Unshare 현재 세션. [더 알아보기](/docs/share#un-sharing). $env:EDITOR = "code --wait" ``` -영구적으로 만들려면 PowerShell 프로파일에 추가하십시오. + 영구적으로 설정하려면 PowerShell 프로필에 추가하십시오. -인기있는 편집기 옵션은 다음과 같습니다 : +인기 있는 편집기 옵션은 다음과 같습니다: -- `code` - Visual Studio 코드 -- `cursor` - 커서 -- `windsurf` - 윈드 서핑 -- `nvim` - Neovim 편집기 -- `vim` - Vim 편집기 -- `nano` - 나노 편집기 -- `notepad` - 윈도우 노트패드 -- `subl` - 승화 텍스트 +- `code` - Visual Studio Code +- `cursor` - Cursor +- `windsurf` - Windsurf +- `nvim` - Neovim editor +- `vim` - Vim editor +- `nano` - Nano editor +- `notepad` - Windows Notepad +- `subl` - Sublime Text :::note -VS Code와 같은 일부 편집기는 `--wait` 플래그와 함께 시작해야합니다. +VS Code와 같은 일부 편집기는 `--wait` 플래그와 함께 시작해야 합니다. ::: -일부 편집기는 명령줄 인수가 차단 모드에서 실행되어야 합니다. `--wait` 플래그는 닫힐 때까지 편집기 프로세스 블록을 만듭니다. +일부 편집기는 차단 모드에서 실행하려면 명령줄 인수가 필요합니다. `--wait` 플래그는 편집기 프로세스가 닫힐 때까지 차단되도록 합니다. --- ## 구성 -opencode config 파일을 통해 TUI 동작을 사용자 정의할 수 있습니다. +`tui.json` (또는 `tui.jsonc`) 파일을 통해 TUI 동작을 사용자 정의할 수 있습니다. -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", - "tui": { - "scroll_speed": 3, - "scroll_acceleration": { - "enabled": true - } - } + "$schema": "https://opencode.ai/tui.json", + "theme": "opencode", + "keybinds": { + "leader": "ctrl+x" + }, + "scroll_speed": 3, + "scroll_acceleration": { + "enabled": true + }, + "diff_style": "auto" } ``` +이는 서버/런타임 동작을 구성하는 `opencode.json`과는 별개입니다. + ### 옵션 -- `scroll_acceleration` - 부드러운 자연 스크롤을위한 macOS 스타일 스크롤 가속 가능. 사용할 때, 스크롤 속도는 빠른 스크롤 제스처로 증가하고 느린 움직임을 위해 정확한 유지. **이 설정은 `scroll_speed`를 통해 우선 순위를 부여하고 활성화 할 때. ** -- `scroll_speed` - 스크롤 명령 (최소 : `1`)을 사용하여 TUI 스크롤을 빠르게 제어합니다. 기본 `3`. ** 참고: `scroll_acceleration.enabled`가 `true`로 설정되면 무시됩니다.** +- `theme` - UI 테마를 설정합니다. [더 알아보기](/docs/themes). +- `keybinds` - 키보드 단축키를 사용자 정의합니다. [더 알아보기](/docs/keybinds). +- `scroll_acceleration.enabled` - 부드럽고 자연스러운 스크롤을 위해 macOS 스타일의 스크롤 가속을 활성화합니다. 활성화하면 빠른 스크롤 제스처로 스크롤 속도가 증가하고 느린 움직임에서는 정밀하게 유지됩니다. **이 설정은 `scroll_speed`보다 우선하며 활성화 시 이를 덮어씁니다.** +- `scroll_speed` - 스크롤 명령을 사용할 때 TUI 스크롤 속도를 제어합니다 (최소: `0.001`, 소수점 값 지원). 기본값은 `3`입니다. **참고: `scroll_acceleration.enabled`가 `true`로 설정되면 무시됩니다.** +- `diff_style` - diff 렌더링 방식을 제어합니다. `"auto"`는 터미널 너비에 적응하고, `"stacked"`는 항상 단일 열 레이아웃을 표시합니다. + +`OPENCODE_TUI_CONFIG`를 사용하여 사용자 정의 TUI 설정 경로를 로드할 수 있습니다. --- ## 사용자 정의 -명령 팔레트 (`ctrl+x h` 또는 `/help`)를 사용하여 TUI보기의 다양한 측면을 사용자 정의 할 수 있습니다. 재시작에 따른 설정 persist. +명령 팔레트(`ctrl+x h` 또는 `/help`)를 사용하여 TUI 보기의 다양한 측면을 사용자 정의할 수 있습니다. 설정은 다시 시작해도 유지됩니다. --- #### 사용자 이름 표시 -사용자 이름이 채팅 메시지에 나타나는지 여부를 수정합니다. 이것을 통해 접근: +채팅 메시지에 사용자 이름이 표시되는지 여부를 토글합니다. 다음을 통해 액세스: -- 명령 팔레트 : "username" 또는 "hide 사용자" 검색 -- 자동 설정은 TUI 세션을 통해 기억됩니다. +- 명령 팔레트: "username" 또는 "hide username" 검색 +- 설정은 자동으로 유지되며 TUI 세션 간에 기억됩니다. diff --git a/packages/web/src/content/docs/ko/zen.mdx b/packages/web/src/content/docs/ko/zen.mdx index 04d5c0df8e9..ae598cee187 100644 --- a/packages/web/src/content/docs/ko/zen.mdx +++ b/packages/web/src/content/docs/ko/zen.mdx @@ -55,6 +55,7 @@ OpenCode Zen은 OpenCode의 다른 제공자와 동일한 방식으로 작동합 | 모델 | 모델 ID | 엔드포인트 | AI SDK 패키지 | | ------------------ | ------------------ | -------------------------------------------------- | --------------------------- | +| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | @@ -64,28 +65,30 @@ OpenCode Zen은 OpenCode의 다른 제공자와 동일한 방식으로 작동합 | GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | | Gemini 3 Pro | gemini-3-pro | `https://opencode.ai/zen/v1/models/gemini-3-pro` | `@ai-sdk/google` | | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.1 | minimax-m2.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.1 Free | minimax-m2.1-free | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| GLM 5 | glm-5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 4.7 | glm-4.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM 4.7 Free | glm-4.7-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 4.6 | glm-4.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 Free | kimi-k2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2 Thinking | kimi-k2-thinking | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2 | kimi-k2 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Qwen3 Coder 480B | qwen3-coder | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -OpenCode 설정 파일에서 사용하는 [모델 ID](/docs/config/#models)는 `opencode/` 형식을 따릅니다. +OpenCode 설정 파일에서 사용하는 [모델 ID](/docs/config/#models)는 `opencode/` 형식을 따릅니다. 예를 들어 GPT 5.2 Codex의 경우 설정에서 `opencode/gpt-5.2-codex`와 같이 사용합니다. --- @@ -107,29 +110,34 @@ https://opencode.ai/zen/v1/models | 모델 | 입력 | 출력 | 캐시 읽기 | 캐시 쓰기 | | --------------------------------- | ------ | ------ | --------- | --------- | | Big Pickle | Free | Free | Free | - | -| MiniMax M2.1 Free | Free | Free | Free | - | +| MiniMax M2.5 Free | Free | Free | Free | - | +| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | - | | MiniMax M2.1 | $0.30 | $1.20 | $0.10 | - | -| GLM 4.7 Free | Free | Free | Free | - | +| GLM 5 | $1.00 | $3.20 | $0.20 | - | | GLM 4.7 | $0.60 | $2.20 | $0.10 | - | | GLM 4.6 | $0.60 | $2.20 | $0.10 | - | -| Kimi K2.5 Free | Free | Free | Free | - | | Kimi K2.5 | $0.60 | $3.00 | $0.08 | - | | Kimi K2 Thinking | $0.40 | $2.50 | - | - | | Kimi K2 | $0.40 | $2.50 | - | - | | Qwen3 Coder 480B | $0.45 | $1.50 | - | - | +| Claude Opus 4.6 (≤ 200K tokens) | $5.00 | $25.00 | $0.50 | $6.25 | +| Claude Opus 4.6 (> 200K tokens) | $10.00 | $37.50 | $1.00 | $12.50 | +| Claude Opus 4.5 | $5.00 | $25.00 | $0.50 | $6.25 | +| Claude Opus 4.1 | $15.00 | $75.00 | $1.50 | $18.75 | +| Claude Sonnet 4.6 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 | +| Claude Sonnet 4.6 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 | | Claude Sonnet 4.5 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 | | Claude Sonnet 4.5 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 | | Claude Sonnet 4 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 | | Claude Sonnet 4 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 | | Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 | | Claude Haiku 3.5 | $0.80 | $4.00 | $0.08 | $1.00 | -| Claude Opus 4.6 (≤ 200K tokens) | $5.00 | $25.00 | $0.50 | $6.25 | -| Claude Opus 4.6 (> 200K tokens) | $10.00 | $37.50 | $1.00 | $12.50 | -| Claude Opus 4.5 | $5.00 | $25.00 | $0.50 | $6.25 | -| Claude Opus 4.1 | $15.00 | $75.00 | $1.50 | $18.75 | +| Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | +| Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | +| GPT 5.3 Codex | $1.75 | $14.00 | $0.175 | - | | GPT 5.2 | $1.75 | $14.00 | $0.175 | - | | GPT 5.2 Codex | $1.75 | $14.00 | $0.175 | - | | GPT 5.1 | $1.07 | $8.50 | $0.107 | - | @@ -148,8 +156,6 @@ https://opencode.ai/zen/v1/models 무료 모델: -- GLM 5 Free는 한정된 기간 동안 OpenCode에서 제공됩니다. 해당 기간 동안 팀은 사용자 피드백을 수집하고 모델을 개선할 예정입니다. -- Kimi K2.5 Free는 한정된 기간 동안 OpenCode에서 제공됩니다. 해당 기간 동안 팀은 사용자 피드백을 수집하고 모델을 개선할 예정입니다. - MiniMax M2.5 Free는 한정된 기간 동안 OpenCode에서 제공됩니다. 해당 기간 동안 팀은 사용자 피드백을 수집하고 모델을 개선할 예정입니다. - Big Pickle은 한정된 기간 동안 OpenCode에서 무료로 제공되는 스텔스 모델입니다. 해당 기간 동안 팀은 사용자 피드백을 수집하고 모델을 개선할 예정입니다. @@ -169,7 +175,7 @@ https://opencode.ai/zen/v1/models 워크스페이스 전체 및 각 팀 구성원별로 월간 사용 한도를 설정할 수 있습니다. -예를 들어 월간 사용 한도를 $20로 설정한 경우, Zen은 한 달 동안 $20을 초과하여 사용하지 않습니다. +예를 들어 월간 사용 한도를 $20로 설정한 경우, Zen은 한 달 동안 $20을 초과하여 사용하지 않습니다. 다만 자동 충전이 활성화되어 있는 경우, 잔액이 $5 미만으로 내려가면 자동으로 충전이 이루어질 수 있으므로 실제 청구 금액이 $20을 초과할 수 있습니다. --- @@ -179,8 +185,6 @@ https://opencode.ai/zen/v1/models 당사의 모든 모델은 미국에서 호스팅됩니다. 당사 제공자는 데이터 무보존(zero-retention) 정책을 따르며, 아래의 예외를 제외하고는 귀하의 데이터를 모델 학습에 사용하지 않습니다. - Big Pickle: 무료 제공 기간 동안 수집된 데이터는 모델 개선을 위해 사용될 수 있습니다. -- GLM 5 Free: 무료 제공 기간 동안 수집된 데이터는 모델 개선을 위해 사용될 수 있습니다. -- Kimi K2.5 Free: 무료 제공 기간 동안 수집된 데이터는 모델 개선을 위해 사용될 수 있습니다. - MiniMax M2.5 Free: 무료 제공 기간 동안 수집된 데이터는 모델 개선을 위해 사용될 수 있습니다. - OpenAI APIs: 요청 데이터는 [OpenAI의 데이터 정책](https://platform.openai.com/docs/guides/your-data)에 따라 30일간 보관됩니다. - Anthropic APIs: 요청 데이터는 [Anthropic의 데이터 정책](https://docs.anthropic.com/en/docs/claude-code/data-usage)에 따라 30일간 보관됩니다. diff --git a/packages/web/src/content/docs/nb/cli.mdx b/packages/web/src/content/docs/nb/cli.mdx index 2f1b3884ea0..409fdb23783 100644 --- a/packages/web/src/content/docs/nb/cli.mdx +++ b/packages/web/src/content/docs/nb/cli.mdx @@ -558,6 +558,7 @@ OpenCode kan konfigureres ved hjelp av miljøvariabler. | `OPENCODE_AUTO_SHARE` | boolsk | Del økter automatisk | | `OPENCODE_GIT_BASH_PATH` | streng | Bane til Git Bash-kjørbar på Windows | | `OPENCODE_CONFIG` | streng | Bane til konfigurasjonsfil | +| `OPENCODE_TUI_CONFIG` | streng | Bane til TUI-konfigurasjonsfil | | `OPENCODE_CONFIG_DIR` | streng | Bane til konfigurasjonskatalog | | `OPENCODE_CONFIG_CONTENT` | streng | Innebygd json-konfigurasjonsinnhold | | `OPENCODE_DISABLE_AUTOUPDATE` | boolsk | Deaktiver automatiske oppdateringskontroller | diff --git a/packages/web/src/content/docs/nb/config.mdx b/packages/web/src/content/docs/nb/config.mdx index 8f54335794c..e8b32d5a067 100644 --- a/packages/web/src/content/docs/nb/config.mdx +++ b/packages/web/src/content/docs/nb/config.mdx @@ -14,10 +14,11 @@ OpenCode støtter både **JSON** og **JSONC** (JSON med kommentarer) formater. ```jsonc title="opencode.jsonc" { "$schema": "https://opencode.ai/config.json", - // Theme configuration - "theme": "opencode", "model": "anthropic/claude-sonnet-4-5", "autoupdate": true, + "server": { + "port": 4096, + }, } ``` @@ -301,12 +302,12 @@ Bærer-tokens (`AWS_BEARER_TOKEN_BEDROCK` eller `/connect`) har forrang over pro ### Temaer -Du kan konfigurere temaet du vil bruke i OpenCode-konfigurasjonen gjennom alternativet `theme`. +Angi UI-temaet ditt i `tui.json`. -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", - "theme": "" + "$schema": "https://opencode.ai/tui.json", + "theme": "tokyonight" } ``` @@ -406,11 +407,11 @@ Du kan også definere kommandoer ved å bruke markdown-filer i `~/.config/openco ### Tastebindinger -Du kan tilpasse tastebindingene dine gjennom alternativet `keybinds`. +Tilpass tastebindinger i `tui.json`. -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "keybinds": {} } ``` @@ -490,13 +491,15 @@ Du kan styre kontekstkomprimering gjennom alternativet `compaction`. "$schema": "https://opencode.ai/config.json", "compaction": { "auto": true, - "prune": true + "prune": true, + "reserved": 10000 } } ``` - `auto` - Komprimer økten automatisk når konteksten er full (standard: `true`). - `prune` - Fjern gamle verktøyutdata for å spare tokens (standard: `true`). +- `reserved` - Token-buffer for komprimering. Etterlater nok vindu til å unngå overflyt under komprimering --- diff --git a/packages/web/src/content/docs/nb/custom-tools.mdx b/packages/web/src/content/docs/nb/custom-tools.mdx index 0b88f750773..505c261a0b2 100644 --- a/packages/web/src/content/docs/nb/custom-tools.mdx +++ b/packages/web/src/content/docs/nb/custom-tools.mdx @@ -79,6 +79,32 @@ Dette lager to verktøy: `math_add` og `math_multiply`. --- +#### Navnekollisjoner med innebygde verktøy + +Egendefinerte verktøy er nøklet etter verktøynavn. Hvis et egendefinert verktøy bruker samme navn som et innebygd verktøy, vil det egendefinerte verktøyet ha forrang. + +For eksempel erstatter denne filen det innebygde `bash`-verktøyet: + +```ts title=".opencode/tools/bash.ts" +import { tool } from "@opencode-ai/plugin" + +export default tool({ + description: "Restricted bash wrapper", + args: { + command: tool.schema.string(), + }, + async execute(args) { + return `blocked: ${args.command}` + }, +}) +``` + +:::note +Foretrekk unike navn med mindre du med vilje ønsker å erstatte et innebygd verktøy. Hvis du vil deaktivere et innebygd verktøy, men ikke overstyre det, bruk [tillatelser](/docs/permissions). +::: + +--- + ### Argumenter Du kan bruke `tool.schema`, som bare er [Zod](https://zod.dev), for å definere argumenttyper. diff --git a/packages/web/src/content/docs/nb/ecosystem.mdx b/packages/web/src/content/docs/nb/ecosystem.mdx index 714a9ee95e1..bc4a5773758 100644 --- a/packages/web/src/content/docs/nb/ecosystem.mdx +++ b/packages/web/src/content/docs/nb/ecosystem.mdx @@ -15,38 +15,39 @@ Du kan også sjekke ut [awesome-opencode](https://github.com/awesome-opencode/aw ## Utvidelser -| Navn | Beskrivelse | -| --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | -| [opencode-daytona](https://github.com/jamesmurdza/daytona/blob/main/guides/typescript/opencode/README.md) | Kjør OpenCode-økter automatisk i isolerte Daytona-sandkasser med git-synkronisering og live-forhåndsvisninger | -| [opencode-helicone-session](https://github.com/H2Shami/opencode-helicone-session) | Injiser automatisk Helicone-headers for forespørselsgruppering | -| [opencode-type-inject](https://github.com/nick-vi/opencode-type-inject) | Auto-injiser TypeScript/Svelte-typer i fillesninger med oppslagsverktøy | -| [opencode-openai-codex-auth](https://github.com/numman-ali/opencode-openai-codex-auth) | Bruk ChatGPT Plus/Pro-abonnementet ditt i stedet for API kreditter | -| [opencode-gemini-auth](https://github.com/jenslys/opencode-gemini-auth) | Bruk din eksisterende Gemini-plan i stedet for API-fakturering | -| [opencode-antigravity-auth](https://github.com/NoeFabris/opencode-antigravity-auth) | Bruk Antigravitys gratis modeller i stedet for API-fakturering | -| [opencode-devcontainers](https://github.com/athal7/opencode-devcontainers) | Multi-branch devcontainer-isolasjon med grunne kloner og automatisk tildelte porter | -| [opencode-google-antigravity-auth](https://github.com/shekohex/opencode-google-antigravity-auth) | Google Antigravity OAuth-plugin, med støtte for Google Søk og mer robust API-håndtering | -| [opencode-dynamic-context-pruning](https://github.com/Tarquinen/opencode-dynamic-context-pruning) | Optimaliser bruken av token ved å beskjære utdaterte verktøy | -| [opencode-websearch-cited](https://github.com/ghoulr/opencode-websearch-cited.git) | Legg til innebygd støtte for nettsøk for støttede leverandører med Googles kildebaserte stil | -| [opencode-pty](https://github.com/shekohex/opencode-pty.git) | Gjør det mulig for AI-agenter å kjøre bakgrunnsprosesser i en PTY, sende interaktiv inndata til dem. | -| [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Instruksjoner for ikke-interaktive skallkommandoer - forhindrer heng ved TTY-avhengige operasjoner | -| [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Spor OpenCode-bruk med Wakatime | -| [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Rydd opp i markdown-tabeller produsert av LLMs | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | 10 ganger raskere koderedigering med Morph Fast Apply API og lazy-redigeringsmarkører | -| [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Bakgrunnsagenter, forhåndsbygde LSP/AST/MCP verktøy, kurerte agenter, Claude Code-kompatibel | -| [opencode-notificator](https://github.com/panta82/opencode-notificator) | Skrivebordsvarsler og lydvarsler for OpenCode-økter | -| [opencode-notifier](https://github.com/mohak34/opencode-notifier) | Skrivebordsvarsler og lydvarsler for tillatelse, fullføring og feilhendelser | -| [opencode-zellij-namer](https://github.com/24601/opencode-zellij-namer) | AI-drevet automatisk Zellij-sesjonsnavn basert på OpenCode-kontekst | -| [opencode-skillful](https://github.com/zenobi-us/opencode-skillful) | Tillat OpenCode-agenter å lazy-loade meldinger på forespørsel med ferdighetsoppdagelse og injeksjon | -| [opencode-supermemory](https://github.com/supermemoryai/opencode-supermemory) | Vedvarende minne på tvers av økter ved hjelp av Supermemory | -| [@plannotator/opencode](https://github.com/backnotprop/plannotator/tree/main/apps/opencode-plugin) | Interaktiv plangjennomgang med visuell merknad og privat/offline deling | -| [@openspoon/subtask2](https://github.com/spoons-and-mirrors/subtask2) | Utvid OpenCode /kommandoer til et kraftig orkestreringssystem med granulær flytkontroll | -| [opencode-scheduler](https://github.com/different-ai/opencode-scheduler) | Planlegg gjentakende jobber ved hjelp av launchd (Mac) eller systemd (Linux) med cron-syntaks | -| [micode](https://github.com/vtemian/micode) | Strukturert brainstorm → Plan → Implementer arbeidsflyt med øktkontinuitet | -| [octto](https://github.com/vtemian/octto) | Interaktiv nettleser UI for AI idédugnad med flerspørsmålsskjemaer | -| [opencode-background-agents](https://github.com/kdcokenny/opencode-background-agents) | Claude Bakgrunnsagenter i kodestil med asynkrondelegering og kontekstutholdenhet | -| [opencode-notify](https://github.com/kdcokenny/opencode-notify) | Innfødte OS-varsler for OpenCode – vet når oppgaver fullføres | -| [opencode-workspace](https://github.com/kdcokenny/opencode-workspace) | Medfølgende multi-agent orkestreringsrammeverk – 16 komponenter, én installasjon | -| [opencode-worktree](https://github.com/kdcokenny/opencode-worktree) | Nullfriksjon git-arbeidstre for OpenCode | +| Navn | Beskrivelse | +| -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | +| [opencode-daytona](https://github.com/daytonaio/daytona/tree/main/libs/opencode-plugin) | Kjør OpenCode-økter automatisk i isolerte Daytona-sandkasser med git-synkronisering og live-forhåndsvisninger | +| [opencode-helicone-session](https://github.com/H2Shami/opencode-helicone-session) | Injiser automatisk Helicone-headers for forespørselsgruppering | +| [opencode-type-inject](https://github.com/nick-vi/opencode-type-inject) | Auto-injiser TypeScript/Svelte-typer i fillesninger med oppslagsverktøy | +| [opencode-openai-codex-auth](https://github.com/numman-ali/opencode-openai-codex-auth) | Bruk ChatGPT Plus/Pro-abonnementet ditt i stedet for API kreditter | +| [opencode-gemini-auth](https://github.com/jenslys/opencode-gemini-auth) | Bruk din eksisterende Gemini-plan i stedet for API-fakturering | +| [opencode-antigravity-auth](https://github.com/NoeFabris/opencode-antigravity-auth) | Bruk Antigravitys gratis modeller i stedet for API-fakturering | +| [opencode-devcontainers](https://github.com/athal7/opencode-devcontainers) | Multi-branch devcontainer-isolasjon med grunne kloner og automatisk tildelte porter | +| [opencode-google-antigravity-auth](https://github.com/shekohex/opencode-google-antigravity-auth) | Google Antigravity OAuth-plugin, med støtte for Google Søk og mer robust API-håndtering | +| [opencode-dynamic-context-pruning](https://github.com/Tarquinen/opencode-dynamic-context-pruning) | Optimaliser bruken av token ved å beskjære utdaterte verktøy | +| [opencode-vibeguard](https://github.com/inkdust2021/opencode-vibeguard) | Sladd hemmeligheter/PII til VibeGuard-stil plassholdere før LLM-kall; gjenopprett lokalt | +| [opencode-websearch-cited](https://github.com/ghoulr/opencode-websearch-cited.git) | Legg til innebygd støtte for nettsøk for støttede leverandører med Googles kildebaserte stil | +| [opencode-pty](https://github.com/shekohex/opencode-pty.git) | Gjør det mulig for AI-agenter å kjøre bakgrunnsprosesser i en PTY, sende interaktiv inndata til dem. | +| [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Instruksjoner for ikke-interaktive skallkommandoer - forhindrer heng ved TTY-avhengige operasjoner | +| [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Spor OpenCode-bruk med Wakatime | +| [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Rydd opp i markdown-tabeller produsert av LLMs | +| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | 10 ganger raskere koderedigering med Morph Fast Apply API og lazy-redigeringsmarkører | +| [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Bakgrunnsagenter, forhåndsbygde LSP/AST/MCP verktøy, kurerte agenter, Claude Code-kompatibel | +| [opencode-notificator](https://github.com/panta82/opencode-notificator) | Skrivebordsvarsler og lydvarsler for OpenCode-økter | +| [opencode-notifier](https://github.com/mohak34/opencode-notifier) | Skrivebordsvarsler og lydvarsler for tillatelse, fullføring og feilhendelser | +| [opencode-zellij-namer](https://github.com/24601/opencode-zellij-namer) | AI-drevet automatisk Zellij-sesjonsnavn basert på OpenCode-kontekst | +| [opencode-skillful](https://github.com/zenobi-us/opencode-skillful) | Tillat OpenCode-agenter å lazy-loade meldinger på forespørsel med ferdighetsoppdagelse og injeksjon | +| [opencode-supermemory](https://github.com/supermemoryai/opencode-supermemory) | Vedvarende minne på tvers av økter ved hjelp av Supermemory | +| [@plannotator/opencode](https://github.com/backnotprop/plannotator/tree/main/apps/opencode-plugin) | Interaktiv plangjennomgang med visuell merknad og privat/offline deling | +| [@openspoon/subtask2](https://github.com/spoons-and-mirrors/subtask2) | Utvid OpenCode /kommandoer til et kraftig orkestreringssystem med granulær flytkontroll | +| [opencode-scheduler](https://github.com/different-ai/opencode-scheduler) | Planlegg gjentakende jobber ved hjelp av launchd (Mac) eller systemd (Linux) med cron-syntaks | +| [micode](https://github.com/vtemian/micode) | Strukturert brainstorm → Plan → Implementer arbeidsflyt med øktkontinuitet | +| [octto](https://github.com/vtemian/octto) | Interaktiv nettleser UI for AI idédugnad med flerspørsmålsskjemaer | +| [opencode-background-agents](https://github.com/kdcokenny/opencode-background-agents) | Claude Code-stil bakgrunnsagenter med asynkrondelegering og kontekstbevaring | +| [opencode-notify](https://github.com/kdcokenny/opencode-notify) | Innfødte OS-varsler for OpenCode – vet når oppgaver fullføres | +| [opencode-workspace](https://github.com/kdcokenny/opencode-workspace) | Medfølgende multi-agent orkestreringsrammeverk – 16 komponenter, én installasjon | +| [opencode-worktree](https://github.com/kdcokenny/opencode-worktree) | Nullfriksjon git-arbeidstre for OpenCode | --- diff --git a/packages/web/src/content/docs/nb/go.mdx b/packages/web/src/content/docs/nb/go.mdx new file mode 100644 index 00000000000..dcda3ec3465 --- /dev/null +++ b/packages/web/src/content/docs/nb/go.mdx @@ -0,0 +1,159 @@ +--- +title: Go +description: Lavkostnadsabonnement for åpne kodemodeller. +--- + +import config from "../../../../config.mjs" +export const console = config.console +export const email = `mailto:${config.email}` + +OpenCode Go er et lavkostnadsabonnement til **$10/måned** som gir deg pålitelig tilgang til populære åpne kodemodeller. + +:::note +OpenCode Go er for tiden i beta. +::: + +Go fungerer som enhver annen leverandør i OpenCode. Du abonnerer på OpenCode Go og +får din API-nøkkel. Det er **helt valgfritt** og du trenger ikke bruke det for å +bruke OpenCode. + +Det er designet primært for internasjonale brukere, med modeller driftet i USA, EU og Singapore for stabil global tilgang. + +--- + +## Bakgrunn + +Åpne modeller har blitt veldig bra. De når nå ytelse nær +proprietære modeller for kodeoppgaver. Og fordi mange leverandører kan servere dem +konkurransedyktig, er de vanligvis mye billigere. + +Imidlertid kan det være vanskelig å få pålitelig tilgang med lav ventetid. Leverandører +varierer i kvalitet og tilgjengelighet. + +:::tip +Vi testet en utvalgt gruppe modeller og leverandører som fungerer bra med OpenCode. +::: + +For å fikse dette gjorde vi et par ting: + +1. Vi testet en utvalgt gruppe åpne modeller og snakket med teamene deres om hvordan man + best kjører dem. +2. Vi jobbet deretter med noen få leverandører for å sikre at disse ble servert + riktig. +3. Til slutt ytelsestestet vi kombinasjonen av modell/leverandør og kom opp + med en liste som vi føler oss trygge på å anbefale. + +OpenCode Go gir deg tilgang til disse modellene for **$10/måned**. + +--- + +## Hvordan det fungerer + +OpenCode Go fungerer som enhver annen leverandør i OpenCode. + +1. Du logger deg inn på **OpenCode Zen**, abonnerer på Go, og + kopierer API-nøkkelen din. +2. Du kjører kommandoen `/connect` i TUI-en, velger `OpenCode Go`, og limer inn + API-nøkkelen din. +3. Kjør `/models` i TUI-en for å se listen over modeller tilgjengelig gjennom Go. + +:::note +Bare ett medlem per arbeidsområde kan abonnere på OpenCode Go. +::: + +Den nåværende listen over modeller inkluderer: + +- **GLM-5** +- **Kimi K2.5** +- **MiniMax M2.5** + +Listen over modeller kan endres etter hvert som vi tester og legger til nye. + +--- + +## Bruksgrenser + +OpenCode Go inkluderer følgende grenser: + +- **5 timers grense** — $12 i bruk +- **Ukentlig grense** — $30 i bruk +- **Månedlig grense** — $60 i bruk + +Grensene er definert i dollarverdi. Dette betyr at ditt faktiske antall forespørsler avhenger av modellen du bruker. Billigere modeller som MiniMax M2.5 tillater flere forespørsler, mens dyrere modeller som GLM-5 tillater færre. + +Tabellen nedenfor gir et estimert antall forespørsler basert på typiske Go-bruksmønstre: + +| | GLM-5 | Kimi K2.5 | MiniMax M2.5 | +| ------------------------ | ----- | --------- | ------------ | +| forespørsler per 5 timer | 1,150 | 1,850 | 30,000 | +| forespørsler per uke | 2,880 | 4,630 | 75,000 | +| forespørsler per måned | 5,750 | 9,250 | 150,000 | + +Estimater er basert på observerte gjennomsnittlige forespørselsmønstre: + +- GLM-5 — 700 input, 52,000 cached, 150 output tokens per forespørsel +- Kimi K2.5 — 870 input, 55,000 cached, 200 output tokens per forespørsel +- MiniMax M2.5 — 300 input, 55,000 cached, 125 output tokens per forespørsel + +Du kan spore din nåværende bruk i **konsollen**. + +:::tip +Hvis du når bruksgrensen, kan du fortsette å bruke de gratis modellene. +::: + +Bruksgrenser kan endres etter hvert som vi lærer fra tidlig bruk og tilbakemeldinger. + +--- + +### Priser + +OpenCode Go er et **$10/måned** abonnementsplan. Nedenfor er prisene **per 1M tokens**. + +| Modell | Input | Output | Bufret lesing | +| ------------ | ----- | ------ | ------------- | +| GLM-5 | $1.00 | $3.20 | $0.20 | +| Kimi K2.5 | $0.60 | $3.00 | $0.10 | +| MiniMax M2.5 | $0.30 | $1.20 | $0.03 | + +--- + +### Bruk utover grensene + +Hvis du også har kreditter på din Zen-saldo, kan du aktivere alternativet **Bruk saldo** +i konsollen. Når aktivert, vil Go falle tilbake til Zen-saldoen din +etter at du har nådd bruksgrensene dine i stedet for å blokkere forespørsler. + +--- + +## Endepunkter + +Du kan også få tilgang til Go-modeller gjennom følgende API-endepunkter. + +| Modell | Modell-ID | Endepunkt | AI SDK Pakke | +| ------------ | ------------ | ------------------------------------------------ | --------------------------- | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | + +[Modell-ID-en](/docs/config/#models) i din OpenCode-konfigurasjon +bruker formatet `opencode-go/`. For eksempel, for Kimi K2.5, ville du +bruke `opencode-go/kimi-k2.5` i konfigurasjonen din. + +--- + +## Personvern + +Planen er designet primært for internasjonale brukere, med modeller driftet i USA, EU og Singapore for stabil global tilgang. + +Kontakt oss hvis du har noen spørsmål. + +--- + +## Mål + +Vi opprettet OpenCode Go for å: + +1. Gjøre AI-koding **tilgjengelig** for flere mennesker med et lavkostnadsabonnement. +2. Gi **pålitelig** tilgang til de beste åpne kodemodellene. +3. Kurere modeller som er **testet og ytelsestestet** for bruk av kodeagenter. +4. Ha **ingen innlåsing** ved å tillate deg å bruke hvilken som helst annen leverandør med OpenCode også. diff --git a/packages/web/src/content/docs/nb/keybinds.mdx b/packages/web/src/content/docs/nb/keybinds.mdx index d762a647f20..8314b4dd82b 100644 --- a/packages/web/src/content/docs/nb/keybinds.mdx +++ b/packages/web/src/content/docs/nb/keybinds.mdx @@ -3,11 +3,11 @@ title: Tastebindinger description: Tilpass tastebindingene dine. --- -OpenCode har en liste over tastebindinger som du kan tilpasse gjennom OpenCode-konfigurasjonen. +OpenCode har en liste over tastebindinger som du kan tilpasse gjennom `tui.json`. -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "keybinds": { "leader": "ctrl+x", "app_exit": "ctrl+c,ctrl+d,q", @@ -117,11 +117,11 @@ Du trenger ikke å bruke en ledertast for tastebindingene dine, men vi anbefaler ## Deaktivering av tastebindinger -Du kan deaktivere en tastebinding ved å legge til tasten til konfigurasjonen med verdien "none". +Du kan deaktivere en tastebinding ved å legge til tasten til `tui.json` med en verdi på "none". -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "keybinds": { "session_compact": "none" } diff --git a/packages/web/src/content/docs/nb/lsp.mdx b/packages/web/src/content/docs/nb/lsp.mdx index c52b79a32d3..924b0dad1a0 100644 --- a/packages/web/src/content/docs/nb/lsp.mdx +++ b/packages/web/src/content/docs/nb/lsp.mdx @@ -27,6 +27,7 @@ OpenCode kommer med flere innebygde LSP-servere for populære språk: | gopls | .go | `go` kommando tilgjengelig | | hls | .hs, .lhs | `haskell-language-server-wrapper` kommando tilgjengelig | | jdtls | .java | `Java SDK (version 21+)` installert | +| julials | .jl | `julia` og `LanguageServer.jl` installert | | kotlin-ls | .kt, .kts | Installeres automatisk for Kotlin-prosjekter | | lua-ls | .lua | Installeres automatisk for Lua-prosjekter | | nixd | .nix | `nixd` kommando tilgjengelig | diff --git a/packages/web/src/content/docs/nb/modes.mdx b/packages/web/src/content/docs/nb/modes.mdx index 0322f069302..ccf9180e4ee 100644 --- a/packages/web/src/content/docs/nb/modes.mdx +++ b/packages/web/src/content/docs/nb/modes.mdx @@ -4,7 +4,7 @@ description: Ulike moduser for forskjellige brukstilfeller. --- :::caution -Moduser er nå konfigurert gjennom alternativet `agent` i OpenCode-konfigurasjonen. De +Moduser konfigureres nå gjennom alternativet `agent` i OpenCode-konfigurasjonen. Alternativet `mode` er nå utdatert. [Finn ut mer](/docs/agents). ::: @@ -13,6 +13,14 @@ Moduser i OpenCode lar deg tilpasse oppførselen, verktøyene og prompter for ul Den kommer med to innebygde moduser: **bygg** og **plan**. Du kan tilpasse disse eller konfigurer din egen gjennom OpenCode-konfigurasjonen. +Du kan bytte mellom moduser under en økt eller konfigurere dem i konfigurasjonsfilen din. + +--- + +## Innebygd + +OpenCode kommer med to innebygde moduser. + --- ### Bygg @@ -34,6 +42,96 @@ Denne modusen er nyttig når du vil at AI skal analysere kode, foreslå endringe --- +## Bytte + +Du kan bytte mellom moduser under en økt ved å bruke _Tab_-tasten. Eller din konfigurerte `switch_mode` hurtigtast. + +Se også: [Formatters](/docs/formatters) for informasjon om kodeformateringskonfigurasjon. + +--- + +## Konfigurer + +Du kan tilpasse de innebygde modusene eller opprette din egen gjennom konfigurasjon. Moduser kan konfigureres på to måter: + +### JSON-konfigurasjon + +Konfigurer moduser i din `opencode.json` konfigurasjonsfil: + +```json title="opencode.json" +{ + "$schema": "https://opencode.ai/config.json", + "mode": { + "build": { + "model": "anthropic/claude-sonnet-4-20250514", + "prompt": "{file:./prompts/build.txt}", + "tools": { + "write": true, + "edit": true, + "bash": true + } + }, + "plan": { + "model": "anthropic/claude-haiku-4-20250514", + "tools": { + "write": false, + "edit": false, + "bash": false + } + } + } +} +``` + +### Markdown-konfigurasjon + +Du kan også definere moduser ved hjelp av markdown-filer. Plasser dem i: + +- Globalt: `~/.config/opencode/modes/` +- Prosjekt: `.opencode/modes/` + +```markdown title="~/.config/opencode/modes/review.md" +--- +model: anthropic/claude-sonnet-4-20250514 +temperature: 0.1 +tools: + write: false + edit: false + bash: false +--- + +You are in code review mode. Focus on: + +- Code quality and best practices +- Potential bugs and edge cases +- Performance implications +- Security considerations + +Provide constructive feedback without making direct changes. +``` + +Filnavnet til markdown-filen blir modusnavnet (f.eks. `review.md` oppretter en `review`-modus). + +La oss se på disse konfigurasjonsalternativene i detalj. + +--- + +### Modell + +Bruk `model`-konfigurasjonen for å overstyre standardmodellen for denne modusen. Nyttig for å bruke forskjellige modeller optimalisert for forskjellige oppgaver. For eksempel en raskere modell for planlegging, en mer kapabel modell for implementering. + +```json title="opencode.json" +{ + "mode": { + "plan": { + "model": "anthropic/claude-haiku-4-20250514" + } + } +} +``` + +--- + ### Temperatur Kontroller tilfeldigheten og kreativiteten til AI-ens svar med `temperature`-konfigurasjonen. Lavere verdier gjør svarene mer fokuserte og deterministiske, mens høyere verdier øker kreativiteten og variasjonen. @@ -54,13 +152,171 @@ Kontroller tilfeldigheten og kreativiteten til AI-ens svar med `temperature`-kon Temperaturverdier varierer vanligvis fra 0,0 til 1,0: - **0.0-0.2**: Veldig fokuserte og deterministiske svar, ideelt for kodeanalyse og planlegging -- **0,3-0,5**: Balanserte svar med litt kreativitet, bra for generelle utviklingsoppgaver +- **0.3-0.5**: Balanserte svar med litt kreativitet, bra for generelle utviklingsoppgaver - **0.6-1.0**: Mer kreative og varierte svar, nyttig for idédugnad og utforskning +```json title="opencode.json" +{ + "mode": { + "analyze": { + "temperature": 0.1, + "prompt": "{file:./prompts/analysis.txt}" + }, + "build": { + "temperature": 0.3 + }, + "brainstorm": { + "temperature": 0.7, + "prompt": "{file:./prompts/creative.txt}" + } + } +} +``` + Hvis ingen temperatur er spesifisert, bruker OpenCode modellspesifikke standardinnstillinger (vanligvis 0 for de fleste modeller, 0,55 for Qwen-modeller). --- +### Prompt + +Angi en tilpasset systemprompt-fil for denne modusen med `prompt`-konfigurasjonen. Prompt-filen skal inneholde instruksjoner som er spesifikke for modusens formål. + +```json title="opencode.json" +{ + "mode": { + "review": { + "prompt": "{file:./prompts/code-review.txt}" + } + } +} +``` + +Denne banen er relativ til der konfigurasjonsfilen er plassert. Så dette fungerer for både den globale OpenCode-konfigurasjonen og den prosjektspesifikke konfigurasjonen. + +--- + +### Verktøy + +Kontroller hvilke verktøy som er tilgjengelige i denne modusen med `tools`-konfigurasjonen. Du kan aktivere eller deaktivere spesifikke verktøy ved å sette dem til `true` eller `false`. + +```json +{ + "mode": { + "readonly": { + "tools": { + "write": false, + "edit": false, + "bash": false, + "read": true, + "grep": true, + "glob": true + } + } + } +} +``` + +Hvis ingen verktøy er spesifisert, er alle verktøy aktivert som standard. + +--- + +#### Tilgjengelige verktøy + +Her er alle verktøyene som kan kontrolleres gjennom moduskonfigurasjonen. + +| Verktøy | Beskrivelse | +| ----------- | --------------------------- | +| `bash` | Utfør shell-kommandoer | +| `edit` | Endre eksisterende filer | +| `write` | Opprett nye filer | +| `read` | Les filinnhold | +| `grep` | Søk i filinnhold | +| `glob` | Finn filer etter mønster | +| `list` | List opp kataloginnhold | +| `patch` | Bruk patcher på filer | +| `todowrite` | Administrer gjøremålslister | +| `todoread` | Les gjøremålslister | +| `webfetch` | Hent webinnhold | + +--- + +## Egendefinerte moduser + +Du kan opprette dine egne tilpassede moduser ved å legge dem til i konfigurasjonen. Her er eksempler på bruk av begge metodene: + +### Bruke JSON-konfigurasjon + +```json title="opencode.json" {4-14} +{ + "$schema": "https://opencode.ai/config.json", + "mode": { + "docs": { + "prompt": "{file:./prompts/documentation.txt}", + "tools": { + "write": true, + "edit": true, + "bash": false, + "read": true, + "grep": true, + "glob": true + } + } + } +} +``` + +### Bruke markdown-filer + +Opprett modusfiler i `.opencode/modes/` for prosjektspesifikke moduser eller `~/.config/opencode/modes/` for globale moduser: + +```markdown title=".opencode/modes/debug.md" +--- +temperature: 0.1 +tools: + bash: true + read: true + grep: true + write: false + edit: false +--- + +You are in debug mode. Your primary goal is to help investigate and diagnose issues. + +Focus on: + +- Understanding the problem through careful analysis +- Using bash commands to inspect system state +- Reading relevant files and logs +- Searching for patterns and anomalies +- Providing clear explanations of findings + +Do not make any changes to files. Only investigate and report. +``` + +```markdown title="~/.config/opencode/modes/refactor.md" +--- +model: anthropic/claude-sonnet-4-20250514 +temperature: 0.2 +tools: + edit: true + read: true + grep: true + glob: true +--- + +You are in refactoring mode. Focus on improving code quality without changing functionality. + +Priorities: + +- Improve code readability and maintainability +- Apply consistent naming conventions +- Reduce code duplication +- Optimize performance where appropriate +- Ensure all tests continue to pass +``` + +--- + ### Bruksområder Her er noen vanlige bruksområder for forskjellige moduser. diff --git a/packages/web/src/content/docs/nb/plugins.mdx b/packages/web/src/content/docs/nb/plugins.mdx index 6b6e1edf3f0..27d23ec63a8 100644 --- a/packages/web/src/content/docs/nb/plugins.mdx +++ b/packages/web/src/content/docs/nb/plugins.mdx @@ -1,6 +1,6 @@ --- title: Programtillegg -description: Skriv dine egne programtillegg for å utvide opencode. +description: Skriv dine egne programtillegg for å utvide OpenCode. --- Plugins lar deg utvide OpenCode ved å koble til ulike hendelser og tilpasse atferd. Du kan lage plugins for å legge til nye funksjoner, integrere med eksterne tjenester eller endre standardoppførselen til OpenCode. @@ -308,6 +308,10 @@ export const CustomToolsPlugin: Plugin = async (ctx) => { Dine egendefinerte verktøy vil være tilgjengelige for OpenCode sammen med innebygde verktøy. +:::note +Hvis et plugin-verktøy bruker samme navn som et innebygd verktøy, vil plugin-verktøyet ha forrang. +::: + --- ### Logging diff --git a/packages/web/src/content/docs/nb/providers.mdx b/packages/web/src/content/docs/nb/providers.mdx index 58d325cab8c..682f923f8c4 100644 --- a/packages/web/src/content/docs/nb/providers.mdx +++ b/packages/web/src/content/docs/nb/providers.mdx @@ -57,7 +57,7 @@ testet og verifisert for å fungere godt med OpenCode. [Finn ut mer](/docs/zen). Hvis du er ny, anbefaler vi å starte med OpenCode Zen. ::: -1. Kjør kommandoen `/connect` i TUI, velg opencode og gå til [opencode.ai/auth](https://opencode.ai/auth). +1. Kjør kommandoen `/connect` i TUI, velg `OpenCode Zen` og gå til [opencode.ai/auth](https://opencode.ai/zen). ```txt /connect @@ -84,6 +84,39 @@ Det fungerer som alle andre leverandører i OpenCode og er helt valgfritt å bru --- +## OpenCode Go + +OpenCode Go er en lavpris abonnementsplan som gir pålitelig tilgang til populære åpne kodemodeller levert av OpenCode-teamet som har vært +testet og verifisert for å fungere godt med OpenCode. + +1. Kjør kommandoen `/connect` i TUI, velg `OpenCode Go`, og gå til [opencode.ai/auth](https://opencode.ai/zen). + + ```txt + /connect + ``` + +2. Logg på, legg til faktureringsdetaljene dine og kopier API-nøkkelen. + +3. Lim inn API-nøkkelen. + + ```txt + ┌ API key + │ + │ + │ + └ enter + ``` + +4. Kjør `/models` i TUI for å se listen over modeller vi anbefaler. + + ```txt + /models + ``` + +Det fungerer som alle andre leverandører i OpenCode og er helt valgfritt å bruke. + +--- + ## Katalog La oss se på noen av leverandørene i detalj. Hvis du vil legge til en leverandør til diff --git a/packages/web/src/content/docs/nb/sdk.mdx b/packages/web/src/content/docs/nb/sdk.mdx index bbff8ebecea..a9470944767 100644 --- a/packages/web/src/content/docs/nb/sdk.mdx +++ b/packages/web/src/content/docs/nb/sdk.mdx @@ -117,6 +117,78 @@ try { --- +## Strukturert Utdata + +Du kan be om strukturert JSON-utdata fra modellen ved å spesifisere et `format` med et JSON-skjema. Modellen vil bruke et `StructuredOutput`-verktøy for å returnere validert JSON som samsvarer med skjemaet ditt. + +### Grunnleggende bruk + +```typescript +const result = await client.session.prompt({ + path: { id: sessionId }, + body: { + parts: [{ type: "text", text: "Research Anthropic and provide company info" }], + format: { + type: "json_schema", + schema: { + type: "object", + properties: { + company: { type: "string", description: "Company name" }, + founded: { type: "number", description: "Year founded" }, + products: { + type: "array", + items: { type: "string" }, + description: "Main products", + }, + }, + required: ["company", "founded"], + }, + }, + }, +}) + +// Access the structured output +console.log(result.data.info.structured_output) +// { company: "Anthropic", founded: 2021, products: ["Claude", "Claude API"] } +``` + +### Utdataformattyper + +| Type | Beskrivelse | +| ------------- | --------------------------------------------------------------- | +| `text` | Standard. Standard tekstrespons (ingen strukturert utdata) | +| `json_schema` | Returnerer validert JSON som samsvarer med det angitte skjemaet | + +### JSON Skjemaformat + +Når du bruker `type: 'json_schema'`, oppgi: + +| Felt | Type | Beskrivelse | +| ------------ | --------------- | ---------------------------------------------------------- | +| `type` | `'json_schema'` | Påkrevd. Spesifiserer JSON-skjemamodus | +| `schema` | `object` | Påkrevd. JSON Schema-objekt som definerer utdatastrukturen | +| `retryCount` | `number` | Valgfritt. Antall valideringsforsøk (standard: 2) | + +### Feilhåndtering + +Hvis modellen ikke klarer å produsere gyldig strukturert utdata etter alle forsøk, vil svaret inkludere en `StructuredOutputError`: + +```typescript +if (result.data.info.error?.name === "StructuredOutputError") { + console.error("Failed to produce structured output:", result.data.info.error.message) + console.error("Attempts:", result.data.info.error.retries) +} +``` + +### Beste praksis + +1. **Gi klare beskrivelser** i skjemaegenskapene dine for å hjelpe modellen med å forstå hvilke data som skal trekkes ut +2. **Bruk `required`** for å spesifisere hvilke felt som må være til stede +3. **Hold skjemaer fokuserte** - komplekse nøstede skjemaer kan være vanskeligere for modellen å fylle ut riktig +4. **Angi passende `retryCount`** - øk for komplekse skjemaer, reduser for enkle + +--- + ## API-er SDK-en eksponerer alle server-API-er gjennom en typesikker klient. @@ -226,27 +298,27 @@ const { providers, default: defaults } = await client.config.providers() ### Sesjoner -| Metode | Beskrivelse | Merknader | -| ---------------------------------------------------------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | -| `session.list()` | List økter | Returnerer Session[] | -| `session.get({ path })` | Hent økt | Returnerer Session | -| `session.children({ path })` | List barneøkter | Returnerer Session[] | -| `session.create({ body })` | Opprett økt | Returnerer Session | -| `session.delete({ path })` | Slett økt | Returnerer `boolean` | -| `session.update({ path, body })` | Oppdater øktegenskaper | Returnerer Session | -| `session.init({ path, body })` | Analyser appen og lag `AGENTS.md` | Returnerer `boolean` | -| `session.abort({ path })` | Avbryt en kjørende økt | Returnerer `boolean` | -| `session.share({ path })` | Del økten | Returnerer Session | -| `session.unshare({ path })` | Slutt å dele økten | Returnerer Session | -| `session.summarize({ path, body })` | Oppsummer økten | Returnerer `boolean` | -| `session.messages({ path })` | List meldinger i en økt | Returnerer `{ info: `Message`, parts: `Part[]`}[]` | -| `session.message({ path })` | Hent meldingsdetaljer | Returnerer `{ info: `Message`, parts: `Part[]`}` | -| `session.prompt({ path, body })` | Send melding | `body.noReply: true` returnerer UserMessage (kun kontekst). Standard returnerer AssistantMessage med AI svar | -| `session.command({ path, body })` | Send kommando til økt | Returnerer `{ info: `AssistantMessage`, parts: `Part[]`}` | -| `session.shell({ path, body })` | Kjør en shell-kommando | Returnerer AssistantMessage | -| `session.revert({ path, body })` | Tilbakestill en melding | Returnerer Session | -| `session.unrevert({ path })` | Gjenopprett reverserte meldinger | Returnerer Session | -| `postSessionByIdPermissionsByPermissionId({ path, body })` | Svar på en tillatelsesforespørsel | Returnerer `boolean` | +| Metode | Beskrivelse | Merknader | +| ---------------------------------------------------------- | --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `session.list()` | List økter | Returnerer Session[] | +| `session.get({ path })` | Hent økt | Returnerer Session | +| `session.children({ path })` | List barneøkter | Returnerer Session[] | +| `session.create({ body })` | Opprett økt | Returnerer Session | +| `session.delete({ path })` | Slett økt | Returnerer `boolean` | +| `session.update({ path, body })` | Oppdater øktegenskaper | Returnerer Session | +| `session.init({ path, body })` | Analyser appen og lag `AGENTS.md` | Returnerer `boolean` | +| `session.abort({ path })` | Avbryt en kjørende økt | Returnerer `boolean` | +| `session.share({ path })` | Del økten | Returnerer Session | +| `session.unshare({ path })` | Slutt å dele økten | Returnerer Session | +| `session.summarize({ path, body })` | Oppsummer økten | Returnerer `boolean` | +| `session.messages({ path })` | List meldinger i en økt | Returnerer `{ info: `Message`, parts: `Part[]`}[]` | +| `session.message({ path })` | Hent meldingsdetaljer | Returnerer `{ info: `Message`, parts: `Part[]`}` | +| `session.prompt({ path, body })` | Send melding | `body.noReply: true` returnerer UserMessage (kun kontekst). Standard returnerer AssistantMessage med AI svar. Støtter `body.outputFormat` for [strukturert utdata](#strukturert-utdata) | +| `session.command({ path, body })` | Send kommando til økt | Returnerer `{ info: `AssistantMessage`, parts: `Part[]`}` | +| `session.shell({ path, body })` | Kjør en shell-kommando | Returnerer AssistantMessage | +| `session.revert({ path, body })` | Tilbakestill en melding | Returnerer Session | +| `session.unrevert({ path })` | Gjenopprett reverserte meldinger | Returnerer Session | +| `postSessionByIdPermissionsByPermissionId({ path, body })` | Svar på en tillatelsesforespørsel | Returnerer `boolean` | --- diff --git a/packages/web/src/content/docs/nb/themes.mdx b/packages/web/src/content/docs/nb/themes.mdx index a294a6a0fa4..2284848e318 100644 --- a/packages/web/src/content/docs/nb/themes.mdx +++ b/packages/web/src/content/docs/nb/themes.mdx @@ -3,9 +3,9 @@ title: Temaer description: Velg et innebygd tema eller definer ditt eget. --- -Med opencode kan du velge fra ett av flere innebygde temaer, bruke et tema som tilpasser seg terminaltemaet ditt, eller definere ditt eget tilpassede tema. +Med OpenCode kan du velge fra ett av flere innebygde temaer, bruke et tema som tilpasser seg terminaltemaet ditt, eller definere ditt eget tilpassede tema. -Som standard bruker opencode vårt eget `opencode`-tema. +Som standard bruker OpenCode vårt eget `opencode`-tema. --- @@ -23,7 +23,7 @@ Uten truecolor-støtte kan temaer vises med redusert fargenøyaktighet eller fal ## Innebygde temaer -opencode kommer med flere innebygde temaer. +OpenCode kommer med flere innebygde temaer. | Navn | Beskrivelse | | ---------------------- | ------------------------------------------------------------------------- | @@ -53,7 +53,7 @@ Og mer, vi legger stadig til nye temaer. Systemtemaet er for brukere som: -- Vil at opencode skal matche terminalens utseende +- Vil at OpenCode skal matche terminalens utseende - Bruker tilpassede terminalfargeskjemaer - Foretrekker et konsistent utseende på tvers av alle terminalapplikasjoner @@ -61,11 +61,11 @@ Systemtemaet er for brukere som: ## Bruke et tema -Du kan velge et tema ved å åpne temavelgeren med kommandoen `/theme`. Eller du kan spesifisere det i [config](/docs/config). +Du kan velge et tema ved å åpne temavelgeren med kommandoen `/theme`. Eller du kan spesifisere det i `tui.json`. -```json title="opencode.json" {3} +```json title="tui.json" {3} { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "theme": "tokyonight" } ``` @@ -74,7 +74,7 @@ Du kan velge et tema ved å åpne temavelgeren med kommandoen `/theme`. Eller du ## Egendefinerte temaer -opencode støtter et fleksibelt JSON-basert temasystem som lar brukere enkelt lage og tilpasse temaer. +OpenCode støtter et fleksibelt JSON-basert temasystem som lar brukere enkelt lage og tilpasse temaer. --- diff --git a/packages/web/src/content/docs/nb/tui.mdx b/packages/web/src/content/docs/nb/tui.mdx index fae2a2364c1..741ba46029c 100644 --- a/packages/web/src/content/docs/nb/tui.mdx +++ b/packages/web/src/content/docs/nb/tui.mdx @@ -5,9 +5,9 @@ description: Bruke opencode-terminalbrukergrensesnittet. import { Tabs, TabItem } from "@astrojs/starlight/components" -opencode gir et interaktivt terminalgrensesnitt (TUI) for å jobbe med prosjektene dine med en LLM. +OpenCode gir et interaktivt terminalgrensesnitt (TUI) for å jobbe med prosjektene dine med en LLM. -Å kjøre opencode starter TUI for gjeldende katalog. +Å kjøre OpenCode starter TUI for gjeldende katalog. ```bash opencode @@ -57,7 +57,7 @@ Utdataene fra kommandoen legges til samtalen som et verktøyresultat. ## Kommandoer -Når du bruker opencode TUI, kan du skrive `/` etterfulgt av et kommandonavn for raskt å utføre handlinger. For eksempel: +Når du bruker OpenCode TUI, kan du skrive `/` etterfulgt av et kommandonavn for raskt å utføre handlinger. For eksempel: ```bash frame="none" /help @@ -71,7 +71,7 @@ Her er alle tilgjengelige slash-kommandoer: ### connect -Legg til en leverandør til opencode. Lar deg velge fra tilgjengelige leverandører og legge til deres API-nøkler. +Legg til en leverandør til OpenCode. Lar deg velge fra tilgjengelige leverandører og legge til deres API-nøkler. ```bash frame="none" /connect @@ -117,7 +117,7 @@ Veksle visning av verktøydetaljer. ### exit -Avslutt opencode. _Aliaser_: `/quit`, `/q` +Avslutt OpenCode. _Aliaser_: `/quit`, `/q` ```bash frame="none" /exit @@ -230,12 +230,12 @@ Del gjeldende økt. [Finn ut mer](/docs/share). --- -### theme +### themes Liste over tilgjengelige temaer. ```bash frame="none" -/theme +/themes ``` **Nøkkelbinding:** `ctrl+x t` @@ -355,24 +355,34 @@ Noen editorer trenger kommandolinjeargumenter for å kjøre i blokkeringsmodus. ## Konfigurasjon -Du kan tilpasse TUI-oppførselen gjennom opencode-konfigurasjonsfilen. +Du kan tilpasse TUI-oppførselen gjennom `tui.json` (eller `tui.jsonc`). -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", - "tui": { - "scroll_speed": 3, - "scroll_acceleration": { - "enabled": true - } - } + "$schema": "https://opencode.ai/tui.json", + "theme": "opencode", + "keybinds": { + "leader": "ctrl+x" + }, + "scroll_speed": 3, + "scroll_acceleration": { + "enabled": true + }, + "diff_style": "auto" } ``` +Dette er atskilt fra `opencode.json`, som konfigurerer server-/kjøretidsoppførsel. + ### Alternativer -- `scroll_acceleration` - Aktiver rulleakselerasjon i macOS-stil for jevn, naturlig rulling. Når aktivert, øker rullehastigheten med raske rullebevegelser og forblir presis for langsommere bevegelser. **Denne innstillingen har forrang over `scroll_speed` og overstyrer den når den er aktivert.** -- `scroll_speed` - Styrer hvor raskt TUI ruller når du bruker rullekommandoer (minimum: `1`). Standard er `3`. **Merk: Dette ignoreres hvis `scroll_acceleration.enabled` er satt til `true`.** +- `theme` - Angir UI-temaet ditt. [Finn ut mer](/docs/themes). +- `keybinds` - Tilpasser hurtigtaster. [Finn ut mer](/docs/keybinds). +- `scroll_acceleration.enabled` - Aktiver rulleakselerasjon i macOS-stil for jevn, naturlig rulling. Når aktivert, øker rullehastigheten med raske rullebevegelser og forblir presis for langsommere bevegelser. **Denne innstillingen har forrang over `scroll_speed` og overstyrer den når den er aktivert.** +- `scroll_speed` - Styrer hvor raskt TUI ruller når du bruker rullekommandoer (minimum: `0.001`, støtter desimalverdier). Standard er `3`. **Merk: Dette ignoreres hvis `scroll_acceleration.enabled` er satt til `true`.** +- `diff_style` - Kontrollerer diff-gjengivelse. `"auto"` tilpasser seg terminalbredden, `"stacked"` viser alltid en enkeltkolonneoppsett. + +Bruk `OPENCODE_TUI_CONFIG` for å laste en egendefinert TUI-konfigurasjonsbane. --- diff --git a/packages/web/src/content/docs/nb/zen.mdx b/packages/web/src/content/docs/nb/zen.mdx index f8dac0f3919..51399615e56 100644 --- a/packages/web/src/content/docs/nb/zen.mdx +++ b/packages/web/src/content/docs/nb/zen.mdx @@ -14,7 +14,7 @@ OpenCode Zen er for øyeblikket i beta. ::: Zen fungerer som alle andre leverandører i OpenCode. Du logger på OpenCode Zen og får -din API nøkkel. Den er **helt valgfri** og du trenger ikke bruke den for å bruke den +din API nøkkel. Den er **helt valgfri** og du trenger ikke bruke den for å bruke OpenCode. --- @@ -64,6 +64,7 @@ Du kan også få tilgang til modellene våre gjennom følgende API-endepunkter. | Modell | Modell ID | Endepunkt | AI SDK Pakke | | ------------------ | ------------------ | -------------------------------------------------- | --------------------------- | +| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | @@ -73,22 +74,24 @@ Du kan også få tilgang til modellene våre gjennom følgende API-endepunkter. | GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | | Gemini 3 Pro | gemini-3-pro | `https://opencode.ai/zen/v1/models/gemini-3-pro` | `@ai-sdk/google` | | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.1 | minimax-m2.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.1 Free | minimax-m2.1-free | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| GLM 5 | glm-5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 4.7 | glm-4.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM 4.7 Free | glm-4.7-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 4.6 | glm-4.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 Free | kimi-k2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2 Thinking | kimi-k2-thinking | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2 | kimi-k2 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Qwen3 Coder 480B | qwen3-coder | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -117,29 +120,34 @@ Vi støtter en pay-as-you-go-modell. Nedenfor er prisene **per 1 million tokens* | Modell | Inndata | Utdata | Bufret lesing | Bufret skriving | | --------------------------------- | ------- | ------ | ------------- | --------------- | | Big Pickle | Gratis | Gratis | Gratis | - | -| MiniMax M2.1 Free | Gratis | Gratis | Gratis | - | +| MiniMax M2.5 Free | Gratis | Gratis | Gratis | - | +| MiniMax M2.5 | $0,30 | $1,20 | $0,06 | - | | MiniMax M2.1 | $0,30 | $1,20 | $0,10 | - | -| GLM 4.7 Free | Gratis | Gratis | Gratis | - | +| GLM 5 | $1,00 | $3,20 | $0,20 | - | | GLM 4.7 | $0,60 | $2,20 | $0,10 | - | | GLM 4.6 | $0,60 | $2,20 | $0,10 | - | -| Kimi K2.5 Free | Gratis | Gratis | Gratis | - | | Kimi K2.5 | $0,60 | $3,00 | $0,08 | - | | Kimi K2 Thinking | $0,40 | $2,50 | - | - | | Kimi K2 | $0,40 | $2,50 | - | - | | Qwen3 Coder 480B | $0,45 | $1,50 | - | - | +| Claude Opus 4.6 (≤ 200K tokens) | $5,00 | $25,00 | $0,50 | $6,25 | +| Claude Opus 4.6 (> 200K tokens) | $10,00 | $37,50 | $1,00 | $12,50 | +| Claude Opus 4.5 | $5,00 | $25,00 | $0,50 | $6,25 | +| Claude Opus 4.1 | $15,00 | $75,00 | $1,50 | $18,75 | +| Claude Sonnet 4.6 (≤ 200K tokens) | $3,00 | $15,00 | $0,30 | $3,75 | +| Claude Sonnet 4.6 (> 200K tokens) | $6,00 | $22,50 | $0,60 | $7,50 | | Claude Sonnet 4.5 (≤ 200K tokens) | $3,00 | $15,00 | $0,30 | $3,75 | | Claude Sonnet 4.5 (> 200K tokens) | $6,00 | $22,50 | $0,60 | $7,50 | | Claude Sonnet 4 (≤ 200K tokens) | $3,00 | $15,00 | $0,30 | $3,75 | | Claude Sonnet 4 (> 200K tokens) | $6,00 | $22,50 | $0,60 | $7,50 | | Claude Haiku 4.5 | $1,00 | $5,00 | $0,10 | $1,25 | | Claude Haiku 3.5 | $0,80 | $4,00 | $0,08 | $1,00 | -| Claude Opus 4.6 (≤ 200K tokens) | $5,00 | $25,00 | $0,50 | $6,25 | -| Claude Opus 4.6 (> 200K tokens) | $10,00 | $37,50 | $1,00 | $12,50 | -| Claude Opus 4.5 | $5,00 | $25,00 | $0,50 | $6,25 | -| Claude Opus 4.1 | $15,00 | $75,00 | $1,50 | $18,75 | +| Gemini 3.1 Pro (≤ 200K tokens) | $2,00 | $12,00 | $0,20 | - | +| Gemini 3.1 Pro (> 200K tokens) | $4,00 | $18,00 | $0,40 | - | | Gemini 3 Pro (≤ 200K tokens) | $2,00 | $12,00 | $0,20 | - | | Gemini 3 Pro (> 200K tokens) | $4,00 | $18,00 | $0,40 | - | | Gemini 3 Flash | $0,50 | $3,00 | $0,05 | - | +| GPT 5.3 Codex | $1,75 | $14,00 | $0,175 | - | | GPT 5.2 | $1,75 | $14,00 | $0,175 | - | | GPT 5.2 Codex | $1,75 | $14,00 | $0,175 | - | | GPT 5.1 | $1,07 | $8,50 | $0,107 | - | @@ -158,9 +166,7 @@ Kredittkortgebyrer overføres til kostpris (4,4 % + $0,30 per transaksjon); vi b De gratis modellene: -- GLM 4.7 Free er tilgjengelig på OpenCode i en begrenset periode. Teamet bruker denne tiden til å samle tilbakemeldinger og forbedre modellen. -- Kimi K2.5 Free er tilgjengelig på OpenCode i en begrenset periode. Teamet bruker denne tiden til å samle tilbakemeldinger og forbedre modellen. -- MiniMax M2.1 Free er tilgjengelig på OpenCode i en begrenset periode. Teamet bruker denne tiden til å samle tilbakemeldinger og forbedre modellen. +- MiniMax M2.5 Free er tilgjengelig på OpenCode i en begrenset periode. Teamet bruker denne tiden til å samle tilbakemeldinger og forbedre modellen. - Big Pickle er en stealth-modell som er gratis på OpenCode i en begrenset periode. Teamet bruker denne tiden til å samle tilbakemeldinger og forbedre modellen. Kontakt oss hvis du har spørsmål. @@ -191,9 +197,7 @@ belaster deg mer enn $20 hvis saldoen din går under $5. Alle våre modeller er hostet i USA. Leverandørene våre følger retningslinjer om ingen datalagring og bruker ikke dataene dine til modellopplæring, med følgende unntak: - Big Pickle: I løpet av gratisperioden kan innsamlede data brukes til å forbedre modellen. -- GLM 4.7 Free: I løpet av gratisperioden kan innsamlede data brukes til å forbedre modellen. -- Kimi K2.5 Free: I løpet av gratisperioden kan innsamlede data brukes til å forbedre modellen. -- MiniMax M2.1 Free: I løpet av gratisperioden kan innsamlede data brukes til å forbedre modellen. +- MiniMax M2.5 Free: I løpet av gratisperioden kan innsamlede data brukes til å forbedre modellen. - OpenAI APIer: Forespørsler oppbevares i 30 dager i samsvar med [OpenAIs datapolicyer](https://platform.openai.com/docs/guides/your-data). - Anthropic APIer: Forespørsler oppbevares i 30 dager i samsvar med [Anthropics datapolicyer](https://docs.anthropic.com/en/docs/claude-code/data-usage). @@ -251,4 +255,4 @@ Vi opprettet OpenCode Zen for å: 1. **Benchmark** de beste modellene/leverandørene for kodingsagenter. 2. Ha tilgang til alternativene for **høyeste kvalitet** og ikke nedgrader ytelsen eller rute trafikk til billigere leverandører. 3. Gi videre eventuelle **prisfall** ved å selge til kostpris; så det eneste påslaget er å dekke behandlingsgebyrene våre. -4. Ha **ingen låsing** ved å la deg bruke den med en hvilken som helst annen kodeagent. Og la deg alltid bruke en hvilken som helst annen leverandør med opencode også. +4. Ha **ingen låsing** ved å la deg bruke den med en hvilken som helst annen kodeagent. Og la deg alltid bruke en hvilken som helst annen leverandør med OpenCode også. diff --git a/packages/web/src/content/docs/pl/cli.mdx b/packages/web/src/content/docs/pl/cli.mdx index 35218590181..82ff7f52b22 100644 --- a/packages/web/src/content/docs/pl/cli.mdx +++ b/packages/web/src/content/docs/pl/cli.mdx @@ -558,6 +558,7 @@ OpenCode można skonfigurować za pomocą zmiennych środowiskowych. | `OPENCODE_AUTO_SHARE` | boolean | Automatycznie udostępniaj sesje | | `OPENCODE_GIT_BASH_PATH` | string | Ścieżka do pliku wykonywalnego Git Bash w systemie Windows | | `OPENCODE_CONFIG` | string | Ścieżka do pliku konfiguracyjnego | +| `OPENCODE_TUI_CONFIG` | string | Ścieżka do pliku konfiguracyjnego TUI | | `OPENCODE_CONFIG_DIR` | string | Ścieżka do katalogu konfiguracyjnego | | `OPENCODE_CONFIG_CONTENT` | string | Treść konfiguracji JSON (inline) | | `OPENCODE_DISABLE_AUTOUPDATE` | boolean | Wyłącz automatyczne sprawdzanie aktualizacji | diff --git a/packages/web/src/content/docs/pl/config.mdx b/packages/web/src/content/docs/pl/config.mdx index cde2e312eaa..a6a6fb156d7 100644 --- a/packages/web/src/content/docs/pl/config.mdx +++ b/packages/web/src/content/docs/pl/config.mdx @@ -94,7 +94,9 @@ Możesz włączyć serwer w konfiguracji projektu: ### Globalna -Umieść swoją globalną konfigurację OpenCode w `~/.config/opencode/opencode.json`. Użyj jej do ustawień ogólnych dla użytkownika, takich jak motywy, domyślny dostawca lub skróty klawiszowe. +Umieść swoją globalną konfigurację OpenCode w `~/.config/opencode/opencode.json`. Użyj jej do ustawień ogólnych dla użytkownika, takich jak dostawcy, modele i uprawnienia. + +Dla ustawień specyficznych dla TUI, użyj `~/.config/opencode/tui.json`. Konfiguracja globalna ma pierwszeństwo przed konfiguracją zdalną. @@ -104,8 +106,10 @@ Konfiguracja globalna ma pierwszeństwo przed konfiguracją zdalną. Dodaj `opencode.json` w katalogu głównym projektu. Konfiguracja projektu ma najwyższy priorytet wśród plików konfiguracyjnych — nadpisuje konfiguracje globalne i zdalne. +Dla ustawień TUI specyficznych dla projektu, dodaj plik `tui.json` obok niego. + :::tip -Dodaj ten plik do kontroli wersji, aby udostępniać konfigurację zespołowi. +Umieść konfigurację specyficzną dla projektu w katalogu głównym projektu. ::: Kiedy OpenCode się uruchamia, szuka pliku konfiguracyjnego w katalogu głównym repozytorium Git. @@ -150,28 +154,24 @@ Twój edytor powinien zapewniać walidację i autouzupełnianie na podstawie teg ### TUI -Możesz skonfigurować zachowanie TUI za pomocą opcji `tui`. +Użyj dedykowanego pliku `tui.json` (lub `tui.jsonc`) dla ustawień specyficznych dla TUI. -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", - "tui": { - "scroll_speed": 3, - "scroll_acceleration": { - "enabled": true - }, - "diff_style": "auto" - } + "$schema": "https://opencode.ai/tui.json", + "scroll_speed": 3, + "scroll_acceleration": { + "enabled": true + }, + "diff_style": "auto" } ``` -Dostępne opcje: +Użyj `OPENCODE_TUI_CONFIG`, aby wskazać niestandardowy plik konfiguracyjny TUI. -- `scroll_acceleration.enabled` - Włącz przyspieszenie przewijania na gładzikach macOS. **Ma pierwszeństwo przed `scroll_speed`.** -- `scroll_speed` - Niestandardowy mnożnik szybkości przewijania (domyślnie: `3`, minimalnie: `1`). Ignorowane, jeśli `scroll_acceleration.enabled` ustawiono na `true`. -- `diff_style` – Sterowanie renderowaniem różnic. `"auto"` przełącza się w zależności od szerokości terminala, `"stacked"` zawsze wymusza pojedynczą kolumnę. +Przestarzałe klucze `theme`, `keybinds` i `tui` w `opencode.json` są wycofywane i automatycznie migrowane, gdy to możliwe. -[Dowiedz się więcej o korzystaniu z TUI](/docs/tui). +[Dowiedz się więcej o konfiguracji TUI tutaj](/docs/tui#configure). --- @@ -297,16 +297,16 @@ Token okaziciela (`AWS_BEARER_TOKEN_BEDROCK` lub `/connect`) ma pierwszeństwo p ### Theme (Motyw) -Skonfiguruj motyw interfejsu OpenCode za pomocą opcji `theme`. +Ustaw motyw interfejsu użytkownika w `tui.json`. -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", - "theme": "" + "$schema": "https://opencode.ai/tui.json", + "theme": "tokyonight" } ``` -[Dowiedz się więcej o motywach](/docs/themes). +[Dowiedz się więcej tutaj](/docs/themes). --- @@ -402,16 +402,16 @@ Możesz także definiować polecenia przy użyciu plików Markdown w `~/.config/ ### Keybinds (Skróty klawiszowe) -Możesz dostosować skróty klawiszowe za pomocą opcji `keybinds`. +Dostosuj skróty klawiszowe w `tui.json`. -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "keybinds": {} } ``` -[Dowiedz się więcej o skrótach klawiszowych](/docs/keybinds). +[Dowiedz się więcej tutaj](/docs/keybinds). --- diff --git a/packages/web/src/content/docs/pl/custom-tools.mdx b/packages/web/src/content/docs/pl/custom-tools.mdx index eaf403d2de8..7a38512c18c 100644 --- a/packages/web/src/content/docs/pl/custom-tools.mdx +++ b/packages/web/src/content/docs/pl/custom-tools.mdx @@ -3,7 +3,7 @@ title: Narzędzia specjalistyczne description: Twórz narzędzi, które LLM mogą być uruchamiane w otwartym kodzie. --- -Narzędzia stosowane do funkcji, z których LLM może korzystać podczas rozmów. Współpracują z [wbudowanymi narzędziami] (./tools) opencode, wtyczka jak `read`, `write` i `bash`. +Narzędzia specjalistyczne to funkcje, które tworzysz i które LLM może wywoływać podczas rozmów. Współpracują one z [wbudowanymi narzędziami](/docs/tools) opencode, takimi jak `read`, `write` i `bash`. --- @@ -75,7 +75,33 @@ export const multiply = tool({ }) ``` -Tworzy do dwóch narzędzi: `math_add` i `math_multiply`. +Tworzy to dwa narzędzia: `math_add` i `math_multiply`. + +--- + +#### Kolizje nazw z wbudowanymi narzędziami + +Niestandardowe narzędzia są identyfikowane według nazwy narzędzia. Jeśli niestandardowe narzędzie używa tej samej nazwy co wbudowane narzędzie, niestandardowe narzędzie ma pierwszeństwo. + +Na przykład ten plik zastępuje wbudowane narzędzie `bash`: + +```ts title=".opencode/tools/bash.ts" +import { tool } from "@opencode-ai/plugin" + +export default tool({ + description: "Restricted bash wrapper", + args: { + command: tool.schema.string(), + }, + async execute(args) { + return `blocked: ${args.command}` + }, +}) +``` + +:::note +Preferuj unikalne nazwy, chyba że celowo chcesz zastąpić wbudowane narzędzie. Jeśli chcesz wyłączyć wbudowane narzędzie, ale go nie nadpisywać, użyj [uprawnień](/docs/permissions). +::: --- @@ -135,9 +161,9 @@ export default tool({ ### Napisz narzędzie w Pythonie -Napisz swoje narzędzie w języku angielskim. Oto przykład dodania dwóch liczb przy użyciu języka Python. +Możesz pisać swoje narzędzia w dowolnym języku. Oto przykład dodania dwóch liczb przy użyciu języka Python. -Fragment utworu jako skrypt w języku Python: +Najpierw utwórz narzędzie jako skrypt w języku Python: ```python title=".opencode/tools/add.py" import sys @@ -147,7 +173,7 @@ b = int(sys.argv[2]) print(a + b) ``` -Utwór instrumentalny, który jest ukryty: +Następnie utwórz definicję narzędzia, która go wywołuje: ```ts title=".opencode/tools/python-add.ts" {10} import { tool } from "@opencode-ai/plugin" @@ -167,4 +193,4 @@ export default tool({ }) ``` -Tutaj istnieje narzędzie [`Bun.$`](https://bun.com/docs/runtime/shell) uruchamiające skryptu w języku Python. +Tutaj używamy narzędzia [`Bun.$`](https://bun.com/docs/runtime/shell) do uruchomienia skryptu w języku Python. diff --git a/packages/web/src/content/docs/pl/ecosystem.mdx b/packages/web/src/content/docs/pl/ecosystem.mdx index 7c75340c57d..7ae5cce3eeb 100644 --- a/packages/web/src/content/docs/pl/ecosystem.mdx +++ b/packages/web/src/content/docs/pl/ecosystem.mdx @@ -1,76 +1,77 @@ --- title: Ekosystem -description: Projekty i integracje zbudowane w opencode. +description: Projekty i integracje zbudowane przy użyciu OpenCode. --- -Zgromadzenie stowarzyszenia organizacji na opencode. +Zbiór projektów społeczności zbudowanych na OpenCode. :::note -Chcesz zadać swój projekt badawczy z opencode do tej listy? Prześlij PR. +Chcesz dodać swój projekt związany z OpenCode do tej listy? Prześlij PR. ::: -Możesz także sprawdzić [awesome-opencode](https://github.com/awesome-opencode/awesome-opencode) i [opencode.cafe](https://opencode.cafe), grupę skupiającą ekosystem i społeczność. +Możesz również sprawdzić [awesome-opencode](https://github.com/awesome-opencode/awesome-opencode) oraz [opencode.cafe](https://opencode.cafe), społeczność agregującą ekosystem i społeczność. --- -## Wtyki +## Wtyczki -| Imię | Opis | -| --------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | -| [opencode-daytona](https://github.com/jamesmurdza/daytona/blob/main/guides/typescript/opencode/README.md) | Automatycznie uruchamiaj sesje opencode w izolowanych piaskownicach Daytona z synchronizacją git i podglądami na żywo | -| [opencode-helicone-session](https://github.com/H2Shami/opencode-helicone-session) | Automatycznie wstawiaj nagłówki sesji Helicone w celu grupowania urządzeń | -| [opencode-type-inject](https://github.com/nick-vi/opencode-type-inject) | Automatyczne wstrzykiwacze TypeScript/Svelte do odczytania plików za pomocą narzędzi wyszukiwania | -| [opencode-openai-codex-auth](https://github.com/numman-ali/opencode-openai-codex-auth) | wykorzystać do wykorzystania ChatGPT Plus/Pro zamiast kredytu API | -| [opencode-gemini-auth](https://github.com/jenslys/opencode-gemini-auth) | korzystać z planu Gemini zamiast rozliczeń API | -| [opencode-antigravity-auth](https://github.com/NoeFabris/opencode-antigravity-auth) | Wykorzystanie z bezpłatnych modeli Antigravity zamiast rozliczeń API | -| [opencode-devcontainers](https://github.com/athal7/opencode-devcontainers) | Izolacja wielooddziałowych kontenerów deweloperskich z płytkami klonami i automatycznie przypisywanymi portami | -| [opencode-google-antigravity-auth](https://github.com/shekohex/opencode-google-antigravity-auth) | Wtyczka Google Antigravity OAuth z obsługą obsługi Google i bardziej niezawodną obsługą API | -| [opencode-dynamic-context-pruning](https://github.com/Tarquinen/opencode-dynamic-context-pruning) | Zoptymalizuj wykorzystanie tokena, usuwając przestarzałe dane wyjściowe narzędzia | -| [opencode-websearch-cited](https://github.com/ghoulr/opencode-websearch-cited.git) | Dodaj natywną obsługę wyszukiwania w sieci dla dostawców w stylu opartym na Google | -| [opencode-pty](https://github.com/shekohex/opencode-pty.git) | Uruchomienie agenta AI uruchamiającego się w tle w PTY i wytwarzanie ich interaktywnych danych. | -| [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Instrukcje dla nieinteraktywnych obowiązków - zaniechanie zawieszenia operacji zależnych od TTY | -| [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Śledź udostępnić opencode za pomocą Wakatime | -| [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Oczyść tabelę przecenioną przez LLM | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | 10x szybsza edycja kodu dzięki Morph Fast Apply API i znacznikom leniwej edycji | -| [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Agencje odpowiedzialne w tle, gotowe narzędzia LSP/AST/MCP, wyselekcjonowani agenci, kompatybilni z Claude Code | -| [opencode-notificator](https://github.com/panta82/opencode-notificator) | Powiadomienia na pulpicie i alerty dźwiękowe dotyczące sesji opencode | -| [opencode-notifier](https://github.com/mohak34/opencode-notifier) | Powiadomienia na pulpicie i alerty dźwiękowe dotyczące uprawnień, wyników i zdarzeń o błędach | -| [opencode-zellij-namer](https://github.com/24601/opencode-zellij-namer) | Automatyczne nazewnictwo sesji Zellij oparte na sztucznej inteligencji w oparciu o kontekst opencode | -| [opencode-skillful](https://github.com/zenobi-us/opencode-skillful) | Zezwalaj agentom opencode na leniwe ładowanie podpowiedzi na podstawie odkrywania możliwości i wstrzykiwania | -| [opencode-supermemory](https://github.com/supermemoryai/opencode-supermemory) | Trwała pamięć w sesjach przy użyciu Supermemory | -| [@plannotator/opencode](https://github.com/backnotprop/plannotator/tree/main/apps/opencode-plugin) | Interaktywny przegląd planu z adnotacją wizualną i użytkową prywatną/offline | -| [@openspoon/subtask2](https://github.com/spoons-and-mirrors/subtask2) | Rozszerzony opencode/polecenia do połączenia sieciowego ze szczegółową kontrolą bezpieczeństwa | -| [opencode-scheduler](https://github.com/different-ai/opencode-scheduler) | Zaplanuj powtarzające się zadania, używając launchd (Mac) lub systemd (Linux) ze składaną cron | -| [micode](https://github.com/vtemian/micode) | Ustrukturyzowana burza mózgów → Plan → Wdrożenie wyjścia z ciągłością sesji | -| [octto](https://github.com/vtemian/octto) | Interaktywny interfejs do burzy mózgów AI z formularzami kontrolnymi wielu pytań | -| [opencode-background-agents](https://github.com/kdcokenny/opencode-background-agents) | Agencje krytyczne w tle w stylu Claude Code z delegowaniem asynchronicznym i trwałością kontekstu | -| [opencode-notify](https://github.com/kdcokenny/opencode-notify) | Natywne uruchomienie systemu dla opencode – wiesz, kiedy zadania zostaną zakończone | -| [opencode-workspace](https://github.com/kdcokenny/opencode-workspace) | Lista wiązek orkiestracji wieloagentowej – 16 dostępna, jedna instalacja | -| [opencode-worktree](https://github.com/kdcokenny/opencode-worktree) | Drzewa robocze Git o zerowym tarciu dla opencode | +| Nazwa | Opis | +| -------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | +| [opencode-daytona](https://github.com/daytonaio/daytona/tree/main/libs/opencode-plugin) | Automatyczne uruchamianie sesji OpenCode w izolowanych piaskownicach Daytona z synchronizacją git i podglądem na żywo | +| [opencode-helicone-session](https://github.com/H2Shami/opencode-helicone-session) | Automatyczne wstrzykiwanie nagłówków sesji Helicone do grupowania żądań | +| [opencode-type-inject](https://github.com/nick-vi/opencode-type-inject) | Automatyczne wstrzykiwanie typów TypeScript/Svelte do odczytów plików za pomocą narzędzi wyszukiwania | +| [opencode-openai-codex-auth](https://github.com/numman-ali/opencode-openai-codex-auth) | Użyj subskrypcji ChatGPT Plus/Pro zamiast kredytów API | +| [opencode-gemini-auth](https://github.com/jenslys/opencode-gemini-auth) | Użyj istniejącego planu Gemini zamiast płatności za API | +| [opencode-antigravity-auth](https://github.com/NoeFabris/opencode-antigravity-auth) | Użyj darmowych modeli Antigravity zamiast płatności za API | +| [opencode-devcontainers](https://github.com/athal7/opencode-devcontainers) | Izolacja kontenerów deweloperskich dla wielu gałęzi z płytkim klonowaniem i automatycznie przypisywanymi portami | +| [opencode-google-antigravity-auth](https://github.com/shekohex/opencode-google-antigravity-auth) | Wtyczka Google Antigravity OAuth ze wsparciem dla wyszukiwarki Google i bardziej niezawodną obsługą API | +| [opencode-dynamic-context-pruning](https://github.com/Tarquinen/opencode-dynamic-context-pruning) | Optymalizacja zużycia tokenów poprzez usuwanie przestarzałych wyników narzędzi | +| [opencode-vibeguard](https://github.com/inkdust2021/opencode-vibeguard) | Redagowanie sekretów/danych osobowych do placeholderów w stylu VibeGuard przed wywołaniem LLM; przywracanie lokalnie | +| [opencode-websearch-cited](https://github.com/ghoulr/opencode-websearch-cited.git) | Dodaj natywną obsługę wyszukiwania w sieci dla wspieranych dostawców w stylu Google grounded | +| [opencode-pty](https://github.com/shekohex/opencode-pty.git) | Umożliwia agentom AI uruchamianie procesów w tle w PTY i wysyłanie do nich interaktywnych danych wejściowych. | +| [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Instrukcje dla nieinteraktywnych poleceń powłoki - zapobiega zawieszeniom operacji zależnych od TTY | +| [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Śledź użycie OpenCode za pomocą Wakatime | +| [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Oczyść tabele markdown generowane przez LLM | +| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | 10x szybsza edycja kodu dzięki API Morph Fast Apply i leniwym znacznikom edycji | +| [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Agenci w tle, wbudowane narzędzia LSP/AST/MCP, wyselekcjonowani agenci, kompatybilność z Claude Code | +| [opencode-notificator](https://github.com/panta82/opencode-notificator) | Powiadomienia na pulpicie i alerty dźwiękowe dla sesji OpenCode | +| [opencode-notifier](https://github.com/mohak34/opencode-notifier) | Powiadomienia na pulpicie i alerty dźwiękowe dla uprawnień, zakończeń zadań i błędów | +| [opencode-zellij-namer](https://github.com/24601/opencode-zellij-namer) | Automatyczne nazywanie sesji Zellij oparte na AI w oparciu o kontekst OpenCode | +| [opencode-skillful](https://github.com/zenobi-us/opencode-skillful) | Pozwól agentom OpenCode na leniwe ładowanie promptów na żądanie z odkrywaniem umiejętności i wstrzykiwaniem | +| [opencode-supermemory](https://github.com/supermemoryai/opencode-supermemory) | Trwała pamięć między sesjami przy użyciu Supermemory | +| [@plannotator/opencode](https://github.com/backnotprop/plannotator/tree/main/apps/opencode-plugin) | Interaktywny przegląd planów z wizualnymi adnotacjami i prywatnym/offline udostępnianiem | +| [@openspoon/subtask2](https://github.com/spoons-and-mirrors/subtask2) | Rozszerz komendy /commands OpenCode w potężny system orkiestracji ze szczegółową kontrolą przepływu | +| [opencode-scheduler](https://github.com/different-ai/opencode-scheduler) | Planuj cykliczne zadania używając launchd (Mac) lub systemd (Linux) ze składnią cron | +| [micode](https://github.com/vtemian/micode) | Ustrukturyzowany przepływ pracy Burza mózgów → Plan → Implementacja z ciągłością sesji | +| [octto](https://github.com/vtemian/octto) | Interaktywny interfejs przeglądarkowy do burzy mózgów AI z formularzami wielopytaniowymi | +| [opencode-background-agents](https://github.com/kdcokenny/opencode-background-agents) | Agenci w tle w stylu Claude Code z asynchronicznym delegowaniem i trwałością kontekstu | +| [opencode-notify](https://github.com/kdcokenny/opencode-notify) | Natywne powiadomienia systemowe dla OpenCode – wiedz, kiedy zadania się zakończą | +| [opencode-workspace](https://github.com/kdcokenny/opencode-workspace) | Zestaw orkiestracji wieloagentowej – 16 komponentów, jedna instalacja | +| [opencode-worktree](https://github.com/kdcokenny/opencode-worktree) | Bezproblemowe drzewa robocze git dla OpenCode | --- -## Projektowanie +## Projekty -| Imię | Opis | -| ------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------- | -| [kimaki](https://github.com/remorses/kimaki) | Bot Discord do kontrolowania sesji opencode, zbudowany na SDK | -| [opencode.nvim](https://github.com/NickvanDyke/opencode.nvim) | Wtyczka Neovim do podpowiedzi, zbudowana w oparciu o API | -| [portal](https://github.com/hosenur/portal) | Interfejs sieciowy do urządzeń mobilnych dla opencode poprzez Tailscale/VPN | -| [opencode plugin template](https://github.com/zenobi-us/opencode-plugin-template/) | Szablon do budowy wtyczek opencode | -| [opencode.nvim](https://github.com/sudo-tee/opencode.nvim) | Frontend Neovim dla opencode - agent kodujący AI oparty na terminalu | -| [ai-sdk-provider-opencode-sdk](https://github.com/ben-vargas/ai-sdk-provider-opencode-sdk) | Stosowanie Vercel AI SDK do użytku z opencode poprzez @opencode-ai/sdk | -| [OpenChamber](https://github.com/btriapitsyn/openchamber) | Aplikacja internetowa/stacjonarna i rozszerzenie VS Code dla opencode | -| [OpenCode-Obsidian](https://github.com/mtymek/opencode-obsidian) | Wtyczka Obsidian osadzająca opencode w interfejsie użytkownika Obsidian | -| [OpenWork](https://github.com/different-ai/openwork) | Alternatywa typu open source dla Claude Cowork, obsługa przez opencode | -| [ocx](https://github.com/kdcokenny/ocx) | Menedżer rozszerzony opencode z przenośnymi, izolowanymi profilami. | -| [CodeNomad](https://github.com/NeuralNomadsAI/CodeNomad) | Aplikacja komputerowa, internetowa, mobilna i zdalna dla opencode | +| Nazwa | Opis | +| ------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------- | +| [kimaki](https://github.com/remorses/kimaki) | Bot Discord do sterowania sesjami OpenCode, zbudowany na SDK | +| [opencode.nvim](https://github.com/NickvanDyke/opencode.nvim) | Wtyczka Neovim dla promptów świadomych edytora, zbudowana na API | +| [portal](https://github.com/hosenur/portal) | Interfejs webowy mobile-first dla OpenCode przez Tailscale/VPN | +| [opencode plugin template](https://github.com/zenobi-us/opencode-plugin-template/) | Szablon do tworzenia wtyczek OpenCode | +| [opencode.nvim](https://github.com/sudo-tee/opencode.nvim) | Frontend Neovim dla OpenCode - terminalowy agent kodujący AI | +| [ai-sdk-provider-opencode-sdk](https://github.com/ben-vargas/ai-sdk-provider-opencode-sdk) | Dostawca Vercel AI SDK do używania OpenCode przez @opencode-ai/sdk | +| [OpenChamber](https://github.com/btriapitsyn/openchamber) | Aplikacja Web / Desktop i rozszerzenie VS Code dla OpenCode | +| [OpenCode-Obsidian](https://github.com/mtymek/opencode-obsidian) | Wtyczka Obsidian osadzająca OpenCode w interfejsie Obsidian | +| [OpenWork](https://github.com/different-ai/openwork) | Otwartoźródłowa alternatywa dla Claude Cowork, napędzana przez OpenCode | +| [ocx](https://github.com/kdcokenny/ocx) | Menedżer rozszerzeń OpenCode z przenośnymi, izolowanymi profilami. | +| [CodeNomad](https://github.com/NeuralNomadsAI/CodeNomad) | Aplikacja kliencka Desktop, Web, Mobile i Remote dla OpenCode | --- -## Agencja +## Agenci -| Imię | Opis | -| ----------------------------------------------------------------- | ------------------------------------------------------------------------- | -| [Agentic](https://github.com/Cluster444/agentic) | Modułowi agencje i polecenia AI do rozwoju strukturalnego | -| [opencode-agents](https://github.com/darrenhinde/opencode-agents) | Konfiguracje, podpowiedzi, agencje i wtyczki usprawniające przepływ pracy | +| Nazwa | Opis | +| ----------------------------------------------------------------- | ------------------------------------------------------------------------ | +| [Agentic](https://github.com/Cluster444/agentic) | Modułowi agenci AI i komendy do strukturalnego rozwoju | +| [opencode-agents](https://github.com/darrenhinde/opencode-agents) | Konfiguracje, prompty, agenci i wtyczki dla ulepszonych przepływów pracy | diff --git a/packages/web/src/content/docs/pl/go.mdx b/packages/web/src/content/docs/pl/go.mdx new file mode 100644 index 00000000000..99547da18c1 --- /dev/null +++ b/packages/web/src/content/docs/pl/go.mdx @@ -0,0 +1,145 @@ +--- +title: Go +description: Tani abonament na otwarte modele kodowania. +--- + +import config from "../../../../config.mjs" +export const console = config.console +export const email = `mailto:${config.email}` + +OpenCode Go to tania subskrypcja za **10 USD miesięcznie**, która zapewnia niezawodny dostęp do popularnych otwartych modeli kodowania. + +:::note +OpenCode Go jest obecnie w fazie beta. +::: + +Go działa jak każdy inny dostawca w OpenCode. Subskrybujesz OpenCode Go i otrzymujesz swój klucz API. Jest to **całkowicie opcjonalne** i nie musisz z tego korzystać, aby używać OpenCode. + +Jest przeznaczony głównie dla użytkowników międzynarodowych, z modelami hostowanymi w USA, UE i Singapurze dla stabilnego dostępu globalnego. + +--- + +## Tło + +Otwarte modele stały się naprawdę dobre. Osiągają teraz wydajność zbliżoną do modeli komercyjnych w zadaniach związanych z kodowaniem. A ponieważ wielu dostawców może je obsługiwać konkurencyjnie, są zazwyczaj znacznie tańsze. + +Jednak uzyskanie niezawodnego dostępu o niskim opóźnieniu może być trudne. Dostawcy różnią się jakością i dostępnością. + +:::tip +Przetestowaliśmy wybraną grupę modeli i dostawców, którzy dobrze współpracują z OpenCode. +::: + +Aby to naprawić, zrobiliśmy kilka rzeczy: + +1. Przetestowaliśmy wybraną grupę otwartych modeli i rozmawialiśmy z ich zespołami o tym, jak najlepiej je uruchamiać. +2. Następnie współpracowaliśmy z kilkoma dostawcami, aby upewnić się, że są one obsługiwane poprawnie. +3. Na koniec przeprowadziliśmy testy porównawcze kombinacji modelu/dostawcy i stworzyliśmy listę, którą z czystym sumieniem polecamy. + +OpenCode Go daje dostęp do tych modeli za **10 USD miesięcznie**. + +--- + +## Jak to działa + +OpenCode Go działa jak każdy inny dostawca w OpenCode. + +1. Logujesz się do **OpenCode Zen**, subskrybujesz Go i kopiujesz swój klucz API. +2. Uruchamiasz polecenie `/connect` w TUI, wybierasz `OpenCode Go` i wklejasz swój klucz API. +3. Uruchom `/models` w TUI, aby zobaczyć listę modeli dostępnych przez Go. + +:::note +Tylko jeden członek na obszar roboczy może subskrybować OpenCode Go. +::: + +Obecna lista modeli obejmuje: + +- **GLM-5** +- **Kimi K2.5** +- **MiniMax M2.5** + +Lista modeli może ulec zmianie w miarę testowania i dodawania nowych. + +--- + +## Limity użycia + +OpenCode Go obejmuje następujące limity: + +- **Limit 5-godzinny** — zużycie o wartości 12 USD +- **Limit tygodniowy** — zużycie o wartości 30 USD +- **Limit miesięczny** — zużycie o wartości 60 USD + +Limity są definiowane w wartości dolarowej. Oznacza to, że rzeczywista liczba żądań zależy od używanego modelu. Tańsze modele, takie jak MiniMax M2.5, pozwalają na więcej żądań, podczas gdy droższe modele, takie jak GLM-5, na mniej. + +Poniższa tabela przedstawia szacunkową liczbę żądań w oparciu o typowe wzorce użytkowania Go: + +| | GLM-5 | Kimi K2.5 | MiniMax M2.5 | +| ------------------- | ----- | --------- | ------------ | +| żądania na 5 godzin | 1 150 | 1 850 | 30 000 | +| żądania na tydzień | 2 880 | 4 630 | 75 000 | +| żądania na miesiąc | 5 750 | 9 250 | 150 000 | + +Szacunki opierają się na zaobserwowanych średnich wzorcach żądań: + +- GLM-5 — 700 wejściowych, 52 000 zbuforowanych, 150 wyjściowych tokenów na żądanie +- Kimi K2.5 — 870 wejściowych, 55 000 zbuforowanych, 200 wyjściowych tokenów na żądanie +- MiniMax M2.5 — 300 wejściowych, 55 000 zbuforowanych, 125 wyjściowych tokenów na żądanie + +Możesz śledzić swoje bieżące zużycie w **konsoli**. + +:::tip +Jeśli osiągniesz limit użycia, możesz kontynuować korzystanie z darmowych modeli. +::: + +Limity użycia mogą ulec zmianie, gdy będziemy uczyć się na podstawie wczesnego użytkowania i opinii. + +--- + +### Cennik + +OpenCode Go to plan subskrypcji za **10 USD miesięcznie**. Poniżej znajdują się ceny **za 1 mln tokenów**. + +| Model | Wejście | Wyjście | Odczyt cache | +| ------------ | ------- | ------- | ------------ | +| GLM-5 | 1,00 $ | 3,20 $ | 0,20 $ | +| Kimi K2.5 | 0,60 $ | 3,00 $ | 0,10 $ | +| MiniMax M2.5 | 0,30 $ | 1,20 $ | 0,03 $ | + +--- + +### Użycie poza limitami + +Jeśli posiadasz również środki na swoim saldzie Zen, możesz włączyć opcję **Use balance** (Użyj salda) w konsoli. Po włączeniu, Go przełączy się na twoje saldo Zen po osiągnięciu limitów użycia, zamiast blokować żądania. + +--- + +## Punkty końcowe + +Możesz również uzyskać dostęp do modeli Go poprzez następujące punkty końcowe API. + +| Model | Identyfikator modelu | Endpoint | Pakiet AI SDK | +| ------------ | -------------------- | ------------------------------------------------ | --------------------------- | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | + +[Identyfikator modelu](/docs/config/#models) w twojej konfiguracji OpenCode używa formatu `opencode-go/`. Na przykład dla Kimi K2.5 użyłbyś `opencode-go/kimi-k2.5` w swojej konfiguracji. + +--- + +## Prywatność + +Plan jest przeznaczony głównie dla użytkowników międzynarodowych, z modelami hostowanymi w USA, UE i Singapurze dla stabilnego dostępu globalnego. + +Skontaktuj się z nami, jeśli masz jakiekolwiek pytania. + +--- + +## Cele + +Stworzyliśmy OpenCode Go, aby: + +1. Uczynić kodowanie z AI **dostępnym** dla większej liczby osób dzięki taniej subskrypcji. +2. Zapewnić **niezawodny** dostęp do najlepszych otwartych modeli kodowania. +3. Wyselekcjonować modele, które są **przetestowane i sprawdzone** pod kątem użycia z agentami kodującymi. +4. Nie wprowadzać **żadnych blokad (lock-in)**, pozwalając na korzystanie z dowolnego innego dostawcy w OpenCode. diff --git a/packages/web/src/content/docs/pl/keybinds.mdx b/packages/web/src/content/docs/pl/keybinds.mdx index f4995600dac..03b9ec9c2a2 100644 --- a/packages/web/src/content/docs/pl/keybinds.mdx +++ b/packages/web/src/content/docs/pl/keybinds.mdx @@ -3,11 +3,11 @@ title: Skróty klawiszowe description: Dostosuj swoje skróty klawiszowe. --- -opencode zawiera listę skrótów klawiszowych, które można zastosować poprzez opencode. +OpenCode zawiera listę skrótów klawiszowych, które można dostosować za pomocą `tui.json`. -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "keybinds": { "leader": "ctrl+x", "app_exit": "ctrl+c,ctrl+d,q", @@ -117,11 +117,11 @@ Nie musisz mieć klawisza wiodącego do skrótów klawiszowych, ale zalecamy to ## Wyłącz powiązanie klawiszy -Możesz podłączyć powiązanie klawiszy, dodając klucz do swojej konfiguracji z wartością „none”. +Możesz wyłączyć skrót klawiszowy, dodając klucz do `tui.json` z wartością "none". -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "keybinds": { "session_compact": "none" } diff --git a/packages/web/src/content/docs/pl/lsp.mdx b/packages/web/src/content/docs/pl/lsp.mdx index 8430bd9ca91..ec2edec4309 100644 --- a/packages/web/src/content/docs/pl/lsp.mdx +++ b/packages/web/src/content/docs/pl/lsp.mdx @@ -27,6 +27,7 @@ OpenCode posiada kilka wbudowanych serwerów LSP dla następujących języków: | gopls | .go | Dostępne polecenie `go` | | hls | .hs, .lhs | Dostępne polecenie `haskell-language-server-wrapper` | | jdtls | .java | Zainstalowany `Java SDK (version 21+)` | +| julials | .jl | Zainstalowane `julia` i `LanguageServer.jl` | | kotlin-ls | .kt, .kts | Automatyczna instalacja dla Kotlin | | lua-ls | .lua | Automatyczna instalacja dla Lua | | nixd | .nix | Dostępne polecenie `nixd` | diff --git a/packages/web/src/content/docs/pl/plugins.mdx b/packages/web/src/content/docs/pl/plugins.mdx index e91900ad270..510c8a22e4c 100644 --- a/packages/web/src/content/docs/pl/plugins.mdx +++ b/packages/web/src/content/docs/pl/plugins.mdx @@ -9,7 +9,7 @@ Aby znaleźć się z przykładami, przejrzyj [wtyczki](/docs/ecosystem#plugins) --- -## użyj wtyczki +## Użyj wtyczki Istnieją dwa sposoby ładowania wtyczek. @@ -51,7 +51,7 @@ Przeglądaj dostępną wtyczkę w [ekosystemie](/docs/ecosystem#plugins). --- -### Załaduj zamówienie +### Kolejność ładowania Wtyczki są ładowane ze wszystkich źródeł, a wszystkie hooki napisane po kolei. Kolejność ładowania do: @@ -310,7 +310,7 @@ Twoje narzędzie będzie dostępne dla otwartego kodu wraz z narzędziami użytk --- -### Wycięcie lasu +### Logowanie użyj `client.app.log()` zamiast `console.log` do rejestracji strukturalnego: diff --git a/packages/web/src/content/docs/pl/providers.mdx b/packages/web/src/content/docs/pl/providers.mdx index 58f824764ef..fa509011031 100644 --- a/packages/web/src/content/docs/pl/providers.mdx +++ b/packages/web/src/content/docs/pl/providers.mdx @@ -57,7 +57,38 @@ przetestowane i zweryfikowane, aby dobrze współpracować z opencode. [Dowiedz Jeśli jesteś nowy, zalecamy rozpoczęcie od OpenCode Zen. ::: -1. Uruchom polecenie `/connect` w TUI, wybierz opencode i przejdź do [opencode.ai/auth](https://opencode.ai/auth). +1. Uruchom polecenie `/connect` w TUI, wybierz `OpenCode Zen` i przejdź do [opencode.ai/auth](https://opencode.ai/zen). + + ```txt + /connect + ``` + +2. Zaloguj się, dodaj szczegóły rozliczeniowe i skopiuj klucz API. + +3. Wklej swój klucz API. + + ```txt + ┌ API key + │ + │ + └ enter + ``` + +4. Uruchom `/models` w TUI, aby zobaczyć listę zalecanych przez nas modeli. + + ```txt + /models + ``` + +Działa jak każdy inny dostawca w opencode i jest całkowicie opcjonalny w użyciu. + +--- + +## OpenCode Go + +OpenCode Go to tani plan subskrypcji, który zapewnia niezawodny dostęp do popularnych modeli open coding dostarczanych przez zespół opencode, które zostały przetestowane i zweryfikowane pod kątem dobrej współpracy z opencode. + +1. Uruchom polecenie `/connect` w TUI, wybierz `OpenCode Go` i przejdź do [opencode.ai/auth](https://opencode.ai/zen). ```txt /connect @@ -470,7 +501,7 @@ Cloudflare AI Gateway umożliwia dostęp do modeli z OpenAI, Anthropic, Workers └ enter ``` - Or set it as an environment variable. + Lub ustaw go jako zmienną środowiskową. ```bash title="~/.bash_profile" export CLOUDFLARE_API_TOKEN=your-api-token @@ -834,11 +865,11 @@ Aby używać Google Vertex AI z opencode: 2. Ustaw wymagane zmienne środowiskowe: - `GOOGLE_CLOUD_PROJECT`: identyfikator Twojego projektu Google Cloud - `VERTEX_LOCATION` (opcjonalnie): region Vertex AI (domyślnie `global`) - - Authentication (choose one): + - Uwierzytelnianie (wybierz jedno): - `GOOGLE_APPLICATION_CREDENTIALS`: Ścieżka do pliku klucza JSON konta usługi - Uwierzytelnij się za pomocą interfejsu CLI gcloud: `gcloud auth application-default login` - Set them while running opencode. + Ustaw je podczas uruchamiania opencode. ```bash GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json GOOGLE_CLOUD_PROJECT=your-project-id opencode @@ -1479,6 +1510,39 @@ SAP AI Core zapewnia dostęp do ponad 40 modeli z OpenAI, Anthropic, Google, Ama --- +### STACKIT + +STACKIT AI Model Serving zapewnia w pełni zarządzane suwerenne środowisko hostingu dla modeli AI, koncentrując się na LLM, takich jak Llama, Mistral i Qwen, z maksymalną suwerennością danych na infrastrukturze europejskiej. + +1. Przejdź do [portalu STACKIT](https://portal.stackit.cloud), przejdź do **AI Model Serving**, i utwórz token autoryzacyjny dla swojego projektu. + + :::tip + Musisz posiadać konto klienta STACKIT, konto użytkownika i projekt przed utworzeniem tokenów autoryzacyjnych. + ::: + +2. Uruchom polecenie `/connect` i wyszukaj **STACKIT**. + + ```txt + /connect + ``` + +3. Wprowadź swój token autoryzacyjny STACKIT AI Model Serving. + + ```txt + ┌ API key + │ + │ + └ enter + ``` + +4. Uruchom polecenie `/models`, aby wybrać jeden z dostępnych modeli, np. _Qwen3-VL 235B_ lub _Llama 3.3 70B_. + + ```txt + /models + ``` + +--- + ### OVHcloud AI Endpoints 1. Przejdź do [panelu OVHcloud](https://ovh.com/manager). Przejdź do sekcji `Public Cloud`, `AI & Machine Learning` > `AI Endpoints` i na karcie `API Keys` kliknij **Utwórz nowy klucz API**. diff --git a/packages/web/src/content/docs/pl/sdk.mdx b/packages/web/src/content/docs/pl/sdk.mdx index 3236e38c29d..40099713eff 100644 --- a/packages/web/src/content/docs/pl/sdk.mdx +++ b/packages/web/src/content/docs/pl/sdk.mdx @@ -117,6 +117,78 @@ try { --- +## Ustrukturyzowane Dane Wyjściowe + +Możesz zażądać ustrukturyzowanych danych wyjściowych JSON od modelu, określając `format` za pomocą schematu JSON. Model użyje narzędzia `StructuredOutput`, aby zwrócić zweryfikowany kod JSON pasujący do Twojego schematu. + +### Podstawowe użycie + +```typescript +const result = await client.session.prompt({ + path: { id: sessionId }, + body: { + parts: [{ type: "text", text: "Research Anthropic and provide company info" }], + format: { + type: "json_schema", + schema: { + type: "object", + properties: { + company: { type: "string", description: "Company name" }, + founded: { type: "number", description: "Year founded" }, + products: { + type: "array", + items: { type: "string" }, + description: "Main products", + }, + }, + required: ["company", "founded"], + }, + }, + }, +}) + +// Access the structured output +console.log(result.data.info.structured_output) +// { company: "Anthropic", founded: 2021, products: ["Claude", "Claude API"] } +``` + +### Typy formatu wyjściowego + +| Typ | Opis | +| ------------- | ------------------------------------------------------------------------- | +| `text` | Domyślny. Standardowa odpowiedź tekstowa (brak ustrukturyzowanych danych) | +| `json_schema` | Zwraca zweryfikowany JSON pasujący do dostarczonego schematu | + +### Format JSON Schema + +Używając `type: 'json_schema'`, podaj: + +| Pole | Typ | Opis | +| ------------ | --------------- | ------------------------------------------------------------ | +| `type` | `'json_schema'` | Wymagane. Określa tryb schematu JSON | +| `schema` | `object` | Wymagane. Obiekt JSON Schema definiujący strukturę wyjściową | +| `retryCount` | `number` | Opcjonalne. Liczba ponownych prób walidacji (domyślnie: 2) | + +### Obsługa błędów + +Jeśli model nie wygeneruje prawidłowych ustrukturyzowanych danych wyjściowych po wszystkich próbach, odpowiedź będzie zawierać `StructuredOutputError`: + +```typescript +if (result.data.info.error?.name === "StructuredOutputError") { + console.error("Failed to produce structured output:", result.data.info.error.message) + console.error("Attempts:", result.data.info.error.retries) +} +``` + +### Najlepsze praktyki + +1. **Podawaj jasne opisy** we właściwościach schematu, aby pomóc modelowi zrozumieć, jakie dane wyodrębnić +2. **Używaj `required`**, aby określić, które pola muszą być obecne +3. **Zachowaj schematy skoncentrowane** - złożone zagnieżdżone schematy mogą być trudniejsze dla modelu do poprawnego wypełnienia +4. **Ustaw odpowiedni `retryCount`** - zwiększ dla złożonych schematów, zmniejsz dla prostych + +--- + ## API Zestaw SDK udostępnia wszystkie interfejsy API serwera za pośrednictwem klienta bezpiecznego typu. diff --git a/packages/web/src/content/docs/pl/skills.mdx b/packages/web/src/content/docs/pl/skills.mdx index 9262d87e661..8dbb7301203 100644 --- a/packages/web/src/content/docs/pl/skills.mdx +++ b/packages/web/src/content/docs/pl/skills.mdx @@ -141,8 +141,8 @@ Kontroluj, do których umiejętności agenci mogą uzyskać dostęp, używając | Permission | Behavior | | ---------- | ------------------------------------------------------ | -| `allow` | Skill loads immediately | -| `deny` | Skill hidden from agent, access rejected | +| `allow` | Umiejętność ładuje się natychmiast | +| `deny` | Umiejętność ukryta przed agentem, dostęp odrzucony | | `ask` | Użytkownik proszony o zatwierdzenie przed załadowaniem | Wzorce obsługują symbole wieloznaczne: `internal-*` odpowiada `internal-docs`, `internal-tools` itd. diff --git a/packages/web/src/content/docs/pl/themes.mdx b/packages/web/src/content/docs/pl/themes.mdx index d4272e2c9be..80a78083511 100644 --- a/packages/web/src/content/docs/pl/themes.mdx +++ b/packages/web/src/content/docs/pl/themes.mdx @@ -53,19 +53,19 @@ Motyw `system` został zaprojektowany tak, aby automatycznie dostosowywał się Motyw systemu przeznaczony jest dla użytkowników, którzy: -- Want opencode to match their terminal's appearance -- Użyj niestandardowych schematów kolorów terminali -- Prefer a consistent look across all terminal applications +- Chcą, aby OpenCode pasował do wyglądu ich terminala +- Używają niestandardowych schematów kolorów terminala +- Preferują spójny wygląd we wszystkich aplikacjach terminalowych --- ## Używanie motywu -Możesz wybrać motyw, wywołując opcję wyboru motywu za pomocą polecenia `/theme`. Możesz też określić to w [config](/docs/config). +Możesz wybrać motyw, wywołując opcję wyboru motywu za pomocą polecenia `/theme`. Możesz też określić to w `tui.json`. -```json title="opencode.json" {3} +```json title="tui.json" {3} { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "theme": "tokyonight" } ``` diff --git a/packages/web/src/content/docs/pl/tui.mdx b/packages/web/src/content/docs/pl/tui.mdx index 6d693eb9e65..2d983cf84b1 100644 --- a/packages/web/src/content/docs/pl/tui.mdx +++ b/packages/web/src/content/docs/pl/tui.mdx @@ -355,24 +355,34 @@ Niektórzy edytory potrzebują argumentów wiersza poleceń, aby działać w try ## Skonfiguruj -Możesz dostosować zachowanie TUI za pomocą pliku konfiguracyjnego opencode. +Możesz dostosować zachowanie TUI za pomocą pliku `tui.json` (lub `tui.jsonc`). -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", - "tui": { - "scroll_speed": 3, - "scroll_acceleration": { - "enabled": true - } - } + "$schema": "https://opencode.ai/tui.json", + "theme": "opencode", + "keybinds": { + "leader": "ctrl+x" + }, + "scroll_speed": 3, + "scroll_acceleration": { + "enabled": true + }, + "diff_style": "auto" } ``` +Jest to oddzielny plik od `opencode.json`, który konfiguruje zachowanie serwera/runtime. + ### Opcje -- `scroll_acceleration` — Włącz przyspieszenie przewijania w stylu macOS, aby zapewnić płynne, naturalne przewijanie. Po włączeniu prędkość przewijania wzrasta wraz z szybkimi gestami przewijania i pozostaje precyzyjna w przypadku wolniejszych ruchów. **To ustawienie ma pierwszeństwo przed `scroll_speed` i zastępuje je, gdy jest włączone.** -- `scroll_speed` - Kontroluje szybkość przewijania TUI podczas korzystania z poleceń przewijania (minimum: `1`). Wartość domyślna to `3`. **Uwaga: jest to ignorowane, jeśli `scroll_acceleration.enabled` jest ustawione na `true`.** +- `theme` - Ustawia motyw interfejsu. [Dowiedz się więcej](/docs/themes). +- `keybinds` - Dostosowuje skróty klawiszowe. [Dowiedz się więcej](/docs/keybinds). +- `scroll_acceleration.enabled` — Włącz przyspieszenie przewijania w stylu macOS, aby zapewnić płynne, naturalne przewijanie. Po włączeniu prędkość przewijania wzrasta wraz z szybkimi gestami przewijania i pozostaje precyzyjna w przypadku wolniejszych ruchów. **To ustawienie ma pierwszeństwo przed `scroll_speed` i zastępuje je, gdy jest włączone.** +- `scroll_speed` - Kontroluje szybkość przewijania TUI podczas korzystania z poleceń przewijania (minimum: `0.001`, obsługuje wartości dziesiętne). Wartość domyślna to `3`. **Uwaga: jest to ignorowane, jeśli `scroll_acceleration.enabled` jest ustawione na `true`.** +- `diff_style` - Steruje renderowaniem różnic. `"auto"` dostosowuje się do szerokości terminala, `"stacked"` zawsze pokazuje układ jednokolumnowy. + +Użyj `OPENCODE_TUI_CONFIG`, aby załadować niestandardową ścieżkę konfiguracji TUI. --- diff --git a/packages/web/src/content/docs/pl/zen.mdx b/packages/web/src/content/docs/pl/zen.mdx index 6d5d8c0bd78..dbb75489cb6 100644 --- a/packages/web/src/content/docs/pl/zen.mdx +++ b/packages/web/src/content/docs/pl/zen.mdx @@ -62,8 +62,9 @@ Opłata jest pobierana za każde żądanie i możesz dodać kredyty do swojego k Dostęp do naszych modeli można również uzyskać za pośrednictwem następujących punktów końcowych API. -| Modelka | Identyfikator modelu | Punkt końcowy | Pakiet SDK AI | +| Model | Identyfikator modelu | Punkt końcowy | Pakiet SDK AI | | ------------------ | -------------------- | -------------------------------------------------- | --------------------------- | +| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | @@ -73,22 +74,24 @@ Dostęp do naszych modeli można również uzyskać za pośrednictwem następuj | GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | | Gemini 3 Pro | gemini-3-pro | `https://opencode.ai/zen/v1/models/gemini-3-pro` | `@ai-sdk/google` | | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.1 | minimax-m2.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.1 Free | minimax-m2.1-free | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| GLM 5 | glm-5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 4.7 | glm-4.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM 4.7 Free | glm-4.7-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 4.6 | glm-4.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 Free | kimi-k2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2 Thinking | kimi-k2-thinking | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2 | kimi-k2 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Qwen3 Coder 480B | qwen3-coder | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -114,32 +117,37 @@ https://opencode.ai/zen/v1/models Wspieramy model pay-as-you-go. Poniżej znajdują się ceny **za 1M tokenów**. -| Modelka | Wejście | Wyjście | Odczyt w pamięci podręcznej | Zapis w pamięci podręcznej | +| Model | Wejście | Wyjście | Odczyt w pamięci podręcznej | Zapis w pamięci podręcznej | | --------------------------------- | ------- | ------- | --------------------------- | -------------------------- | | Big Pickle | Free | Free | Free | - | -| MiniMax M2.1 Free | Free | Free | Free | - | +| MiniMax M2.5 Free | Free | Free | Free | - | +| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | - | | MiniMax M2.1 | $0.30 | $1.20 | $0.10 | - | -| GLM 4.7 Free | Free | Free | Free | - | +| GLM 5 | $1.00 | $3.20 | $0.20 | - | | GLM 4.7 | $0.60 | $2.20 | $0.10 | - | | GLM 4.6 | $0.60 | $2.20 | $0.10 | - | -| Kimi K2.5 Free | Free | Free | Free | - | | Kimi K2.5 | $0.60 | $3.00 | $0.08 | - | | Kimi K2 Thinking | $0.40 | $2.50 | - | - | | Kimi K2 | $0.40 | $2.50 | - | - | | Qwen3 Coder 480B | $0.45 | $1.50 | - | - | +| Claude Opus 4.6 (≤ 200K tokens) | $5.00 | $25.00 | $0.50 | $6.25 | +| Claude Opus 4.6 (> 200K tokens) | $10.00 | $37.50 | $1.00 | $12.50 | +| Claude Opus 4.5 | $5.00 | $25.00 | $0.50 | $6.25 | +| Claude Opus 4.1 | $15.00 | $75.00 | $1.50 | $18.75 | +| Claude Sonnet 4.6 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 | +| Claude Sonnet 4.6 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 | | Claude Sonnet 4.5 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 | | Claude Sonnet 4.5 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 | | Claude Sonnet 4 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 | | Claude Sonnet 4 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 | | Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 | | Claude Haiku 3.5 | $0.80 | $4.00 | $0.08 | $1.00 | -| Claude Opus 4.6 (≤ 200K tokens) | $5.00 | $25.00 | $0.50 | $6.25 | -| Claude Opus 4.6 (> 200K tokens) | $10.00 | $37.50 | $1.00 | $12.50 | -| Claude Opus 4.5 | $5.00 | $25.00 | $0.50 | $6.25 | -| Claude Opus 4.1 | $15.00 | $75.00 | $1.50 | $18.75 | +| Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | +| Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | +| GPT 5.3 Codex | $1.75 | $14.00 | $0.175 | - | | GPT 5.2 | $1.75 | $14.00 | $0.175 | - | | GPT 5.2 Codex | $1.75 | $14.00 | $0.175 | - | | GPT 5.1 | $1.07 | $8.50 | $0.107 | - | @@ -158,10 +166,8 @@ Opłaty za karty kredytowe są przenoszone na koszt (4,4% + 0,30 USD za transakc Darmowe modele: -- GLM 4.7 Free jest dostępny na platformie opencode przez ograniczony czas. Zespół wykorzystuje ten czas na zbieranie opinii i ulepszanie modelu. -- Kimi K2.5 Free jest dostępny na opencode przez ograniczony czas. Zespół wykorzystuje ten czas na zbieranie opinii i ulepszanie modelu. -- MiniMax M2.1 Free jest dostępny na platformie opencode przez ograniczony czas. Zespół wykorzystuje ten czas na zbieranie opinii i ulepszanie modelu. -- Big Pickle to ukryty model, który jest bezpłatny w opencode przez ograniczony czas. Zespół wykorzystuje ten czas na zbieranie opinii i ulepszanie modelu. +- MiniMax M2.5 Free jest dostępny w OpenCode przez ograniczony czas. Zespół wykorzystuje ten czas na zbieranie opinii i ulepszanie modelu. +- Big Pickle to ukryty model, który jest bezpłatny w OpenCode przez ograniczony czas. Zespół wykorzystuje ten czas na zbieranie opinii i ulepszanie modelu. Skontaktuj się z nami, jeśli masz jakieś pytania. @@ -191,11 +197,9 @@ obciąży Cię kwotą wyższą niż 20 USD, jeśli saldo spadnie poniżej 5 USD. Wszystkie nasze modele są hostowane w USA. Nasi dostawcy przestrzegają polityki zerowego przechowywania i nie wykorzystują Twoich danych do szkolenia modeli, z następującymi wyjątkami: - Big Pickle: W okresie bezpłatnym zebrane dane mogą zostać wykorzystane do udoskonalenia modelu. -- GLM 4.7 Bezpłatna: W okresie bezpłatnym zebrane dane mogą zostać wykorzystane do udoskonalenia modelu. -- Kimi K2.5 Free: W okresie bezpłatnym zebrane dane mogą zostać wykorzystane do udoskonalenia modelu. -- MiniMax M2.1 Free: W okresie bezpłatnym zebrane dane mogą zostać wykorzystane do udoskonalenia modelu. +- MiniMax M2.5 Free: W okresie bezpłatnym zebrane dane mogą zostać wykorzystane do udoskonalenia modelu. - Interfejsy API OpenAI: żądania są przechowywane przez 30 dni zgodnie z [Zasadami dotyczącymi danych OpenAI](https://platform.openai.com/docs/guides/your-data). -- Interfejsy API Anthropic: żądania są przechowywane przez 30 dni zgodnie z [Zasadami dotyczącymi danych firmy Anthropic] (https://docs.anthropic.com/en/docs/claude-code/data-usage). +- Interfejsy API Anthropic: żądania są przechowywane przez 30 dni zgodnie z [Zasadami dotyczącymi danych firmy Anthropic](https://docs.anthropic.com/en/docs/claude-code/data-usage). --- diff --git a/packages/web/src/content/docs/pt-br/config.mdx b/packages/web/src/content/docs/pt-br/config.mdx index 405c5379456..4684bb199ec 100644 --- a/packages/web/src/content/docs/pt-br/config.mdx +++ b/packages/web/src/content/docs/pt-br/config.mdx @@ -14,10 +14,11 @@ O opencode suporta os formatos **JSON** e **JSONC** (JSON com Comentários). ```jsonc title="opencode.jsonc" { "$schema": "https://opencode.ai/config.json", - // Theme configuration - "theme": "opencode", "model": "anthropic/claude-sonnet-4-5", "autoupdate": true, + "server": { + "port": 4096, + }, } ``` @@ -34,7 +35,7 @@ Os arquivos de configuração são **mesclados**, não substituídos. Os arquivos de configuração são mesclados, não substituídos. As configurações das seguintes localizações de configuração são combinadas. Configurações posteriores substituem as anteriores apenas para chaves conflitantes. Configurações não conflitantes de todas as configurações são preservadas. -Por exemplo, se sua configuração global define `theme: "opencode"` e `autoupdate: true`, e sua configuração de projeto define `model: "anthropic/claude-sonnet-4-5"`, a configuração final incluirá as três configurações. +Por exemplo, se sua configuração global define `autoupdate: true` e sua configuração de projeto define `model: "anthropic/claude-sonnet-4-5"`, a configuração final incluirá as duas configurações. --- @@ -95,7 +96,9 @@ Você pode habilitar servidores específicos em sua configuração local: ### Global -Coloque sua configuração global do opencode em `~/.config/opencode/opencode.json`. Use a configuração global para preferências de usuário, como temas, provedores ou atalhos de teclado. +Coloque sua configuração global do opencode em `~/.config/opencode/opencode.json`. Use a configuração global para preferências de todo o usuário, como provedores, modelos e permissões. + +Para configurações específicas do TUI, use `~/.config/opencode/tui.json`. A configuração global substitui os padrões organizacionais remotos. @@ -105,6 +108,8 @@ A configuração global substitui os padrões organizacionais remotos. Adicione `opencode.json` na raiz do seu projeto. A configuração do projeto tem a maior precedência entre os arquivos de configuração padrão - ela substitui tanto as configurações globais quanto as remotas. +Para configurações específicas do TUI do projeto, adicione `tui.json` junto a ele. + :::tip Coloque a configuração específica do projeto na raiz do seu projeto. ::: @@ -143,7 +148,9 @@ O diretório personalizado é carregado após a configuração global e os diret ## Esquema -O arquivo de configuração tem um esquema que está definido em [**`opencode.ai/config.json`**](https://opencode.ai/config.json). +O esquema de configuração do servidor/tempo de execução é definido em [**`opencode.ai/config.json`**](https://opencode.ai/config.json). + +A configuração do TUI usa [**`opencode.ai/tui.json`**](https://opencode.ai/tui.json). Seu editor deve ser capaz de validar e autocompletar com base no esquema. @@ -151,28 +158,24 @@ Seu editor deve ser capaz de validar e autocompletar com base no esquema. ### TUI -Você pode configurar as configurações específicas do TUI através da opção `tui`. +Use um arquivo `tui.json` (ou `tui.jsonc`) dedicado para configurações específicas do TUI. -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", - "tui": { - "scroll_speed": 3, - "scroll_acceleration": { - "enabled": true - }, - "diff_style": "auto" - } + "$schema": "https://opencode.ai/tui.json", + "scroll_speed": 3, + "scroll_acceleration": { + "enabled": true + }, + "diff_style": "auto" } ``` -Opções disponíveis: +Use `OPENCODE_TUI_CONFIG` para apontar para um arquivo de configuração TUI personalizado. -- `scroll_acceleration.enabled` - Habilitar aceleração de rolagem estilo macOS. **Tem precedência sobre `scroll_speed`.** -- `scroll_speed` - Multiplicador de velocidade de rolagem personalizada (padrão: `3`, mínimo: `1`). Ignorado se `scroll_acceleration.enabled` for `true`. -- `diff_style` - Controlar a renderização de diffs. `"auto"` se adapta à largura do terminal, `"stacked"` sempre mostra uma coluna única. +Chaves legadas `theme`, `keybinds` e `tui` em `opencode.json` estão obsoletas e são migradas automaticamente quando possível. -[Saiba mais sobre o uso do TUI aqui](/docs/tui). +[Saiba mais sobre a configuração do TUI aqui](/docs/tui#configure). --- @@ -298,12 +301,12 @@ Tokens Bearer (`AWS_BEARER_TOKEN_BEDROCK` ou `/connect`) têm precedência sobre ### Temas -Você pode configurar o tema que deseja usar em sua configuração do opencode através da opção `theme`. +Defina seu tema de interface em `tui.json`. -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", - "theme": "" + "$schema": "https://opencode.ai/tui.json", + "theme": "tokyonight" } ``` @@ -403,11 +406,11 @@ Você também pode definir comandos usando arquivos markdown em `~/.config/openc ### Atalhos de teclado -Você pode personalizar seus atalhos de teclado através da opção `keybinds`. +Personalize atalhos de teclado em `tui.json`. -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "keybinds": {} } ``` @@ -487,13 +490,15 @@ Você pode controlar o comportamento de compactação de contexto através da op "$schema": "https://opencode.ai/config.json", "compaction": { "auto": true, - "prune": true + "prune": true, + "reserved": 10000 } } ``` - `auto` - Compactar automaticamente a sessão quando o contexto estiver cheio (padrão: `true`). - `prune` - Remover saídas antigas de ferramentas para economizar tokens (padrão: `true`). +- `reserved` - Buffer de tokens para compactação. Deixa janela suficiente para evitar estouro durante a compactação --- @@ -673,6 +678,7 @@ Os caminhos dos arquivos podem ser: - Relativos ao diretório do arquivo de configuração - Ou caminhos absolutos começando com `/` ou `~` +- Esses são úteis para: diff --git a/packages/web/src/content/docs/pt-br/custom-tools.mdx b/packages/web/src/content/docs/pt-br/custom-tools.mdx index 43749200f86..515b8985a04 100644 --- a/packages/web/src/content/docs/pt-br/custom-tools.mdx +++ b/packages/web/src/content/docs/pt-br/custom-tools.mdx @@ -79,6 +79,32 @@ Isso cria duas ferramentas: `math_add` e `math_multiply`. --- +#### Colisões de nome com ferramentas integradas + +Ferramentas personalizadas são chaveadas pelo nome da ferramenta. Se uma ferramenta personalizada usar o mesmo nome que uma ferramenta integrada, a ferramenta personalizada tem precedência. + +Por exemplo, este arquivo substitui a ferramenta `bash` integrada: + +```ts title=".opencode/tools/bash.ts" +import { tool } from "@opencode-ai/plugin" + +export default tool({ + description: "Restricted bash wrapper", + args: { + command: tool.schema.string(), + }, + async execute(args) { + return `blocked: ${args.command}` + }, +}) +``` + +:::note +Prefira nomes únicos, a menos que você queira intencionalmente substituir uma ferramenta integrada. Se você quiser desabilitar uma ferramenta integrada, mas não substituí-la, use [permissões](/docs/permissions). +::: + +--- + ### Argumentos Você pode usar `tool.schema`, que é apenas [Zod](https://zod.dev), para definir tipos de argumentos. @@ -126,7 +152,7 @@ export default tool({ }) ``` -Use `context.directory` para o diretório de trabalho da sessão. +Use `context.directory` para o diretório de trabalho da sessão. Use `context.worktree` para a raiz do worktree do git. --- diff --git a/packages/web/src/content/docs/pt-br/ecosystem.mdx b/packages/web/src/content/docs/pt-br/ecosystem.mdx index ac5d3544411..64c38c4f0da 100644 --- a/packages/web/src/content/docs/pt-br/ecosystem.mdx +++ b/packages/web/src/content/docs/pt-br/ecosystem.mdx @@ -1,12 +1,12 @@ --- title: Ecossistema -description: Projetos e integrações construídos com o opencode. +description: Projetos e integrações construídos com o OpenCode. --- -Uma coleção de projetos da comunidade construídos sobre o opencode. +Uma coleção de projetos da comunidade construídos sobre o OpenCode. :::note -Quer adicionar seu projeto relacionado ao opencode a esta lista? Envie um PR. +Quer adicionar seu projeto relacionado ao OpenCode a esta lista? Envie um PR. ::: Você também pode conferir [awesome-opencode](https://github.com/awesome-opencode/awesome-opencode) e [opencode.cafe](https://opencode.cafe), uma comunidade que agrega o ecossistema e a comunidade. @@ -15,38 +15,39 @@ Você também pode conferir [awesome-opencode](https://github.com/awesome-openco ## Plugins -| Nome | Descrição | -| --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | -| [opencode-daytona](https://github.com/jamesmurdza/daytona/blob/main/guides/typescript/opencode/README.md) | Execute automaticamente sessões do opencode em sandboxes isoladas do Daytona com sincronização git e pré-visualizações ao vivo | -| [opencode-helicone-session](https://github.com/H2Shami/opencode-helicone-session) | Injete automaticamente cabeçalhos de sessão Helicone para agrupamento de requisições | -| [opencode-type-inject](https://github.com/nick-vi/opencode-type-inject) | Auto-injetar tipos TypeScript/Svelte em leituras de arquivos com ferramentas de busca | -| [opencode-openai-codex-auth](https://github.com/numman-ali/opencode-openai-codex-auth) | Use sua assinatura ChatGPT Plus/Pro em vez de créditos de API | -| [opencode-gemini-auth](https://github.com/jenslys/opencode-gemini-auth) | Use seu plano Gemini existente em vez de cobrança de API | -| [opencode-antigravity-auth](https://github.com/NoeFabris/opencode-antigravity-auth) | Use os modelos gratuitos do Antigravity em vez de cobrança de API | -| [opencode-devcontainers](https://github.com/athal7/opencode-devcontainers) | Isolamento de devcontainer multi-branch com clones rasos e portas atribuídas automaticamente | -| [opencode-google-antigravity-auth](https://github.com/shekohex/opencode-google-antigravity-auth) | Plugin Google Antigravity OAuth, com suporte para Google Search e manuseio de API mais robusto | -| [opencode-dynamic-context-pruning](https://github.com/Tarquinen/opencode-dynamic-context-pruning) | Otimize o uso de tokens podando saídas de ferramentas obsoletas | -| [opencode-websearch-cited](https://github.com/ghoulr/opencode-websearch-cited.git) | Adicione suporte nativo de pesquisa na web para provedores suportados com estilo fundamentado no Google | -| [opencode-pty](https://github.com/shekohex/opencode-pty.git) | Permite que agentes de IA executem processos em segundo plano em um PTY, enviando entrada interativa para eles. | -| [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Instruções para comandos de shell não interativos - evita travamentos de operações dependentes de TTY | -| [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Acompanhe o uso do opencode com Wakatime | -| [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Limpe tabelas markdown produzidas por LLMs | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | Edição de código 10x mais rápida com a API Morph Fast Apply e marcadores de edição preguiçosos | -| [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Agentes em segundo plano, ferramentas LSP/AST/MCP pré-construídas, agentes curados, compatível com Claude Code | -| [opencode-notificator](https://github.com/panta82/opencode-notificator) | Notificações de desktop e alertas sonoros para sessões do opencode | -| [opencode-notifier](https://github.com/mohak34/opencode-notifier) | Notificações de desktop e alertas sonoros para eventos de permissão, conclusão e erro | -| [opencode-zellij-namer](https://github.com/24601/opencode-zellij-namer) | Nomeação automática de sessões Zellij com suporte de IA com base no contexto do opencode | -| [opencode-skillful](https://github.com/zenobi-us/opencode-skillful) | Permite que agentes do opencode carreguem prompts sob demanda com descoberta e injeção de habilidades | -| [opencode-supermemory](https://github.com/supermemoryai/opencode-supermemory) | Memória persistente entre sessões usando Supermemory | -| [@plannotator/opencode](https://github.com/backnotprop/plannotator/tree/main/apps/opencode-plugin) | Revisão de plano interativa com anotação visual e compartilhamento privado/offline | -| [@openspoon/subtask2](https://github.com/spoons-and-mirrors/subtask2) | Estenda opencode /commands em um poderoso sistema de orquestração com controle de fluxo granular | -| [opencode-scheduler](https://github.com/different-ai/opencode-scheduler) | Agende trabalhos recorrentes usando launchd (Mac) ou systemd (Linux) com sintaxe cron | -| [micode](https://github.com/vtemian/micode) | Fluxo de trabalho Estruturado Brainstorm → Planejar → Implementar com continuidade de sessão | -| [octto](https://github.com/vtemian/octto) | UI interativa do navegador para brainstorming de IA com formulários de múltiplas perguntas | -| [opencode-background-agents](https://github.com/kdcokenny/opencode-background-agents) | Agentes em segundo plano estilo Claude Code com delegação assíncrona e persistência de contexto | -| [opencode-notify](https://github.com/kdcokenny/opencode-notify) | Notificações nativas do OS para opencode – saiba quando as tarefas são concluídas | -| [opencode-workspace](https://github.com/kdcokenny/opencode-workspace) | Conjunto de orquestração multi-agente – 16 componentes, uma instalação | -| [opencode-worktree](https://github.com/kdcokenny/opencode-worktree) | Worktrees git sem atrito para opencode | +| Nome | Descrição | +| -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | +| [opencode-daytona](https://github.com/daytonaio/daytona/tree/main/libs/opencode-plugin) | Execute automaticamente sessões do OpenCode em sandboxes isoladas do Daytona com sincronização git e pré-visualizações ao vivo | +| [opencode-helicone-session](https://github.com/H2Shami/opencode-helicone-session) | Injete automaticamente cabeçalhos de sessão Helicone para agrupamento de requisições | +| [opencode-type-inject](https://github.com/nick-vi/opencode-type-inject) | Auto-injetar tipos TypeScript/Svelte em leituras de arquivos com ferramentas de busca | +| [opencode-openai-codex-auth](https://github.com/numman-ali/opencode-openai-codex-auth) | Use sua assinatura ChatGPT Plus/Pro em vez de créditos de API | +| [opencode-gemini-auth](https://github.com/jenslys/opencode-gemini-auth) | Use seu plano Gemini existente em vez de cobrança de API | +| [opencode-antigravity-auth](https://github.com/NoeFabris/opencode-antigravity-auth) | Use os modelos gratuitos do Antigravity em vez de cobrança de API | +| [opencode-devcontainers](https://github.com/athal7/opencode-devcontainers) | Isolamento de devcontainer multi-branch com clones rasos e portas atribuídas automaticamente | +| [opencode-google-antigravity-auth](https://github.com/shekohex/opencode-google-antigravity-auth) | Plugin Google Antigravity OAuth, com suporte para Google Search e manuseio de API mais robusto | +| [opencode-dynamic-context-pruning](https://github.com/Tarquinen/opencode-dynamic-context-pruning) | Otimize o uso de tokens podando saídas de ferramentas obsoletas | +| [opencode-vibeguard](https://github.com/inkdust2021/opencode-vibeguard) | Oculte segredos/PII em marcadores estilo VibeGuard antes de chamadas LLM; restaure localmente | +| [opencode-websearch-cited](https://github.com/ghoulr/opencode-websearch-cited.git) | Adicione suporte nativo de pesquisa na web para provedores suportados com estilo fundamentado no Google | +| [opencode-pty](https://github.com/shekohex/opencode-pty.git) | Permite que agentes de IA executem processos em segundo plano em um PTY, enviando entrada interativa para eles. | +| [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Instruções para comandos de shell não interativos - evita travamentos de operações dependentes de TTY | +| [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Acompanhe o uso do OpenCode com Wakatime | +| [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Limpe tabelas markdown produzidas por LLMs | +| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | Edição de código 10x mais rápida com a API Morph Fast Apply e marcadores de edição preguiçosos | +| [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Agentes em segundo plano, ferramentas LSP/AST/MCP pré-construídas, agentes curados, compatível com Claude Code | +| [opencode-notificator](https://github.com/panta82/opencode-notificator) | Notificações de desktop e alertas sonoros para sessões do OpenCode | +| [opencode-notifier](https://github.com/mohak34/opencode-notifier) | Notificações de desktop e alertas sonoros para eventos de permissão, conclusão e erro | +| [opencode-zellij-namer](https://github.com/24601/opencode-zellij-namer) | Nomeação automática de sessões Zellij com suporte de IA com base no contexto do OpenCode | +| [opencode-skillful](https://github.com/zenobi-us/opencode-skillful) | Permite que agentes do OpenCode carreguem prompts sob demanda com descoberta e injeção de habilidades | +| [opencode-supermemory](https://github.com/supermemoryai/opencode-supermemory) | Memória persistente entre sessões usando Supermemory | +| [@plannotator/opencode](https://github.com/backnotprop/plannotator/tree/main/apps/opencode-plugin) | Revisão de plano interativa com anotação visual e compartilhamento privado/offline | +| [@openspoon/subtask2](https://github.com/spoons-and-mirrors/subtask2) | Estenda opencode /commands em um poderoso sistema de orquestração com controle de fluxo granular | +| [opencode-scheduler](https://github.com/different-ai/opencode-scheduler) | Agende trabalhos recorrentes usando launchd (Mac) ou systemd (Linux) com sintaxe cron | +| [micode](https://github.com/vtemian/micode) | Fluxo de trabalho Estruturado Brainstorm → Planejar → Implementar com continuidade de sessão | +| [octto](https://github.com/vtemian/octto) | UI interativa do navegador para brainstorming de IA com formulários de múltiplas perguntas | +| [opencode-background-agents](https://github.com/kdcokenny/opencode-background-agents) | Agentes em segundo plano estilo Claude Code com delegação assíncrona e persistência de contexto | +| [opencode-notify](https://github.com/kdcokenny/opencode-notify) | Notificações nativas do OS para OpenCode – saiba quando as tarefas são concluídas | +| [opencode-workspace](https://github.com/kdcokenny/opencode-workspace) | Conjunto de orquestração multi-agente – 16 componentes, uma instalação | +| [opencode-worktree](https://github.com/kdcokenny/opencode-worktree) | Worktrees git sem atrito para OpenCode | --- @@ -54,17 +55,17 @@ Você também pode conferir [awesome-opencode](https://github.com/awesome-openco | Nome | Descrição | | ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------- | -| [kimaki](https://github.com/remorses/kimaki) | Bot do Discord para controlar sessões do opencode, construído sobre o SDK | +| [kimaki](https://github.com/remorses/kimaki) | Bot do Discord para controlar sessões do OpenCode, construído sobre o SDK | | [opencode.nvim](https://github.com/NickvanDyke/opencode.nvim) | Plugin Neovim para prompts cientes do editor, construído sobre a API | -| [portal](https://github.com/hosenur/portal) | UI web mobile-first para opencode sobre Tailscale/VPN | -| [opencode plugin template](https://github.com/zenobi-us/opencode-plugin-template/) | Template para construir plugins do opencode | -| [opencode.nvim](https://github.com/sudo-tee/opencode.nvim) | Frontend Neovim para opencode - um agente de codificação IA baseado em terminal | -| [ai-sdk-provider-opencode-sdk](https://github.com/ben-vargas/ai-sdk-provider-opencode-sdk) | Provedor Vercel AI SDK para usar opencode via @opencode-ai/sdk | -| [OpenChamber](https://github.com/btriapitsyn/openchamber) | Aplicativo Web / Desktop e Extensão do VS Code para opencode | -| [OpenCode-Obsidian](https://github.com/mtymek/opencode-obsidian) | Plugin Obsidian que incorpora opencode na UI do Obsidian | -| [OpenWork](https://github.com/different-ai/openwork) | Uma alternativa de código aberto ao Claude Cowork, alimentada pelo opencode | -| [ocx](https://github.com/kdcokenny/ocx) | Gerenciador de extensões opencode com perfis portáteis e isolados. | -| [CodeNomad](https://github.com/NeuralNomadsAI/CodeNomad) | Aplicativo Desktop, Web, Mobile e Cliente Remoto para opencode | +| [portal](https://github.com/hosenur/portal) | UI web mobile-first para OpenCode sobre Tailscale/VPN | +| [opencode plugin template](https://github.com/zenobi-us/opencode-plugin-template/) | Template para construir plugins do OpenCode | +| [opencode.nvim](https://github.com/sudo-tee/opencode.nvim) | Frontend Neovim para OpenCode - um agente de codificação IA baseado em terminal | +| [ai-sdk-provider-opencode-sdk](https://github.com/ben-vargas/ai-sdk-provider-opencode-sdk) | Provedor Vercel AI SDK para usar OpenCode via @opencode-ai/sdk | +| [OpenChamber](https://github.com/btriapitsyn/openchamber) | Aplicativo Web / Desktop e Extensão do VS Code para OpenCode | +| [OpenCode-Obsidian](https://github.com/mtymek/opencode-obsidian) | Plugin Obsidian que incorpora OpenCode na UI do Obsidian | +| [OpenWork](https://github.com/different-ai/openwork) | Uma alternativa de código aberto ao Claude Cowork, alimentada pelo OpenCode | +| [ocx](https://github.com/kdcokenny/ocx) | Gerenciador de extensões OpenCode com perfis portáteis e isolados. | +| [CodeNomad](https://github.com/NeuralNomadsAI/CodeNomad) | Aplicativo Desktop, Web, Mobile e Cliente Remoto para OpenCode | --- diff --git a/packages/web/src/content/docs/pt-br/go.mdx b/packages/web/src/content/docs/pt-br/go.mdx new file mode 100644 index 00000000000..ee72ed49cbf --- /dev/null +++ b/packages/web/src/content/docs/pt-br/go.mdx @@ -0,0 +1,145 @@ +--- +title: Go +description: Assinatura de baixo custo para modelos de codificação abertos. +--- + +import config from "../../../../config.mjs" +export const console = config.console +export const email = `mailto:${config.email}` + +O OpenCode Go é uma assinatura de baixo custo de **$10/mês** que oferece acesso confiável a modelos de codificação abertos populares. + +:::note +O OpenCode Go está atualmente em beta. +::: + +O Go funciona como qualquer outro provedor no OpenCode. Você assina o OpenCode Go e obtém sua chave de API. É **totalmente opcional** e você não precisa usá-lo para usar o OpenCode. + +Ele é projetado principalmente para usuários internacionais, com modelos hospedados nos EUA, UE e Singapura para acesso global estável. + +--- + +## Contexto + +Modelos abertos ficaram realmente bons. Eles agora alcançam desempenho próximo aos modelos proprietários para tarefas de codificação. E como muitos provedores podem servi-los competitivamente, eles geralmente são muito mais baratos. + +No entanto, obter acesso confiável e de baixa latência a eles pode ser difícil. Os provedores variam em qualidade e disponibilidade. + +:::tip +Testamos um grupo selecionado de modelos e provedores que funcionam bem com o OpenCode. +::: + +Para corrigir isso, fizemos algumas coisas: + +1. Testamos um grupo selecionado de modelos abertos e conversamos com suas equipes sobre a melhor forma de executá-los. +2. Trabalhamos com alguns provedores para garantir que eles estivessem sendo servidos corretamente. +3. Finalmente, fizemos benchmarks da combinação modelo/provedor e chegamos a uma lista que nos sentimos bem em recomendar. + +O OpenCode Go oferece acesso a esses modelos por **$10/mês**. + +--- + +## Como funciona + +O OpenCode Go funciona como qualquer outro provedor no OpenCode. + +1. Você faz login no **OpenCode Zen**, assina o Go e copia sua chave de API. +2. Você executa o comando `/connect` na TUI, seleciona `OpenCode Go` e cola sua chave de API. +3. Execute `/models` na TUI para ver a lista de modelos disponíveis através do Go. + +:::note +Apenas um membro por workspace pode assinar o OpenCode Go. +::: + +A lista atual de modelos inclui: + +- **GLM-5** +- **Kimi K2.5** +- **MiniMax M2.5** + +A lista de modelos pode mudar conforme testamos e adicionamos novos. + +--- + +## Limites de uso + +O OpenCode Go inclui os seguintes limites: + +- **Limite de 5 horas** — $12 de uso +- **Limite semanal** — $30 de uso +- **Limite mensal** — $60 de uso + +Os limites são definidos em valor monetário. Isso significa que sua contagem real de requisições depende do modelo que você usa. Modelos mais baratos como o MiniMax M2.5 permitem mais requisições, enquanto modelos de custo mais alto como o GLM-5 permitem menos. + +A tabela abaixo fornece uma estimativa de contagem de requisições baseada em padrões típicos de uso do Go: + +| | GLM-5 | Kimi K2.5 | MiniMax M2.5 | +| ----------------------- | ----- | --------- | ------------ | +| requisições por 5 horas | 1.150 | 1.850 | 30.000 | +| requisições por semana | 2.880 | 4.630 | 75.000 | +| requisições por mês | 5.750 | 9.250 | 150.000 | + +As estimativas são baseadas em padrões médios de requisição observados: + +- GLM-5 — 700 tokens de entrada, 52.000 em cache, 150 tokens de saída por requisição +- Kimi K2.5 — 870 tokens de entrada, 55.000 em cache, 200 tokens de saída por requisição +- MiniMax M2.5 — 300 tokens de entrada, 55.000 em cache, 125 tokens de saída por requisição + +Você pode acompanhar seu uso atual no **console**. + +:::tip +Se você atingir o limite de uso, pode continuar usando os modelos gratuitos. +::: + +Os limites de uso podem mudar conforme aprendemos com o uso inicial e feedback. + +--- + +### Preços + +O OpenCode Go é um plano de assinatura de **$10/mês**. Abaixo estão os preços **por 1M de tokens**. + +| Modelo | Entrada | Saída | Leitura em Cache | +| ------------ | ------- | ----- | ---------------- | +| GLM-5 | $1.00 | $3.20 | $0.20 | +| Kimi K2.5 | $0.60 | $3.00 | $0.10 | +| MiniMax M2.5 | $0.30 | $1.20 | $0.03 | + +--- + +### Uso além dos limites + +Se você também tiver créditos em seu saldo Zen, pode ativar a opção **Use balance** (Usar saldo) no console. Quando ativada, o Go recorrerá ao seu saldo Zen depois que você atingir seus limites de uso, em vez de bloquear as requisições. + +--- + +## Endpoints + +Você também pode acessar os modelos Go através dos seguintes endpoints de API. + +| Modelo | ID do Modelo | Endpoint | Pacote AI SDK | +| ------------ | ------------ | ------------------------------------------------ | --------------------------- | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | + +O [model id](/docs/config/#models) (ID do modelo) na sua configuração do OpenCode usa o formato `opencode-go/`. Por exemplo, para o Kimi K2.5, você usaria `opencode-go/kimi-k2.5` na sua configuração. + +--- + +## Privacidade + +O plano é projetado principalmente para usuários internacionais, com modelos hospedados nos EUA, UE e Singapura para acesso global estável. + +Entre em contato conosco se tiver alguma dúvida. + +--- + +## Objetivos + +Criamos o OpenCode Go para: + +1. Tornar a IA de codificação **acessível** a mais pessoas com uma assinatura de baixo custo. +2. Fornecer acesso **confiável** aos melhores modelos de codificação abertos. +3. Curar modelos que são **testados e avaliados** para uso em agentes de codificação. +4. Não ter **nenhum bloqueio (lock-in)**, permitindo que você use qualquer outro provedor com o OpenCode também. diff --git a/packages/web/src/content/docs/pt-br/keybinds.mdx b/packages/web/src/content/docs/pt-br/keybinds.mdx index b4da4471aa5..1829763ade1 100644 --- a/packages/web/src/content/docs/pt-br/keybinds.mdx +++ b/packages/web/src/content/docs/pt-br/keybinds.mdx @@ -3,11 +3,11 @@ title: Atalhos de Teclado description: Personalize seus atalhos de teclado. --- -O opencode tem uma lista de atalhos de teclado que você pode personalizar através da configuração do opencode. +O opencode tem uma lista de atalhos de teclado que você pode personalizar através de `tui.json`. -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "keybinds": { "leader": "ctrl+x", "app_exit": "ctrl+c,ctrl+d,q", @@ -119,9 +119,9 @@ Você não precisa usar uma tecla líder para seus atalhos, mas recomendamos que Você pode desativar um atalho adicionando a tecla à sua configuração com um valor de "none". -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "keybinds": { "session_compact": "none" } diff --git a/packages/web/src/content/docs/pt-br/lsp.mdx b/packages/web/src/content/docs/pt-br/lsp.mdx index 6baf1813b34..cd8937b244f 100644 --- a/packages/web/src/content/docs/pt-br/lsp.mdx +++ b/packages/web/src/content/docs/pt-br/lsp.mdx @@ -27,6 +27,7 @@ O opencode vem com vários servidores LSP integrados para linguagens populares: | gopls | .go | Comando `go` disponível | | hls | .hs, .lhs | Comando `haskell-language-server-wrapper` disponível | | jdtls | .java | `Java SDK (version 21+)` instalado | +| julials | .jl | `julia` e `LanguageServer.jl` instalados | | kotlin-ls | .kt, .kts | Instala automaticamente para projetos Kotlin | | lua-ls | .lua | Instala automaticamente para projetos Lua | | nixd | .nix | Comando `nixd` disponível | diff --git a/packages/web/src/content/docs/pt-br/plugins.mdx b/packages/web/src/content/docs/pt-br/plugins.mdx index bcf8f379bc3..54c5b1ffaca 100644 --- a/packages/web/src/content/docs/pt-br/plugins.mdx +++ b/packages/web/src/content/docs/pt-br/plugins.mdx @@ -307,6 +307,10 @@ O helper `tool` cria uma ferramenta personalizada que o opencode pode chamar. El Suas ferramentas personalizadas estarão disponíveis para o opencode junto com as ferramentas integradas. +:::note +Se uma ferramenta de plugin usar o mesmo nome que uma ferramenta integrada, a ferramenta de plugin tem precedência. +::: + --- ### Registro diff --git a/packages/web/src/content/docs/pt-br/providers.mdx b/packages/web/src/content/docs/pt-br/providers.mdx index 43f2e385f13..2ef2ebdc006 100644 --- a/packages/web/src/content/docs/pt-br/providers.mdx +++ b/packages/web/src/content/docs/pt-br/providers.mdx @@ -54,7 +54,39 @@ OpenCode Zen é uma lista de modelos fornecidos pela equipe do opencode que fora Se você é novo, recomendamos começar com o OpenCode Zen. ::: -1. Execute o comando `/connect` no TUI, selecione opencode e acesse [opencode.ai/auth](https://opencode.ai/auth). +1. Execute o comando `/connect` no TUI, selecione `OpenCode Zen` e acesse [opencode.ai/zen](https://opencode.ai/zen). + + ```txt + /connect + ``` + +2. Faça login, adicione seus dados de cobrança e copie sua chave da API. + +3. Cole sua chave da API. + + ```txt + ┌ API key + │ + │ + └ enter + ``` + +4. Execute `/models` no TUI para ver a lista de modelos que recomendamos. + + ```txt + /models + ``` + +Funciona como qualquer outro provedor no opencode e é completamente opcional. + +--- + +## OpenCode Go + +OpenCode Go é um plano de assinatura de baixo custo que fornece acesso confiável a modelos de codificação abertos populares fornecidos pela equipe do opencode que foram +testados e verificados para funcionar bem com o opencode. + +1. Execute o comando `/connect` no TUI, selecione `OpenCode Go` e acesse [opencode.ai/zen](https://opencode.ai/zen). ```txt /connect @@ -130,6 +162,8 @@ Para usar o Amazon Bedrock com o opencode: 2. **Configure a autenticação** usando um dos seguintes métodos: + *** + #### Variáveis de Ambiente (Início Rápido) Defina uma dessas variáveis de ambiente ao executar o opencode: @@ -152,6 +186,8 @@ Para usar o Amazon Bedrock com o opencode: export AWS_REGION=us-east-1 ``` + *** + #### Arquivo de Configuração (Recomendado) Para configuração específica do projeto ou persistente, use `opencode.json`: @@ -179,6 +215,8 @@ Para usar o Amazon Bedrock com o opencode: As opções do arquivo de configuração têm precedência sobre as variáveis de ambiente. ::: + *** + #### Avançado: Endpoints VPC Se você estiver usando endpoints VPC para Bedrock: @@ -202,12 +240,16 @@ Para usar o Amazon Bedrock com o opencode: A opção `endpoint` é um alias para a opção genérica `baseURL`, usando terminologia específica da AWS. Se tanto `endpoint` quanto `baseURL` forem especificados, `endpoint` tem precedência. ::: + *** + #### Métodos de Autenticação - **`AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY`**: Crie um usuário IAM e gere chaves de acesso no Console da AWS - **`AWS_PROFILE`**: Use perfis nomeados de `~/.aws/credentials`. Primeiro configure com `aws configure --profile my-profile` ou `aws sso login` - **`AWS_BEARER_TOKEN_BEDROCK`**: Gere chaves de API de longo prazo no console do Amazon Bedrock - **`AWS_WEB_IDENTITY_TOKEN_FILE` / `AWS_ROLE_ARN`**: Para EKS IRSA (IAM Roles for Service Accounts) ou outros ambientes Kubernetes com federação OIDC. Essas variáveis de ambiente são injetadas automaticamente pelo Kubernetes ao usar anotações de conta de serviço. + *** + #### Precedência de Autenticação O Amazon Bedrock usa a seguinte prioridade de autenticação: @@ -225,7 +267,8 @@ Para usar o Amazon Bedrock com o opencode: ``` :::note -Para perfis de inferência personalizados, use o nome do modelo e do provedor na chave e defina a propriedade `id` para o arn. Isso garante o cache correto: +Para perfis de inferência personalizados, use o nome do modelo e do provedor na chave e defina a propriedade `id` para o arn. Isso garante o cache correto. +::: ```json title="opencode.json" { @@ -243,8 +286,6 @@ Para perfis de inferência personalizados, use o nome do modelo e do provedor na } ``` -::: - --- ### Anthropic @@ -784,8 +825,6 @@ Para usar sua assinatura do GitHub Copilot com o opencode: :::note Alguns modelos podem precisar de uma [assinatura Pro+](https://github.com/features/copilot/plans) para usar. - -Alguns modelos precisam ser habilitados manualmente nas suas [configurações do GitHub Copilot](https://docs.github.com/en/copilot/how-tos/use-ai-models/configure-access-to-ai-models#setup-for-individual-use). ::: 1. Execute o comando `/connect` e procure por GitHub Copilot. @@ -803,7 +842,8 @@ Alguns modelos precisam ser habilitados manualmente nas suas [configurações do │ │ Digite o código: 8F43-6FCF │ - └ Aguardando autorização... + │ Aguardando autorização... + └ ``` 3. Agora execute o comando `/models` para selecionar o modelo que você deseja. @@ -1309,6 +1349,7 @@ Recomendamos se inscrever para [ChatGPT Plus ou Pro](https://chatgpt.com/pricing │ │ ChatGPT Plus/Pro │ Inserir manualmente a chave de API + │ └ ``` @@ -1472,6 +1513,39 @@ SAP AI Core fornece acesso a mais de 40 modelos do OpenAI, Anthropic, Google, Am --- +### STACKIT + +O STACKIT AI Model Serving fornece um ambiente de hospedagem soberano totalmente gerenciado para modelos de IA, com foco em LLMs como Llama, Mistral e Qwen, com máxima soberania de dados na infraestrutura europeia. + +1. Vá para o [Portal STACKIT](https://portal.stackit.cloud), navegue até **AI Model Serving** e crie um token de autenticação para o seu projeto. + + :::tip + Você precisa de uma conta de cliente STACKIT, conta de usuário e projeto antes de criar tokens de autenticação. + ::: + +2. Execute o comando `/connect` e procure por **STACKIT**. + + ```txt + /connect + ``` + +3. Insira seu token de autenticação do STACKIT AI Model Serving. + + ```txt + ┌ API key + │ + │ + └ enter + ``` + +4. Execute o comando `/models` para selecionar entre os modelos disponíveis, como _Qwen3-VL 235B_ ou _Llama 3.3 70B_. + + ```txt + /models + ``` + +--- + ### OVHcloud AI Endpoints 1. Acesse o [painel OVHcloud](https://ovh.com/manager). Navegue até a seção `Public Cloud`, `AI & Machine Learning` > `AI Endpoints` e na aba `API Keys`, clique em **Criar uma nova chave da API**. @@ -1751,10 +1825,9 @@ Você pode usar qualquer provedor compatível com OpenAI com o opencode. A maior ┌ Adicionar credencial │ - ▲ Isso armazena apenas uma credencial para myprovider - você precisará configurá-lo no opencode.json, verifique a documentação para exemplos. - │ - ◇ Digite sua chave de API - │ sk-... + ◆ Select provider + │ ... + │ ● Other └ ``` @@ -1765,10 +1838,8 @@ Você pode usar qualquer provedor compatível com OpenAI com o opencode. A maior ┌ Adicionar credencial │ - ▲ Isso armazena apenas uma credencial para myprovider - você precisará configurá-lo no opencode.json, verifique a documentação para exemplos. - │ - ◇ Digite sua chave de API - │ sk-... + ◇ Enter provider id + │ myprovider └ ``` @@ -1808,8 +1879,6 @@ Você pode usar qualquer provedor compatível com OpenAI com o opencode. A maior } } } - } - } } } ``` @@ -1856,18 +1925,6 @@ Aqui está um exemplo definindo as opções `apiKey`, `headers` e `limit` do mod } } } - } - }, - "models": { - "my-model-name": { - "name": "Nome de Exibição do Meu Modelo", - "limit": { - "context": 200000, - "output": 65536 - } - } - } - } } } ``` diff --git a/packages/web/src/content/docs/pt-br/sdk.mdx b/packages/web/src/content/docs/pt-br/sdk.mdx index 672b852f6be..7fd765e4c14 100644 --- a/packages/web/src/content/docs/pt-br/sdk.mdx +++ b/packages/web/src/content/docs/pt-br/sdk.mdx @@ -117,6 +117,78 @@ try { --- +## Saída Estruturada + +Você pode solicitar uma saída JSON estruturada do modelo especificando um `format` com um esquema JSON. O modelo usará uma ferramenta `StructuredOutput` para retornar um JSON validado correspondente ao seu esquema. + +### Uso Básico + +```typescript +const result = await client.session.prompt({ + path: { id: sessionId }, + body: { + parts: [{ type: "text", text: "Research Anthropic and provide company info" }], + format: { + type: "json_schema", + schema: { + type: "object", + properties: { + company: { type: "string", description: "Company name" }, + founded: { type: "number", description: "Year founded" }, + products: { + type: "array", + items: { type: "string" }, + description: "Main products", + }, + }, + required: ["company", "founded"], + }, + }, + }, +}) + +// Acessar a saída estruturada +console.log(result.data.info.structured_output) +// { company: "Anthropic", founded: 2021, products: ["Claude", "Claude API"] } +``` + +### Tipos de Formato de Saída + +| Tipo | Descrição | +| ------------- | --------------------------------------------------------- | +| `text` | Padrão. Resposta de texto padrão (sem saída estruturada) | +| `json_schema` | Retorna JSON validado correspondente ao esquema fornecido | + +### Formato do Esquema JSON + +Ao usar `type: 'json_schema'`, forneça: + +| Campo | Tipo | Descrição | +| ------------ | --------------- | -------------------------------------------------------------- | +| `type` | `'json_schema'` | Obrigatório. Especifica o modo de esquema JSON | +| `schema` | `object` | Obrigatório. Objeto JSON Schema definindo a estrutura de saída | +| `retryCount` | `number` | Opcional. Número de tentativas de validação (padrão: 2) | + +### Tratamento de Erros + +Se o modelo falhar em produzir uma saída estruturada válida após todas as tentativas, a resposta incluirá um `StructuredOutputError`: + +```typescript +if (result.data.info.error?.name === "StructuredOutputError") { + console.error("Failed to produce structured output:", result.data.info.error.message) + console.error("Attempts:", result.data.info.error.retries) +} +``` + +### Melhores Práticas + +1. **Forneça descrições claras** nas propriedades do seu esquema para ajudar o modelo a entender quais dados extrair +2. **Use `required`** para especificar quais campos devem estar presentes +3. **Mantenha os esquemas focados** - esquemas aninhados complexos podem ser mais difíceis para o modelo preencher corretamente +4. **Defina um `retryCount` apropriado** - aumente para esquemas complexos, diminua para os simples + +--- + ## APIs O SDK expõe todas as APIs do servidor através de um cliente seguro em tipos. @@ -226,27 +298,27 @@ const { providers, default: defaults } = await client.config.providers() ### Sessões -| Método | Descrição | Notas | -| ---------------------------------------------------------- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | -| `session.list()` | Listar sessões | Retorna Session[] | -| `session.get({ path })` | Obter sessão | Retorna Session | -| `session.children({ path })` | Listar sessões filhas | Retorna Session[] | -| `session.create({ body })` | Criar sessão | Retorna Session | -| `session.delete({ path })` | Deletar sessão | Retorna `boolean` | -| `session.update({ path, body })` | Atualizar propriedades da sessão | Retorna Session | -| `session.init({ path, body })` | Analisar app e criar `AGENTS.md` | Retorna `boolean` | -| `session.abort({ path })` | Abortar uma sessão em execução | Retorna `boolean` | -| `session.share({ path })` | Compartilhar sessão | Retorna Session | -| `session.unshare({ path })` | Descompartilhar sessão | Retorna Session | -| `session.summarize({ path, body })` | Resumir sessão | Retorna `boolean` | -| `session.messages({ path })` | Listar mensagens em uma sessão | Retorna `{ info: `Message`, parts: `Part[]`}[]` | -| `session.message({ path })` | Obter detalhes da mensagem | Retorna `{ info: `Message`, parts: `Part[]`}` | -| `session.prompt({ path, body })` | Enviar mensagem de prompt | `body.noReply: true` retorna UserMessage (apenas contexto). O padrão retorna AssistantMessage com resposta da AI | -| `session.command({ path, body })` | Enviar comando para a sessão | Retorna `{ info: `AssistantMessage`, parts: `Part[]`}` | -| `session.shell({ path, body })` | Executar um comando shell | Retorna AssistantMessage | -| `session.revert({ path, body })` | Reverter uma mensagem | Retorna Session | -| `session.unrevert({ path })` | Restaurar mensagens revertidas | Retorna Session | -| `postSessionByIdPermissionsByPermissionId({ path, body })` | Responder a um pedido de permissão | Retorna `boolean` | +| Método | Descrição | Notas | +| ---------------------------------------------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `session.list()` | Listar sessões | Retorna Session[] | +| `session.get({ path })` | Obter sessão | Retorna Session | +| `session.children({ path })` | Listar sessões filhas | Retorna Session[] | +| `session.create({ body })` | Criar sessão | Retorna Session | +| `session.delete({ path })` | Deletar sessão | Retorna `boolean` | +| `session.update({ path, body })` | Atualizar propriedades da sessão | Retorna Session | +| `session.init({ path, body })` | Analisar app e criar `AGENTS.md` | Retorna `boolean` | +| `session.abort({ path })` | Abortar uma sessão em execução | Retorna `boolean` | +| `session.share({ path })` | Compartilhar sessão | Retorna Session | +| `session.unshare({ path })` | Descompartilhar sessão | Retorna Session | +| `session.summarize({ path, body })` | Resumir sessão | Retorna `boolean` | +| `session.messages({ path })` | Listar mensagens em uma sessão | Retorna `{ info: `Message`, parts: `Part[]`}[]` | +| `session.message({ path })` | Obter detalhes da mensagem | Retorna `{ info: `Message`, parts: `Part[]`}` | +| `session.prompt({ path, body })` | Enviar mensagem de prompt | `body.noReply: true` retorna UserMessage (apenas contexto). O padrão retorna AssistantMessage com resposta da AI. Suporta `body.outputFormat` para [saída estruturada](#saída-estruturada) | +| `session.command({ path, body })` | Enviar comando para a sessão | Retorna `{ info: `AssistantMessage`, parts: `Part[]`}` | +| `session.shell({ path, body })` | Executar um comando shell | Retorna AssistantMessage | +| `session.revert({ path, body })` | Reverter uma mensagem | Retorna Session | +| `session.unrevert({ path })` | Restaurar mensagens revertidas | Retorna Session | +| `postSessionByIdPermissionsByPermissionId({ path, body })` | Responder a um pedido de permissão | Retorna `boolean` | --- diff --git a/packages/web/src/content/docs/pt-br/themes.mdx b/packages/web/src/content/docs/pt-br/themes.mdx index a1a5083cb86..5706d6e8366 100644 --- a/packages/web/src/content/docs/pt-br/themes.mdx +++ b/packages/web/src/content/docs/pt-br/themes.mdx @@ -61,11 +61,11 @@ O tema do sistema é para usuários que: ## Usando um tema -Você pode selecionar um tema chamando a seleção de tema com o comando `/theme`. Ou você pode especificá-lo em sua [configuração](/docs/config). +Você pode selecionar um tema chamando a seleção de tema com o comando `/theme`. Ou você pode especificá-lo em `tui.json`. -```json title="opencode.json" {3} +```json title="tui.json" {3} { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "theme": "tokyonight" } ``` diff --git a/packages/web/src/content/docs/pt-br/tui.mdx b/packages/web/src/content/docs/pt-br/tui.mdx index 17b9c7de0b3..67de2138b6f 100644 --- a/packages/web/src/content/docs/pt-br/tui.mdx +++ b/packages/web/src/content/docs/pt-br/tui.mdx @@ -234,7 +234,7 @@ Compartilhe a sessão atual. [Saiba mais](/docs/share). Liste os temas disponíveis. ```bash frame="none" -/theme +/themes ``` **Atalho:** `ctrl+x t` @@ -352,24 +352,34 @@ Alguns editores precisam de argumentos de linha de comando para rodar em modo bl ## Configuração -Você pode personalizar o comportamento do TUI através do seu arquivo de configuração do opencode. +Você pode personalizar o comportamento do TUI através de `tui.json` (ou `tui.jsonc`). -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", - "tui": { - "scroll_speed": 3, - "scroll_acceleration": { - "enabled": true - } - } + "$schema": "https://opencode.ai/tui.json", + "theme": "opencode", + "keybinds": { + "leader": "ctrl+x" + }, + "scroll_speed": 3, + "scroll_acceleration": { + "enabled": true + }, + "diff_style": "auto" } ``` +Isso é separado do `opencode.json`, que configura o comportamento do servidor/runtime. + ### Opções -- `scroll_acceleration` - Ative a aceleração de rolagem no estilo macOS para uma rolagem suave e natural. Quando ativado, a velocidade de rolagem aumenta com gestos de rolagem rápidos e permanece precisa para movimentos mais lentos. **Esta configuração tem precedência sobre `scroll_speed` e a substitui quando ativada.** -- `scroll_speed` - Controla quão rápido o TUI rola ao usar comandos de rolagem (mínimo: `1`). O padrão é `3`. **Nota: Isso é ignorado se `scroll_acceleration.enabled` estiver definido como `true`.** +- `theme` - Define o tema da sua interface. [Saiba mais](/docs/themes). +- `keybinds` - Personaliza atalhos de teclado. [Saiba mais](/docs/keybinds). +- `scroll_acceleration.enabled` - Ative a aceleração de rolagem no estilo macOS para uma rolagem suave e natural. Quando ativado, a velocidade de rolagem aumenta com gestos de rolagem rápidos e permanece precisa para movimentos mais lentos. **Esta configuração tem precedência sobre `scroll_speed` e a substitui quando ativada.** +- `scroll_speed` - Controla quão rápido o TUI rola ao usar comandos de rolagem (mínimo: `0.001`, suporta valores decimais). O padrão é `3`. **Nota: Isso é ignorado se `scroll_acceleration.enabled` estiver definido como `true`.** +- `diff_style` - Controla a renderização de diffs. `"auto"` se adapta à largura do terminal, `"stacked"` sempre mostra um layout de coluna única. + +Use `OPENCODE_TUI_CONFIG` para carregar um caminho de configuração TUI personalizado. --- diff --git a/packages/web/src/content/docs/pt-br/zen.mdx b/packages/web/src/content/docs/pt-br/zen.mdx index 203f0b3f9df..ba029fb7fca 100644 --- a/packages/web/src/content/docs/pt-br/zen.mdx +++ b/packages/web/src/content/docs/pt-br/zen.mdx @@ -1,19 +1,19 @@ --- title: Zen -description: Lista selecionada de modelos fornecidos pelo opencode. +description: Lista selecionada de modelos fornecidos pelo OpenCode. --- import config from "../../../../config.mjs" export const console = config.console export const email = `mailto:${config.email}` -O OpenCode Zen é uma lista de modelos testados e verificados fornecidos pela equipe do opencode. +O OpenCode Zen é uma lista de modelos testados e verificados fornecidos pela equipe do OpenCode. :::note O OpenCode Zen está atualmente em beta. ::: -O Zen funciona como qualquer outro provedor no opencode. Você faz login no OpenCode Zen e obtém sua chave de API. É **completamente opcional** e você não precisa usá-lo para utilizar o opencode. +O Zen funciona como qualquer outro provedor no OpenCode. Você faz login no OpenCode Zen e obtém sua chave de API. É **completamente opcional** e você não precisa usá-lo para utilizar o OpenCode. --- @@ -22,7 +22,7 @@ O Zen funciona como qualquer outro provedor no opencode. Você faz login no Open Existe um grande número de modelos disponíveis, mas apenas alguns desses modelos funcionam bem como agentes de codificação. Além disso, a maioria dos provedores é configurada de maneira muito diferente; portanto, você obtém desempenhos e qualidades muito diferentes. :::tip -Testamos um grupo selecionado de modelos e provedores que funcionam bem com o opencode. +Testamos um grupo selecionado de modelos e provedores que funcionam bem com o OpenCode. ::: Portanto, se você estiver usando um modelo através de algo como OpenRouter, você nunca pode ter certeza se está obtendo a melhor versão do modelo que deseja. @@ -39,7 +39,7 @@ O OpenCode Zen é um gateway de IA que lhe dá acesso a esses modelos. ## Como funciona -O OpenCode Zen funciona como qualquer outro provedor no opencode. +O OpenCode Zen funciona como qualquer outro provedor no OpenCode. 1. Você faz login no **OpenCode Zen**, adiciona seus dados de cobrança e copia sua chave de API. 2. Você executa o comando `/connect` no TUI, seleciona OpenCode Zen e cola sua chave de API. @@ -55,6 +55,7 @@ Você também pode acessar nossos modelos através dos seguintes endpoints da AP | Modelo | ID do Modelo | Endpoint | Pacote AI SDK | | ------------------ | ------------------ | -------------------------------------------------- | --------------------------- | +| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | @@ -64,28 +65,30 @@ Você também pode acessar nossos modelos através dos seguintes endpoints da AP | GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | | Gemini 3 Pro | gemini-3-pro | `https://opencode.ai/zen/v1/models/gemini-3-pro` | `@ai-sdk/google` | | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.1 | minimax-m2.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.1 Free | minimax-m2.1-free | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| GLM 5 | glm-5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 4.7 | glm-4.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM 4.7 Free | glm-4.7-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 4.6 | glm-4.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 Free | kimi-k2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2 Thinking | kimi-k2-thinking | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2 | kimi-k2 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Qwen3 Coder 480B | qwen3-coder | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -O [id do modelo](/docs/config/#models) na sua configuração do opencode usa o formato `opencode/`. Por exemplo, para GPT 5.2 Codex, você usaria `opencode/gpt-5.2-codex` na sua configuração. +O [id do modelo](/docs/config/#models) na sua configuração do OpenCode usa o formato `opencode/`. Por exemplo, para GPT 5.2 Codex, você usaria `opencode/gpt-5.2-codex` na sua configuração. --- @@ -106,29 +109,34 @@ Nós suportamos um modelo de pagamento conforme o uso. Abaixo estão os preços | Modelo | Entrada | Saída | Leitura em Cache | Escrita em Cache | | --------------------------------- | ------- | ------ | ---------------- | ---------------- | | Big Pickle | Grátis | Grátis | Grátis | - | -| MiniMax M2.1 Free | Grátis | Grátis | Grátis | - | +| MiniMax M2.5 Free | Grátis | Grátis | Grátis | - | +| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | - | | MiniMax M2.1 | $0.30 | $1.20 | $0.10 | - | -| GLM 4.7 Free | Grátis | Grátis | Grátis | - | +| GLM 5 | $1.00 | $3.20 | $0.20 | - | | GLM 4.7 | $0.60 | $2.20 | $0.10 | - | | GLM 4.6 | $0.60 | $2.20 | $0.10 | - | -| Kimi K2.5 Free | Grátis | Grátis | Grátis | - | | Kimi K2.5 | $0.60 | $3.00 | $0.08 | - | | Kimi K2 Thinking | $0.40 | $2.50 | - | - | | Kimi K2 | $0.40 | $2.50 | - | - | | Qwen3 Coder 480B | $0.45 | $1.50 | - | - | +| Claude Opus 4.6 (≤ 200K tokens) | $5.00 | $25.00 | $0.50 | $6.25 | +| Claude Opus 4.6 (> 200K tokens) | $10.00 | $37.50 | $1.00 | $12.50 | +| Claude Opus 4.5 | $5.00 | $25.00 | $0.50 | $6.25 | +| Claude Opus 4.1 | $15.00 | $75.00 | $1.50 | $18.75 | +| Claude Sonnet 4.6 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 | +| Claude Sonnet 4.6 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 | | Claude Sonnet 4.5 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 | | Claude Sonnet 4.5 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 | | Claude Sonnet 4 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 | | Claude Sonnet 4 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 | | Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 | | Claude Haiku 3.5 | $0.80 | $4.00 | $0.08 | $1.00 | -| Claude Opus 4.6 (≤ 200K tokens) | $5.00 | $25.00 | $0.50 | $6.25 | -| Claude Opus 4.6 (> 200K tokens) | $10.00 | $37.50 | $1.00 | $12.50 | -| Claude Opus 4.5 | $5.00 | $25.00 | $0.50 | $6.25 | -| Claude Opus 4.1 | $15.00 | $75.00 | $1.50 | $18.75 | +| Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | +| Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | +| GPT 5.3 Codex | $1.75 | $14.00 | $0.175 | - | | GPT 5.2 | $1.75 | $14.00 | $0.175 | - | | GPT 5.2 Codex | $1.75 | $14.00 | $0.175 | - | | GPT 5.1 | $1.07 | $8.50 | $0.107 | - | @@ -147,10 +155,8 @@ As taxas de cartão de crédito são repassadas ao custo (4,4% + $0,30 por trans Os modelos gratuitos: -- GLM 4.7 Free está disponível no opencode por tempo limitado. A equipe está usando esse tempo para coletar feedback e melhorar o modelo. -- Kimi K2.5 Free está disponível no opencode por tempo limitado. A equipe está usando esse tempo para coletar feedback e melhorar o modelo. -- MiniMax M2.1 Free está disponível no opencode por tempo limitado. A equipe está usando esse tempo para coletar feedback e melhorar o modelo. -- Big Pickle é um modelo oculto que está gratuito no opencode por tempo limitado. A equipe está usando esse tempo para coletar feedback e melhorar o modelo. +- MiniMax M2.5 Free está disponível no OpenCode por tempo limitado. A equipe está usando esse tempo para coletar feedback e melhorar o modelo. +- Big Pickle é um modelo oculto que está gratuito no OpenCode por tempo limitado. A equipe está usando esse tempo para coletar feedback e melhorar o modelo. Entre em contato conosco se você tiver alguma dúvida. @@ -177,9 +183,7 @@ Por exemplo, digamos que você defina um limite de uso mensal de $20, o Zen não Todos os nossos modelos estão hospedados nos EUA. Nossos provedores seguem uma política de zero retenção e não usam seus dados para treinamento de modelos, com as seguintes exceções: - Big Pickle: Durante seu período gratuito, os dados coletados podem ser usados para melhorar o modelo. -- GLM 4.7 Free: Durante seu período gratuito, os dados coletados podem ser usados para melhorar o modelo. -- Kimi K2.5 Free: Durante seu período gratuito, os dados coletados podem ser usados para melhorar o modelo. -- MiniMax M2.1 Free: Durante seu período gratuito, os dados coletados podem ser usados para melhorar o modelo. +- MiniMax M2.5 Free: Durante seu período gratuito, os dados coletados podem ser usados para melhorar o modelo. - APIs da OpenAI: As solicitações são retidas por 30 dias de acordo com as [Políticas de Dados da OpenAI](https://platform.openai.com/docs/guides/your-data). - APIs da Anthropic: As solicitações são retidas por 30 dias de acordo com as [Políticas de Dados da Anthropic](https://docs.anthropic.com/en/docs/claude-code/data-usage). @@ -233,4 +237,4 @@ Criamos o OpenCode Zen para: 1. **Benchmark** os melhores modelos/provedores para agentes de codificação. 2. Ter acesso às opções de **mais alta qualidade** e não degradar o desempenho ou redirecionar para provedores mais baratos. 3. Repassar quaisquer **reduções de preço** vendendo ao custo; assim, a única margem é para cobrir nossas taxas de processamento. -4. Não ter **vinculação** permitindo que você o use com qualquer outro agente de codificação. E sempre permitir que você use qualquer outro provedor com o opencode também. +4. Não ter **vinculação** permitindo que você o use com qualquer outro agente de codificação. E sempre permitir que você use qualquer outro provedor com o OpenCode também. diff --git a/packages/web/src/content/docs/ru/cli.mdx b/packages/web/src/content/docs/ru/cli.mdx index 1a1003a63a1..5fa85a18fce 100644 --- a/packages/web/src/content/docs/ru/cli.mdx +++ b/packages/web/src/content/docs/ru/cli.mdx @@ -558,6 +558,7 @@ opencode можно настроить с помощью переменных с | `OPENCODE_AUTO_SHARE` | логическое значение | Автоматически делиться сеансами | | `OPENCODE_GIT_BASH_PATH` | строка | Путь к исполняемому файлу Git Bash в Windows | | `OPENCODE_CONFIG` | строка | Путь к файлу конфигурации | +| `OPENCODE_TUI_CONFIG` | строка | Путь к файлу конфигурации TUI | | `OPENCODE_CONFIG_DIR` | строка | Путь к каталогу конфигурации | | `OPENCODE_CONFIG_CONTENT` | строка | Встроенное содержимое конфигурации json | | `OPENCODE_DISABLE_AUTOUPDATE` | логическое значение | Отключить автоматическую проверку обновлений | diff --git a/packages/web/src/content/docs/ru/config.mdx b/packages/web/src/content/docs/ru/config.mdx index 14af31cfd76..5d91dc5e01b 100644 --- a/packages/web/src/content/docs/ru/config.mdx +++ b/packages/web/src/content/docs/ru/config.mdx @@ -97,6 +97,8 @@ opencode поддерживает форматы **JSON** и **JSONC** (JSON с Поместите глобальную конфигурацию opencode в `~/.config/opencode/opencode.json`. Используйте глобальную конфигурацию для общепользовательских настроек, таких как темы, поставщики или привязки клавиш. +Для настроек, специфичных для TUI, используйте `~/.config/opencode/tui.json`. + Глобальная конфигурация переопределяет настройки по умолчанию для удаленной организации. --- @@ -105,6 +107,8 @@ opencode поддерживает форматы **JSON** и **JSONC** (JSON с Добавьте `opencode.json` в корень вашего проекта. Конфигурация проекта имеет наивысший приоритет среди стандартных файлов конфигурации — она переопределяет как глобальные, так и удаленные конфигурации. +Для настроек TUI, специфичных для проекта, добавьте `tui.json` рядом с ним. + :::tip Поместите конфигурацию конкретного проекта в корень вашего проекта. ::: @@ -145,34 +149,32 @@ opencode run "Hello world" Файл конфигурации имеет схему, определенную в [**`opencode.ai/config.json`**](https://opencode.ai/config.json). +Конфигурация TUI использует [**`opencode.ai/tui.json`**](https://opencode.ai/tui.json). + Ваш редактор должен иметь возможность проверять и автозаполнять данные на основе схемы. --- ### TUI -Вы можете настроить параметры TUI с помощью опции `tui`. +Используйте специальный файл `tui.json` (или `tui.jsonc`) для настроек, специфичных для TUI. -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", - "tui": { - "scroll_speed": 3, - "scroll_acceleration": { - "enabled": true - }, - "diff_style": "auto" - } + "$schema": "https://opencode.ai/tui.json", + "scroll_speed": 3, + "scroll_acceleration": { + "enabled": true + }, + "diff_style": "auto" } ``` -Доступные варианты: +Используйте `OPENCODE_TUI_CONFIG`, чтобы указать на пользовательский файл конфигурации TUI. -- `scroll_acceleration.enabled` — включить ускорение прокрутки в стиле MacOS. **Имеет приоритет над `scroll_speed`.** -- `scroll_speed` — пользовательский множитель скорости прокрутки (по умолчанию: `3`, минимум: `1`). Игнорируется, если `scroll_acceleration.enabled` равен `true`. -- `diff_style` — управление рендерингом различий. `"auto"` адаптируется к ширине terminal, `"stacked"` всегда отображает один столбец. +Устаревшие ключи `theme`, `keybinds` и `tui` в `opencode.json` устарели и автоматически переносятся, когда это возможно. -[Подробнее об использовании TUI можно узнать здесь](/docs/tui). +[Подробнее об использовании TUI можно узнать здесь](/docs/tui#configure). --- @@ -298,12 +300,12 @@ Amazon Bedrock поддерживает конфигурацию, специфи ### theme -Вы можете настроить тему, которую хотите использовать, в конфигурации opencode с помощью опции `theme`. +Установите тему пользовательского интерфейса в `tui.json`. -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", - "theme": "" + "$schema": "https://opencode.ai/tui.json", + "theme": "tokyonight" } ``` @@ -403,11 +405,11 @@ Amazon Bedrock поддерживает конфигурацию, специфи ### Сочетания клавиш -Вы можете настроить привязки клавиш с помощью опции `keybinds`. +Настройте привязки клавиш в `tui.json`. -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "keybinds": {} } ``` @@ -487,13 +489,15 @@ opencode автоматически загрузит все новые обно "$schema": "https://opencode.ai/config.json", "compaction": { "auto": true, - "prune": true + "prune": true, + "reserved": 10000 } } ``` - `auto` — автоматически сжимать сеанс при заполнении контекста (по умолчанию: `true`). - `prune` — удалить старые выходные данные инструмента для сохранения токенов (по умолчанию: `true`). +- `reserved` — Буфер токенов для сжатия. Оставляет достаточное окно, чтобы избежать переполнения во время сжатия. --- diff --git a/packages/web/src/content/docs/ru/ecosystem.mdx b/packages/web/src/content/docs/ru/ecosystem.mdx index a278043611a..8b2d3668568 100644 --- a/packages/web/src/content/docs/ru/ecosystem.mdx +++ b/packages/web/src/content/docs/ru/ecosystem.mdx @@ -1,12 +1,12 @@ --- title: Экосистема -description: Проекты и интеграции, созданные с помощью opencode. +description: Проекты и интеграции, созданные с помощью OpenCode. --- -Коллекция проектов сообщества, построенных на opencode. +Коллекция проектов сообщества, построенных на OpenCode. :::note -Хотите добавить свой проект, связанный с opencode, в этот список? Разместите PR. +Хотите добавить свой проект, связанный с OpenCode, в этот список? Разместите PR. ::: Вы также можете посетить [awesome-opencode](https://github.com/awesome-opencode/awesome-opencode) и [opencode.cafe](https://opencode.cafe) — хаб, объединяющий экосистему и сообщество. @@ -15,61 +15,63 @@ description: Проекты и интеграции, созданные с по ## Плагины -| Имя | Описание | -| --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | -| [opencode-daytona](https://github.com/jamesmurdza/daytona/blob/main/guides/typescript/opencode/README.md) | Автоматически запускайте сеансы opencode в изолированных песочницах Daytona с синхронизацией git и предварительным просмотром в реальном времени. | -| [opencode-helicone-session](https://github.com/H2Shami/opencode-helicone-session) | Автоматически внедрять заголовки сеансов Helicone для группировки запросов. | -| [opencode-type-inject](https://github.com/nick-vi/opencode-type-inject) | Автоматическое внедрение типов TypeScript/Svelte в файлы, считываемые с помощью инструментов поиска. | -| [opencode-openai-codex-auth](https://github.com/numman-ali/opencode-openai-codex-auth) | Используйте подписку ChatGPT Plus/Pro вместо кредитов API. | -| [opencode-gemini-auth](https://github.com/jenslys/opencode-gemini-auth) | Используйте существующий план Gemini вместо выставления счетов через API. | -| [opencode-antigravity-auth](https://github.com/NoeFabris/opencode-antigravity-auth) | Используйте бесплатные модели Antigravity вместо выставления счетов через API. | -| [opencode-devcontainers](https://github.com/athal7/opencode-devcontainers) | Многоветвевая изоляция контейнеров разработки с мелкими клонами и автоматическим назначением портов. | -| [opencode-google-antigravity-auth](https://github.com/shekohex/opencode-google-antigravity-auth) | Плагин Google Antigravity OAuth с поддержкой поиска Google и более надежной обработкой API. | -| [opencode-dynamic-context-pruning](https://github.com/Tarquinen/opencode-dynamic-context-pruning) | Оптимизируйте использование токенов за счет сокращения выходных данных устаревших инструментов. | -| [opencode-websearch-cited](https://github.com/ghoulr/opencode-websearch-cited.git) | Добавьте встроенную поддержку веб-поиска для поддерживаемых поставщиков в стиле Google. | -| [opencode-pty](https://github.com/shekohex/opencode-pty.git) | Позволяет агентам ИИ запускать фоновые процессы в PTY и отправлять им интерактивные данные. | -| [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Инструкции для неинтерактивных shell-команд — предотвращают зависания из-за операций, зависящих от TTY. | -| [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Отслеживайте использование opencode с помощью Wakatime | -| [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Очистка таблиц Markdown, созданных LLM | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | Редактирование кода в 10 раз быстрее с помощью API Morph Fast Apply и маркеров отложенного редактирования. | -| [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Фоновые агенты, встроенные инструменты LSP/AST/MCP, курируемые агенты, совместимость с Claude Code | -| [opencode-notificator](https://github.com/panta82/opencode-notificator) | Уведомления на рабочем столе и звуковые оповещения для сеансов opencode | -| [opencode-notifier](https://github.com/mohak34/opencode-notifier) | Уведомления на рабочем столе и звуковые оповещения о разрешениях, завершении и событиях ошибок. | -| [opencode-zellij-namer](https://github.com/24601/opencode-zellij-namer) | Автоматическое именование сеансов Zellij на основе искусственного интеллекта на основе контекста opencode. | -| [opencode-skillful](https://github.com/zenobi-us/opencode-skillful) | Разрешить агентам opencode отложенную загрузку подсказок по требованию с обнаружением и внедрением навыков. | -| [opencode-supermemory](https://github.com/supermemoryai/opencode-supermemory) | Постоянная память между сеансами с использованием Supermemory | -| [@plannotator/opencode](https://github.com/backnotprop/plannotator/tree/main/apps/opencode-plugin) | Интерактивный обзор плана с визуальными аннотациями и возможностью совместного использования в частном или автономном режиме. | -| [@openspoon/subtask2](https://github.com/spoons-and-mirrors/subtask2) | Расширьте opencode/команды до мощной системы оркестровки с детальным управлением потоком данных. | -| [opencode-scheduler](https://github.com/different-ai/opencode-scheduler) | Планируйте повторяющиеся задания с помощью launchd (Mac) или systemd (Linux) с синтаксисом cron. | -| [micode](https://github.com/vtemian/micode) | Структурированный мозговой штурм → План → Реализация рабочего процесса с непрерывностью сеанса | -| [octto](https://github.com/vtemian/octto) | Интерактивный пользовательский интерфейс браузера для мозгового штурма с помощью искусственного интеллекта с формами из нескольких вопросов | -| [opencode-background-agents](https://github.com/kdcokenny/opencode-background-agents) | Фоновые агенты в стиле Claude Code с асинхронным делегированием и сохранением контекста. | -| [opencode-notify](https://github.com/kdcokenny/opencode-notify) | Встроенные уведомления ОС для opencode — узнайте, когда задачи завершены | -| [opencode-workspace](https://github.com/kdcokenny/opencode-workspace) | Комплексный пакет многоагентной оркестровки — 16 компонентов, одна установка | -| [opencode-worktree](https://github.com/kdcokenny/opencode-worktree) | Рабочие деревья git с нулевым трением для opencode | +| Имя | Описание | +| -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | +| [opencode-daytona](https://github.com/daytonaio/daytona/tree/main/libs/opencode-plugin) | Автоматически запускайте сеансы OpenCode в изолированных песочницах Daytona с синхронизацией git и предварительным просмотром в реальном времени | +| [opencode-helicone-session](https://github.com/H2Shami/opencode-helicone-session) | Автоматически внедрять заголовки сеансов Helicone для группировки запросов | +| [opencode-type-inject](https://github.com/nick-vi/opencode-type-inject) | Автоматическое внедрение типов TypeScript/Svelte в файлы, считываемые с помощью инструментов поиска | +| [opencode-openai-codex-auth](https://github.com/numman-ali/opencode-openai-codex-auth) | Используйте подписку ChatGPT Plus/Pro вместо кредитов API | +| [opencode-gemini-auth](https://github.com/jenslys/opencode-gemini-auth) | Используйте существующий план Gemini вместо выставления счетов через API | +| [opencode-antigravity-auth](https://github.com/NoeFabris/opencode-antigravity-auth) | Используйте бесплатные модели Antigravity вместо выставления счетов через API | +| [opencode-devcontainers](https://github.com/athal7/opencode-devcontainers) | Многоветвевая изоляция контейнеров разработки с мелкими клонами и автоматическим назначением портов | +| [opencode-google-antigravity-auth](https://github.com/shekohex/opencode-google-antigravity-auth) | Плагин Google Antigravity OAuth с поддержкой поиска Google и более надежной обработкой API | +| [opencode-dynamic-context-pruning](https://github.com/Tarquinen/opencode-dynamic-context-pruning) | Оптимизируйте использование токенов за счет сокращения выходных данных устаревших инструментов | +| [opencode-vibeguard](https://github.com/inkdust2021/opencode-vibeguard) | Скрывайте секреты/PII, заменяя их плейсхолдерами в стиле VibeGuard перед отправкой в LLM; восстанавливайте локально | +| [opencode-websearch-cited](https://github.com/ghoulr/opencode-websearch-cited.git) | Добавьте встроенную поддержку веб-поиска для поддерживаемых поставщиков в стиле Google | +| [opencode-pty](https://github.com/shekohex/opencode-pty.git) | Позволяет агентам ИИ запускать фоновые процессы в PTY и отправлять им интерактивные данные | +| [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Инструкции для неинтерактивных shell-команд — предотвращают зависания из-за операций, зависящих от TTY | +| [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Отслеживайте использование OpenCode с помощью Wakatime | +| [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Очистка таблиц Markdown, созданных LLM | +| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | Редактирование кода в 10 раз быстрее с помощью API Morph Fast Apply и маркеров отложенного редактирования | +| [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Фоновые агенты, встроенные инструменты LSP/AST/MCP, курируемые агенты, совместимость с Claude Code | +| [opencode-notificator](https://github.com/panta82/opencode-notificator) | Уведомления на рабочем столе и звуковые оповещения для сеансов OpenCode | +| [opencode-notifier](https://github.com/mohak34/opencode-notifier) | Уведомления на рабочем столе и звуковые оповещения о разрешениях, завершении и событиях ошибок | +| [opencode-zellij-namer](https://github.com/24601/opencode-zellij-namer) | Автоматическое именование сеансов Zellij на основе искусственного интеллекта на основе контекста OpenCode | +| [opencode-skillful](https://github.com/zenobi-us/opencode-skillful) | Разрешить агентам OpenCode отложенную загрузку подсказок по требованию с обнаружением и внедрением навыков | +| [opencode-supermemory](https://github.com/supermemoryai/opencode-supermemory) | Постоянная память между сеансами с использованием Supermemory | +| [@plannotator/opencode](https://github.com/backnotprop/plannotator/tree/main/apps/opencode-plugin) | Интерактивный обзор плана с визуальными аннотациями и возможностью совместного использования в частном или автономном режиме | +| [@openspoon/subtask2](https://github.com/spoons-and-mirrors/subtask2) | Расширьте opencode/команды до мощной системы оркестровки с детальным управлением потоком данных | +| [opencode-scheduler](https://github.com/different-ai/opencode-scheduler) | Планируйте повторяющиеся задания с помощью launchd (Mac) или systemd (Linux) с синтаксисом cron | +| [micode](https://github.com/vtemian/micode) | Структурированный мозговой штурм → План → Реализация рабочего процесса с непрерывностью сеанса | +| [octto](https://github.com/vtemian/octto) | Интерактивный пользовательский интерфейс браузера для мозгового штурма с помощью искусственного интеллекта с формами из нескольких вопросов | +| [opencode-background-agents](https://github.com/kdcokenny/opencode-background-agents) | Фоновые агенты в стиле Claude Code с асинхронным делегированием и сохранением контекста | +| [opencode-notify](https://github.com/kdcokenny/opencode-notify) | Встроенные уведомления ОС для OpenCode – узнайте, когда задачи завершены | +| [opencode-workspace](https://github.com/kdcokenny/opencode-workspace) | Комплексный пакет многоагентной оркестровки — 16 компонентов, одна установка | +| [opencode-worktree](https://github.com/kdcokenny/opencode-worktree) | Рабочие деревья git с нулевым трением для OpenCode | --- ## Проекты -| Имя | Описание | -| ------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------- | -| [opencode.nvim](https://github.com/NickvanDyke/opencode.nvim) | Плагин Neovim для подсказок с поддержкой редактора, созданный на основе API | -| [portal](https://github.com/hosenur/portal) | Мобильный веб-интерфейс для opencode через Tailscale/VPN | -| [opencode plugin template](https://github.com/zenobi-us/opencode-plugin-template/) | Шаблон для создания плагинов opencode | -| [opencode.nvim](https://github.com/sudo-tee/opencode.nvim) | Интерфейс Neovim для opencode — агент кодирования искусственного интеллекта на базе terminal | -| [ai-sdk-provider-opencode-sdk](https://github.com/ben-vargas/ai-sdk-provider-opencode-sdk) | Поставщик Vercel AI SDK для использования opencode через @opencode-ai/sdk | -| [OpenChamber](https://github.com/btriapitsyn/openchamber) | Веб-приложение или настольное приложение и расширение VS Code для opencode | -| [OpenCode-Obsidian](https://github.com/mtymek/opencode-obsidian) | Плагин Obsidian, встраивающий opencode в пользовательский интерфейс Obsidian. | -| [OpenWork](https://github.com/different-ai/openwork) | Альтернатива Claude Cowork с открытым исходным кодом на базе opencode. | -| [ocx](https://github.com/kdcokenny/ocx) | Менеджер расширений opencode с переносимыми изолированными профилями. | -| [CodeNomad](https://github.com/NeuralNomadsAI/CodeNomad) | Настольное, веб-, мобильное и удаленное клиентское приложение для opencode | +| Имя | Описание | +| ------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------- | +| [kimaki](https://github.com/remorses/kimaki) | Discord-бот для управления сеансами OpenCode, созданный на базе SDK | +| [opencode.nvim](https://github.com/NickvanDyke/opencode.nvim) | Плагин Neovim для подсказок с поддержкой редактора, созданный на основе API | +| [portal](https://github.com/hosenur/portal) | Мобильный веб-интерфейс для OpenCode через Tailscale/VPN | +| [opencode plugin template](https://github.com/zenobi-us/opencode-plugin-template/) | Шаблон для создания плагинов OpenCode | +| [opencode.nvim](https://github.com/sudo-tee/opencode.nvim) | Интерфейс Neovim для OpenCode - агент кодирования искусственного интеллекта на базе терминала | +| [ai-sdk-provider-opencode-sdk](https://github.com/ben-vargas/ai-sdk-provider-opencode-sdk) | Поставщик Vercel AI SDK для использования OpenCode через @opencode-ai/sdk | +| [OpenChamber](https://github.com/btriapitsyn/openchamber) | Веб-приложение или настольное приложение и расширение VS Code для OpenCode | +| [OpenCode-Obsidian](https://github.com/mtymek/opencode-obsidian) | Плагин Obsidian, встраивающий OpenCode в пользовательский интерфейс Obsidian | +| [OpenWork](https://github.com/different-ai/openwork) | Альтернатива Claude Cowork с открытым исходным кодом на базе OpenCode | +| [ocx](https://github.com/kdcokenny/ocx) | Менеджер расширений OpenCode с переносимыми изолированными профилями | +| [CodeNomad](https://github.com/NeuralNomadsAI/CodeNomad) | Настольное, веб-, мобильное и удаленное клиентское приложение для OpenCode | --- ## Агенты -| Имя | Описание | -| ----------------------------------------------------------------- | -------------------------------------------------------------------------- | -| [Agentic](https://github.com/Cluster444/agentic) | Модульные ИИ-агенты и команды для структурированной разработки | -| [opencode-agents](https://github.com/darrenhinde/opencode-agents) | Конфигурации, подсказки, агенты и плагины для улучшения рабочих процессов. | +| Имя | Описание | +| ----------------------------------------------------------------- | ------------------------------------------------------------------------- | +| [Agentic](https://github.com/Cluster444/agentic) | Модульные ИИ-агенты и команды для структурированной разработки | +| [opencode-agents](https://github.com/darrenhinde/opencode-agents) | Конфигурации, подсказки, агенты и плагины для улучшения рабочих процессов | diff --git a/packages/web/src/content/docs/ru/go.mdx b/packages/web/src/content/docs/ru/go.mdx new file mode 100644 index 00000000000..17d1881f66c --- /dev/null +++ b/packages/web/src/content/docs/ru/go.mdx @@ -0,0 +1,145 @@ +--- +title: Go +description: Недорогая подписка на открытые модели для кодинга. +--- + +import config from "../../../../config.mjs" +export const console = config.console +export const email = `mailto:${config.email}` + +OpenCode Go — это недорогая подписка за **$10/месяц**, которая предоставляет надежный доступ к популярным открытым моделям для кодинга. + +:::note +OpenCode Go в настоящее время находится в бета-версии. +::: + +Go работает как любой другой провайдер в OpenCode. Вы подписываетесь на OpenCode Go и получаете свой API ключ. Это **полностью опционально**, и вам не нужно использовать его, чтобы пользоваться OpenCode. + +Он разработан в первую очередь для международных пользователей, с моделями, размещенными в США, ЕС и Сингапуре для стабильного глобального доступа. + +--- + +## Предыстория + +Открытые модели стали действительно хорошими. Теперь они достигают производительности, близкой к проприетарным моделям для задач кодинга. И поскольку многие провайдеры могут обслуживать их на конкурентной основе, они обычно намного дешевле. + +Однако получение надежного доступа к ним с низкой задержкой может быть сложным. Качество и доступность провайдеров варьируются. + +:::tip +Мы протестировали избранную группу моделей и провайдеров, которые хорошо работают с OpenCode. +::: + +Чтобы исправить это, мы сделали пару вещей: + +1. Мы протестировали избранную группу открытых моделей и поговорили с их командами о том, как лучше всего их запускать. +2. Затем мы работали с несколькими провайдерами, чтобы убедиться, что они обслуживаются правильно. +3. Наконец, мы провели бенчмаркинг комбинации модели/провайдера и составили список, который мы можем смело рекомендовать. + +OpenCode Go дает вам доступ к этим моделям за **$10/месяц**. + +--- + +## Как это работает + +OpenCode Go работает как любой другой провайдер в OpenCode. + +1. Вы входите в **OpenCode Zen**, подписываетесь на Go и копируете свой API ключ. +2. Вы запускаете команду `/connect` в TUI, выбираете `OpenCode Go` и вставляете свой API ключ. +3. Запустите `/models` в TUI, чтобы увидеть список моделей, доступных через Go. + +:::note +Только один участник рабочей области может подписаться на OpenCode Go. +::: + +Текущий список моделей включает: + +- **GLM-5** +- **Kimi K2.5** +- **MiniMax M2.5** + +Список моделей может меняться по мере того, как мы тестируем и добавляем новые. + +--- + +## Лимиты использования + +OpenCode Go включает следующие лимиты: + +- **5-часовой лимит** — $12 использования +- **Недельный лимит** — $30 использования +- **Месячный лимит** — $60 использования + +Лимиты определены в денежном выражении. Это означает, что ваше фактическое количество запросов зависит от модели, которую вы используете. Более дешевые модели, такие как MiniMax M2.5, позволяют делать больше запросов, в то время как более дорогие модели, такие как GLM-5, позволяют меньше. + +Таблица ниже предоставляет примерное количество запросов на основе типичных паттернов использования Go: + +| | GLM-5 | Kimi K2.5 | MiniMax M2.5 | +| ------------------- | ----- | --------- | ------------ | +| запросов за 5 часов | 1,150 | 1,850 | 30,000 | +| запросов в неделю | 2,880 | 4,630 | 75,000 | +| запросов в месяц | 5,750 | 9,250 | 150,000 | + +Оценки основаны на наблюдаемых средних паттернах запросов: + +- GLM-5 — 700 входных, 52,000 кэшированных, 150 выходных токенов на запрос +- Kimi K2.5 — 870 входных, 55,000 кэшированных, 200 выходных токенов на запрос +- MiniMax M2.5 — 300 входных, 55,000 кэшированных, 125 выходных токенов на запрос + +Вы можете отслеживать свое текущее использование в **консоли**. + +:::tip +Если вы достигнете лимита использования, вы можете продолжить использовать бесплатные модели. +::: + +Лимиты использования могут меняться по мере того, как мы учимся на раннем использовании и отзывах. + +--- + +### Ценообразование + +OpenCode Go — это план подписки за **$10/месяц**. Ниже приведены цены **за 1 млн токенов**. + +| Модель | Ввод | Вывод | Кэшированное чтение | +| ------------ | ----- | ----- | ------------------- | +| GLM-5 | $1.00 | $3.20 | $0.20 | +| Kimi K2.5 | $0.60 | $3.00 | $0.10 | +| MiniMax M2.5 | $0.30 | $1.20 | $0.03 | + +--- + +### Использование сверх лимитов + +Если у вас также есть кредиты на балансе Zen, вы можете включить опцию **Use balance** (Использовать баланс) в консоли. Когда она включена, Go переключится на ваш баланс Zen после того, как вы исчерпаете свои лимиты использования, вместо блокировки запросов. + +--- + +## Эндпоинты + +Вы также можете получить доступ к моделям Go через следующие API эндпоинты. + +| Модель | ID модели | Эндпоинт | Пакет AI SDK | +| ------------ | ------------ | ------------------------------------------------ | --------------------------- | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | + +[Model id](/docs/config/#models) в вашей конфигурации OpenCode использует формат `opencode-go/`. Например, для Kimi K2.5 вы бы использовали `opencode-go/kimi-k2.5` в вашей конфигурации. + +--- + +## Конфиденциальность + +План разработан в первую очередь для международных пользователей, с моделями, размещенными в США, ЕС и Сингапуре для стабильного глобального доступа. + +Свяжитесь с нами, если у вас есть вопросы. + +--- + +## Цели + +Мы создали OpenCode Go, чтобы: + +1. Сделать ИИ-кодинг **доступным** большему количеству людей с недорогой подпиской. +2. Обеспечить **надежный** доступ к лучшим открытым моделям для кодинга. +3. Отобрать модели, которые **протестированы и проверены** для использования агентами кодинга. +4. Не иметь **привязки к поставщику** (no lock-in), позволяя вам использовать любого другого провайдера с OpenCode. diff --git a/packages/web/src/content/docs/ru/keybinds.mdx b/packages/web/src/content/docs/ru/keybinds.mdx index 67d191ea2ca..8a9a14ca1ae 100644 --- a/packages/web/src/content/docs/ru/keybinds.mdx +++ b/packages/web/src/content/docs/ru/keybinds.mdx @@ -3,11 +3,11 @@ title: Сочетания клавиш description: Настройте свои сочетания клавиш. --- -opencode имеет список сочетаний клавиш, которые вы можете настроить через конфигурацию opencode. +opencode имеет список сочетаний клавиш, которые вы можете настроить через `tui.json`. -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "keybinds": { "leader": "ctrl+x", "app_exit": "ctrl+c,ctrl+d,q", @@ -117,11 +117,11 @@ opencode использует клавишу `leader` для большинст ## Отключение привязки клавиш -Вы можете отключить привязку клавиш, добавив ключ в свою конфигурацию со значением «none». +Вы можете отключить привязку клавиш, добавив ключ в `tui.json` со значением «none». -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "keybinds": { "session_compact": "none" } diff --git a/packages/web/src/content/docs/ru/providers.mdx b/packages/web/src/content/docs/ru/providers.mdx index 5984c89f43b..c36dfd9f78c 100644 --- a/packages/web/src/content/docs/ru/providers.mdx +++ b/packages/web/src/content/docs/ru/providers.mdx @@ -57,7 +57,39 @@ OpenCode Zen — это список моделей, предоставленн Если вы новичок, мы рекомендуем начать с OpenCode Zen. ::: -1. Запустите команду `/connect` в TUI, выберите opencode и перейдите по адресу [opencode.ai/auth](https://opencode.ai/auth). +1. Запустите команду `/connect` в TUI, выберите `OpenCode Zen` и перейдите по адресу [opencode.ai/auth](https://opencode.ai/zen). + + ```txt + /connect + ``` + +2. Войдите в систему, добавьте свои платежные данные и скопируйте ключ API. + +3. Вставьте свой ключ API. + + ```txt + ┌ API key + │ + │ + └ enter + ``` + +4. Запустите `/models` в TUI, чтобы просмотреть список рекомендуемых нами моделей. + + ```txt + /models + ``` + +Он работает как любой другой поставщик в opencode и его использование совершенно необязательно. + +--- + +## OpenCode Go + +OpenCode Go — это недорогой план подписки, обеспечивающий надежный доступ к популярным открытым моделям кодирования, предоставляемым командой opencode, которые были +протестированы и проверены на хорошую работу с opencode. + +1. Запустите команду `/connect` в TUI, выберите `OpenCode Go` и перейдите по адресу [opencode.ai/auth](https://opencode.ai/zen). ```txt /connect @@ -1479,6 +1511,39 @@ SAP AI Core предоставляет доступ к более чем 40 мо --- +### STACKIT + +STACKIT AI Model Serving предоставляет полностью управляемую суверенную среду хостинга для моделей ИИ, ориентированную на LLM, таких как Llama, Mistral и Qwen, с максимальным суверенитетом данных в европейской инфраструктуре. + +1. Перейдите на [портал STACKIT](https://portal.stackit.cloud), перейдите в **AI Model Serving** и создайте токен аутентификации для своего проекта. + + :::tip + Вам необходима учетная запись клиента STACKIT, учетная запись пользователя и проект перед созданием токенов аутентификации. + ::: + +2. Запустите команду `/connect` и найдите **STACKIT**. + + ```txt + /connect + ``` + +3. Введите свой токен аутентификации STACKIT AI Model Serving. + + ```txt + ┌ API key + │ + │ + └ enter + ``` + +4. Запустите команду `/models`, чтобы выбрать одну из доступных моделей, например _Qwen3-VL 235B_ или _Llama 3.3 70B_. + + ```txt + /models + ``` + +--- + ### OVHcloud AI Endpoints 1. Перейдите к [OVHcloud Panel](https://ovh.com/manager). Перейдите в раздел `Public Cloud`, `AI & Machine Learning` > `AI Endpoints` и на вкладке `API Keys` нажмите **Создать новый ключ API**. diff --git a/packages/web/src/content/docs/ru/sdk.mdx b/packages/web/src/content/docs/ru/sdk.mdx index 1269d9fc083..0afdea1b6ff 100644 --- a/packages/web/src/content/docs/ru/sdk.mdx +++ b/packages/web/src/content/docs/ru/sdk.mdx @@ -117,6 +117,78 @@ try { --- +## Структурированный вывод + +Вы можете запросить структурированный вывод JSON от модели, указав `format` со схемой JSON. Модель будет использовать инструмент `StructuredOutput` для возврата проверенного JSON, соответствующего вашей схеме. + +### Основное использование + +```typescript +const result = await client.session.prompt({ + path: { id: sessionId }, + body: { + parts: [{ type: "text", text: "Research Anthropic and provide company info" }], + format: { + type: "json_schema", + schema: { + type: "object", + properties: { + company: { type: "string", description: "Company name" }, + founded: { type: "number", description: "Year founded" }, + products: { + type: "array", + items: { type: "string" }, + description: "Main products", + }, + }, + required: ["company", "founded"], + }, + }, + }, +}) + +// Access the structured output +console.log(result.data.info.structured_output) +// { company: "Anthropic", founded: 2021, products: ["Claude", "Claude API"] } +``` + +### Типы форматов вывода + +| Тип | Описание | +| ------------- | ------------------------------------------------------------------------- | +| `text` | По умолчанию. Стандартный текстовый ответ (без структурированного вывода) | +| `json_schema` | Возвращает проверенный JSON, соответствующий предоставленной схеме | + +### Формат схемы JSON + +При использовании `type: 'json_schema'`, укажите: + +| Поле | Тип | Описание | +| ------------ | --------------- | ---------------------------------------------------------------------- | +| `type` | `'json_schema'` | Обязательно. Указывает режим схемы JSON | +| `schema` | `object` | Обязательно. Объект JSON Schema, определяющий структуру вывода | +| `retryCount` | `number` | Необязательно. Количество повторных попыток проверки (по умолчанию: 2) | + +### Обработка ошибок + +Если модель не может выдать действительный структурированный вывод после всех повторных попыток, ответ будет включать `StructuredOutputError`: + +```typescript +if (result.data.info.error?.name === "StructuredOutputError") { + console.error("Failed to produce structured output:", result.data.info.error.message) + console.error("Attempts:", result.data.info.error.retries) +} +``` + +### Лучшие практики + +1. **Предоставляйте четкие описания** в свойствах вашей схемы, чтобы помочь модели понять, какие данные извлекать +2. **Используйте `required`**, чтобы указать, какие поля должны присутствовать +3. **Делайте схемы сфокусированными** — сложные вложенные схемы могут быть труднее для правильного заполнения моделью +4. **Устанавливайте соответствующий `retryCount`** — увеличивайте для сложных схем, уменьшайте для простых + +--- + ## API SDK предоставляет все серверные API через типобезопасный клиент. @@ -226,27 +298,27 @@ const { providers, default: defaults } = await client.config.providers() ### Сессии -| Метод | Описание | Примечания | -| ---------------------------------------------------------- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | -| `session.list()` | List sessions | Returns Session[] | -| `session.get({ path })` | Get session | Returns Session | -| `session.children({ path })` | List child sessions | Returns Session[] | -| `session.create({ body })` | Create session | Returns Session | -| `session.delete({ path })` | Delete session | Returns `boolean` | -| `session.update({ path, body })` | Update session properties | Returns Session | -| `session.init({ path, body })` | Analyze app and create `AGENTS.md` | Returns `boolean` | -| `session.abort({ path })` | Abort a running session | Returns `boolean` | -| `session.share({ path })` | Share session | Returns Session | -| `session.unshare({ path })` | Unshare session | Returns Session | -| `session.summarize({ path, body })` | Summarize session | Returns `boolean` | -| `session.messages({ path })` | List messages in a session | Returns `{ info: `Message`, parts: `Part[]`}[]` | -| `session.message({ path })` | Get message details | Returns `{ info: `Message`, parts: `Part[]`}` | -| `session.prompt({ path, body })` | Send prompt message | `body.noReply: true` returns UserMessage (context only). Default returns AssistantMessage with AI response | -| `session.command({ path, body })` | Send command to session | Returns `{ info: `AssistantMessage`, parts: `Part[]`}` | -| `session.shell({ path, body })` | Run a shell command | Returns AssistantMessage | -| `session.revert({ path, body })` | Revert a message | Returns Session | -| `session.unrevert({ path })` | Restore reverted messages | Returns Session | -| `postSessionByIdPermissionsByPermissionId({ path, body })` | Respond to a permission request | Returns `boolean` | +| Метод | Описание | Примечания | +| ---------------------------------------------------------- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `session.list()` | List sessions | Returns Session[] | +| `session.get({ path })` | Get session | Returns Session | +| `session.children({ path })` | List child sessions | Returns Session[] | +| `session.create({ body })` | Create session | Returns Session | +| `session.delete({ path })` | Delete session | Returns `boolean` | +| `session.update({ path, body })` | Update session properties | Returns Session | +| `session.init({ path, body })` | Analyze app and create `AGENTS.md` | Returns `boolean` | +| `session.abort({ path })` | Abort a running session | Returns `boolean` | +| `session.share({ path })` | Share session | Returns Session | +| `session.unshare({ path })` | Unshare session | Returns Session | +| `session.summarize({ path, body })` | Summarize session | Returns `boolean` | +| `session.messages({ path })` | List messages in a session | Returns `{ info: `Message`, parts: `Part[]`}[]` | +| `session.message({ path })` | Get message details | Returns `{ info: `Message`, parts: `Part[]`}` | +| `session.prompt({ path, body })` | Send prompt message | `body.noReply: true` возвращает UserMessage (только контекст). По умолчанию возвращает AssistantMessage с ответом ИИ. Поддерживает `body.outputFormat` для [структурированного вывода](#структурированный-вывод) | +| `session.command({ path, body })` | Send command to session | Returns `{ info: `AssistantMessage`, parts: `Part[]`}` | +| `session.shell({ path, body })` | Run a shell command | Returns AssistantMessage | +| `session.revert({ path, body })` | Revert a message | Returns Session | +| `session.unrevert({ path })` | Restore reverted messages | Returns Session | +| `postSessionByIdPermissionsByPermissionId({ path, body })` | Respond to a permission request | Returns `boolean` | --- diff --git a/packages/web/src/content/docs/ru/server.mdx b/packages/web/src/content/docs/ru/server.mdx index 898789c29ac..2356543dff2 100644 --- a/packages/web/src/content/docs/ru/server.mdx +++ b/packages/web/src/content/docs/ru/server.mdx @@ -18,13 +18,13 @@ opencode serve [--port ] [--hostname ] [--cors ] #### Параметры -| Флаг | Описание | По умолчанию | -| --------------- | ----------------------------------- | ---------------- | -| `--port` | Port to listen on | `4096` | -| `--hostname` | Hostname to listen on | `127.0.0.1` | -| `--mdns` | Enable mDNS discovery | `false` | -| `--mdns-domain` | Custom domain name for mDNS service | `opencode.local` | -| `--cors` | Additional browser origins to allow | `[]` | +| Флаг | Описание | По умолчанию | +| --------------- | ------------------------------------------- | ---------------- | +| `--port` | Порт для прослушивания | `4096` | +| `--hostname` | Имя хоста для прослушивания | `127.0.0.1` | +| `--mdns` | Включить обнаружение mDNS | `false` | +| `--mdns-domain` | Пользовательское доменное имя для mDNS | `opencode.local` | +| `--cors` | Разрешенные дополнительные источники (CORS) | `[]` | `--cors` можно передать несколько раз: @@ -89,10 +89,10 @@ For example, `http://localhost:4096/doc`. Use the spec to generate clients or in ### Глобальный -| Метод | Путь | Описание | Ответ | -| ----- | ---------------- | ------------------------------ | ------------------------------------ | -| `GET` | `/global/health` | Get server health and version | `{ healthy: true, version: string }` | -| `GET` | `/global/event` | Get global events (SSE stream) | Event stream | +| Метод | Путь | Описание | Ответ | +| ----- | ---------------- | --------------------------------------- | ------------------------------------ | +| `GET` | `/global/health` | Получить состояние и версию сервера | `{ healthy: true, version: string }` | +| `GET` | `/global/event` | Получить глобальные события (поток SSE) | Поток событий | --- @@ -100,105 +100,105 @@ For example, `http://localhost:4096/doc`. Use the spec to generate clients or in | Метод | Путь | Описание | Ответ | | ----- | ------------------ | ----------------------- | --------------------------------------------- | -| `GET` | `/project` | List all projects | Project[] | -| `GET` | `/project/current` | Get the current project | Project | +| `GET` | `/project` | Список всех проектов | Project[] | +| `GET` | `/project/current` | Получить текущий проект | Project | --- ### Путь и система контроля версий -| Метод | Путь | Описание | Ответ | -| ----- | ------- | ------------------------------------ | ------------------------------------------- | -| `GET` | `/path` | Get the current path | Path | -| `GET` | `/vcs` | Get VCS info for the current project | VcsInfo | +| Метод | Путь | Описание | Ответ | +| ----- | ------- | ---------------------------------------------- | ------------------------------------------- | +| `GET` | `/path` | Получить текущий путь | Path | +| `GET` | `/vcs` | Получить информацию о VCS для текущего проекта | VcsInfo | --- ### Экземпляр -| Метод | Путь | Описание | Ответ | -| ------ | ------------------- | ---------------------------- | --------- | -| `POST` | `/instance/dispose` | Dispose the current instance | `boolean` | +| Метод | Путь | Описание | Ответ | +| ------ | ------------------- | ------------------------- | --------- | +| `POST` | `/instance/dispose` | Удалить текущий экземпляр | `boolean` | --- ### Конфигурация -| Метод | Путь | Описание | Ответ | -| ------- | ------------------- | --------------------------------- | ---------------------------------------------------------------------------------------- | -| `GET` | `/config` | Get config info | Config | -| `PATCH` | `/config` | Update config | Config | -| `GET` | `/config/providers` | List providers and default models | `{ providers: `Provider[]`, default: { [key: string]: string } }` | +| Метод | Путь | Описание | Ответ | +| ------- | ------------------- | ----------------------------------------- | ---------------------------------------------------------------------------------------- | +| `GET` | `/config` | Получить информацию о конфигурации | Config | +| `PATCH` | `/config` | Обновить конфигурацию | Config | +| `GET` | `/config/providers` | Список провайдеров и моделей по умолчанию | `{ providers: `Provider[]`, default: { [key: string]: string } }` | --- ### Поставщик -| Метод | Путь | Описание | Ответ | -| ------ | -------------------------------- | ------------------------------------ | ----------------------------------------------------------------------------------- | -| `GET` | `/provider` | List all providers | `{ all: `Provider[]`, default: {...}, connected: string[] }` | -| `GET` | `/provider/auth` | Get provider authentication methods | `{ [providerID: string]: `ProviderAuthMethod[]` }` | -| `POST` | `/provider/{id}/oauth/authorize` | Authorize a provider using OAuth | ProviderAuthAuthorization | -| `POST` | `/provider/{id}/oauth/callback` | Handle OAuth callback for a provider | `boolean` | +| Метод | Путь | Описание | Ответ | +| ------ | -------------------------------- | ----------------------------------------- | ----------------------------------------------------------------------------------- | +| `GET` | `/provider` | Список всех провайдеров | `{ all: `Provider[]`, default: {...}, connected: string[] }` | +| `GET` | `/provider/auth` | Получить методы аутентификации провайдера | `{ [providerID: string]: `ProviderAuthMethod[]` }` | +| `POST` | `/provider/{id}/oauth/authorize` | Авторизация провайдера через OAuth | ProviderAuthAuthorization | +| `POST` | `/provider/{id}/oauth/callback` | Обработка callback OAuth для провайдера | `boolean` | --- ### Сессии -| Метод | Путь | Описание | Примечания | -| -------- | ---------------------------------------- | ------------------------------------- | ---------------------------------------------------------------------------------- | -| `GET` | `/session` | List all sessions | Returns Session[] | -| `POST` | `/session` | Create a new session | body: `{ parentID?, title? }`, returns Session | -| `GET` | `/session/status` | Get session status for all sessions | Returns `{ [sessionID: string]: `SessionStatus` }` | -| `GET` | `/session/:id` | Get session details | Returns Session | -| `DELETE` | `/session/:id` | Delete a session and all its data | Returns `boolean` | -| `PATCH` | `/session/:id` | Update session properties | body: `{ title? }`, returns Session | -| `GET` | `/session/:id/children` | Get a session's child sessions | Returns Session[] | -| `GET` | `/session/:id/todo` | Get the todo list for a session | Returns Todo[] | -| `POST` | `/session/:id/init` | Analyze app and create `AGENTS.md` | body: `{ messageID, providerID, modelID }`, returns `boolean` | -| `POST` | `/session/:id/fork` | Fork an existing session at a message | body: `{ messageID? }`, returns Session | -| `POST` | `/session/:id/abort` | Abort a running session | Returns `boolean` | -| `POST` | `/session/:id/share` | Share a session | Returns Session | -| `DELETE` | `/session/:id/share` | Unshare a session | Returns Session | -| `GET` | `/session/:id/diff` | Get the diff for this session | query: `messageID?`, returns FileDiff[] | -| `POST` | `/session/:id/summarize` | Summarize the session | body: `{ providerID, modelID }`, returns `boolean` | -| `POST` | `/session/:id/revert` | Revert a message | body: `{ messageID, partID? }`, returns `boolean` | -| `POST` | `/session/:id/unrevert` | Restore all reverted messages | Returns `boolean` | -| `POST` | `/session/:id/permissions/:permissionID` | Respond to a permission request | body: `{ response, remember? }`, returns `boolean` | +| Метод | Путь | Описание | Примечания | +| -------- | ---------------------------------------- | ---------------------------------------- | ------------------------------------------------------------------------------------- | +| `GET` | `/session` | Список всех сессий | Возвращает Session[] | +| `POST` | `/session` | Создать новую сессию | body: `{ parentID?, title? }`, возвращает Session | +| `GET` | `/session/status` | Получить статус всех сессий | Возвращает `{ [sessionID: string]: `SessionStatus` }` | +| `GET` | `/session/:id` | Получить детали сессии | Возвращает Session | +| `DELETE` | `/session/:id` | Удалить сессию и все её данные | Возвращает `boolean` | +| `PATCH` | `/session/:id` | Обновить свойства сессии | body: `{ title? }`, возвращает Session | +| `GET` | `/session/:id/children` | Получить дочерние сессии | Возвращает Session[] | +| `GET` | `/session/:id/todo` | Получить список задач для сессии | Возвращает Todo[] | +| `POST` | `/session/:id/init` | Анализ приложения и создание `AGENTS.md` | body: `{ messageID, providerID, modelID }`, возвращает `boolean` | +| `POST` | `/session/:id/fork` | Ответвление сессии от сообщения | body: `{ messageID? }`, возвращает Session | +| `POST` | `/session/:id/abort` | Прервать запущенную сессию | Возвращает `boolean` | +| `POST` | `/session/:id/share` | Поделиться сессией | Возвращает Session | +| `DELETE` | `/session/:id/share` | Отменить общий доступ к сессии | Возвращает Session | +| `GET` | `/session/:id/diff` | Получить diff для этой сессии | query: `messageID?`, возвращает FileDiff[] | +| `POST` | `/session/:id/summarize` | Суммировать сессию | body: `{ providerID, modelID }`, возвращает `boolean` | +| `POST` | `/session/:id/revert` | Отменить сообщение | body: `{ messageID, partID? }`, возвращает `boolean` | +| `POST` | `/session/:id/unrevert` | Восстановить все отмененные сообщения | Возвращает `boolean` | +| `POST` | `/session/:id/permissions/:permissionID` | Ответить на запрос разрешения | body: `{ response, remember? }`, возвращает `boolean` | --- ### Сообщения -| Метод | Путь | Описание | Примечания | -| ------ | --------------------------------- | --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `GET` | `/session/:id/message` | List messages in a session | query: `limit?`, returns `{ info: `Message`, parts: `Part[]`}[]` | -| `POST` | `/session/:id/message` | Send a message and wait for response | body: `{ messageID?, model?, agent?, noReply?, system?, tools?, parts }`, returns `{ info: `Message`, parts: `Part[]`}` | -| `GET` | `/session/:id/message/:messageID` | Get message details | Returns `{ info: `Message`, parts: `Part[]`}` | -| `POST` | `/session/:id/prompt_async` | Send a message asynchronously (no wait) | body: same as `/session/:id/message`, returns `204 No Content` | -| `POST` | `/session/:id/command` | Execute a slash command | body: `{ messageID?, agent?, model?, command, arguments }`, returns `{ info: `Message`, parts: `Part[]`}` | -| `POST` | `/session/:id/shell` | Run a shell command | body: `{ agent, model?, command }`, returns `{ info: `Message`, parts: `Part[]`}` | +| Метод | Путь | Описание | Примечания | +| ------ | --------------------------------- | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `GET` | `/session/:id/message` | Список сообщений в сессии | query: `limit?`, возвращает `{ info: `Message`, parts: `Part[]`}[]` | +| `POST` | `/session/:id/message` | Отправить сообщение и ждать ответа | body: `{ messageID?, model?, agent?, noReply?, system?, tools?, parts }`, возвращает `{ info: `Message`, parts: `Part[]`}` | +| `GET` | `/session/:id/message/:messageID` | Получить детали сообщения | Возвращает `{ info: `Message`, parts: `Part[]`}` | +| `POST` | `/session/:id/prompt_async` | Отправить сообщение асинхронно (без ожидания) | body: как в `/session/:id/message`, возвращает `204 No Content` | +| `POST` | `/session/:id/command` | Выполнить слэш-команду | body: `{ messageID?, agent?, model?, command, arguments }`, возвращает `{ info: `Message`, parts: `Part[]`}` | +| `POST` | `/session/:id/shell` | Запустить команду оболочки | body: `{ agent, model?, command }`, возвращает `{ info: `Message`, parts: `Part[]`}` | --- ### Команды -| Метод | Путь | Описание | Ответ | -| ----- | ---------- | ----------------- | --------------------------------------------- | -| `GET` | `/command` | List all commands | Command[] | +| Метод | Путь | Описание | Ответ | +| ----- | ---------- | ------------------ | --------------------------------------------- | +| `GET` | `/command` | Список всех команд | Command[] | --- ### Файлы -| Метод | Путь | Описание | Ответ | -| ----- | ------------------------ | ---------------------------------- | ------------------------------------------------------------------------------------------- | -| `GET` | `/find?pattern=` | Search for text in files | Array of match objects with `path`, `lines`, `line_number`, `absolute_offset`, `submatches` | -| `GET` | `/find/file?query=` | Find files and directories by name | `string[]` (paths) | -| `GET` | `/find/symbol?query=` | Find workspace symbols | Symbol[] | -| `GET` | `/file?path=` | List files and directories | FileNode[] | -| `GET` | `/file/content?path=

` | Read a file | FileContent | -| `GET` | `/file/status` | Get status for tracked files | File[] | +| Метод | Путь | Описание | Ответ | +| ----- | ------------------------ | ------------------------------------ | -------------------------------------------------------------------------------------------- | +| `GET` | `/find?pattern=` | Поиск текста в файлах | Массив объектов совпадения с `path`, `lines`, `line_number`, `absolute_offset`, `submatches` | +| `GET` | `/find/file?query=` | Поиск файлов и директорий по имени | `string[]` (пути) | +| `GET` | `/find/symbol?query=` | Поиск символов рабочего пространства | Symbol[] | +| `GET` | `/file?path=` | Список файлов и директорий | FileNode[] | +| `GET` | `/file/content?path=

` | Прочитать файл | FileContent | +| `GET` | `/file/status` | Получить статус отслеживаемых файлов | File[] | #### `/find/file` параметры запроса @@ -212,76 +212,76 @@ For example, `http://localhost:4096/doc`. Use the spec to generate clients or in ### Инструменты (Экспериментальные) -| Метод | Путь | Описание | Ответ | -| ----- | ------------------------------------------- | ---------------------------------------- | -------------------------------------------- | -| `GET` | `/experimental/tool/ids` | List all tool IDs | ToolIDs | -| `GET` | `/experimental/tool?provider=

&model=` | List tools with JSON schemas for a model | ToolList | +| Метод | Путь | Описание | Ответ | +| ----- | ------------------------------------------- | ---------------------------------------------- | -------------------------------------------- | +| `GET` | `/experimental/tool/ids` | Список всех идентификаторов инструментов | ToolIDs | +| `GET` | `/experimental/tool?provider=

&model=` | Список инструментов со схемами JSON для модели | ToolList | --- ### LSP, форматтеры и MCP -| Метод | Путь | Описание | Ответ | -| ------ | ------------ | -------------------------- | -------------------------------------------------------- | -| `GET` | `/lsp` | Get LSP server status | LSPStatus[] | -| `GET` | `/formatter` | Get formatter status | FormatterStatus[] | -| `GET` | `/mcp` | Get MCP server status | `{ [name: string]: `MCPStatus` }` | -| `POST` | `/mcp` | Add MCP server dynamically | body: `{ name, config }`, returns MCP status object | +| Метод | Путь | Описание | Ответ | +| ------ | ------------ | ------------------------------- | -------------------------------------------------------- | +| `GET` | `/lsp` | Получить статус сервера LSP | LSPStatus[] | +| `GET` | `/formatter` | Получить статус форматера | FormatterStatus[] | +| `GET` | `/mcp` | Получить статус сервера MCP | `{ [name: string]: `MCPStatus` }` | +| `POST` | `/mcp` | Добавить сервер MCP динамически | body: `{ name, config }`, возвращает статус объекта MCP | --- ### Агенты -| Метод | Путь | Описание | Ответ | -| ----- | -------- | ------------------------- | ------------------------------------------- | -| `GET` | `/agent` | List all available agents | Agent[] | +| Метод | Путь | Описание | Ответ | +| ----- | -------- | ----------------------------- | ------------------------------------------- | +| `GET` | `/agent` | Список всех доступных агентов | Agent[] | --- ### Ведение журнала -| Метод | Путь | Описание | Ответ | -| ------ | ------ | ------------------------------------------------------------ | --------- | -| `POST` | `/log` | Write log entry. Body: `{ service, level, message, extra? }` | `boolean` | +| Метод | Путь | Описание | Ответ | +| ------ | ------ | --------------------------------------------------------------------- | --------- | +| `POST` | `/log` | Записать запись в журнал. Body: `{ service, level, message, extra? }` | `boolean` | --- ### TUI -| Метод | Путь | Описание | Ответ | -| ------ | ----------------------- | ------------------------------------------- | ---------------------- | -| `POST` | `/tui/append-prompt` | Append text to the prompt | `boolean` | -| `POST` | `/tui/open-help` | Open the help dialog | `boolean` | -| `POST` | `/tui/open-sessions` | Open the session selector | `boolean` | -| `POST` | `/tui/open-themes` | Open the theme selector | `boolean` | -| `POST` | `/tui/open-models` | Open the model selector | `boolean` | -| `POST` | `/tui/submit-prompt` | Submit the current prompt | `boolean` | -| `POST` | `/tui/clear-prompt` | Clear the prompt | `boolean` | -| `POST` | `/tui/execute-command` | Execute a command (`{ command }`) | `boolean` | -| `POST` | `/tui/show-toast` | Show toast (`{ title?, message, variant }`) | `boolean` | -| `GET` | `/tui/control/next` | Wait for the next control request | Control request object | -| `POST` | `/tui/control/response` | Respond to a control request (`{ body }`) | `boolean` | +| Метод | Путь | Описание | Ответ | +| ------ | ----------------------- | ----------------------------------------------------- | ------------------------- | +| `POST` | `/tui/append-prompt` | Добавить текст в подсказку | `boolean` | +| `POST` | `/tui/open-help` | Открыть диалог помощи | `boolean` | +| `POST` | `/tui/open-sessions` | Открыть селектор сессий | `boolean` | +| `POST` | `/tui/open-themes` | Открыть селектор тем | `boolean` | +| `POST` | `/tui/open-models` | Открыть селектор моделей | `boolean` | +| `POST` | `/tui/submit-prompt` | Отправить текущую подсказку | `boolean` | +| `POST` | `/tui/clear-prompt` | Очистить подсказку | `boolean` | +| `POST` | `/tui/execute-command` | Выполнить команду (`{ command }`) | `boolean` | +| `POST` | `/tui/show-toast` | Показать уведомление (`{ title?, message, variant }`) | `boolean` | +| `GET` | `/tui/control/next` | Ожидание следующего запроса управления | Объект запроса управления | +| `POST` | `/tui/control/response` | Ответить на запрос управления (`{ body }`) | `boolean` | --- ### Авторизация -| Метод | Путь | Описание | Ответ | -| ----- | ----------- | --------------------------------------------------------------- | --------- | -| `PUT` | `/auth/:id` | Set authentication credentials. Body must match provider schema | `boolean` | +| Метод | Путь | Описание | Ответ | +| ----- | ----------- | -------------------------------------------------------------------------------------- | --------- | +| `PUT` | `/auth/:id` | Установить учетные данные аутентификации. Body должен соответствовать схеме провайдера | `boolean` | --- ### События -| Метод | Путь | Описание | Ответ | -| ----- | -------- | ----------------------------------------------------------------------------- | ------------------------- | -| `GET` | `/event` | Server-sent events stream. First event is `server.connected`, then bus events | Server-sent events stream | +| Метод | Путь | Описание | Ответ | +| ----- | -------- | --------------------------------------------------------------------------------------------- | ------------------------------------ | +| `GET` | `/event` | Поток событий, отправляемых сервером. Первое событие — `server.connected`, затем события шины | Поток событий, отправляемых сервером | --- ### Документы -| Метод | Путь | Описание | Ответ | -| ----- | ------ | ------------------------- | --------------------------- | -| `GET` | `/doc` | OpenAPI 3.1 specification | HTML page with OpenAPI spec | +| Метод | Путь | Описание | Ответ | +| ----- | ------ | ------------------------ | -------------------------------------- | +| `GET` | `/doc` | Спецификация OpenAPI 3.1 | HTML-страница со спецификацией OpenAPI | diff --git a/packages/web/src/content/docs/ru/themes.mdx b/packages/web/src/content/docs/ru/themes.mdx index 2e5219e4c5a..05ace2c7b80 100644 --- a/packages/web/src/content/docs/ru/themes.mdx +++ b/packages/web/src/content/docs/ru/themes.mdx @@ -61,11 +61,11 @@ opencode поставляется с несколькими встроенным ## Использование темы -Вы можете выбрать тему, вызвав выбор темы с помощью команды `/theme`. Или вы можете указать это в файле [config](/docs/config). +Вы можете выбрать тему, вызвав выбор темы с помощью команды `/theme`. Или вы можете указать это в файле [tui.json](/docs/config#tui). -```json title="opencode.json" {3} +```json title="tui.json" {3} { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "theme": "tokyonight" } ``` diff --git a/packages/web/src/content/docs/ru/tui.mdx b/packages/web/src/content/docs/ru/tui.mdx index 0134e29c482..1694908dbac 100644 --- a/packages/web/src/content/docs/ru/tui.mdx +++ b/packages/web/src/content/docs/ru/tui.mdx @@ -355,24 +355,34 @@ How is auth handled in @packages/functions/src/api/index.ts? ## Настройка -Вы можете настроить поведение TUI через файл конфигурации opencode. +Вы можете настроить поведение TUI через `tui.json` (или `tui.jsonc`). -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", - "tui": { - "scroll_speed": 3, - "scroll_acceleration": { - "enabled": true - } - } + "$schema": "https://opencode.ai/tui.json", + "theme": "opencode", + "keybinds": { + "leader": "ctrl+x" + }, + "scroll_speed": 3, + "scroll_acceleration": { + "enabled": true + }, + "diff_style": "auto" } ``` +Это отдельный файл от `opencode.json`, который настраивает поведение сервера/выполнения. + ### Параметры -- `scroll_acceleration` — включите ускорение прокрутки в стиле macOS для плавной и естественной прокрутки. Если этот параметр включен, скорость прокрутки увеличивается при быстрой прокрутке и остается точной при более медленных движениях. **Этот параметр имеет приоритет над `scroll_speed` и переопределяет его, если он включен.** -- `scroll_speed` — контролирует скорость прокрутки TUI при использовании команд прокрутки (минимум: `1`). По умолчанию `3`. **Примечание. Это игнорируется, если для `scroll_acceleration.enabled` установлено значение `true`.** +- `theme` — Устанавливает тему пользовательского интерфейса. [Подробнее](/docs/themes). +- `keybinds` — Настраивает сочетания клавиш. [Подробнее](/docs/keybinds). +- `scroll_acceleration.enabled` — включите ускорение прокрутки в стиле macOS для плавной и естественной прокрутки. Если этот параметр включен, скорость прокрутки увеличивается при быстрой прокрутке и остается точной при более медленных движениях. **Этот параметр имеет приоритет над `scroll_speed` и переопределяет его, если он включен.** +- `scroll_speed` — контролирует скорость прокрутки TUI при использовании команд прокрутки (минимум: `0.001`, поддерживает десятичные значения). По умолчанию `3`. **Примечание. Это игнорируется, если для `scroll_acceleration.enabled` установлено значение `true`.** +- `diff_style` — Управляет отображением различий. `"auto"` адаптируется к ширине терминала, `"stacked"` всегда показывает одноколоночный макет. + +Используйте `OPENCODE_TUI_CONFIG` для загрузки пользовательского пути конфигурации TUI. --- diff --git a/packages/web/src/content/docs/ru/zen.mdx b/packages/web/src/content/docs/ru/zen.mdx index 3fe03a47fac..078d1a38194 100644 --- a/packages/web/src/content/docs/ru/zen.mdx +++ b/packages/web/src/content/docs/ru/zen.mdx @@ -63,6 +63,7 @@ OpenCode Zen работает так же, как и любой другой п | Модель | Идентификатор модели | Конечная точка | Пакет AI SDK | | ------------------ | -------------------- | -------------------------------------------------- | --------------------------- | +| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | @@ -72,13 +73,15 @@ OpenCode Zen работает так же, как и любой другой п | GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | | Gemini 3 Pro | gemini-3-pro | `https://opencode.ai/zen/v1/models/gemini-3-pro` | `@ai-sdk/google` | | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -86,10 +89,8 @@ OpenCode Zen работает так же, как и любой другой п | MiniMax M2.1 | minimax-m2.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 5 | glm-5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 4.7 | glm-4.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM 4.7 Free | glm-4.7-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 4.6 | glm-4.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 Free | kimi-k2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2 Thinking | kimi-k2-thinking | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2 | kimi-k2 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Qwen3 Coder 480B | qwen3-coder | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -123,27 +124,29 @@ https://opencode.ai/zen/v1/models | MiniMax M2.1 | $0.30 | $1.20 | $0.10 | - | | GLM 5 | $1.00 | $3.20 | $0.20 | - | | GLM 4.7 | $0.60 | $2.20 | $0.10 | - | -| GLM 4.7 | $0.60 | $2.20 | $0.10 | - | | GLM 4.6 | $0.60 | $2.20 | $0.10 | - | -| GLM 4.7 Free | Бесплатно | Бесплатно | Бесплатно | - | -| Kimi K2.5 Free | Бесплатно | Бесплатно | Бесплатно | - | | Kimi K2.5 | $0.60 | $3.00 | $0.08 | - | | Kimi K2 Thinking | $0.40 | $2.50 | - | - | | Kimi K2 | $0.40 | $2.50 | - | - | | Qwen3 Coder 480B | $0.45 | $1.50 | - | - | +| Claude Opus 4.6 (≤ 200 тыс. токенов) | $5.00 | $25.00 | $0.50 | $6.25 | +| Claude Opus 4.6 (> 200 тыс. токенов) | $10.00 | $37.50 | $1.00 | $12.50 | +| Claude Opus 4.5 | $5.00 | $25.00 | $0.50 | $6.25 | +| Claude Opus 4.1 | $15.00 | $75.00 | $1.50 | $18.75 | +| Claude Sonnet 4.6 (≤ 200 тыс. токенов) | $3.00 | $15.00 | $0.30 | $3.75 | +| Claude Sonnet 4.6 (> 200 тыс. токенов) | $6.00 | $22.50 | $0.60 | $7.50 | | Claude Sonnet 4.5 (≤ 200 тыс. токенов) | $3.00 | $15.00 | $0.30 | $3.75 | | Claude Sonnet 4.5 (> 200 тыс. токенов) | $6.00 | $22.50 | $0.60 | $7.50 | | Claude Sonnet 4 (≤ 200 тыс. токенов) | $3.00 | $15.00 | $0.30 | $3.75 | | Claude Sonnet 4 (> 200 тыс. токенов) | $6.00 | $22.50 | $0.60 | $7.50 | | Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 | | Claude Haiku 3.5 | $0.80 | $4.00 | $0.08 | $1.00 | -| Claude Opus 4.6 (≤ 200 тыс. токенов) | $5.00 | $25.00 | $0.50 | $6.25 | -| Claude Opus 4.6 (> 200 тыс. токенов) | $10.00 | $37.50 | $1.00 | $12.50 | -| Claude Opus 4.5 | $5.00 | $25.00 | $0.50 | $6.25 | -| Claude Opus 4.1 | $15.00 | $75.00 | $1.50 | $18.75 | +| Gemini 3.1 Pro (≤ 200 тыс. токенов) | $2.00 | $12.00 | $0.20 | - | +| Gemini 3.1 Pro (> 200 тыс. токенов) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Pro (≤ 200 тыс. токенов) | $2.00 | $12.00 | $0.20 | - | | Gemini 3 Pro (> 200 тыс. токенов) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | +| GPT 5.3 Codex | $1.75 | $14.00 | $0.175 | - | | GPT 5.2 | $1.75 | $14.00 | $0.175 | - | | GPT 5.2 Codex | $1.75 | $14.00 | $0.175 | - | | GPT 5.1 | $1.07 | $8.50 | $0.107 | - | @@ -162,7 +165,6 @@ https://opencode.ai/zen/v1/models Бесплатные модели: -- Kimi K2.5 Free доступен на OpenCode в течение ограниченного времени. Команда использует это время для сбора отзывов и улучшения модели. - MiniMax M2.5 Free доступен на OpenCode в течение ограниченного времени. Команда использует это время для сбора отзывов и улучшения модели. - Big Pickle — это стелс-модель, которая доступна бесплатно на OpenCode в течение ограниченного времени. Команда использует это время для сбора отзывов и улучшения модели. @@ -194,7 +196,6 @@ https://opencode.ai/zen/v1/models Все наши модели размещены в США. Наши поставщики придерживаются политики нулевого хранения и не используют ваши данные для обучения моделей, за следующими исключениями: - Big Pickle: во время бесплатного периода собранные данные могут быть использованы для улучшения модели. -- Kimi K2.5 Free: в течение бесплатного периода собранные данные могут использоваться для улучшения модели. - MiniMax M2.5 Free: в течение бесплатного периода собранные данные могут использоваться для улучшения модели. - API OpenAI: запросы хранятся в течение 30 дней в соответствии с [Политикой данных OpenAI](https://platform.openai.com/docs/guides/your-data). - API-интерфейсы Anthropic: запросы хранятся в течение 30 дней в соответствии с [Политикой данных Anthropic](https://docs.anthropic.com/en/docs/claude-code/data-usage). diff --git a/packages/web/src/content/docs/th/cli.mdx b/packages/web/src/content/docs/th/cli.mdx index 60e3fac764c..875f517fc26 100644 --- a/packages/web/src/content/docs/th/cli.mdx +++ b/packages/web/src/content/docs/th/cli.mdx @@ -559,6 +559,7 @@ OpenCode สามารถกำหนดค่าโดยใช้ตัว | `OPENCODE_AUTO_SHARE` | Boolean | แชร์เซสชันอัตโนมัติเมื่อสร้าง | | `OPENCODE_GIT_BASH_PATH` | String | เส้นทางไปยัง Git Bash บน Windows | | `OPENCODE_CONFIG` | String | เส้นทางไปยังไฟล์การกำหนดค่า | +| `OPENCODE_TUI_CONFIG` | String | เส้นทางไปยังไฟล์การกำหนดค่า TUI | | `OPENCODE_CONFIG_DIR` | String | เส้นทางไปยังไดเร็กทอรีการกำหนดค่า | | `OPENCODE_CONFIG_CONTENT` | String | เนื้อหาการกำหนดค่าแบบ inline JSON | | `OPENCODE_DISABLE_AUTOUPDATE` | Boolean | ปิดใช้งานการอัปเดตอัตโนมัติ | diff --git a/packages/web/src/content/docs/th/config.mdx b/packages/web/src/content/docs/th/config.mdx index 06836aca3be..c58469c77ab 100644 --- a/packages/web/src/content/docs/th/config.mdx +++ b/packages/web/src/content/docs/th/config.mdx @@ -14,10 +14,11 @@ OpenCode รองรับทั้งรูปแบบ **JSON** และ **J ```jsonc title="opencode.jsonc" { "$schema": "https://opencode.ai/config.json", - // Theme configuration - "theme": "opencode", "model": "anthropic/claude-sonnet-4-5", "autoupdate": true, + "server": { + "port": 4096, + }, } ``` @@ -34,7 +35,7 @@ OpenCode รองรับทั้งรูปแบบ **JSON** และ **J ไฟล์การกำหนดค่าจะถูกรวมเข้าด้วยกัน โดยไม่มีการแทนที่ การตั้งค่าจากตำแหน่งการกำหนดค่าต่อไปนี้จะรวมกัน การกำหนดค่าในภายหลังจะแทนที่การกำหนดค่าก่อนหน้าสำหรับคีย์ที่ขัดแย้งกันเท่านั้น การตั้งค่าที่ไม่ขัดแย้งจากการกำหนดค่าทั้งหมดจะยังคงอยู่ -ตัวอย่างเช่น หากการกำหนดค่าส่วนกลางของคุณตั้งค่า `theme: "opencode"` และ `autoupdate: true` และการกำหนดค่าโปรเจ็กต์ของคุณตั้งค่า `model: "anthropic/claude-sonnet-4-5"` การกำหนดค่าสุดท้ายจะรวมการตั้งค่าทั้งสามรายการไว้ด้วย +ตัวอย่างเช่น หากการกำหนดค่าส่วนกลางของคุณตั้งค่า `autoupdate: true` และการกำหนดค่าโปรเจ็กต์ของคุณตั้งค่า `model: "anthropic/claude-sonnet-4-5"` การกำหนดค่าสุดท้ายจะรวมการตั้งค่าทั้งสองรายการไว้ด้วย --- @@ -95,7 +96,9 @@ OpenCode รองรับทั้งรูปแบบ **JSON** และ **J ### ทั่วโลก -วางการกำหนดค่า OpenCode ส่วนกลางของคุณใน `~/.config/opencode/opencode.json` ใช้การกำหนดค่าส่วนกลางสำหรับการตั้งค่าทั้งผู้ใช้ เช่น ธีม ผู้ให้บริการ หรือปุ่มลัด +วางการกำหนดค่า OpenCode ส่วนกลางของคุณใน `~/.config/opencode/opencode.json` ใช้การกำหนดค่าส่วนกลางสำหรับการตั้งค่าทั้งผู้ใช้ เช่น ผู้ให้บริการ รุ่น และสิทธิ์ + +สำหรับการตั้งค่าเฉพาะ TUI ให้ใช้ `~/.config/opencode/tui.json` การกำหนดค่าส่วนกลางจะแทนที่ค่าเริ่มต้นขององค์กรระยะไกล @@ -105,6 +108,8 @@ OpenCode รองรับทั้งรูปแบบ **JSON** และ **J เพิ่ม `opencode.json` ในรูทโปรเจ็กต์ของคุณ การกำหนดค่าโปรเจ็กต์มีความสำคัญสูงสุดในบรรดาไฟล์กำหนดค่ามาตรฐาน โดยจะแทนที่การกำหนดค่าทั้งส่วนกลางและระยะไกล +สำหรับการตั้งค่า TUI เฉพาะโครงการ ให้เพิ่ม `tui.json` ควบคู่ไปกับมัน + :::tip วางการกำหนดค่าเฉพาะโปรเจ็กต์ไว้ที่รากของโปรเจ็กต์ของคุณ ::: @@ -148,34 +153,32 @@ opencode run "Hello world" ไฟล์กำหนดค่ามีสคีมาที่กำหนดไว้ใน [**`opencode.ai/config.json`**](https://opencode.ai/config.json) +การกำหนดค่า TUI ใช้ [**`opencode.ai/tui.json`**](https://opencode.ai/tui.json) + ผู้แก้ไขของคุณควรสามารถตรวจสอบและเติมข้อความอัตโนมัติตามสคีมาได้ --- ### TUI -คุณสามารถกำหนดการตั้งค่าเฉพาะ TUI ผ่านตัวเลือก `tui` +ใช้ไฟล์ `tui.json` (หรือ `tui.jsonc`) เฉพาะสำหรับการตั้งค่าเฉพาะ TUI -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", - "tui": { - "scroll_speed": 3, - "scroll_acceleration": { - "enabled": true - }, - "diff_style": "auto" - } + "$schema": "https://opencode.ai/tui.json", + "scroll_speed": 3, + "scroll_acceleration": { + "enabled": true + }, + "diff_style": "auto" } ``` -ตัวเลือกที่มี: +ใช้ `OPENCODE_TUI_CONFIG` เพื่อชี้ไปยังไฟล์กำหนดค่า TUI ที่กำหนดเอง -- `scroll_acceleration.enabled` - ​​เปิดใช้งานการเร่งความเร็วการเลื่อนแบบ macOS **มีลำดับความสำคัญมากกว่า `scroll_speed`.** -- `scroll_speed` - ​​ตัวคูณความเร็วการเลื่อนแบบกำหนดเอง (ค่าเริ่มต้น: `3` ขั้นต่ำ: `1`) ไม่สนใจหาก `scroll_acceleration.enabled` คือ `true` -- `diff_style` - ​​ควบคุมการเรนเดอร์ต่าง `"auto"` ปรับให้เข้ากับความกว้างของ terminal `"stacked"` จะแสดงคอลัมน์เดียวเสมอ +คีย์ `theme`, `keybinds` และ `tui` แบบเดิมใน `opencode.json` เลิกใช้แล้วและจะถูกย้ายโดยอัตโนมัติเมื่อเป็นไปได้ -[เรียนรู้เพิ่มเติมเกี่ยวกับการใช้ TUI ที่นี่](/docs/tui) +[เรียนรู้เพิ่มเติมเกี่ยวกับการใช้ TUI ที่นี่](/docs/tui#configure) --- @@ -301,12 +304,12 @@ Bearer Token (`AWS_BEARER_TOKEN_BEDROCK` หรือ `/connect`) มีคว ### Themes -คุณสามารถกำหนดค่าธีมที่คุณต้องการใช้ในการกำหนดค่า OpenCode ของคุณได้ผ่านตัวเลือก `theme` +ตั้งค่าธีม UI ของคุณใน `tui.json` -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", - "theme": "" + "$schema": "https://opencode.ai/tui.json", + "theme": "tokyonight" } ``` @@ -406,11 +409,11 @@ Bearer Token (`AWS_BEARER_TOKEN_BEDROCK` หรือ `/connect`) มีคว ### คีย์ลัด -คุณสามารถปรับแต่งปุ่มลัดของคุณได้ผ่านตัวเลือก `keybinds` +ปรับแต่งปุ่มลัดใน `tui.json` -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "keybinds": {} } ``` @@ -490,13 +493,15 @@ OpenCode จะดาวน์โหลดการอัปเดตใหม "$schema": "https://opencode.ai/config.json", "compaction": { "auto": true, - "prune": true + "prune": true, + "reserved": 10000 } } ``` - `auto` - ​​กระชับเซสชันโดยอัตโนมัติเมื่อบริบทเต็ม (ค่าเริ่มต้น: `true`) - `prune` - ​​ลบเอาท์พุตเครื่องมือเก่าเพื่อบันทึก tokens (ค่าเริ่มต้น: `true`) +- `reserved` - บัฟเฟอร์โทเค็นสำหรับการบีบอัด ให้หน้าต่างเพียงพอเพื่อหลีกเลี่ยงการล้นระหว่างการบีบอัด --- diff --git a/packages/web/src/content/docs/th/custom-tools.mdx b/packages/web/src/content/docs/th/custom-tools.mdx index 28cd229cfd8..c5e63853575 100644 --- a/packages/web/src/content/docs/th/custom-tools.mdx +++ b/packages/web/src/content/docs/th/custom-tools.mdx @@ -79,6 +79,32 @@ export const multiply = tool({ --- +#### ชื่อซ้ำกับเครื่องมือในตัว + +เครื่องมือแบบกำหนดเองจะถูกระบุด้วยชื่อเครื่องมือ หากเครื่องมือแบบกำหนดเองใช้ชื่อเดียวกับเครื่องมือในตัว เครื่องมือแบบกำหนดเองจะมีความสำคัญเหนือกว่า + +ตัวอย่างเช่น ไฟล์นี้จะแทนที่เครื่องมือ `bash` ในตัว: + +```ts title=".opencode/tools/bash.ts" +import { tool } from "@opencode-ai/plugin" + +export default tool({ + description: "Restricted bash wrapper", + args: { + command: tool.schema.string(), + }, + async execute(args) { + return `blocked: ${args.command}` + }, +}) +``` + +:::note +ชอบชื่อที่ไม่ซ้ำกันเว้นแต่คุณตั้งใจจะแทนที่เครื่องมือในตัว หากคุณต้องการปิดใช้งานเครื่องมือในตัวแต่ไม่ต้องการแทนที่ ให้ใช้ [permissions](/docs/permissions) +::: + +--- + ### ข้อโต้แย้ง คุณสามารถใช้ `tool.schema` ซึ่งก็คือ [Zod](https://zod.dev) เพื่อกำหนดประเภทอาร์กิวเมนต์ diff --git a/packages/web/src/content/docs/th/ecosystem.mdx b/packages/web/src/content/docs/th/ecosystem.mdx index f1630f9a2f8..a468534863f 100644 --- a/packages/web/src/content/docs/th/ecosystem.mdx +++ b/packages/web/src/content/docs/th/ecosystem.mdx @@ -15,38 +15,39 @@ description: โปรเจ็กต์และการผสานรวม ## ปลั๊กอิน -| ชื่อ | คำอธิบาย | -| --------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | -| [opencode-daytona](https://github.com/jamesmurdza/daytona/blob/main/guides/typescript/opencode/README.md) | เรียกใช้เซสชัน OpenCode โดยอัตโนมัติในแซนด์บ็อกซ์ Daytona ที่แยกออกมาพร้อม git sync และการแสดงตัวอย่างแบบสด | -| [opencode-helicone-session](https://github.com/H2Shami/opencode-helicone-session) | แทรกส่วนหัวเซสชัน Helicone โดยอัตโนมัติสำหรับการจัดกลุ่มคำขอ | -| [opencode-type-inject](https://github.com/nick-vi/opencode-type-inject) | ฉีดประเภท TypeScript/Svelte ลงในไฟล์ที่อ่านโดยอัตโนมัติด้วยเครื่องมือค้นหา | -| [opencode-openai-codex-auth](https://github.com/numman-ali/opencode-openai-codex-auth) | ใช้การสมัครสมาชิก ChatGPT Plus/Pro แทนเครดิต API | -| [opencode-gemini-auth](https://github.com/jenslys/opencode-gemini-auth) | ใช้แผน Gemini ที่มีอยู่ของคุณแทนการเรียกเก็บเงิน API | -| [opencode-antigravity-auth](https://github.com/NoeFabris/opencode-antigravity-auth) | ใช้โมเดลฟรีของ Antigravity แทนการเรียกเก็บเงิน API | -| [opencode-devcontainers](https://github.com/athal7/opencode-devcontainers) | การแยกคอนเทนเนอร์ Devcontainer แบบหลายสาขาพร้อมโคลนแบบตื้นและพอร์ตที่กำหนดอัตโนมัติ | -| [opencode-google-antigravity-auth](https://github.com/shekohex/opencode-google-antigravity-auth) | ปลั๊กอิน Google Antigravity OAuth พร้อมรองรับ Google Search และการจัดการ API ที่แข็งแกร่งยิ่งขึ้น | -| [opencode-dynamic-context-pruning](https://github.com/Tarquinen/opencode-dynamic-context-pruning) | ปรับการใช้โทเค็นให้เหมาะสมโดยการตัดเอาท์พุตของเครื่องมือที่ล้าสมัย | -| [opencode-websearch-cited](https://github.com/ghoulr/opencode-websearch-cited.git) | เพิ่มการสนับสนุนการค้นหาเว็บแบบเนทีฟสำหรับผู้ให้บริการที่รองรับด้วยรูปแบบที่มีเหตุผลของ Google | -| [opencode-pty](https://github.com/shekohex/opencode-pty.git) | ช่วยให้ตัวแทน AI สามารถเรียกใช้กระบวนการเบื้องหลังใน PTY และส่งข้อมูลเชิงโต้ตอบให้พวกเขาได้ | -| [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | คำแนะนำสำหรับคำสั่ง shell แบบไม่โต้ตอบ - ป้องกันการแฮงค์จากการดำเนินการที่ขึ้นอยู่กับ TTY | -| [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | ติดตามการใช้งาน OpenCode ด้วย Wakatime | -| [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | ทำความสะอาดตาราง Markdown ที่ผลิตโดย LLM | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | การแก้ไขโค้ดเร็วขึ้น 10 เท่าด้วย Morph Fast Apply API และเครื่องหมายแก้ไขแบบ Lazy | -| [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | ตัวแทนเบื้องหลัง, เครื่องมือ LSP/AST/MCP ที่สร้างไว้ล่วงหน้า, ตัวแทนที่ได้รับการดูแลจัดการ, เข้ากันได้กับ Claude Code | -| [opencode-notificator](https://github.com/panta82/opencode-notificator) | การแจ้งเตือนบนเดสก์ท็อปและเสียงเตือนสำหรับเซสชัน OpenCode | -| [opencode-notifier](https://github.com/mohak34/opencode-notifier) | การแจ้งเตือนบนเดสก์ท็อปและเสียงเตือนสำหรับการอนุญาต การดำเนินการเสร็จสิ้น และเหตุการณ์ข้อผิดพลาด | -| [opencode-zellij-namer](https://github.com/24601/opencode-zellij-namer) | การตั้งชื่อเซสชัน Zellij อัตโนมัติที่ขับเคลื่อนด้วย AI ตามบริบทของ OpenCode | -| [opencode-skillful](https://github.com/zenobi-us/opencode-skillful) | อนุญาตให้ตัวแทน OpenCode โหลดแบบ Lazy Load ตามความต้องการพร้อมการค้นพบทักษะและการแทรก | -| [opencode-supermemory](https://github.com/supermemoryai/opencode-supermemory) | หน่วยความจำถาวรตลอดเซสชันโดยใช้ Supermemory | -| [@plannotator/opencode](https://github.com/backnotprop/plannotator/tree/main/apps/opencode-plugin) | การตรวจสอบแผนเชิงโต้ตอบพร้อมคำอธิบายประกอบแบบภาพและการแชร์ส่วนตัว/offline | -| [@openspoon/subtask2](https://github.com/spoons-and-mirrors/subtask2) | ขยาย opencode /commands ไปสู่ระบบการประสานที่มีประสิทธิภาพพร้อมการควบคุมโฟลว์แบบละเอียด | -| [opencode-scheduler](https://github.com/different-ai/opencode-scheduler) | กำหนดเวลางานที่เกิดซ้ำโดยใช้ launchd (Mac) หรือ systemd (Linux) ด้วยไวยากรณ์ cron | -| [micode](https://github.com/vtemian/micode) | ระดมความคิดอย่างมีโครงสร้าง → วางแผน → นำเวิร์กโฟลว์ไปใช้ด้วยความต่อเนื่องของเซสชัน | -| [octto](https://github.com/vtemian/octto) | UI เบราว์เซอร์แบบโต้ตอบสำหรับการระดมความคิด AI ด้วยแบบฟอร์มคำถามหลายข้อ | -| [opencode-background-agents](https://github.com/kdcokenny/opencode-background-agents) | เอเจนต์พื้นหลังสไตล์ Claude Code พร้อมการมอบหมายแบบอะซิงก์และการคงอยู่ของบริบท | -| [opencode-notify](https://github.com/kdcokenny/opencode-notify) | การแจ้งเตือนระบบปฏิบัติการดั้งเดิมสำหรับ OpenCode – ทราบเมื่องานเสร็จสมบูรณ์ | -| [opencode-workspace](https://github.com/kdcokenny/opencode-workspace) | ชุดสายรัดประสานหลายเอเจนต์ที่ให้มา – ส่วนประกอบ 16 ชิ้น ติดตั้งเพียงครั้งเดียว | -| [opencode-worktree](https://github.com/kdcokenny/opencode-worktree) | เวิร์กทรีคอมไพล์ไร้แรงเสียดทานสำหรับ OpenCode | +| ชื่อ | คำอธิบาย | +| -------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | +| [opencode-daytona](https://github.com/daytonaio/daytona/tree/main/libs/opencode-plugin) | เรียกใช้เซสชัน OpenCode โดยอัตโนมัติในแซนด์บ็อกซ์ Daytona ที่แยกออกมาพร้อม git sync และการแสดงตัวอย่างแบบสด | +| [opencode-helicone-session](https://github.com/H2Shami/opencode-helicone-session) | แทรกส่วนหัวเซสชัน Helicone โดยอัตโนมัติสำหรับการจัดกลุ่มคำขอ | +| [opencode-type-inject](https://github.com/nick-vi/opencode-type-inject) | ฉีดประเภท TypeScript/Svelte ลงในไฟล์ที่อ่านโดยอัตโนมัติด้วยเครื่องมือค้นหา | +| [opencode-openai-codex-auth](https://github.com/numman-ali/opencode-openai-codex-auth) | ใช้การสมัครสมาชิก ChatGPT Plus/Pro แทนเครดิต API | +| [opencode-gemini-auth](https://github.com/jenslys/opencode-gemini-auth) | ใช้แผน Gemini ที่มีอยู่ของคุณแทนการเรียกเก็บเงิน API | +| [opencode-antigravity-auth](https://github.com/NoeFabris/opencode-antigravity-auth) | ใช้โมเดลฟรีของ Antigravity แทนการเรียกเก็บเงิน API | +| [opencode-devcontainers](https://github.com/athal7/opencode-devcontainers) | การแยกคอนเทนเนอร์ Devcontainer แบบหลายสาขาพร้อมโคลนแบบตื้นและพอร์ตที่กำหนดอัตโนมัติ | +| [opencode-google-antigravity-auth](https://github.com/shekohex/opencode-google-antigravity-auth) | ปลั๊กอิน Google Antigravity OAuth พร้อมรองรับ Google Search และการจัดการ API ที่แข็งแกร่งยิ่งขึ้น | +| [opencode-dynamic-context-pruning](https://github.com/Tarquinen/opencode-dynamic-context-pruning) | ปรับการใช้โทเค็นให้เหมาะสมโดยการตัดเอาท์พุตของเครื่องมือที่ล้าสมัย | +| [opencode-vibeguard](https://github.com/inkdust2021/opencode-vibeguard) | ปกปิดความลับ/PII เป็นตัวยึดตำแหน่งแบบ VibeGuard ก่อนการเรียก LLM และกู้คืนในเครื่อง | +| [opencode-websearch-cited](https://github.com/ghoulr/opencode-websearch-cited.git) | เพิ่มการสนับสนุนการค้นหาเว็บแบบเนทีฟสำหรับผู้ให้บริการที่รองรับด้วยรูปแบบที่มีเหตุผลของ Google | +| [opencode-pty](https://github.com/shekohex/opencode-pty.git) | ช่วยให้ตัวแทน AI สามารถเรียกใช้กระบวนการเบื้องหลังใน PTY และส่งข้อมูลเชิงโต้ตอบให้พวกเขาได้ | +| [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | คำแนะนำสำหรับคำสั่ง shell แบบไม่โต้ตอบ - ป้องกันการแฮงค์จากการดำเนินการที่ขึ้นอยู่กับ TTY | +| [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | ติดตามการใช้งาน OpenCode ด้วย Wakatime | +| [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | ทำความสะอาดตาราง Markdown ที่ผลิตโดย LLM | +| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | การแก้ไขโค้ดเร็วขึ้น 10 เท่าด้วย Morph Fast Apply API และเครื่องหมายแก้ไขแบบ Lazy | +| [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | ตัวแทนเบื้องหลัง, เครื่องมือ LSP/AST/MCP ที่สร้างไว้ล่วงหน้า, ตัวแทนที่ได้รับการดูแลจัดการ, เข้ากันได้กับ Claude Code | +| [opencode-notificator](https://github.com/panta82/opencode-notificator) | การแจ้งเตือนบนเดสก์ท็อปและเสียงเตือนสำหรับเซสชัน OpenCode | +| [opencode-notifier](https://github.com/mohak34/opencode-notifier) | การแจ้งเตือนบนเดสก์ท็อปและเสียงเตือนสำหรับการอนุญาต การดำเนินการเสร็จสิ้น และเหตุการณ์ข้อผิดพลาด | +| [opencode-zellij-namer](https://github.com/24601/opencode-zellij-namer) | การตั้งชื่อเซสชัน Zellij อัตโนมัติที่ขับเคลื่อนด้วย AI ตามบริบทของ OpenCode | +| [opencode-skillful](https://github.com/zenobi-us/opencode-skillful) | อนุญาตให้ตัวแทน OpenCode โหลดแบบ Lazy Load ตามความต้องการพร้อมการค้นพบทักษะและการแทรก | +| [opencode-supermemory](https://github.com/supermemoryai/opencode-supermemory) | หน่วยความจำถาวรตลอดเซสชันโดยใช้ Supermemory | +| [@plannotator/opencode](https://github.com/backnotprop/plannotator/tree/main/apps/opencode-plugin) | การตรวจสอบแผนเชิงโต้ตอบพร้อมคำอธิบายประกอบแบบภาพและการแชร์ส่วนตัว/offline | +| [@openspoon/subtask2](https://github.com/spoons-and-mirrors/subtask2) | ขยาย opencode /commands ไปสู่ระบบการประสานที่มีประสิทธิภาพพร้อมการควบคุมโฟลว์แบบละเอียด | +| [opencode-scheduler](https://github.com/different-ai/opencode-scheduler) | กำหนดเวลางานที่เกิดซ้ำโดยใช้ launchd (Mac) หรือ systemd (Linux) ด้วยไวยากรณ์ cron | +| [micode](https://github.com/vtemian/micode) | ระดมความคิดอย่างมีโครงสร้าง → วางแผน → นำเวิร์กโฟลว์ไปใช้ด้วยความต่อเนื่องของเซสชัน | +| [octto](https://github.com/vtemian/octto) | UI เบราว์เซอร์แบบโต้ตอบสำหรับการระดมความคิด AI ด้วยแบบฟอร์มคำถามหลายข้อ | +| [opencode-background-agents](https://github.com/kdcokenny/opencode-background-agents) | เอเจนต์พื้นหลังสไตล์ Claude Code พร้อมการมอบหมายแบบอะซิงก์และการคงอยู่ของบริบท | +| [opencode-notify](https://github.com/kdcokenny/opencode-notify) | การแจ้งเตือนระบบปฏิบัติการดั้งเดิมสำหรับ OpenCode – ทราบเมื่องานเสร็จสมบูรณ์ | +| [opencode-workspace](https://github.com/kdcokenny/opencode-workspace) | ชุดสายรัดประสานหลายเอเจนต์ที่ให้มา – ส่วนประกอบ 16 ชิ้น ติดตั้งเพียงครั้งเดียว | +| [opencode-worktree](https://github.com/kdcokenny/opencode-worktree) | เวิร์กทรีคอมไพล์ไร้แรงเสียดทานสำหรับ OpenCode | --- diff --git a/packages/web/src/content/docs/th/go.mdx b/packages/web/src/content/docs/th/go.mdx new file mode 100644 index 00000000000..7dcaf398a57 --- /dev/null +++ b/packages/web/src/content/docs/th/go.mdx @@ -0,0 +1,145 @@ +--- +title: Go +description: การสมัครสมาชิกราคาประหยัดสำหรับโมเดลการเขียนโค้ดแบบเปิด +--- + +import config from "../../../../config.mjs" +export const console = config.console +export const email = `mailto:${config.email}` + +OpenCode Go คือการสมัครสมาชิกราคาประหยัดเพียง **$10/เดือน** ที่ให้คุณเข้าถึงโมเดลการเขียนโค้ดแบบเปิดยอดนิยมได้อย่างน่าเชื่อถือ + +:::note +ขณะนี้ OpenCode Go อยู่ในช่วงเบต้า +::: + +Go ทำงานเหมือนกับผู้ให้บริการรายอื่นใน OpenCode คุณสมัครสมาชิก OpenCode Go และรับคีย์ API ของคุณ มันเป็น**ตัวเลือกเสริมทั้งหมด** และคุณไม่จำเป็นต้องใช้มันเพื่อใช้งาน OpenCode + +มันถูกออกแบบมาสำหรับผู้ใช้งานระดับนานาชาติเป็นหลัก โดยมีโมเดลโฮสต์อยู่ในสหรัฐอเมริกา สหภาพยุโรป และสิงคโปร์ เพื่อการเข้าถึงที่เสถียรทั่วโลก + +--- + +## ความเป็นมา + +โมเดลแบบเปิดมีคุณภาพดีขึ้นมาก ปัจจุบันมีประสิทธิภาพใกล้เคียงกับโมเดลที่เป็นกรรมสิทธิ์สำหรับงานเขียนโค้ด และเนื่องจากผู้ให้บริการหลายรายสามารถให้บริการโมเดลเหล่านี้ได้อย่างแข่งขันกัน จึงมักจะมีราคาถูกกว่ามาก + +อย่างไรก็ตาม การเข้าถึงโมเดลเหล่านี้อย่างน่าเชื่อถือและมีความหน่วงต่ำอาจเป็นเรื่องยาก ผู้ให้บริการมีคุณภาพและความพร้อมใช้งานที่แตกต่างกัน + +:::tip +เราได้ทดสอบกลุ่มโมเดลและผู้ให้บริการที่เลือกสรรแล้วซึ่งทำงานได้ดีกับ OpenCode +::: + +เพื่อแก้ไขปัญหานี้ เราได้ทำสิ่งต่อไปนี้: + +1. เราทดสอบกลุ่มโมเดลแบบเปิดที่เลือกสรรและพูดคุยกับทีมของพวกเขาเกี่ยวกับวิธีการรันโมเดลให้ดีที่สุด +2. จากนั้นเราทำงานร่วมกับผู้ให้บริการบางรายเพื่อให้แน่ใจว่าโมเดลเหล่านี้ได้รับการให้บริการอย่างถูกต้อง +3. สุดท้าย เราทำการทดสอบประสิทธิภาพ (Benchmark) การรวมกันของโมเดล/ผู้ให้บริการ และได้รายชื่อที่เรารู้สึกดีที่จะแนะนำ + +OpenCode Go ให้คุณเข้าถึงโมเดลเหล่านี้ในราคา **$10/เดือน** + +--- + +## วิธีการทำงาน + +OpenCode Go ทำงานเหมือนกับผู้ให้บริการรายอื่นใน OpenCode + +1. ลงชื่อเข้าใช้ **OpenCode Zen** สมัครสมาชิก Go และคัดลอกคีย์ API ของคุณ +2. รันคำสั่ง `/connect` ใน TUI เลือก `OpenCode Go` และวางคีย์ API ของคุณ +3. รัน `/models` ใน TUI เพื่อดูรายชื่อโมเดลที่สามารถใช้งานได้ผ่าน Go + +:::note +สมาชิกเพียงหนึ่งคนต่อพื้นที่ทำงาน (Workspace) เท่านั้นที่สามารถสมัครสมาชิก OpenCode Go ได้ +::: + +รายชื่อโมเดลปัจจุบันประกอบด้วย: + +- **GLM-5** +- **Kimi K2.5** +- **MiniMax M2.5** + +รายชื่อโมเดลอาจมีการเปลี่ยนแปลงเมื่อเราทดสอบและเพิ่มโมเดลใหม่ + +--- + +## ขีดจำกัดการใช้งาน + +OpenCode Go มีขีดจำกัดดังต่อไปนี้: + +- **ขีดจำกัด 5 ชั่วโมง** — การใช้งานมูลค่า $12 +- **ขีดจำกัดรายสัปดาห์** — การใช้งานมูลค่า $30 +- **ขีดจำกัดรายเดือน** — การใช้งานมูลค่า $60 + +ขีดจำกัดถูกกำหนดเป็นมูลค่าดอลลาร์ ซึ่งหมายความว่าจำนวนคำขอจริงของคุณจะขึ้นอยู่กับโมเดลที่คุณใช้ โมเดลที่ถูกกว่าเช่น MiniMax M2.5 อนุญาตให้ส่งคำขอได้มากกว่า ในขณะที่โมเดลที่มีราคาสูงกว่าเช่น GLM-5 จะอนุญาตให้ส่งคำขอได้น้อยกว่า + +ตารางด้านล่างแสดงจำนวนคำขอโดยประมาณตามรูปแบบการใช้งาน Go ทั่วไป: + +| | GLM-5 | Kimi K2.5 | MiniMax M2.5 | +| ----------------- | ----- | --------- | ------------ | +| คำขอต่อ 5 ชั่วโมง | 1,150 | 1,850 | 30,000 | +| คำขอต่อสัปดาห์ | 2,880 | 4,630 | 75,000 | +| คำขอต่อเดือน | 5,750 | 9,250 | 150,000 | + +การประมาณการขึ้นอยู่กับรูปแบบคำขอเฉลี่ยที่สังเกตได้: + +- GLM-5 — 700 input, 52,000 cached, 150 output tokens ต่อคำขอ +- Kimi K2.5 — 870 input, 55,000 cached, 200 output tokens ต่อคำขอ +- MiniMax M2.5 — 300 input, 55,000 cached, 125 output tokens ต่อคำขอ + +คุณสามารถติดตามการใช้งานปัจจุบันของคุณได้ใน **คอนโซล** + +:::tip +หากคุณใช้งานจนถึงขีดจำกัด คุณสามารถใช้โมเดลฟรีต่อไปได้ +::: + +ขีดจำกัดการใช้งานอาจมีการเปลี่ยนแปลงเมื่อเราเรียนรู้จากการใช้งานและข้อเสนอแนะในช่วงแรก + +--- + +### ราคา + +OpenCode Go เป็นแผนการสมัครสมาชิกราคา **$10/เดือน** ด้านล่างคือราคา**ต่อ 1 ล้านโทเค็น** + +| Model | Input | Output | Cached Read | +| ------------ | ----- | ------ | ----------- | +| GLM-5 | $1.00 | $3.20 | $0.20 | +| Kimi K2.5 | $0.60 | $3.00 | $0.10 | +| MiniMax M2.5 | $0.30 | $1.20 | $0.03 | + +--- + +### การใช้งานเกินขีดจำกัด + +หากคุณมีเครดิตในยอดคงเหลือ Zen ของคุณ คุณสามารถเปิดใช้งานตัวเลือก **Use balance** ในคอนโซล เมื่อเปิดใช้งาน Go จะเปลี่ยนไปใช้ยอดคงเหลือ Zen ของคุณหลังจากที่คุณใช้งานถึงขีดจำกัดแล้ว แทนที่จะบล็อกคำขอ + +--- + +## Endpoints + +คุณยังสามารถเข้าถึงโมเดล Go ผ่าน API endpoints ต่อไปนี้ + +| Model | Model ID | Endpoint | AI SDK Package | +| ------------ | ------------ | ------------------------------------------------ | --------------------------- | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | + +[รหัสโมเดล](/docs/config/#models) ในการกำหนดค่า OpenCode ของคุณใช้รูปแบบ `opencode-go/` ตัวอย่างเช่น สำหรับ Kimi K2.5 คุณจะใช้ `opencode-go/kimi-k2.5` ในการกำหนดค่าของคุณ + +--- + +## ความเป็นส่วนตัว + +แผนนี้ออกแบบมาสำหรับผู้ใช้ระดับนานาชาติเป็นหลัก โดยมีโมเดลโฮสต์อยู่ในสหรัฐอเมริกา สหภาพยุโรป และสิงคโปร์ เพื่อการเข้าถึงที่เสถียรทั่วโลก + +ติดต่อเรา หากคุณมีข้อสงสัยใดๆ + +--- + +## เป้าหมาย + +เราสร้าง OpenCode Go เพื่อ: + +1. ทำให้การเขียนโค้ดด้วย AI **เข้าถึงได้** สำหรับผู้คนมากขึ้นด้วยการสมัครสมาชิกราคาประหยัด +2. ให้การเข้าถึงโมเดลการเขียนโค้ดแบบเปิดที่ดีที่สุดอย่าง **น่าเชื่อถือ** +3. คัดสรรโมเดลที่ผ่านการ **ทดสอบและวัดประสิทธิภาพ** สำหรับการใช้งานตัวแทน (Agent) เขียนโค้ด +4. **ไม่มีการผูกมัด** โดยอนุญาตให้คุณใช้ผู้ให้บริการรายอื่นกับ OpenCode ได้เช่นกัน diff --git a/packages/web/src/content/docs/th/keybinds.mdx b/packages/web/src/content/docs/th/keybinds.mdx index 2fbcd02a6e3..8cc7586e5f1 100644 --- a/packages/web/src/content/docs/th/keybinds.mdx +++ b/packages/web/src/content/docs/th/keybinds.mdx @@ -3,11 +3,11 @@ title: ปุ่มลัด description: ปรับแต่งปุ่มลัดของคุณ --- -OpenCode มีรายการปุ่มลัดที่คุณปรับแต่งได้ผ่านการกำหนดค่า OpenCode +OpenCode มีรายการปุ่มลัดที่คุณปรับแต่งได้ผ่าน `tui.json` -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "keybinds": { "leader": "ctrl+x", "app_exit": "ctrl+c,ctrl+d,q", @@ -117,11 +117,11 @@ OpenCode ใช้ปุ่ม `leader` สำหรับการเชื่ ## ปิดการใช้งานการผูกปุ่ม -คุณสามารถปิดการใช้งานการผูกปุ่มได้โดยการเพิ่มคีย์ลงในการกำหนดค่าของคุณด้วยค่า "none" +คุณสามารถปิดการใช้งานการผูกปุ่มได้โดยการเพิ่มคีย์ลงใน `tui.json` ด้วยค่า "none" -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "keybinds": { "session_compact": "none" } diff --git a/packages/web/src/content/docs/th/lsp.mdx b/packages/web/src/content/docs/th/lsp.mdx index 0e5e28c4a6f..91d3f479fb0 100644 --- a/packages/web/src/content/docs/th/lsp.mdx +++ b/packages/web/src/content/docs/th/lsp.mdx @@ -11,40 +11,41 @@ OpenCode ทำงานร่วมกับ Language Server Protocol (LSP) เ OpenCode มาพร้อมกับเซิร์ฟเวอร์ LSP ในตัวหลายตัวสำหรับภาษายอดนิยม: -| LSP เซิร์ฟเวอร์ | ส่วนขยาย | ความต้องการ | -| -------------------- | ------------------------------------------------------------------- | ------------------------------------------------------- | -| astro | .astro | ติดตั้งอัตโนมัติสำหรับโปรเจ็กต์ Astro | -| bash | .sh, .bash, .zsh, .ksh | ติดตั้ง bash-Language-Server โดยอัตโนมัติ | -| clang | .c, .cpp, .cc, .cxx, .c++, .h, .hpp, .hh, .hxx, .h++ | ติดตั้งอัตโนมัติสำหรับโครงการ C/C++ | -| csharp | .cs | `.NET SDK` ติดตั้งแล้ว | -| clojure-lsp | .clj, .cljs, .cljc, .edn | `clojure-lsp` คำสั่งใช้ได้ | -| dart | .dart | `dart` คำสั่งใช้ได้ | -| deno | .ts, .tsx, .js, .jsx, .mjs | มีคำสั่ง `deno` (ตรวจจับอัตโนมัติ deno.json/deno.jsonc) | -| elixir-ls | .ex, .exs | `elixir` คำสั่งใช้ได้ | -| eslint | .ts, .tsx, .js, .jsx, .mjs, .cjs, .mts, .cts, .vue | `eslint` การพึ่งพาในโครงการ | -| fsharp | .fs, .fsi, .fsx, .fsscript | `.NET SDK` ติดตั้งแล้ว | -| gleam | .gleam | `gleam` คำสั่งใช้ได้ | -| gopls | .go | `go` คำสั่งใช้ได้ | -| haskell | .hs, .lhs | `haskell-language-server-wrapper` คำสั่งใช้ได้ | -| jdtls | .java | `Java SDK (version 21+)` ติดตั้งแล้ว | -| kotlin-ls | .kt, .kts | ติดตั้งอัตโนมัติสำหรับโปรเจ็กต์ Kotlin | -| lua-ls | .lua | ติดตั้งอัตโนมัติสำหรับโปรเจ็กต์ Lua | -| nix | .nix | `nixd` คำสั่งใช้ได้ | -| ocaml-lsp | .ml, .mli | `ocamllsp` คำสั่งใช้ได้ | -| oxlint | .ts, .tsx, .js, .jsx, .mjs, .cjs, .mts, .cts, .vue, .astro, .svelte | `oxlint` การพึ่งพาในโครงการ | -| php intelephense.php | .php | ติดตั้งอัตโนมัติสำหรับโครงการ PHP | -| prisma | .prisma | `prisma` คำสั่งใช้ได้ | -| pyright | .py, .pyi | `pyright` ติดตั้งการพึ่งพาแล้ว | -| ruby-lsp (rubocop) | .rb, .rake, .gemspec, .ru | มีคำสั่ง `ruby` และ `gem` | -| rust | .rs | `rust-analyzer` คำสั่งใช้ได้ | -| sourcekit-lsp | .swift, .objc, .objcpp | ติดตั้ง `swift` (`xcode` บน macOS) | -| svelte | .svelte | ติดตั้งอัตโนมัติสำหรับโครงการ Svelte | -| terraform | .tf, .tfvars | ติดตั้งอัตโนมัติจากรุ่น GitHub | -| tinymist | .typ, .typst | ติดตั้งอัตโนมัติจากรุ่น GitHub | -| typescript | .ts, .tsx, .js, .jsx, .mjs, .cjs, .mts, .cts | `typescript` การพึ่งพาในโครงการ | -| vue | .vue | ติดตั้งอัตโนมัติสำหรับโปรเจ็กต์ Vue | -| yaml-ls | .yaml, .yml | ติดตั้งเซิร์ฟเวอร์ภาษา Red Hat yaml โดยอัตโนมัติ | -| zls | .zig, .zon | `zig` คำสั่งใช้ได้ | +| LSP เซิร์ฟเวอร์ | ส่วนขยาย | ความต้องการ | +| ------------------ | ------------------------------------------------------------------- | ------------------------------------------------------- | +| astro | .astro | ติดตั้งอัตโนมัติสำหรับโปรเจ็กต์ Astro | +| bash | .sh, .bash, .zsh, .ksh | ติดตั้ง bash-Language-Server โดยอัตโนมัติ | +| clangd | .c, .cpp, .cc, .cxx, .c++, .h, .hpp, .hh, .hxx, .h++ | ติดตั้งอัตโนมัติสำหรับโครงการ C/C++ | +| csharp | .cs | `.NET SDK` ติดตั้งแล้ว | +| clojure-lsp | .clj, .cljs, .cljc, .edn | `clojure-lsp` คำสั่งใช้ได้ | +| dart | .dart | `dart` คำสั่งใช้ได้ | +| deno | .ts, .tsx, .js, .jsx, .mjs | มีคำสั่ง `deno` (ตรวจจับอัตโนมัติ deno.json/deno.jsonc) | +| elixir-ls | .ex, .exs | `elixir` คำสั่งใช้ได้ | +| eslint | .ts, .tsx, .js, .jsx, .mjs, .cjs, .mts, .cts, .vue | `eslint` การพึ่งพาในโครงการ | +| fsharp | .fs, .fsi, .fsx, .fsscript | `.NET SDK` ติดตั้งแล้ว | +| gleam | .gleam | `gleam` คำสั่งใช้ได้ | +| gopls | .go | `go` คำสั่งใช้ได้ | +| hls | .hs, .lhs | `haskell-language-server-wrapper` คำสั่งใช้ได้ | +| jdtls | .java | `Java SDK (version 21+)` ติดตั้งแล้ว | +| julials | .jl | ติดตั้ง `julia` และ `LanguageServer.jl` แล้ว | +| kotlin-ls | .kt, .kts | ติดตั้งอัตโนมัติสำหรับโปรเจ็กต์ Kotlin | +| lua-ls | .lua | ติดตั้งอัตโนมัติสำหรับโปรเจ็กต์ Lua | +| nixd | .nix | `nixd` คำสั่งใช้ได้ | +| ocaml-lsp | .ml, .mli | `ocamllsp` คำสั่งใช้ได้ | +| oxlint | .ts, .tsx, .js, .jsx, .mjs, .cjs, .mts, .cts, .vue, .astro, .svelte | `oxlint` การพึ่งพาในโครงการ | +| php intelephense | .php | ติดตั้งอัตโนมัติสำหรับโครงการ PHP | +| prisma | .prisma | `prisma` คำสั่งใช้ได้ | +| pyright | .py, .pyi | `pyright` ติดตั้งการพึ่งพาแล้ว | +| ruby-lsp (rubocop) | .rb, .rake, .gemspec, .ru | มีคำสั่ง `ruby` และ `gem` | +| rust | .rs | `rust-analyzer` คำสั่งใช้ได้ | +| sourcekit-lsp | .swift, .objc, .objcpp | ติดตั้ง `swift` (`xcode` บน macOS) | +| svelte | .svelte | ติดตั้งอัตโนมัติสำหรับโครงการ Svelte | +| terraform | .tf, .tfvars | ติดตั้งอัตโนมัติจากรุ่น GitHub | +| tinymist | .typ, .typc | ติดตั้งอัตโนมัติจากรุ่น GitHub | +| typescript | .ts, .tsx, .js, .jsx, .mjs, .cjs, .mts, .cts | `typescript` การพึ่งพาในโครงการ | +| vue | .vue | ติดตั้งอัตโนมัติสำหรับโปรเจ็กต์ Vue | +| yaml-ls | .yaml, .yml | ติดตั้งเซิร์ฟเวอร์ภาษา Red Hat yaml โดยอัตโนมัติ | +| zls | .zig, .zon | `zig` คำสั่งใช้ได้ | เซิร์ฟเวอร์ LSP จะถูกเปิดใช้งานโดยอัตโนมัติเมื่อตรวจพบนามสกุลไฟล์ใดนามสกุลหนึ่งข้างต้นและเป็นไปตามข้อกำหนด diff --git a/packages/web/src/content/docs/th/plugins.mdx b/packages/web/src/content/docs/th/plugins.mdx index a2d74ceb9cb..e672715a4ef 100644 --- a/packages/web/src/content/docs/th/plugins.mdx +++ b/packages/web/src/content/docs/th/plugins.mdx @@ -308,6 +308,10 @@ export const CustomToolsPlugin: Plugin = async (ctx) => { เครื่องมือที่คุณกำหนดเองจะพร้อมใช้งานสำหรับ opencode ควบคู่ไปกับเครื่องมือในตัว +:::note +หากเครื่องมือปลั๊กอินใช้ชื่อเดียวกับเครื่องมือในตัว เครื่องมือปลั๊กอินจะมีความสำคัญเหนือกว่า +::: + --- ### การบันทึก diff --git a/packages/web/src/content/docs/th/providers.mdx b/packages/web/src/content/docs/th/providers.mdx index e9fbb351d7d..122ade42772 100644 --- a/packages/web/src/content/docs/th/providers.mdx +++ b/packages/web/src/content/docs/th/providers.mdx @@ -84,6 +84,37 @@ OpenCode Zen คือรายชื่อโมเดลที่จัดท --- +## OpenCode Go + +OpenCode Go คือแผนการสมัครสมาชิกราคาประหยัดที่ให้การเข้าถึงโมเดลการเขียนโค้ดแบบเปิดยอดนิยมที่เชื่อถือได้ ซึ่งจัดเตรียมโดยทีมงาน OpenCode ที่ได้รับการทดสอบและตรวจสอบแล้วว่าทำงานได้ดีกับ OpenCode + +1. เรียกใช้คำสั่ง `/connect` ใน TUI เลือก `OpenCode Go` และไปที่ [opencode.ai/auth](https://opencode.ai/zen) + + ```txt + /connect + ``` + +2. ลงชื่อเข้าใช้ เพิ่มรายละเอียดการเรียกเก็บเงินของคุณ และคัดลอกรหัส API ของคุณ + +3. วางคีย์ API ของคุณ + + ```txt + ┌ API key + │ + │ + └ enter + ``` + +4. เรียกใช้ `/models` ใน TUI เพื่อดูรายการรุ่นที่เราแนะนำ + + ```txt + /models + ``` + +มันทำงานเหมือนกับผู้ให้บริการรายอื่นใน OpenCode และเป็นทางเลือกในการใช้งานโดยสมบูรณ์ + +--- + ## ไดเรกทอรี มาดูรายละเอียดผู้ให้บริการบางรายกัน หากคุณต้องการเพิ่มผู้ให้บริการให้กับ @@ -1354,6 +1385,583 @@ OpenCode Zen คือรายการโมเดลที่ได้รั └ enter ``` +4. รันคำสั่ง `/models` เพื่อเลือกโมเดลเช่น _Qwen 3 Coder 480B_ + + ```txt + /models + ``` + +--- + +### OpenRouter + +1. ไปที่ [OpenRouter dashboard](https://openrouter.ai/settings/keys) คลิก **Create API Key** และคัดลอกคีย์ + +2. เรียกใช้คำสั่ง `/connect` และค้นหา OpenRouter + + ```txt + /connect + ``` + +3. ป้อนคีย์ API สำหรับผู้ให้บริการ + + ```txt + ┌ API key + │ + │ + └ enter + ``` + +4. โมเดล OpenRouter จำนวนมากถูกโหลดไว้ล่วงหน้าตามค่าเริ่มต้น รันคำสั่ง `/models` เพื่อเลือกโมเดลที่คุณต้องการ + + ```txt + /models + ``` + + คุณยังสามารถเพิ่มโมเดลเพิ่มเติมผ่านการกำหนดค่า opencode ของคุณได้ + + ```json title="opencode.json" {6} + { + "$schema": "https://opencode.ai/config.json", + "provider": { + "openrouter": { + "models": { + "somecoolnewmodel": {} + } + } + } + } + ``` + +5. คุณยังสามารถปรับแต่งได้ผ่านการกำหนดค่า opencode ของคุณ นี่คือตัวอย่างการระบุผู้ให้บริการ + + ```json title="opencode.json" + { + "$schema": "https://opencode.ai/config.json", + "provider": { + "openrouter": { + "models": { + "moonshotai/kimi-k2": { + "options": { + "provider": { + "order": ["baseten"], + "allow_fallbacks": false + } + } + } + } + } + } + } + ``` + +--- + +### SAP AI Core + +SAP AI Core ให้การเข้าถึงโมเดลมากกว่า 40 รุ่นจาก OpenAI, Anthropic, Google, Amazon, Meta, Mistral และ AI21 ผ่านแพลตฟอร์มเดียว + +1. ไปที่ [SAP BTP Cockpit](https://account.hana.ondemand.com/) ของคุณ ไปที่อินสแตนซ์บริการ SAP AI Core และสร้างคีย์บริการ + + :::tip + คีย์บริการคือวัตถุ JSON ที่มี `clientid`, `clientsecret`, `url` และ `serviceurls.AI_API_URL` คุณสามารถค้นหาอินสแตนซ์ AI Core ของคุณได้ภายใต้ **Services** > **Instances and Subscriptions** ใน BTP Cockpit + ::: + +2. เรียกใช้คำสั่ง `/connect` และค้นหา **SAP AI Core** + + ```txt + /connect + ``` + +3. ป้อน JSON คีย์บริการของคุณ + + ```txt + ┌ Service key + │ + │ + └ enter + ``` + + หรือตั้งค่าตัวแปรสภาพแวดล้อม `AICORE_SERVICE_KEY`: + + ```bash + AICORE_SERVICE_KEY='{"clientid":"...","clientsecret":"...","url":"...","serviceurls":{"AI_API_URL":"..."}}' opencode + ``` + + หรือเพิ่มลงในโปรไฟล์ทุบตีของคุณ: + + ```bash title="~/.bash_profile" + export AICORE_SERVICE_KEY='{"clientid":"...","clientsecret":"...","url":"...","serviceurls":{"AI_API_URL":"..."}}' + ``` + +4. เลือกตั้งค่ารหัสการปรับใช้และกลุ่มทรัพยากร: + + ```bash + AICORE_DEPLOYMENT_ID=your-deployment-id AICORE_RESOURCE_GROUP=your-resource-group opencode + ``` + + :::note + การตั้งค่าเหล่านี้เป็นทางเลือกและควรกำหนดค่าตามการตั้งค่า SAP AI Core ของคุณ + ::: + +5. รันคำสั่ง `/models` เพื่อเลือกจาก 40+ รุ่นที่มีอยู่ + + ```txt + /models + ``` + +--- + +### STACKIT + +STACKIT AI Model Serving มอบสภาพแวดล้อมการโฮสต์ที่มีการจัดการเต็มรูปแบบสำหรับโมเดล AI โดยเน้นที่ LLM เช่น Llama, Mistral และ Qwen โดยมีอธิปไตยของข้อมูลสูงสุดบนโครงสร้างพื้นฐานยุโรป + +1. ไปที่ [STACKIT Portal](https://portal.stackit.cloud) ไปที่ **AI Model Serving** และสร้างโทเค็นการตรวจสอบสิทธิ์สำหรับโครงการของคุณ + + :::tip + คุณต้องมีบัญชีลูกค้า STACKIT บัญชีผู้ใช้ และโครงการก่อนสร้างโทเค็นการตรวจสอบสิทธิ์ + ::: + +2. เรียกใช้คำสั่ง `/connect` และค้นหา **STACKIT** + + ```txt + /connect + ``` + +3. ป้อนโทเค็นการตรวจสอบสิทธิ์ STACKIT AI Model Serving ของคุณ + + ```txt + ┌ API key + │ + │ + └ enter + ``` + +4. รันคำสั่ง `/models` เพื่อเลือกจากโมเดลที่มีอยู่ เช่น _Qwen3-VL 235B_ หรือ _Llama 3.3 70B_ + + ```txt + /models + ``` + +--- + +### OVHcloud AI Endpoints + +1. ไปที่ [OVHcloud panel](https://ovh.com/manager) ไปที่ส่วน `Public Cloud`, `AI & Machine Learning` > `AI Endpoints` และในแท็บ `API Keys` ให้คลิก **Create a new API key** + +2. เรียกใช้คำสั่ง `/connect` และค้นหา **OVHcloud AI Endpoints** + + ```txt + /connect + ``` + +3. ป้อนคีย์ API OVHcloud AI Endpoints ของคุณ + + ```txt + ┌ API key + │ + │ + └ enter + ``` + +4. รันคำสั่ง `/models` เพื่อเลือกโมเดลเช่น _gpt-oss-120b_ + + ```txt + /models + ``` + +--- + +### Scaleway + +วิธีใช้ [Scaleway Generative APIs](https://www.scaleway.com/en/docs/generative-apis/) กับ Opencode: + +1. ไปที่ [Scaleway Console IAM settings](https://console.scaleway.com/iam/api-keys) เพื่อสร้างคีย์ API ใหม่ + +2. เรียกใช้คำสั่ง `/connect` และค้นหา **Scaleway** + + ```txt + /connect + ``` + +3. ป้อนคีย์ Scaleway API ของคุณ + + ```txt + ┌ API key + │ + │ + └ enter + ``` + +4. รันคำสั่ง `/models` เพื่อเลือกโมเดลเช่น _devstral-2-123b-instruct-2512_ หรือ _gpt-oss-120b_ + + ```txt + /models + ``` + +--- + +### Together AI + +1. ไปที่ [Together AI console](https://api.together.ai) สร้างบัญชี และคลิก **Add Key** + +2. เรียกใช้คำสั่ง `/connect` และค้นหา **Together AI** + + ```txt + /connect + ``` + +3. ป้อนคีย์ Together AI API ของคุณ + + ```txt + ┌ API key + │ + │ + └ enter + ``` + +4. รันคำสั่ง `/models` เพื่อเลือกโมเดลเช่น _Kimi K2 Instruct_ + + ```txt + /models + ``` + +--- + +### Venice AI + +1. ไปที่ [Venice AI console](https://venice.ai) สร้างบัญชี และสร้างคีย์ API + +2. เรียกใช้คำสั่ง `/connect` และค้นหา **Venice AI** + + ```txt + /connect + ``` + +3. ป้อนคีย์ Venice AI API ของคุณ + + ```txt + ┌ API key + │ + │ + └ enter + ``` + +4. รันคำสั่ง `/models` เพื่อเลือกโมเดลเช่น _Llama 3.3 70B_ + + ```txt + /models + ``` + +--- + +### Vercel AI Gateway + +Vercel AI Gateway ช่วยให้คุณเข้าถึงโมเดลจาก OpenAI, Anthropic, Google, xAI และอื่นๆ อีกมากมายผ่านจุดสิ้นสุดแบบรวม โมเดลถูกนำเสนอในราคาปลีกโดยไม่มีการบวกเพิ่ม + +1. ไปที่ [Vercel dashboard](https://vercel.com/) ไปที่แท็บ **AI Gateway** และคลิก **API keys** เพื่อสร้างคีย์ API ใหม่ + +2. เรียกใช้คำสั่ง `/connect` และค้นหา **Vercel AI Gateway** + + ```txt + /connect + ``` + +3. ป้อนคีย์ Vercel AI Gateway API ของคุณ + + ```txt + ┌ API key + │ + │ + └ enter + ``` + +4. รันคำสั่ง `/models` เพื่อเลือกรุ่น + + ```txt + /models + ``` + +คุณยังสามารถปรับแต่งโมเดลผ่านการกำหนดค่า opencode ของคุณได้ นี่คือตัวอย่างการระบุลำดับการกำหนดเส้นทางผู้ให้บริการ + +```json title="opencode.json" +{ + "$schema": "https://opencode.ai/config.json", + "provider": { + "vercel": { + "models": { + "anthropic/claude-sonnet-4": { + "options": { + "order": ["anthropic", "vertex"] + } + } + } + } + } +} +``` + +ตัวเลือกการกำหนดเส้นทางที่มีประโยชน์บางประการ: + +| ตัวเลือก | คำอธิบาย | +| ------------------- | ---------------------------------------------------------- | +| `order` | ลำดับผู้ให้บริการที่จะลอง | +| `only` | จำกัดเฉพาะผู้ให้บริการที่ระบุ | +| `zeroDataRetention` | ใช้เฉพาะผู้ให้บริการที่มีนโยบายการเก็บรักษาข้อมูลเป็นศูนย์ | + +--- + +### xAI + +1. ไปที่ [xAI console](https://console.x.ai/) สร้างบัญชี และสร้างคีย์ API + +2. เรียกใช้คำสั่ง `/connect` และค้นหา **xAI** + + ```txt + /connect + ``` + +3. ป้อนคีย์ xAI API ของคุณ + + ```txt + ┌ API key + │ + │ + └ enter + ``` + +4. รันคำสั่ง `/models` เพื่อเลือกโมเดลเช่น _Grok Beta_ + + ```txt + /models + ``` + +--- + +### Z.AI + +1. ไปที่ [Z.AI API console](https://z.ai/manage-apikey/apikey-list) สร้างบัญชี และคลิก **Create a new API key** + +2. เรียกใช้คำสั่ง `/connect` และค้นหา **Z.AI** + + ```txt + /connect + ``` + + หากคุณสมัครรับ **GLM Coding Plan** ให้เลือก **Z.AI Coding Plan** + +3. ป้อนคีย์ Z.AI API ของคุณ + + ```txt + ┌ API key + │ + │ + └ enter + ``` + +4. รันคำสั่ง `/models` เพื่อเลือกโมเดลเช่น _GLM-4.7_ + + ```txt + /models + ``` + +--- + +### ZenMux + +1. ไปที่ [ZenMux dashboard](https://zenmux.ai/settings/keys) คลิก **Create API Key** และคัดลอกคีย์ + +2. เรียกใช้คำสั่ง `/connect` และค้นหา ZenMux + + ```txt + /connect + ``` + +3. ป้อนคีย์ API สำหรับผู้ให้บริการ + + ```txt + ┌ API key + │ + │ + └ enter + ``` + +4. โมเดล ZenMux จำนวนมากถูกโหลดไว้ล่วงหน้าตามค่าเริ่มต้น รันคำสั่ง `/models` เพื่อเลือกโมเดลที่คุณต้องการ + + ```txt + /models + ``` + + คุณยังสามารถเพิ่มโมเดลเพิ่มเติมผ่านการกำหนดค่า opencode ของคุณได้ + + ```json title="opencode.json" {6} + { + "$schema": "https://opencode.ai/config.json", + "provider": { + "zenmux": { + "models": { + "somecoolnewmodel": {} + } + } + } + } + ``` + +--- + +## ผู้ให้บริการที่กำหนดเอง + +หากต้องการเพิ่มผู้ให้บริการที่ **เข้ากันได้กับ OpenAI** ใดๆ ที่ไม่มีรายชื่ออยู่ในคำสั่ง `/connect`: + +:::tip +คุณสามารถใช้ผู้ให้บริการที่เข้ากันได้กับ OpenAI กับ opencode ได้ ผู้ให้บริการ AI สมัยใหม่ส่วนใหญ่เสนอ API ที่เข้ากันได้กับ OpenAI +::: + +1. เรียกใช้คำสั่ง `/connect` และเลื่อนลงไปที่ **Other** + + ```bash + $ /connect + + ┌ Add credential + │ + ◆ Select provider + │ ... + │ ● Other + └ + ``` + +2. ป้อน ID เฉพาะสำหรับผู้ให้บริการ + + ```bash + $ /connect + + ┌ Add credential + │ + ◇ Enter provider id + │ myprovider + └ + ``` + + :::note + เลือก ID ที่น่าจดจำ คุณจะใช้ ID นี้ในไฟล์กำหนดค่าของคุณ + ::: + +3. ป้อนคีย์ API สำหรับผู้ให้บริการ + + ```bash + $ /connect + + ┌ Add credential + │ + ▲ This only stores a credential for myprovider - you will need to configure it in opencode.json, check the docs for examples. + │ + ◇ Enter your API key + │ sk-... + └ + ``` + +4. สร้างหรืออัปเดตไฟล์ `opencode.json` ของคุณในไดเร็กทอรีโครงการของคุณ: + + ```json title="opencode.json" ""myprovider"" {5-15} + { + "$schema": "https://opencode.ai/config.json", + "provider": { + "myprovider": { + "npm": "@ai-sdk/openai-compatible", + "name": "My AI ProviderDisplay Name", + "options": { + "baseURL": "https://api.myprovider.com/v1" + }, + "models": { + "my-model-name": { + "name": "My Model Display Name" + } + } + } + } + } + ``` + + นี่คือตัวเลือกการกำหนดค่า: + - **npm**: แพ็คเกจ AI SDK ที่จะใช้ `@ai-sdk/openai-compatible` สำหรับผู้ให้บริการที่เข้ากันได้กับ OpenAI + - **name**: ชื่อที่แสดงใน UI + - **models**: โมเดลที่มีจำหน่าย + - **options.baseURL**: URL จุดสิ้นสุด API + - **options.apiKey**: ตั้งค่าคีย์ API ทางเลือก หากไม่ได้ใช้การรับรองความถูกต้อง + - **options.headers**: ตั้งค่าส่วนหัวที่กำหนดเองได้ตามต้องการ + + ข้อมูลเพิ่มเติมเกี่ยวกับตัวเลือกขั้นสูงในตัวอย่างด้านล่าง + +5. รันคำสั่ง `/models` และผู้ให้บริการและโมเดลที่คุณกำหนดเองจะปรากฏในรายการตัวเลือก + +--- + +##### ตัวอย่าง + +นี่คือตัวอย่างการตั้งค่าตัวเลือก `apiKey`, `headers` และรุ่น `limit` + +```json title="opencode.json" {9,11,17-20} +{ + "$schema": "https://opencode.ai/config.json", + "provider": { + "myprovider": { + "npm": "@ai-sdk/openai-compatible", + "name": "My AI ProviderDisplay Name", + "options": { + "baseURL": "https://api.myprovider.com/v1", + "apiKey": "{env:ANTHROPIC_API_KEY}", + "headers": { + "Authorization": "Bearer custom-token" + } + }, + "models": { + "my-model-name": { + "name": "My Model Display Name", + "limit": { + "context": 200000, + "output": 65536 + } + } + } + } + } +} +``` + +รายละเอียดการกำหนดค่า: + +- **apiKey**: ตั้งค่าโดยใช้ไวยากรณ์ตัวแปร `env` [เรียนรู้เพิ่มเติม](/docs/config#env-vars) +- **headers**: ส่วนหัวที่กำหนดเองที่ส่งไปกับแต่ละคำขอ +- **limit.context**: โทเค็นอินพุตสูงสุดที่โมเดลยอมรับ +- **limit.output**: โทเค็นสูงสุดที่โมเดลสามารถสร้างได้ + +ฟิลด์ `limit` ช่วยให้ OpenCode เข้าใจว่าคุณมีบริบทเหลืออยู่เท่าใด ผู้ให้บริการมาตรฐานจะดึงข้อมูลเหล่านี้จาก models.dev โดยอัตโนมัติ + +--- + +## การแก้ไขปัญหา + +หากคุณประสบปัญหาในการกำหนดค่าผู้ให้บริการ ให้ตรวจสอบสิ่งต่อไปนี้: + +1. **ตรวจสอบการตั้งค่าการรับรองความถูกต้อง**: รัน `opencode auth list` เพื่อดูว่ามีการเพิ่มข้อมูลรับรอง + สำหรับผู้ให้บริการในการกำหนดค่าของคุณหรือไม่ + + สิ่งนี้ใช้ไม่ได้กับผู้ให้บริการเช่น Amazon Bedrock ซึ่งอาศัยตัวแปรสภาพแวดล้อมสำหรับการตรวจสอบสิทธิ์ + +2. สำหรับผู้ให้บริการที่กำหนดเอง ให้ตรวจสอบการกำหนดค่า opencode และ: + - ตรวจสอบให้แน่ใจว่า ID ผู้ให้บริการที่ใช้ในคำสั่ง `/connect` ตรงกับ ID ในการกำหนดค่า opencode ของคุณ + - ใช้แพ็คเกจ npm ที่ถูกต้องสำหรับผู้ให้บริการ ตัวอย่างเช่น ใช้ `@ai-sdk/cerebras` สำหรับ Cerebras และสำหรับผู้ให้บริการอื่นๆ ที่เข้ากันได้กับ OpenAI ให้ใช้ `@ai-sdk/openai-compatible` + - ตรวจสอบว่าใช้จุดสิ้นสุด API ที่ถูกต้องในฟิลด์ `options.baseURL` + +3. ป้อนคีย์ OpenCode API ของคุณ + + ```txt + ┌ API key + │ + │ + └ enter + ``` + 4. รันคำสั่ง `/models` เพื่อเลือกรุ่นเช่น _Qwen 3 Coder 480B_ ```txt diff --git a/packages/web/src/content/docs/th/sdk.mdx b/packages/web/src/content/docs/th/sdk.mdx index b592c1a4e4b..3454b576670 100644 --- a/packages/web/src/content/docs/th/sdk.mdx +++ b/packages/web/src/content/docs/th/sdk.mdx @@ -117,6 +117,78 @@ try { --- +## ผลลัพธ์แบบมีโครงสร้าง + +คุณสามารถร้องขอผลลัพธ์ JSON แบบมีโครงสร้างจากโมเดลได้โดยระบุ `format` ด้วย JSON schema โมเดลจะใช้เครื่องมือ `StructuredOutput` เพื่อส่งคืน JSON ที่ผ่านการตรวจสอบความถูกต้องซึ่งตรงกับสคีมาของคุณ + +### การใช้งานพื้นฐาน + +```typescript +const result = await client.session.prompt({ + path: { id: sessionId }, + body: { + parts: [{ type: "text", text: "Research Anthropic and provide company info" }], + format: { + type: "json_schema", + schema: { + type: "object", + properties: { + company: { type: "string", description: "Company name" }, + founded: { type: "number", description: "Year founded" }, + products: { + type: "array", + items: { type: "string" }, + description: "Main products", + }, + }, + required: ["company", "founded"], + }, + }, + }, +}) + +// Access the structured output +console.log(result.data.info.structured_output) +// { company: "Anthropic", founded: 2021, products: ["Claude", "Claude API"] } +``` + +### ประเภทรูปแบบผลลัพธ์ + +| ประเภท | คำอธิบาย | +| ------------- | ----------------------------------------------------------------- | +| `text` | ค่าเริ่มต้น การตอบสนองข้อความมาตรฐาน (ไม่มีผลลัพธ์แบบมีโครงสร้าง) | +| `json_schema` | ส่งคืน JSON ที่ผ่านการตรวจสอบความถูกต้องซึ่งตรงกับสคีมาที่ระบุ | + +### รูปแบบ JSON Schema + +เมื่อใช้ `type: 'json_schema'` ให้ระบุ: + +| ฟิลด์ | Type | คำอธิบาย | +| ------------ | --------------- | --------------------------------------------------------------------- | +| `type` | `'json_schema'` | จำเป็น ระบุโหมด JSON schema | +| `schema` | `object` | จำเป็น วัตถุ JSON Schema ที่กำหนดโครงสร้างผลลัพธ์ | +| `retryCount` | `number` | ไม่จำเป็น จำนวนการลองใหม่สำหรับการตรวจสอบความถูกต้อง (ค่าเริ่มต้น: 2) | + +### การจัดการข้อผิดพลาด + +หากโมเดลล้มเหลวในการสร้างผลลัพธ์แบบมีโครงสร้างที่ถูกต้องหลังจากลองใหม่ครบทุกครั้ง การตอบสนองจะรวม `StructuredOutputError`: + +```typescript +if (result.data.info.error?.name === "StructuredOutputError") { + console.error("Failed to produce structured output:", result.data.info.error.message) + console.error("Attempts:", result.data.info.error.retries) +} +``` + +### แนวปฏิบัติที่ดีที่สุด + +1. **ระบุคำอธิบายที่ชัดเจน** ในคุณสมบัติสคีมาของคุณเพื่อช่วยให้โมเดลเข้าใจว่าข้อมูลใดที่ต้องดึงออกมา +2. **ใช้ `required`** เพื่อระบุฟิลด์ที่ต้องมี +3. **รักษาสคีมาให้โฟกัส** - สคีมาที่ซ้อนกันซับซ้อนอาจยากสำหรับโมเดลในการกรอกให้ถูกต้อง +4. **ตั้งค่า `retryCount` ที่เหมาะสม** - เพิ่มสำหรับสคีมาที่ซับซ้อน ลดลงสำหรับสคีมาที่เรียบง่าย + +--- + ## API SDK เปิดเผย API ของเซิร์ฟเวอร์ทั้งหมดผ่านไคลเอ็นต์ประเภทที่ปลอดภัย @@ -226,27 +298,27 @@ const { providers, default: defaults } = await client.config.providers() ### เซสชัน -| Method | คำอธิบาย | หมายเหตุ | -| ---------------------------------------------------------- | -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | -| `session.list()` | แสดงรายการเซสชัน | ส่งคืน เซสชัน[] | -| `session.get({ path })` | รับเซสชัน | ส่งคืน เซสชัน | -| `session.children({ path })` | แสดงรายการเซสชันย่อย | ส่งคืน เซสชัน[] | -| `session.create({ body })` | สร้างเซสชัน | ส่งคืน เซสชัน | -| `session.delete({ path })` | ลบเซสชัน | ส่งคืน `boolean` | -| `session.update({ path, body })` | อัปเดตคุณสมบัติเซสชัน | ส่งคืน เซสชัน | -| `session.init({ path, body })` | วิเคราะห์แอปและสร้าง `AGENTS.md` | ส่งคืน `boolean` | -| `session.abort({ path })` | ยกเลิกเซสชันที่ทำงานอยู่ | ส่งคืน `boolean` | -| `session.share({ path })` | แบ่งปันเซสชั่น | ส่งคืน เซสชัน | -| `session.unshare({ path })` | เลิกแชร์เซสชัน | ส่งคืน เซสชัน | -| `session.summarize({ path, body })` | สรุปเซสชัน | ส่งคืน `boolean` | -| `session.messages({ path })` | แสดงรายการข้อความในเซสชัน | ส่งคืน `{ info: `ข้อความ`, parts: `ส่วน[]`}[]` | -| `session.message({ path })` | รับรายละเอียดข้อความ | ส่งคืน `{ info: `ข้อความ`, parts: `ส่วน[]`}` | -| `session.prompt({ path, body })` | ส่งข้อความแจ้ง | `body.noReply: true` ส่งคืน UserMessage (บริบทเท่านั้น) ค่าเริ่มต้นส่งคืน AssistantMessage พร้อมการตอบสนองของ AI | -| `session.command({ path, body })` | ส่งคำสั่งไปยังเซสชั่น | ส่งคืน `{ info: `AssistantMessage`, parts: `ส่วน[]`}` | -| `session.shell({ path, body })` | รันคำสั่ง shell | ส่งคืน AssistantMessage | -| `session.revert({ path, body })` | คืนค่าข้อความ | ส่งคืน เซสชัน | -| `session.unrevert({ path })` | คืนค่าข้อความที่เปลี่ยนกลับ | ส่งคืน เซสชัน | -| `postSessionByIdPermissionsByPermissionId({ path, body })` | ตอบสนองต่อการร้องขอการอนุญาต | ส่งคืน `boolean` | +| Method | คำอธิบาย | หมายเหตุ | +| ---------------------------------------------------------- | -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `session.list()` | แสดงรายการเซสชัน | ส่งคืน เซสชัน[] | +| `session.get({ path })` | รับเซสชัน | ส่งคืน เซสชัน | +| `session.children({ path })` | แสดงรายการเซสชันย่อย | ส่งคืน เซสชัน[] | +| `session.create({ body })` | สร้างเซสชัน | ส่งคืน เซสชัน | +| `session.delete({ path })` | ลบเซสชัน | ส่งคืน `boolean` | +| `session.update({ path, body })` | อัปเดตคุณสมบัติเซสชัน | ส่งคืน เซสชัน | +| `session.init({ path, body })` | วิเคราะห์แอปและสร้าง `AGENTS.md` | ส่งคืน `boolean` | +| `session.abort({ path })` | ยกเลิกเซสชันที่ทำงานอยู่ | ส่งคืน `boolean` | +| `session.share({ path })` | แบ่งปันเซสชั่น | ส่งคืน เซสชัน | +| `session.unshare({ path })` | เลิกแชร์เซสชัน | ส่งคืน เซสชัน | +| `session.summarize({ path, body })` | สรุปเซสชัน | ส่งคืน `boolean` | +| `session.messages({ path })` | แสดงรายการข้อความในเซสชัน | ส่งคืน `{ info: `ข้อความ`, parts: `ส่วน[]`}[]` | +| `session.message({ path })` | รับรายละเอียดข้อความ | ส่งคืน `{ info: `ข้อความ`, parts: `ส่วน[]`}` | +| `session.prompt({ path, body })` | ส่งข้อความแจ้ง | `body.noReply: true` ส่งคืน UserMessage (บริบทเท่านั้น) ค่าเริ่มต้นส่งคืน AssistantMessage พร้อมการตอบสนองของ AI รองรับ `body.outputFormat` สำหรับ [ผลลัพธ์แบบมีโครงสร้าง](#ผลลัพธ์แบบมีโครงสร้าง) | +| `session.command({ path, body })` | ส่งคำสั่งไปยังเซสชั่น | ส่งคืน `{ info: `AssistantMessage`, parts: `ส่วน[]`}` | +| `session.shell({ path, body })` | รันคำสั่ง shell | ส่งคืน AssistantMessage | +| `session.revert({ path, body })` | คืนค่าข้อความ | ส่งคืน เซสชัน | +| `session.unrevert({ path })` | คืนค่าข้อความที่เปลี่ยนกลับ | ส่งคืน เซสชัน | +| `postSessionByIdPermissionsByPermissionId({ path, body })` | ตอบสนองต่อการร้องขอการอนุญาต | ส่งคืน `boolean` | --- diff --git a/packages/web/src/content/docs/th/themes.mdx b/packages/web/src/content/docs/th/themes.mdx index 2244c3dcf1b..44514148ea2 100644 --- a/packages/web/src/content/docs/th/themes.mdx +++ b/packages/web/src/content/docs/th/themes.mdx @@ -61,11 +61,11 @@ OpenCode มาพร้อมกับธีมในตัวหลายธ ## การใช้ธีม -คุณสามารถเลือกธีมได้โดยเปิดการเลือกธีมขึ้นมาด้วยคำสั่ง `/theme` หรือคุณสามารถระบุได้ใน [config](/docs/config) +คุณสามารถเลือกธีมได้โดยเปิดการเลือกธีมขึ้นมาด้วยคำสั่ง `/theme` หรือคุณสามารถระบุได้ใน `tui.json` -```json title="opencode.json" {3} +```json title="tui.json" {3} { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "theme": "tokyonight" } ``` diff --git a/packages/web/src/content/docs/th/tui.mdx b/packages/web/src/content/docs/th/tui.mdx index 9151462d70d..2a5064b9d72 100644 --- a/packages/web/src/content/docs/th/tui.mdx +++ b/packages/web/src/content/docs/th/tui.mdx @@ -235,7 +235,7 @@ How is auth handled in @packages/functions/src/api/index.ts? แสดงรายการธีมที่มีอยู่ ```bash frame="none" -/theme +/themes ``` **ผูกปุ่ม:** `ctrl+x t` @@ -355,24 +355,34 @@ How is auth handled in @packages/functions/src/api/index.ts? ## กำหนดค่า -คุณสามารถปรับแต่งพฤติกรรม TUI ผ่านไฟล์กำหนดค่า OpenCode ของคุณได้ +คุณสามารถปรับแต่งพฤติกรรม TUI ได้ผ่าน `tui.json` (หรือ `tui.jsonc`) -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", - "tui": { - "scroll_speed": 3, - "scroll_acceleration": { - "enabled": true - } - } + "$schema": "https://opencode.ai/tui.json", + "theme": "opencode", + "keybinds": { + "leader": "ctrl+x" + }, + "scroll_speed": 3, + "scroll_acceleration": { + "enabled": true + }, + "diff_style": "auto" } ``` +สิ่งนี้แยกจาก `opencode.json` ซึ่งกำหนดค่าพฤติกรรมเซิร์ฟเวอร์/รันไทม์ + ### ตัวเลือก -- `scroll_acceleration` - ​​เปิดใช้งานการเร่งความเร็วการเลื่อนแบบ macOS เพื่อการเลื่อนที่ราบรื่นและเป็นธรรมชาติ เมื่อเปิดใช้งาน ความเร็วในการเลื่อนจะเพิ่มขึ้นตามท่าทางการเลื่อนอย่างรวดเร็ว และคงความแม่นยำไว้สำหรับการเคลื่อนไหวที่ช้าลง **การตั้งค่านี้มีความสำคัญมากกว่า `scroll_speed` และแทนที่เมื่อเปิดใช้งาน** -- `scroll_speed` - ​​ควบคุมความเร็วของการเลื่อน TUI เมื่อใช้คำสั่งการเลื่อน (ขั้นต่ำ: `1`) ค่าเริ่มต้นเป็น `3` **หมายเหตุ: สิ่งนี้จะถูกละเว้นหากตั้งค่า `scroll_acceleration.enabled` เป็น `true`.** +- `theme` - ตั้งค่าธีม UI ของคุณ [เรียนรู้เพิ่มเติม](/docs/themes) +- `keybinds` - ปรับแต่งแป้นพิมพ์ลัด [เรียนรู้เพิ่มเติม](/docs/keybinds) +- `scroll_acceleration.enabled` - ​​เปิดใช้งานการเร่งความเร็วการเลื่อนแบบ macOS เพื่อการเลื่อนที่ราบรื่นและเป็นธรรมชาติ เมื่อเปิดใช้งาน ความเร็วในการเลื่อนจะเพิ่มขึ้นตามท่าทางการเลื่อนอย่างรวดเร็ว และคงความแม่นยำไว้สำหรับการเคลื่อนไหวที่ช้าลง **การตั้งค่านี้มีความสำคัญมากกว่า `scroll_speed` และแทนที่เมื่อเปิดใช้งาน** +- `scroll_speed` - ​​ควบคุมความเร็วของการเลื่อน TUI เมื่อใช้คำสั่งการเลื่อน (ขั้นต่ำ: `0.001` รองรับค่าทศนิยม) ค่าเริ่มต้นเป็น `3` **หมายเหตุ: สิ่งนี้จะถูกละเว้นหากตั้งค่า `scroll_acceleration.enabled` เป็น `true`.** +- `diff_style` - ควบคุมการเรนเดอร์ diff `"auto"` ปรับให้เข้ากับความกว้างของ terminal `"stacked"` จะแสดงคอลัมน์เดียวเสมอ + +ใช้ `OPENCODE_TUI_CONFIG` เพื่อโหลดเส้นทางการกำหนดค่า TUI แบบกำหนดเอง --- diff --git a/packages/web/src/content/docs/th/zen.mdx b/packages/web/src/content/docs/th/zen.mdx index f0b39ceaaa0..7b9f172756c 100644 --- a/packages/web/src/content/docs/th/zen.mdx +++ b/packages/web/src/content/docs/th/zen.mdx @@ -64,31 +64,34 @@ OpenCode Zen ทำงานเหมือนกับผู้ให้บร | Model | Model ID | Endpoint | แพ็คเกจ AI SDK | | ------------------ | ------------------ | -------------------------------------------------- | --------------------------- | -| GPT 5.2 | GPT-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 | GPT-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.1 Codex | gpt-5.1-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.1 Codex Max | gpt-5.1-codex-max | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.1 Codex Mini | gpt-5.1-codex-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 | GPT-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Haiku 3.5 | claude-haiku-3-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | | Gemini 3 Pro | gemini-3-pro | `https://opencode.ai/zen/v1/models/gemini-3-pro` | `@ai-sdk/google` | | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.1 | minimax-m2.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.1 Free | minimax-m2.1-free | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| GLM 5 | glm-5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 4.7 | glm-4.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM 4.7 Free | glm-4.7-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 4.6 | glm-4.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 Free | kimi-k2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2 Thinking | kimi-k2-thinking | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2 | kimi-k2 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Qwen3 Coder 480B | qwen3-coder | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -114,41 +117,46 @@ https://opencode.ai/zen/v1/models เราสนับสนุนรูปแบบการจ่ายเงินตามการใช้งาน ด้านล่างนี้คือราคา **ต่อtokens 1M** -| Model | ป้อนข้อมูล | เอาท์พุต | แคชอ่าน | เขียนในแคช | -| --------------------------------------- | ------------ | ------------ | ------------- | ---------- | -| Big Pickle | ฟรี | ฟรี | ฟรี | - | -| MiniMax M2.1 Free | ฟรี | ฟรี | ฟรี | - | -| Miniแม็กซ์ M2.1 | $0.30 | $1.20 | $0.10 | - | -| GLM 4.7 Free | ฟรี | ฟรี | ฟรี | - | -| GLM 4.7 | $0.60 | $2.20 | $0.10 | - | -| GLM 4.6 | $0.60 | $2.20 | $0.10 | - | -| Kimi K2.5 Free | ฟรี | ฟรี | ฟรี | - | -| Kimi K2.5 | $0.60 | $3.00 | $0.08 | - | -| Kimi K2 Thinking | $0.40 | $2.50 | - | - | -| Kimi K2 | $0.40 | $2.50 | - | - | -| Qwen3 Coder 480B | $0.45 | $1.50 | - | - | -| Claude Sonnet 4.5 (tokens ≤ 200K) | $3.00 | $15.00 | $0.30 | $3.75 | -| Claude Sonnet 4.5 (> tokens 200,000) | $6.00 | $22.50 | $0.60 | $7.50 | -| Claude Sonnet 4 (tokens 200,000 tokens) | $3.00 | $15.00 | $0.30 | $3.75 | -| Claude Sonnet 4 (> tokens 200,000) | $6.00 | $22.50 | $0.60 | $7.50 | -| Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 | -| Claude Haiku 3.5 | $0.80 | $4.00 | $0.08 | $1.00 | -| Claude Opus 4.6 (tokens ≤ 200K) | $5.00 | $25.00 | $0.50 | $6.25 | -| Claude Opus 4.6 (> tokens 200,000) | $10.00 | $37.50 | $1.00 | $12.50 | -| Claude Opus 4.5 | $5.00 | $25.00 | $0.50 | $6.25 | -| Claude Opus 4.1 | $15.00 | $75.00 | $1.50 | $18.75 | -| Gemini 3 Pro (tokens ≤ 200,000) | 2.00 ดอลลาร์ | $12.00 | $0.20 | - | -| Gemini 3 Pro (tokens> 200,000) | $4.00 | $18.00 | $0.40 | - | -| Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | -| GPT 5.2 | $1.75 | $14.00 | 0.175 ดอลลาร์ | - | -| GPT 5.2 Codex | $1.75 | $14.00 | 0.175 ดอลลาร์ | - | -| GPT 5.1 | $1.07 | 8.50 ดอลลาร์ | $0.107 | - | -| GPT 5.1 Codex | $1.07 | 8.50 ดอลลาร์ | $0.107 | - | -| GPT 5.1 CodexMax | $1.25 | $10.00 | $0.125 | - | -| GPT 5.1 CodexMini | $0.25 | 2.00 ดอลลาร์ | 0.025 ดอลลาร์ | - | -| GPT 5 | $1.07 | 8.50 ดอลลาร์ | $0.107 | - | -| GPT 5 Codex | $1.07 | 8.50 ดอลลาร์ | $0.107 | - | -| GPT 5Nano | ฟรี | ฟรี | ฟรี | - | +| Model | ป้อนข้อมูล | เอาท์พุต | แคชอ่าน | เขียนในแคช | +| --------------------------------- | ---------- | -------- | ------- | ---------- | +| Big Pickle | ฟรี | ฟรี | ฟรี | - | +| MiniMax M2.5 Free | ฟรี | ฟรี | ฟรี | - | +| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | - | +| MiniMax M2.1 | $0.30 | $1.20 | $0.10 | - | +| GLM 5 | $1.00 | $3.20 | $0.20 | - | +| GLM 4.7 | $0.60 | $2.20 | $0.10 | - | +| GLM 4.6 | $0.60 | $2.20 | $0.10 | - | +| Kimi K2.5 | $0.60 | $3.00 | $0.08 | - | +| Kimi K2 Thinking | $0.40 | $2.50 | - | - | +| Kimi K2 | $0.40 | $2.50 | - | - | +| Qwen3 Coder 480B | $0.45 | $1.50 | - | - | +| Claude Opus 4.6 (≤ 200K tokens) | $5.00 | $25.00 | $0.50 | $6.25 | +| Claude Opus 4.6 (> 200K tokens) | $10.00 | $37.50 | $1.00 | $12.50 | +| Claude Opus 4.5 | $5.00 | $25.00 | $0.50 | $6.25 | +| Claude Opus 4.1 | $15.00 | $75.00 | $1.50 | $18.75 | +| Claude Sonnet 4.6 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 | +| Claude Sonnet 4.6 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 | +| Claude Sonnet 4.5 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 | +| Claude Sonnet 4.5 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 | +| Claude Sonnet 4 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 | +| Claude Sonnet 4 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 | +| Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 | +| Claude Haiku 3.5 | $0.80 | $4.00 | $0.08 | $1.00 | +| Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | +| Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | +| Gemini 3 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | +| Gemini 3 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | +| Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | +| GPT 5.3 Codex | $1.75 | $14.00 | $0.175 | - | +| GPT 5.2 | $1.75 | $14.00 | $0.175 | - | +| GPT 5.2 Codex | $1.75 | $14.00 | $0.175 | - | +| GPT 5.1 | $1.07 | $8.50 | $0.107 | - | +| GPT 5.1 Codex | $1.07 | $8.50 | $0.107 | - | +| GPT 5.1 Codex Max | $1.25 | $10.00 | $0.125 | - | +| GPT 5.1 Codex Mini | $0.25 | $2.00 | $0.025 | - | +| GPT 5 | $1.07 | $8.50 | $0.107 | - | +| GPT 5 Codex | $1.07 | $8.50 | $0.107 | - | +| GPT 5 Nano | ฟรี | ฟรี | ฟรี | - | คุณอาจสังเกตเห็น _Claude Haiku 3.5_ ในประวัติการใช้งานของคุณ นี่คือ [โมเดลราคาประหยัด](/docs/config/#models) ที่ใช้ในการสร้างชื่อเซสชันของคุณ @@ -158,9 +166,7 @@ https://opencode.ai/zen/v1/models รุ่นฟรี: -- GLM 4.7 ใช้งานได้ฟรีบน OpenCode ในระยะเวลาจำกัด ทีมงานใช้เวลานี้เพื่อรวบรวมคำติชมและปรับปรุงโมเดล -- Kimi K2.5 Free พร้อมใช้งานบน OpenCode ในระยะเวลาจำกัด ทีมงานใช้เวลานี้เพื่อรวบรวมคำติชมและปรับปรุงโมเดล -- MiniMax M2.1 Free พร้อมใช้งานบน OpenCode ในระยะเวลาจำกัด ทีมงานใช้เวลานี้เพื่อรวบรวมคำติชมและปรับปรุงโมเดล +- MiniMax M2.5 Free พร้อมใช้งานบน OpenCode ในระยะเวลาจำกัด ทีมงานใช้เวลานี้เพื่อรวบรวมคำติชมและปรับปรุงโมเดล - Big Pickle เป็นโมเดลล่องหนที่ให้บริการฟรีบน OpenCode ในระยะเวลาจำกัด ทีมงานใช้เวลานี้เพื่อรวบรวมคำติชมและปรับปรุงโมเดล Contact us if you have any questions. @@ -191,9 +197,7 @@ https://opencode.ai/zen/v1/models โมเดลทั้งหมดของเราโฮสต์ในสหรัฐอเมริกา ผู้ให้บริการของเราปฏิบัติตามนโยบายการเก็บรักษาเป็นศูนย์ และไม่ใช้ข้อมูลของคุณสำหรับการฝึกโมเดล โดยมีข้อยกเว้นต่อไปนี้: - Big Pickle: ในช่วงระยะเวลาว่าง ข้อมูลที่รวบรวมอาจนำไปใช้ในการปรับปรุงโมเดลได้ -- GLM 4.7 Free: ในช่วงระยะเวลาฟรี ข้อมูลที่รวบรวมอาจนำไปใช้ในการปรับปรุงโมเดล -- Kimi K2.5 Free: ในช่วงระยะเวลาฟรี ข้อมูลที่รวบรวมอาจนำไปใช้ในการปรับปรุงโมเดล -- MiniMax M2.1 Free: ในช่วงระยะเวลาฟรี ข้อมูลที่รวบรวมอาจนำไปใช้ในการปรับปรุงโมเดล +- MiniMax M2.5 Free: ในช่วงระยะเวลาฟรี ข้อมูลที่รวบรวมอาจนำไปใช้ในการปรับปรุงโมเดล - OpenAI API: คำขอจะถูกเก็บไว้เป็นเวลา 30 วันตาม [นโยบายข้อมูลของ OpenAI](https://platform.openai.com/docs/guides/your-data) - Anthropic API: คำขอจะถูกเก็บไว้เป็นเวลา 30 วันตาม [นโยบายข้อมูลของ Anthropic](https://docs.anthropic.com/en/docs/claude-code/data-usage) diff --git a/packages/web/src/content/docs/tr/agents.mdx b/packages/web/src/content/docs/tr/agents.mdx index 47c16abf120..1f582511be0 100644 --- a/packages/web/src/content/docs/tr/agents.mdx +++ b/packages/web/src/content/docs/tr/agents.mdx @@ -21,7 +21,7 @@ opencode'da iki tür agent vardır; birincil agent'lar ve alt agent'lar. ### Birincil agent'lar -Birincil agent'lar, doğrudan etkileşim kurduğunuz ana yardımcılardır. **Sekme** tuşunu veya yapılandırılmış `switch_agent` tuş atamanızı kullanarak bunlar arasında geçiş yapabilirsiniz. Bu agent'lar ana görüşmenizi yönetir. Araç erişimi, izinler aracılığıyla yapılandırılır; örneğin, Plan kısıtlıyken Build'de tüm araçlar etkindir. +Birincil agent'lar, doğrudan etkileşim kurduğunuz ana yardımcılardır. **Sekme** tuşunu veya yapılandırılmış `switch_agent` tuş atamanızı kullanarak bunlar arasında geçiş yapabilirsiniz. Bu agent'lar ana görüşmenizi yönetir. Araç erişimi, izinler aracılığıyla yapılandırılır; örneğin, Build'de tüm araçlar etkindir ancak Plan kısıtlıdır. :::tip Bir oturum sırasında birincil agent'lar arasında geçiş yapmak için **Sekme** tuşunu kullanabilirsiniz. @@ -83,7 +83,7 @@ Kod tabanlarını keşfetmeye yönelik hızlı, salt okunur bir agent. Dosyalar --- -### Compact Kullanımı +### Compaction Kullanımı _Mod_: `primary` @@ -181,7 +181,7 @@ Agent'ları `opencode.json` yapılandırma dosyanızda yapılandırın: Ayrıca agent'ları Markdown dosyalarını kullanarak da tanımlayabilirsiniz. Bunları şuraya yerleştirin: - Global: `~/.config/opencode/agents/` -- Per-project: `.opencode/agents/` +- Proje başına: `.opencode/agents/` ```markdown title="~/.config/opencode/agents/review.md" --- @@ -342,10 +342,10 @@ Bu yol, yapılandırma dosyasının bulunduğu yere göredir. Yani bu hem global ### Model -Bu agent'ın kodu geçersiz için `model` ayarını kullanın. Farklı bölümler için optimize edilmiş farklı modelleri kullanmak için kullanışlıdır. Örneğin planlama için daha hızlı bir model, uygulama için daha yetenekli bir model. +Bu agent'ın modelini geçersiz kılmak için `model` ayarını kullanın. Farklı bölümler için optimize edilmiş farklı modelleri kullanmak için kullanışlıdır. Örneğin planlama için daha hızlı bir model, uygulama için daha yetenekli bir model. :::tip -Bir model belirtmezseniz, birincil agent'lar [model globally configured](/docs/config#models)'yi kullanırken alt agent'lar, alt agent'ı çağıran birincil agent'ın modelini kullanır. +Bir model belirtmezseniz, birincil agent'lar [küresel olarak yapılandırılmış modeli](/docs/config#models) kullanırken alt agent'lar, alt agent'ı çağıran birincil agent'ın modelini kullanır. ::: ```json title="opencode.json" @@ -358,7 +358,7 @@ Bir model belirtmezseniz, birincil agent'lar [model globally configured](/docs/c } ``` -opencode hesabınızdaki model kimliğini `provider/model-id` biçimini kullanır. Örneğin, [OpenCode Zen](/docs/zen) kullanıyorsanız, GPT 5.1 Codex için `opencode/gpt-5.1-codex` kullanırsınız. +opencode yapılandırmanızdaki model kimliği `provider/model-id` biçimini kullanır. Örneğin, [OpenCode Zen](/docs/zen) kullanıyorsanız, GPT 5.1 Codex için `opencode/gpt-5.1-codex` kullanırsınız. --- @@ -599,7 +599,7 @@ Kullanıcılar, agent'ın görev izinleri bunu reddetse bile, her zaman herhangi Agent'ın kullanıcı arayüzündeki görsel görünümünü `color` seçeneğiyle özelleştirin. Bu, agent'ın arayüzde nasıl göründüğünü etkiler. -geçerli bir onaltılık renk (ör. `#FF5733`) veya tema rengini kullanın: `primary`, `secondary`, `accent`, `success`, `warning`, `error`, `info`. +Geçerli bir onaltılık renk (ör. `#FF5733`) veya tema rengini kullanın: `primary`, `secondary`, `accent`, `success`, `warning`, `error`, `info`. ```json title="opencode.json" { @@ -744,627 +744,3 @@ Look for: - Dependency vulnerabilities - Configuration security issues ``` - -3. **Oturumlar arasında gezinme**: Subagent'lar kendi alt oturumlarını oluşturduğunda, aşağıdakileri kullanarak ana oturum ile tüm alt oturumlar arasında gezinebilirsiniz: - - **\+Right** (veya yapılandırılmış `session_child_cycle` tuş atamanız) ebeveyn → çocuk1 → çocuk2 → ... → ebeveyn arasında ileri doğru geçiş yapmak için - - **\+Left** (veya yapılandırılmış `session_child_cycle_reverse` tuş atamanız) ebeveyn ← çocuk1 ← çocuk2 ← ... ← ebeveyn arasında geriye doğru geçiş yapmak için - - Bu, ana görüşme ile özel subagent çalışması arasında sorunsuz bir şekilde geçiş yapmanıza olanak tanır. - ---- - -## Yapılandırma veya yapılandırma yoluyla kendinizinkini oluşturabilirsiniz. Agent'lar iki şekilde yapılandırılabilir: - ---- - -### JSON - -Agent'ları `opencode.json` yapılandırma dosyanızda yapılandırın: - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "agent": { - "build": { - "mode": "primary", - "model": "anthropic/claude-sonnet-4-20250514", - "prompt": "{file:./prompts/build.txt}", - "tools": { - "write": true, - "edit": true, - "bash": true - } - }, - "plan": { - "mode": "primary", - "model": "anthropic/claude-haiku-4-20250514", - "tools": { - "write": false, - "edit": false, - "bash": false - } - }, - "code-reviewer": { - "description": "Reviews code for best practices and potential issues", - "mode": "subagent", - "model": "anthropic/claude-sonnet-4-20250514", - "prompt": "You are a code reviewer. Focus on security, performance, and maintainability.", - "tools": { - "write": false, - "edit": false - } - } - } -} -``` - ---- - -### Markdown - -Ayrıca agent'ları Markdown dosyalarını kullanarak da tanımlayabilirsiniz. Bunları şuraya yerleştirin: - -- Global: `~/.config/opencode/agents/` -- Per-project: `.opencode/agents/` - -```markdown title="~/.config/opencode/agents/review.md" ---- -description: Reviews code for quality and best practices -mode: subagent -model: anthropic/claude-sonnet-4-20250514 -temperature: 0.1 -tools: - write: false - edit: false - bash: false ---- - -You are in code review mode. Focus on: - -- Code quality and best practices -- Potential bugs and edge cases -- Performance implications -- Security considerations - -Provide constructive feedback without making direct changes. -``` - -Markdown dosyasının adı agent'ın adı olur. Örneğin, `review.md` bir `review` agent'ı oluşturur. - ---- - -## Seçenekler - -Bu yapılandırma seçeneklerine ayrıntılı olarak bakalım. - ---- - -### Açıklama - -Agent'ın ne yaptığına ve ne zaman kullanılacağına ilişkin kısa bir açıklama sağlamak için `description` seçeneğini kullanın. - -```json title="opencode.json" -{ - "agent": { - "review": { - "description": "Reviews code for best practices and potential issues" - } - } -} -``` - -Bu **gerekli** bir yapılandırma seçeneğidir. - ---- - -### Sıcaklık - -LLM'nin yanıtlarının rastgeleliğini ve yaratıcılığını `temperature` yapılandırmasıyla kontrol edin. - -Düşük değerler yanıtları daha odaklı ve belirleyici hale getirirken, yüksek değerler yaratıcılığı ve değişkenliği artırır. - -```json title="opencode.json" -{ - "agent": { - "plan": { - "temperature": 0.1 - }, - "creative": { - "temperature": 0.8 - } - } -} -``` - -Sıcaklık değerleri tipik olarak 0,0 ila 1,0 arasındadır: - -- **0,0-0,2**: Çok odaklı ve belirleyici yanıtlar, kod analizi ve planlama için idealdir -- **0,3-0,5**: Biraz yaratıcılık içeren dengeli yanıtlar, genel gelişim görevleri için iyi -- **0,6-1,0**: Daha yaratıcı ve çeşitli yanıtlar, beyin fırtınası ve keşif için yararlı - -```json title="opencode.json" -{ - "agent": { - "analyze": { - "temperature": 0.1, - "prompt": "{file:./prompts/analysis.txt}" - }, - "build": { - "temperature": 0.3 - }, - "brainstorm": { - "temperature": 0.7, - "prompt": "{file:./prompts/creative.txt}" - } - } -} -``` - -Sıcaklık belirtilmezse opencode modeline özgü varsayılanları kullanır; çoğu model için genellikle 0, Qwen modelleri için 0,55. - ---- - -### Maksimum adım - -Bir agent'ın yalnızca metinle yanıt vermeye zorlanmadan önce gerçekleştirebileceği maksimum agent yineleme sayısını kontrol edin. Bu, maliyetleri kontrol etmek isteyen kullanıcıların agent eylemlerine bir sınır koymasına olanak tanır. - -Bu ayarlanmazsa, model durmayı seçene veya kullanıcı oturumu kesene kadar agent yinelemeye devam edecektir. - -```json title="opencode.json" -{ - "agent": { - "quick-thinker": { - "description": "Fast reasoning with limited iterations", - "prompt": "You are a quick thinker. Solve problems with minimal steps.", - "steps": 5 - } - } -} -``` - -Sınıra ulaşıldığında, agent, işinin özeti ve önerilen kalan görevlerin bir özetiyle yanıt vermesi talimatını veren özel bir sistem prompt'u alır. - -:::caution -Eski `maxSteps` alanı kullanımdan kaldırıldı. Bunun yerine `steps` kullanın. -::: - ---- - -### Devre dışı bırakma - -Agent'ı devre dışı bırakmak için `true` olarak ayarlayın. - -```json title="opencode.json" -{ - "agent": { - "review": { - "disable": true - } - } -} -``` - ---- - -### İstem - -Bu agent için `prompt` yapılandırmasıyla özel bir sistem prompt dosyası belirtin. Prompt dosyası, agent'ın amacına özel talimatlar içermelidir. - -```json title="opencode.json" -{ - "agent": { - "review": { - "prompt": "{file:./prompts/code-review.txt}" - } - } -} -``` - -Bu yol, yapılandırma dosyasının bulunduğu yere göredir. Yani bu hem global opencode yapılandırması hem de projeye özel yapılandırma için işe yarar. - ---- - -### Model - -Bu agent'ın varsayılan modelini geçersiz kılmak için `model` ayarını kullanın. Farklı bölümler için optimize edilmiş farklı modelleri kullanmak için kullanışlıdır. Örneğin planlama için daha hızlı bir model, uygulama için daha yetenekli bir model. - -:::tip -Bir model belirtmezseniz, primary agent'lar [model globally configured](/docs/config#models)'yi kullanırken subagent'lar, subagent'ı çağıran primary agent'ın modelini kullanır. -::: - -```json title="opencode.json" -{ - "agent": { - "plan": { - "model": "anthropic/claude-haiku-4-20250514" - } - } -} -``` - -opencode hesabınızdaki model kimliğini `provider/model-id` biçimini kullanır. Örneğin, [OpenCode Zen](/docs/zen) kullanıyorsanız, GPT 5.1 Codex için `opencode/gpt-5.1-codex` kullanırsınız. - ---- - -### Araçlar - -`tools` yapılandırmasıyla bu agent'ta hangi araçların mevcut olduğunu kontrol edin. Belirli araçları `true` veya `false` olarak ayarlayarak etkinleştirebilir veya devre dışı bırakabilirsiniz. - -```json title="opencode.json" {3-6,9-12} -{ - "$schema": "https://opencode.ai/config.json", - "tools": { - "write": true, - "bash": true - }, - "agent": { - "plan": { - "tools": { - "write": false, - "bash": false - } - } - } -} -``` - -:::note -Agent'a özgü yapılandırma, genel yapılandırmayı geçersiz kılar. -::: - -Aynı anda birden fazla aracı kontrol etmek için joker karakterleri de kullanabilirsiniz. Örneğin, bir MCP sunucusundaki tüm araçları devre dışı bırakmak için: - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "agent": { - "readonly": { - "tools": { - "mymcp_*": false, - "write": false, - "edit": false - } - } - } -} -``` - -[Araçlar hakkında daha fazla bilgi](/docs/tools). - ---- - -### İzinler - -Bir agent'ın gerçekleştirebileceği eylemleri yönetmek için izinleri yapılandırabilirsiniz. Şu anda `edit`, `bash` ve `webfetch` araçlarının izinleri şu şekilde yapılandırılabilir: - -- `"ask"` — Agent çalıştırmadan önce onay iste -- `"allow"` — Onay olmadan tüm işlemlere izin ver -- `"deny"` — Agent'ı devre dışı bırakır - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "permission": { - "edit": "deny" - } -} -``` - -Bu izinleri agent başına geçersiz kılabilirsiniz. - -```json title="opencode.json" {3-5,8-10} -{ - "$schema": "https://opencode.ai/config.json", - "permission": { - "edit": "deny" - }, - "agent": { - "build": { - "permission": { - "edit": "ask" - } - } - } -} -``` - -İzinleri Markdown agent'larında da ayarlayabilirsiniz. - -```markdown title="~/.config/opencode/agents/review.md" ---- -description: Code review without edits -mode: subagent -permission: - edit: deny - bash: - "*": ask - "git diff": allow - "git log*": allow - "grep *": allow - webfetch: deny ---- - -Only analyze code and suggest changes. -``` - -Belirli bash komutları için izinleri ayarlayabilirsiniz. - -```json title="opencode.json" {7} -{ - "$schema": "https://opencode.ai/config.json", - "agent": { - "build": { - "permission": { - "bash": { - "git push": "ask", - "grep *": "allow" - } - } - } - } -} -``` - -Bu küresel bir desen alabilir. - -```json title="opencode.json" {7} -{ - "$schema": "https://opencode.ai/config.json", - "agent": { - "build": { - "permission": { - "bash": { - "git *": "ask" - } - } - } - } -} -``` - -Ayrıca tüm komutların izinlerini yönetmek için `*` joker karakterini de kullanabilirsiniz. -Son eşleşen kural öncelikli olduğundan, `*` joker karakterini ilk sıraya ve belirli kuralları sonraya koyun. - -```json title="opencode.json" {8} -{ - "$schema": "https://opencode.ai/config.json", - "agent": { - "build": { - "permission": { - "bash": { - "*": "ask", - "git status *": "allow" - } - } - } - } -} -``` - -[İzinler hakkında daha fazla bilgi](/docs/permissions). - ---- - -### Mod - -Agent'ın modunu `mode` yapılandırmasıyla kontrol edin. `mode` seçeneği agent'ın nasıl kullanılabileceğini belirlemek için kullanılır. - -```json title="opencode.json" -{ - "agent": { - "review": { - "mode": "subagent" - } - } -} -``` - -`mode` seçeneği `primary`, `subagent` veya `all` olarak ayarlanabilir. `mode` belirtilmezse varsayılan olarak `all` olur. - ---- - -### Gizli - -`@` otomatik tamamlama menüsünden bir subagent'ı `hidden: true` ile gizleyin. Yalnızca diğer agent'lar tarafından Task aracı aracılığıyla programlı olarak çağrılması gereken dahili subagent'lar için kullanışlıdır. - -```json title="opencode.json" -{ - "agent": { - "internal-helper": { - "mode": "subagent", - "hidden": true - } - } -} -``` - -Bu yalnızca otomatik menüdeki kullanıcının görünümlerinin etkileri. İzinler izin vermesine, gizli agent'lar modeli tarafından Task aracı aracılığıyla çağrılmaya devam edilebilir. - -:::note -Yalnızca `mode: subagent` agent'ları için geçerlidir. -::: - ---- - -### Görev izinleri - -`permission.task` ile bir agent'ın Task aracı aracılığıyla hangi subagent'ları çağırabileceğini kontrol edin. Esnek eşleştirme için küresel desenleri kullanır. - -```json title="opencode.json" -{ - "agent": { - "orchestrator": { - "mode": "primary", - "permission": { - "task": { - "*": "deny", - "orchestrator-*": "allow", - "code-reviewer": "ask" - } - } - } - } -} -``` - -`deny` olarak ayarlandığında, subagent Task aracı açıklamasından tamamen kaldırılır, böylece model onu çağırmaya çalışmaz. - -:::tip -Kurallar sırayla değerlendirilir ve **son eşleşen kural kazanır**. Yukarıdaki örnekte `orchestrator-planner`, hem `*` (reddet) hem de `orchestrator-*` (izin ver) ile eşleşir, ancak `orchestrator-*`, `*`'den sonra geldiğinden sonuç `allow` olur. -::: - -:::tip -Kullanıcılar, agent'ın görev izinleri bunu reddetse bile, her zaman herhangi bir subagent'ı `@` otomatik tamamlama menüsü aracılığıyla doğrudan çağırabilir. -::: - ---- - -### Renk - -Agent'ın kullanıcı arayüzündeki görsel görünümünü `color` seçeneğiyle özelleştirin. Bu, agent'ın arayüzde nasıl göründüğünü etkiler. - -geçerli bir onaltılık renk (ör. `#FF5733`) veya tema rengini kullanın: `primary`, `secondary`, `accent`, `success`, `warning`, `error`, `info`. - -```json title="opencode.json" -{ - "agent": { - "creative": { - "color": "#ff6b6b" - }, - "code-reviewer": { - "color": "accent" - } - } -} -``` - ---- - -### Top P - -`top_p` seçeneğiyle yanıt çeşitliliğini kontrol edin. Rastgeleliği kontrol etmek için sıcaklığa alternatif. - -```json title="opencode.json" -{ - "agent": { - "brainstorm": { - "top_p": 0.9 - } - } -} -``` - -Değerler 0,0 ile 1,0 arasında değişir. Düşük değerler daha odaklıdır, yüksek değerler ise daha çeşitlidir. - ---- - -### Ek - -Agent yapılandırmanızdaki diğer seçenekler, model seçenekleri olarak **doğrudan sağlayıcıya** iletilecektir. Bu, sağlayıcıya özgü özelliklerin kullanılmasını sağlar. - -Örneğin OpenAI'nin akıl yürütme modelleriyle akıl yürütme çabasını kontrol edebilirsiniz: - -```json title="opencode.json" {6,7} -{ - "agent": { - "deep-thinker": { - "description": "Agent that uses high reasoning effort for complex problems", - "model": "openai/gpt-5", - "reasoningEffort": "high", - "textVerbosity": "low" - } - } -} -``` - -Bu ek seçenekler modele ve sağlayıcıya özeldir. Kullanılabilir parametreler için sağlayıcınızın belgelerine bakın. - -:::tip -Mevcut modellerin listesini görmek için `opencode models` komutunu çalıştırın. -::: - ---- - -## Agent Oluşturma - -Aşağıdaki komutu kullanarak yeni agent'lar oluşturabilirsiniz: - -```bash -opencode agent create -``` - -Bu etkileşimli komut şunları sağlayacaktır: - -1. Agent'ı nereye kaydedeceğinizi sorun; küresel veya projeye özel. -2. Agent'ın ne yapması gerektiğinin açıklaması. -3. Uygun bir sistem istemi ve tanımlayıcı oluşturun. -4. Agent'ın hangi araçlara erişebileceğini seçmenize izin verin. -5. Son olarak agent yapılandırmasıyla bir Markdown dosyası oluşturun. - ---- - -## Kullanım Senaryoları - -Farklı agent'lara yönelik bazı yaygın kullanım senaryoları aşağıda verilmiştir. - -- **Build agent**: Tüm araçların etkinleştirildiği tam geliştirme çalışması -- **Plan agent**: Değişiklik yapmadan analiz ve planlama -- **Review agent**: Salt okunur erişim ve belgeleme araçlarıyla kod incelemesi -- **Debug agent**: Bash ve okuma araçları etkinken araştırmaya odaklanmıştır -- **Docs agent**: Dosya işlemleriyle ancak sistem komutları olmadan belge yazma - ---- - -## Örnekler - -Yararlı bulabileceğiniz bazı örnek agent'ları burada bulabilirsiniz. - -:::tip -Paylaşmak istediğiniz bir agent'ınız var mı? [Submit a PR](https://github.com/anomalyco/opencode). -::: - ---- - -### Dokümantasyon agent'ı - -```markdown title="~/.config/opencode/agents/docs-writer.md" ---- -description: Writes and maintains project documentation -mode: subagent -tools: - bash: false ---- - -You are a technical writer. Create clear, comprehensive documentation. - -Focus on: - -- Clear explanations -- Proper structure -- Code examples -- User-friendly language -``` - ---- - -### Güvenlik denetçisi - -```markdown title="~/.config/opencode/agents/security-auditor.md" ---- -description: Performs security audits and identifies vulnerabilities -mode: subagent -tools: - write: false - edit: false ---- - -You are a security expert. Focus on identifying potential security issues. - -Look for: - -- Input validation vulnerabilities -- Authentication and authorization flaws -- Data exposure risks -- Dependency vulnerabilities -- Configuration security issues -``` diff --git a/packages/web/src/content/docs/tr/cli.mdx b/packages/web/src/content/docs/tr/cli.mdx index ae151bd5c9d..5f3cd4bfcdb 100644 --- a/packages/web/src/content/docs/tr/cli.mdx +++ b/packages/web/src/content/docs/tr/cli.mdx @@ -558,6 +558,7 @@ opencode ortam değişkenleri kullanılarak yapılandırılabilir. | `OPENCODE_AUTO_SHARE` | boolean | Oturumları otomatik olarak paylaş | | `OPENCODE_GIT_BASH_PATH` | string | Windows'ta yürütülebilir Git Bash'in Yolu | | `OPENCODE_CONFIG` | string | Yapılandırma dosyasının yolu | +| `OPENCODE_TUI_CONFIG` | string | TUI yapılandırma dosyasının yolu | | `OPENCODE_CONFIG_DIR` | string | Yapılandırma dizinine giden yol | | `OPENCODE_CONFIG_CONTENT` | string | Satır içi JSON config içeriği | | `OPENCODE_DISABLE_AUTOUPDATE` | boolean | Otomatik güncelleme kontrollerini devre dışı bırak | diff --git a/packages/web/src/content/docs/tr/config.mdx b/packages/web/src/content/docs/tr/config.mdx index fe60991c626..8a769ba6908 100644 --- a/packages/web/src/content/docs/tr/config.mdx +++ b/packages/web/src/content/docs/tr/config.mdx @@ -491,13 +491,15 @@ Bağlam sıkıştırma davranışını `compaction` seçeneği aracılığıyla "$schema": "https://opencode.ai/config.json", "compaction": { "auto": true, - "prune": true + "prune": true, + "reserved": 10000 } } ``` - `auto` - Bağlam dolduğunda oturumu otomatik olarak sıkıştırır (varsayılan: `true`). - `prune` - Belirteçleri kaydetmek için eski araç çıktılarını kaldırın (varsayılan: `true`). +- `reserved` - Sıkıştırma için belirteç tamponu. Sıkıştırma sırasında taşmayı önlemek için yeterli pencere bırakır. --- diff --git a/packages/web/src/content/docs/tr/custom-tools.mdx b/packages/web/src/content/docs/tr/custom-tools.mdx index cb6a12debb2..87ff66d1d6b 100644 --- a/packages/web/src/content/docs/tr/custom-tools.mdx +++ b/packages/web/src/content/docs/tr/custom-tools.mdx @@ -79,6 +79,32 @@ Bu iki araç oluşturur: `math_add` ve `math_multiply`. --- +#### Yerleşik araçlarla ad çakışmaları + +Özel araçlar, araç adına göre anahtarlanır. Özel bir araç yerleşik bir araçla aynı adı kullanıyorsa, özel araç önceliklidir. + +Örneğin, bu dosya yerleşik `bash` aracının yerini alır: + +```ts title=".opencode/tools/bash.ts" +import { tool } from "@opencode-ai/plugin" + +export default tool({ + description: "Restricted bash wrapper", + args: { + command: tool.schema.string(), + }, + async execute(args) { + return `blocked: ${args.command}` + }, +}) +``` + +:::note +Kasıtlı olarak yerleşik bir aracı değiştirmek istemiyorsanız benzersiz adları tercih edin. Yerleşik bir aracı devre dışı bırakmak ancak geçersiz kılmak istemiyorsanız [izinleri](/docs/permissions) kullanın. +::: + +--- + ### Argümanlar Bağımsız değişken türlerini tanımlamak için yalnızca [Zod](https://zod.dev) olan `tool.schema` öğesini kullanabilirsiniz. diff --git a/packages/web/src/content/docs/tr/ecosystem.mdx b/packages/web/src/content/docs/tr/ecosystem.mdx index 835d9ba895b..a3d34bd8079 100644 --- a/packages/web/src/content/docs/tr/ecosystem.mdx +++ b/packages/web/src/content/docs/tr/ecosystem.mdx @@ -1,74 +1,75 @@ --- title: Ekosistem -description: opencode ile ilgili tasarımlar ve entegrasyonlar. +description: OpenCode ile geliştirilen projeler ve entegrasyonlar. --- -opencode üzerine inşa edilmiş bir topluluk projeleri koleksiyonu. +OpenCode üzerine inşa edilmiş topluluk projeleri koleksiyonu. :::note -opencode ile ilgili projenizi bu listeye eklemek ister misiniz? Bir PR gönderin. +OpenCode ile ilgili projenizi bu listeye eklemek ister misiniz? Bir PR gönderin. ::: -Ayrıca ekosistemi ve topluluğu bir araya getiren bir topluluk olan [awesome-opencode](https://github.com/awesome-opencode/awesome-opencode) ve [opencode.cafe](https://opencode.cafe)'e de göz atabilirsiniz. +Ayrıca ekosistemi ve topluluğu bir araya getiren [awesome-opencode](https://github.com/awesome-opencode/awesome-opencode) ve [opencode.cafe](https://opencode.cafe) adreslerine de göz atabilirsiniz. --- ## Eklentiler -| İsim | Açıklama | -| --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | -| [opencode-daytona](https://github.com/jamesmurdza/daytona/blob/main/guides/typescript/opencode/README.md) | opencode oturumlarını git senkronizasyonu ve canlı önizlemelerle izole Daytona sanal alanlarında otomatik olarak çalıştırın | -| [opencode-helicone-session](https://github.com/H2Shami/opencode-helicone-session) | İstek gruplaması için Helicone oturum başlıklarını otomatik olarak ekleme | -| [opencode-type-inject](https://github.com/nick-vi/opencode-type-inject) | Arama araçlarıyla TypeScript/Svelte türlerini dosya okumalarına otomatik olarak enjekte edin | -| [opencode-openai-codex-auth](https://github.com/numman-ali/opencode-openai-codex-auth) | API kredisi yerine ChatGPT Plus/Pro aboneliğinizi kullanın | -| [opencode-gemini-auth](https://github.com/jenslys/opencode-gemini-auth) | API faturalandırma yerine mevcut Gemini planınızı kullanın | -| [opencode-antigravity-auth](https://github.com/NoeFabris/opencode-antigravity-auth) | API faturalandırma yerine Antigravity'nin ücretsiz modellerini kullanın | -| [opencode-devcontainers](https://github.com/athal7/opencode-devcontainers) | Sığ klonlar ve otomatik atanan bağlantı noktalarıyla çok dallı devcontainer izolasyonu | -| [opencode-google-antigravity-auth](https://github.com/shekohex/opencode-google-antigravity-auth) | Google Arama desteği ve daha sağlam API işleme özelliğiyle Google Antigravity OAuth Eklentisi | -| [opencode-dynamic-context-pruning](https://github.com/Tarquinen/opencode-dynamic-context-pruning) | Optimize token usage by pruning obsolete tool outputs | -| [opencode-websearch-cited](https://github.com/ghoulr/opencode-websearch-cited.git) | Desteklenen sağlayıcılar için Google tabanlı stil ile yerel web araması desteği ekleyin | -| [opencode-pty](https://github.com/shekohex/opencode-pty.git) | Yapay zeka aracılarının bir PTY'de arka plan işlemlerini çalıştırmasına ve onlara etkileşimli girdi göndermesine olanak tanır. | -| [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Etkileşimli olmayan kabuk komutlarına yönelik talimatlar - TTY bağımlı işlemlerden kaynaklanan askıda kalmaları önler | -| [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Wakatime ile opencode kullanımını izleyin | -| [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Clean up markdown tables produced by LLMs | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | Morph Fast Apply API ve yavaş düzenleme işaretçileriyle 10 kat daha hızlı kod düzenleme | -| [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Background agents, pre-built LSP/AST/MCP tools, curated agents, Claude Code compatible | -| [opencode-notificator](https://github.com/panta82/opencode-notificator) | opencode oturumları için masaüstü bildirimleri ve sesli uyarılar | -| [opencode-notifier](https://github.com/mohak34/opencode-notifier) | İzin, tamamlama ve hata olayları için masaüstü bildirimleri ve sesli uyarılar | -| [opencode-zellij-namer](https://github.com/24601/opencode-zellij-namer) | opencode bağlamına dayalı yapay zeka destekli otomatik Zellij oturumu adlandırma | -| [opencode-skillful](https://github.com/zenobi-us/opencode-skillful) | opencode temsilcilerinin, beceri keşfi ve ekleme ile istek üzerine istemleri yavaş yüklemesine izin verin | -| [opencode-supermemory](https://github.com/supermemoryai/opencode-supermemory) | Supermemory kullanarak oturumlar arasında kalıcı hafıza | -| [@plannotator/opencode](https://github.com/backnotprop/plannotator/tree/main/apps/opencode-plugin) | Görsel açıklama ve private/offline paylaşımıyla etkileşimli plan incelemesi | -| [@openspoon/subtask2](https://github.com/spoons-and-mirrors/subtask2) | opencode'u/komutları ayrıntılı akış kontrolüyle güçlü bir orkestrasyon sistemine genişletin | -| [opencode-scheduler](https://github.com/different-ai/opencode-scheduler) | Cron sözdizimi ile launchd (Mac) veya systemd (Linux) kullanarak yinelenen işleri planlayın | -| [micode](https://github.com/vtemian/micode) | Yapılandırılmış Beyin Fırtınası → Planla → Oturum sürekliliği ile iş akışını uygulama | -| [octto](https://github.com/vtemian/octto) | Çoklu soru formlarıyla yapay zeka beyin fırtınası için etkileşimli tarayıcı arayüzü | -| [opencode-background-agents](https://github.com/kdcokenny/opencode-background-agents) | Eşzamansız delegasyon ve bağlam kalıcılığına sahip Claude Code tarzı arka plan aracıları | -| [opencode-notify](https://github.com/kdcokenny/opencode-notify) | opencode için yerel işletim sistemi bildirimleri – görevlerin ne zaman tamamlandığını bilin | -| [opencode-workspace](https://github.com/kdcokenny/opencode-workspace) | Birlikte verilen çok aracılı orkestrasyon donanımı – 16 bileşen, tek kurulum | -| [opencode-worktree](https://github.com/kdcokenny/opencode-worktree) | opencode için sıfır sürtünmeli git çalışma ağaçları | +| İsim | Açıklama | +| -------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | +| [opencode-daytona](https://github.com/daytonaio/daytona/tree/main/libs/opencode-plugin) | OpenCode oturumlarını, git senkronizasyonu ve canlı önizlemelerle izole Daytona sanal alanlarında otomatik olarak çalıştırın | +| [opencode-helicone-session](https://github.com/H2Shami/opencode-helicone-session) | İstek gruplaması için Helicone oturum başlıklarını otomatik olarak ekleyin | +| [opencode-type-inject](https://github.com/nick-vi/opencode-type-inject) | Arama araçlarıyla TypeScript/Svelte türlerini dosya okumalarına otomatik olarak enjekte edin | +| [opencode-openai-codex-auth](https://github.com/numman-ali/opencode-openai-codex-auth) | API kredisi yerine ChatGPT Plus/Pro aboneliğinizi kullanın | +| [opencode-gemini-auth](https://github.com/jenslys/opencode-gemini-auth) | API faturalandırması yerine mevcut Gemini planınızı kullanın | +| [opencode-antigravity-auth](https://github.com/NoeFabris/opencode-antigravity-auth) | API faturalandırması yerine Antigravity'nin ücretsiz modellerini kullanın | +| [opencode-devcontainers](https://github.com/athal7/opencode-devcontainers) | Sığ klonlar ve otomatik atanan portlarla çok dallı devcontainer izolasyonu | +| [opencode-google-antigravity-auth](https://github.com/shekohex/opencode-google-antigravity-auth) | Google Arama desteği ve daha sağlam API işleme özelliğiyle Google Antigravity OAuth Eklentisi | +| [opencode-dynamic-context-pruning](https://github.com/Tarquinen/opencode-dynamic-context-pruning) | Eski araç çıktılarını budayarak token kullanımını optimize edin | +| [opencode-vibeguard](https://github.com/inkdust2021/opencode-vibeguard) | LLM çağrılarından önce sırları/kişisel verileri VibeGuard tarzı yer tutucularla gizleyin; yerel olarak geri yükleyin | +| [opencode-websearch-cited](https://github.com/ghoulr/opencode-websearch-cited.git) | Desteklenen sağlayıcılar için Google kaynaklı stil ile yerel web araması desteği ekleyin | +| [opencode-pty](https://github.com/shekohex/opencode-pty.git) | Yapay zeka aracılarının bir PTY'de arka plan işlemlerini çalıştırmasına ve onlara etkileşimli girdi göndermesine olanak tanır | +| [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Etkileşimli olmayan kabuk komutları için talimatlar - TTY bağımlı işlemlerden kaynaklanan takılmaları önler | +| [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Wakatime ile OpenCode kullanımını takip edin | +| [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | LLM'ler tarafından üretilen markdown tablolarını temizleyin | +| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | Morph Fast Apply API ve tembel düzenleme işaretçileriyle 10 kat daha hızlı kod düzenleme | +| [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Arka plan aracıları, hazır LSP/AST/MCP araçları, seçilmiş aracılar, Claude Code uyumlu | +| [opencode-notificator](https://github.com/panta82/opencode-notificator) | OpenCode oturumları için masaüstü bildirimleri ve sesli uyarılar | +| [opencode-notifier](https://github.com/mohak34/opencode-notifier) | İzin, tamamlanma ve hata olayları için masaüstü bildirimleri ve sesli uyarılar | +| [opencode-zellij-namer](https://github.com/24601/opencode-zellij-namer) | OpenCode bağlamına dayalı yapay zeka destekli otomatik Zellij oturum isimlendirmesi | +| [opencode-skillful](https://github.com/zenobi-us/opencode-skillful) | OpenCode aracılarının, beceri keşfi ve enjeksiyonu ile istemleri talep üzerine tembel yüklemesine (lazy load) izin verin | +| [opencode-supermemory](https://github.com/supermemoryai/opencode-supermemory) | Supermemory kullanarak oturumlar arası kalıcı hafıza | +| [@plannotator/opencode](https://github.com/backnotprop/plannotator/tree/main/apps/opencode-plugin) | Görsel not alma ve özel/çevrimdışı paylaşım ile etkileşimli plan incelemesi | +| [@openspoon/subtask2](https://github.com/spoons-and-mirrors/subtask2) | opencode /commands komutlarını, ayrıntılı akış kontrolü ile güçlü bir orkestrasyon sistemine genişletin | +| [opencode-scheduler](https://github.com/different-ai/opencode-scheduler) | Launchd (Mac) veya systemd (Linux) kullanarak cron sözdizimi ile tekrarlayan işler planlayın | +| [micode](https://github.com/vtemian/micode) | Oturum sürekliliği ile Yapılandırılmış Beyin Fırtınası → Planlama → Uygulama iş akışı | +| [octto](https://github.com/vtemian/octto) | Çok sorulu formlarla yapay zeka beyin fırtınası için etkileşimli tarayıcı arayüzü | +| [opencode-background-agents](https://github.com/kdcokenny/opencode-background-agents) | Asenkron delegasyon ve bağlam kalıcılığına sahip Claude Code tarzı arka plan aracıları | +| [opencode-notify](https://github.com/kdcokenny/opencode-notify) | OpenCode için yerel işletim sistemi bildirimleri – görevlerin ne zaman tamamlandığını bilin | +| [opencode-workspace](https://github.com/kdcokenny/opencode-workspace) | Paketlenmiş çoklu aracı orkestrasyon donanımı – 16 bileşen, tek kurulum | +| [opencode-worktree](https://github.com/kdcokenny/opencode-worktree) | OpenCode için sıfır sürtünmeli git çalışma ağaçları (worktrees) | --- ## Projeler -| İsim | Tanım | -| ------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------- | -| [kimaki](https://github.com/remorses/kimaki) | SDK üzerine kurulu opencode oturumlarını kontrol eden Discord botu | -| [opencode.nvim](https://github.com/NickvanDyke/opencode.nvim) | API temel alınarak oluşturulmuş, editöre duyarlı istemler için Neovim eklentisi | -| [portal](https://github.com/hosenur/portal) | Tailscale/VPN üzerinden opencode için mobil öncelikli web kullanıcı arayüzü | -| [opencode plugin template](https://github.com/zenobi-us/opencode-plugin-template/) | opencode eklentileri oluşturmak için şablon | -| [opencode.nvim](https://github.com/sudo-tee/opencode.nvim) | opencode için Neovim ön ucu - terminal tabanlı bir AI kodlama aracısı | -| [ai-sdk-provider-opencode-sdk](https://github.com/ben-vargas/ai-sdk-provider-opencode-sdk) | @opencode-ai/sdk aracılığıyla opencode'u kullanmak için Vercel AI SDK sağlayıcısı | -| [OpenChamber](https://github.com/btriapitsyn/openchamber) | opencode için Web / Masaüstü Uygulaması ve VS Code Uzantısı | -| [OpenCode-Obsidian](https://github.com/mtymek/opencode-obsidian) | opencode'u Obsidian'ın kullanıcı arayüzüne yerleştiren Obsidian eklentisi | -| [OpenWork](https://github.com/different-ai/openwork) | opencode tarafından desteklenen, Claude Cowork'e açık kaynaklı bir alternatif | -| [ocx](https://github.com/kdcokenny/ocx) | Taşınabilir, yalıtılmış profillere sahip opencode uzantı yöneticisi. | -| [CodeNomad](https://github.com/NeuralNomadsAI/CodeNomad) | opencode için Masaüstü, Web, Mobil ve Uzak İstemci Uygulaması | +| İsim | Açıklama | +| ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------- | +| [kimaki](https://github.com/remorses/kimaki) | SDK üzerine inşa edilmiş, OpenCode oturumlarını kontrol eden Discord botu | +| [opencode.nvim](https://github.com/NickvanDyke/opencode.nvim) | API üzerine inşa edilmiş, editör farkındalıklı istemler için Neovim eklentisi | +| [portal](https://github.com/hosenur/portal) | Tailscale/VPN üzerinden OpenCode için mobil öncelikli web arayüzü | +| [opencode plugin template](https://github.com/zenobi-us/opencode-plugin-template/) | OpenCode eklentileri oluşturmak için şablon | +| [opencode.nvim](https://github.com/sudo-tee/opencode.nvim) | opencode için Neovim ön yüzü - terminal tabanlı bir yapay zeka kodlama aracısı | +| [ai-sdk-provider-opencode-sdk](https://github.com/ben-vargas/ai-sdk-provider-opencode-sdk) | @opencode-ai/sdk aracılığıyla OpenCode kullanmak için Vercel AI SDK sağlayıcısı | +| [OpenChamber](https://github.com/btriapitsyn/openchamber) | OpenCode için Web / Masaüstü Uygulaması ve VS Code Uzantısı | +| [OpenCode-Obsidian](https://github.com/mtymek/opencode-obsidian) | OpenCode'u Obsidian arayüzüne gömen Obsidian eklentisi | +| [OpenWork](https://github.com/different-ai/openwork) | OpenCode tarafından desteklenen, Claude Cowork'e açık kaynaklı bir alternatif | +| [ocx](https://github.com/kdcokenny/ocx) | Taşınabilir, izole profillere sahip OpenCode eklenti yöneticisi | +| [CodeNomad](https://github.com/NeuralNomadsAI/CodeNomad) | OpenCode için Masaüstü, Web, Mobil ve Uzak İstemci Uygulaması | --- -## Agent'lar +## Aracılar | İsim | Açıklama | | ----------------------------------------------------------------- | --------------------------------------------------------------------------- | diff --git a/packages/web/src/content/docs/tr/go.mdx b/packages/web/src/content/docs/tr/go.mdx new file mode 100644 index 00000000000..794e7f269e9 --- /dev/null +++ b/packages/web/src/content/docs/tr/go.mdx @@ -0,0 +1,145 @@ +--- +title: Go +description: Açık kodlama modelleri için düşük maliyetli abonelik. +--- + +import config from "../../../../config.mjs" +export const console = config.console +export const email = `mailto:${config.email}` + +OpenCode Go, popüler açık kodlama modellerine güvenilir erişim sağlayan **aylık 10$** tutarında düşük maliyetli bir aboneliktir. + +:::note +OpenCode Go şu anda beta aşamasındadır. +::: + +Go, OpenCode içindeki diğer sağlayıcılar gibi çalışır. OpenCode Go'ya abone olur ve API anahtarınızı alırsınız. Bu **tamamen isteğe bağlıdır** ve OpenCode'u kullanmak için buna ihtiyacınız yoktur. + +Kararlı küresel erişim için ABD, AB ve Singapur'da barındırılan modellerle, öncelikli olarak uluslararası kullanıcılar için tasarlanmıştır. + +--- + +## Arka Plan + +Açık modeller gerçekten iyi hale geldi. Artık kodlama görevleri için tescilli modellere yakın performans sunuyorlar. Ve birçok sağlayıcı bunları rekabetçi bir şekilde sunabildiği için genellikle çok daha ucuzlar. + +Ancak, bunlara güvenilir ve düşük gecikmeli erişim sağlamak zor olabilir. Sağlayıcılar kalite ve kullanılabilirlik açısından farklılık gösterir. + +:::tip +OpenCode ile iyi çalışan seçkin bir model ve sağlayıcı grubunu test ettik. +::: + +Bunu düzeltmek için birkaç şey yaptık: + +1. Seçkin bir açık model grubunu test ettik ve bunları en iyi nasıl çalıştıracakları konusunda ekipleriyle görüştük. +2. Daha sonra bunların doğru şekilde sunulduğundan emin olmak için birkaç sağlayıcıyla çalıştık. +3. Son olarak, model/sağlayıcı kombinasyonunu kıyasladık ve önermekten memnuniyet duyduğumuz bir liste oluşturduk. + +OpenCode Go, bu modellere **aylık 10$** karşılığında erişmenizi sağlar. + +--- + +## Nasıl çalışır + +OpenCode Go, OpenCode'daki diğer herhangi bir sağlayıcı gibi çalışır. + +1. **OpenCode Zen**'de oturum açın, Go'ya abone olun ve API anahtarınızı kopyalayın. +2. TUI'de `/connect` komutunu çalıştırın, `OpenCode Go`yu seçin ve API anahtarınızı yapıştırın. +3. Go üzerinden kullanılabilen modellerin listesini görmek için TUI'de `/models` komutunu çalıştırın. + +:::note +Çalışma alanı başına yalnızca bir üye OpenCode Go'ya abone olabilir. +::: + +Mevcut model listesi şunları içerir: + +- **GLM-5** +- **Kimi K2.5** +- **MiniMax M2.5** + +Test ettikçe ve yenilerini ekledikçe model listesi değişebilir. + +--- + +## Kullanım sınırları + +OpenCode Go aşağıdaki sınırları içerir: + +- **5 saatlik sınır** — 12$ kullanım +- **Haftalık sınır** — 30$ kullanım +- **Aylık sınır** — 60$ kullanım + +Sınırlar dolar değeri üzerinden tanımlanmıştır. Bu, gerçek istek sayınızın kullandığınız modele bağlı olduğu anlamına gelir. MiniMax M2.5 gibi daha ucuz modeller daha fazla isteğe izin verirken, GLM-5 gibi daha yüksek maliyetli modeller daha azına izin verir. + +Aşağıdaki tablo, tipik Go kullanım modellerine dayalı tahmini bir istek sayısı sunmaktadır: + +| | GLM-5 | Kimi K2.5 | MiniMax M2.5 | +| ------------------- | ----- | --------- | ------------ | +| 5 saat başına istek | 1.150 | 1.850 | 30.000 | +| haftalık istek | 2.880 | 4.630 | 75.000 | +| aylık istek | 5.750 | 9.250 | 150.000 | + +Tahminler gözlemlenen ortalama istek modellerine dayanmaktadır: + +- GLM-5 — İstek başına 700 girdi, 52.000 önbelleğe alınmış, 150 çıktı token'ı +- Kimi K2.5 — İstek başına 870 girdi, 55.000 önbelleğe alınmış, 200 çıktı token'ı +- MiniMax M2.5 — İstek başına 300 girdi, 55.000 önbelleğe alınmış, 125 çıktı token'ı + +Mevcut kullanımınızı **konsoldan** takip edebilirsiniz. + +:::tip +Kullanım sınırına ulaşırsanız, ücretsiz modelleri kullanmaya devam edebilirsiniz. +::: + +Erken kullanım ve geri bildirimlerden öğrendiklerimize göre kullanım sınırları değişebilir. + +--- + +### Fiyatlandırma + +OpenCode Go, **aylık 10$** tutarında bir abonelik planıdır. Aşağıda **1M token başına** fiyatlar yer almaktadır. + +| Model | Girdi | Çıktı | Önbelleğe Alınmış Okuma | +| ------------ | ----- | ----- | ----------------------- | +| GLM-5 | $1.00 | $3.20 | $0.20 | +| Kimi K2.5 | $0.60 | $3.00 | $0.10 | +| MiniMax M2.5 | $0.30 | $1.20 | $0.03 | + +--- + +### Sınırların ötesinde kullanım + +Zen bakiyenizde krediniz de varsa, konsoldaki **Bakiyeyi kullan** (Use balance) seçeneğini etkinleştirebilirsiniz. Etkinleştirildiğinde, kullanım sınırlarınıza ulaştıktan sonra Go, istekleri engellemek yerine Zen bakiyenize geri dönecektir. + +--- + +## Uç Noktalar + +Go modellerine aşağıdaki API uç noktaları üzerinden de erişebilirsiniz. + +| Model | Model ID | Endpoint | AI SDK Paketi | +| ------------ | ------------ | ------------------------------------------------ | --------------------------- | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | + +OpenCode yapılandırmanızdaki [model kimliği](/docs/config/#models), `opencode-go/` biçimini kullanır. Örneğin, Kimi K2.5 için yapılandırmanızda `opencode-go/kimi-k2.5` kullanırsınız. + +--- + +## Gizlilik + +Plan öncelikli olarak uluslararası kullanıcılar için tasarlanmıştır; modeller kararlı küresel erişim için ABD, AB ve Singapur'da barındırılmaktadır. + +Herhangi bir sorunuz varsa bizimle iletişime geçin. + +--- + +## Hedefler + +OpenCode Go'yu şu amaçlarla oluşturduk: + +1. Düşük maliyetli bir abonelikle yapay zeka kodlamasını daha fazla insan için **erişilebilir** kılmak. +2. En iyi açık kodlama modellerine **güvenilir** erişim sağlamak. +3. Kodlama ajanı kullanımı için **test edilmiş ve kıyaslanmış** modelleri seçmek. +4. OpenCode ile başka herhangi bir sağlayıcıyı kullanmanıza da izin vererek **kilitlenmeyi önlemek**. diff --git a/packages/web/src/content/docs/tr/index.mdx b/packages/web/src/content/docs/tr/index.mdx index 291d3d490c7..696eddc3a10 100644 --- a/packages/web/src/content/docs/tr/index.mdx +++ b/packages/web/src/content/docs/tr/index.mdx @@ -1,13 +1,13 @@ --- title: Giriş -description: opencode kullanmaya başlayın. +description: OpenCode kullanmaya başlayın. --- import { Tabs, TabItem } from "@astrojs/starlight/components" import config from "../../../../config.mjs" export const console = config.console -[**opencode**](/) açık kaynaklı bir AI kodlama ajanıdır. Terminal tabanlı bir arayüz, masaüstü uygulaması veya IDE uzantısı olarak mevcuttur. +[**OpenCode**](/) açık kaynaklı bir AI kodlama ajanıdır. Terminal tabanlı bir arayüz, masaüstü uygulaması veya IDE uzantısı olarak mevcuttur. ![opencode TUI with the opencode theme](../../../assets/lander/screenshot.png) @@ -17,11 +17,11 @@ Başlayalım. #### Ön koşullar -opencode'u terminalinizde kullanmak için ihtiyacınız olacak: +OpenCode'u terminalinizde kullanmak için ihtiyacınız olacak: 1. Şu gibi modern bir terminal emülatörü: - - [WezTerm](https://wezterm.org), cross-platform - - [Alacritty](https://alacritty.org), cross-platform + - [WezTerm](https://wezterm.org), cross-platform (tüm platformlarda) + - [Alacritty](https://alacritty.org), cross-platform (tüm platformlarda) - [Ghostty](https://ghostty.org), Linux ve macOS - [Kitty](https://sw.kovidgoyal.net/kitty/), Linux ve macOS @@ -31,7 +31,7 @@ opencode'u terminalinizde kullanmak için ihtiyacınız olacak: ## Kurulum -opencode'u kurmanın en kolay yolu kurulum betiğidir. +OpenCode'u kurmanın en kolay yolu kurulum betiğidir. ```bash curl -fsSL https://opencode.ai/install | bash @@ -79,7 +79,7 @@ Ayrıca aşağıdaki komutlarla da yükleyebilirsiniz: brew install anomalyco/tap/opencode ``` - > En güncel sürümler için opencode tap'ini kullanmanızı öneririz. Resmi `brew install opencode` formülü Homebrew ekibi tarafından korunur ve daha sık güncellenir. + > En güncel sürümler için OpenCode tap'ini kullanmanızı öneririz. Resmi `brew install opencode` formülü Homebrew ekibi tarafından korunur ve daha sık güncellenir. - **Paru'yu Arch Linux'ta kullanma** @@ -91,7 +91,7 @@ Ayrıca aşağıdaki komutlarla da yükleyebilirsiniz: #### Windows :::tip[Önerilen: WSL kullanın] -Windows'ta en iyi deneyim için [Windows Subsystem for Linux (WSL)](/docs/windows-wsl) kullanılmasını öneririz. Daha iyi performans ve opencode'un özellikleriyle tam uyumluluğu sağlar. +Windows'ta en iyi deneyim için [Windows Subsystem for Linux (WSL)](/docs/windows-wsl) kullanılmasını öneririz. Daha iyi performans ve OpenCode'un özellikleriyle tam uyumluluğu sağlar. ::: - **Chocolatey Kullanımı** @@ -124,7 +124,7 @@ Windows'ta en iyi deneyim için [Windows Subsystem for Linux (WSL)](/docs/window docker run -it --rm ghcr.io/anomalyco/opencode ``` -opencode'un Bun kullanılarak Windows'a yüklenmesine yönelik destek şu anda devam etmektedir. +OpenCode'un Bun kullanılarak Windows'a yüklenmesine yönelik destek şu anda devam etmektedir. İkili dosyayı [Releases](https://github.com/anomalyco/opencode/releases)'dan da alabilirsiniz. @@ -132,12 +132,12 @@ opencode'un Bun kullanılarak Windows'a yüklenmesine yönelik destek şu anda d ## Yapılandırma -opencode ile herhangi bir LLM sağlayıcısının API anahtarlarını yapılandırarak kullanabilirsiniz. +OpenCode ile herhangi bir LLM sağlayıcısının API anahtarlarını yapılandırarak kullanabilirsiniz. LLM sağlayıcılarını kullanmaya yeni başlıyorsanız, [OpenCode Zen](/docs/zen) kullanmanızı öneririz. -opencode ekibi tarafından test edilmiş ve doğrulanmış modellerin seçilmiş bir listesidir. +OpenCode ekibi tarafından test edilmiş ve doğrulanmış modellerin seçilmiş bir listesidir. -1. TUI'de `/connect` komutunu çalıştırın, opencode'u seçin ve [opencode.ai/auth](https://opencode.ai/auth)'ye gidin. +1. TUI'de `/connect` komutunu çalıştırın, OpenCode'u seçin ve [opencode.ai/auth](https://opencode.ai/auth)'ye gidin. ```txt /connect @@ -160,39 +160,37 @@ Alternatif olarak diğer sağlayıcılardan birini seçebilirsiniz. [Daha fazla ## Başlatma -Artık bir sağlayıcı yapılandırdığınıza göre, bir projeye gidebilirsiniz. -üzerinde çalışmak istiyorsun. +Artık bir sağlayıcı yapılandırdığınıza göre, üzerinde çalışmak istediğiniz bir projeye gidebilirsiniz. ```bash cd /path/to/project ``` -Ve opencode'u çalıştırın. +Ve OpenCode'u çalıştırın. ```bash opencode ``` -Daha sonra aşağıdaki komutu çalıştırarak proje için opencode'u başlatın. +Daha sonra aşağıdaki komutu çalıştırarak proje için OpenCode'u başlatın. ```bash frame="none" /init ``` -Bu, opencode'un projenizi analiz etmesini ve bir `AGENTS.md` dosyası oluşturmasını sağlayacaktır. -proje kökü. +Bu, OpenCode'un projenizi analiz etmesini ve bir `AGENTS.md` proje kökünde dosyası oluşturmasını sağlayacaktır. :::tip Projenizin `AGENTS.md` dosyasını Git'e göndermelisiniz. ::: -Bu, opencode'un proje yapısını ve kullanılan kodlama kalıplarını anlamasına yardımcı olur. +Bu, OpenCode'un proje yapısını ve kullanılan kodlama kalıplarını anlamasına yardımcı olur. --- ## Kullanım -Artık projeniz üzerinde çalışmak için opencode'u kullanmaya hazırsınız. Dilediğiniz soruyu sorabilirsiniz. +Artık projeniz üzerinde çalışmak için OpenCode'u kullanmaya hazırsınız. Dilediğiniz soruyu sorabilirsiniz. AI kodlama ajanını kullanmaya yeniyseniz aşağıdaki örnekler yardımcı olabilir. @@ -200,7 +198,7 @@ AI kodlama ajanını kullanmaya yeniyseniz aşağıdaki örnekler yardımcı ola ### Soru Sorma -opencode'dan kod tabanını size açıklamasını isteyebilirsiniz. +OpenCode'dan kod tabanını size açıklamasını isteyebilirsiniz. :::tip Projedeki dosyaları bulanık aramak için `@` tuşunu kullanın. @@ -216,14 +214,14 @@ Kod tabanının üzerinde çalışmadığınız bir kısmı varsa bu yararlı ol ### Özellik Ekleme -opencode'dan projenize yeni özellikler eklemesini isteyebilirsiniz. Yine de öncelikle ondan bir plan oluşturmasını istemenizi öneririz. +OpenCode'dan projenize yeni özellikler eklemesini isteyebilirsiniz. Yine de öncelikle ondan bir plan oluşturmasını istemenizi öneririz. 1. **Bir plan oluşturun** - opencode, değişiklik yapma özelliğini kapatan bir \_Plan modu_na sahiptir. + OpenCode, değişiklik yapma özelliğini kapatan bir \_Plan modu\_\na sahiptir. Bu modda, özelliğin nasıl uygulanacağını önerir. - **Sekme** tuşunu kullanarak buna geçin. Bunun için sağ alt köşede bir gösterge göreceksiniz. + **Tab** tuşunu kullanarak buna geçin. Bunun için sağ alt köşede bir gösterge göreceksiniz. ```bash frame="none" title="Plan moduna geç" @@ -237,16 +235,13 @@ opencode'dan projenize yeni özellikler eklemesini isteyebilirsiniz. Yine de ön From this screen, the user can undelete a note or permanently delete it. ``` - opencode'un isteğinizi anlaması için yeterli ayrıntı verin. + OpenCode'un isteğinizi anlaması için yeterli ayrıntı verin. Ekibinizdeki junior bir geliştiriciyle konuşur gibi yazmak genelde iyi sonuç verir. :::tip - opencode'a bol bağlam ve örnek verin. + OpenCode'a ne istediğinizi anlamasına yardımcı olacak bol miktarda bağlam ve örnek verin. ::: - opencode verdiğiniz görselleri tarayıp prompt'a ekleyebilir. - Bunu bir görseli terminale sürükleyip bırakarak yapabilirsiniz. - 2. **Planı yineleyin** Size bir plan sunduğunda ona geri bildirimde bulunabilir veya daha fazla ayrıntı ekleyebilirsiniz. @@ -260,12 +255,12 @@ opencode'dan projenize yeni özellikler eklemesini isteyebilirsiniz. Yine de ön İsteme eklemek için görüntüleri terminale sürükleyip bırakın. ::: - opencode verdiğiniz görselleri tarayıp prompt'a ekleyebilir. + OpenCode verdiğiniz görselleri tarayıp prompt'a ekleyebilir. Bunu bir görseli terminale sürükleyip bırakarak yapabilirsiniz. 3. **Özelliği oluşturun** - Planı yeterli bulduğunuzda **Sekme** tuşuna tekrar basarak \_Build modu_na dönün. + Planı yeterli bulduğunuzda **Tab** tuşuna tekrar basarak \_Build modu\_\na dönün. ```bash frame="none" @@ -281,7 +276,7 @@ opencode'dan projenize yeni özellikler eklemesini isteyebilirsiniz. Yine de ön ### Değişiklik Yapma -Daha basit değişikliklerde, önce planı incelemeden opencode'dan doğrudan değişiklik yapmasını isteyebilirsiniz. +Daha basit değişikliklerde, önce planı incelemeden OpenCode'dan doğrudan değişiklik yapmasını isteyebilirsiniz. ```txt frame="none" "@packages/functions/src/settings.ts" "@packages/functions/src/notes.ts" We need to add authentication to the /settings route. Take a look at how this is @@ -289,32 +284,31 @@ handled in the /notes route in @packages/functions/src/notes.ts and implement the same logic in @packages/functions/src/settings.ts ``` -opencode'un doğru değişiklikleri yapması için yeterli ayrıntı verdiğinizden emin olun. +OpenCode'un doğru değişiklikleri yapması için yeterli ayrıntı verdiğinizden emin olun. --- ### Değişiklikleri Geri Alma -Diyelim ki opencode'dan bazı değişiklikler yapmasını istediniz. +Diyelim ki OpenCode'dan bazı değişiklikler yapmasını istediniz. ```txt frame="none" "@packages/functions/src/api/index.ts" Can you refactor the function in @packages/functions/src/api/index.ts? ``` -Ama istediğinin bu olmadığını anlıyorsun. Değişiklikleri **geri alabilirsiniz** -`/undo` komutunu kullanarak. +Ama istediğinin bu olmadığını anlıyorsun. `/undo` komutunu kullanarak değişiklikleri **geri alabilirsiniz**. ```bash frame="none" /undo ``` -opencode değişiklikleri geri alır ve orijinal mesajınızı tekrar gösterir. +OpenCode değişiklikleri geri alır ve orijinal mesajınızı tekrar gösterir. ```txt frame="none" "@packages/functions/src/api/index.ts" Can you refactor the function in @packages/functions/src/api/index.ts? ``` -Buradan komut isteminde ince ayar yapabilir ve opencode'dan tekrar denemesini isteyebilirsiniz. +Buradan komut isteminde ince ayar yapabilir ve OpenCode'dan tekrar denemesini isteyebilirsiniz. :::tip Birden çok değişikliği geri almak için `/undo` komutunu birden çok kez çalıştırabilirsiniz. @@ -330,7 +324,7 @@ Veya `/redo` komutunu kullanarak değişiklikleri **yeniden yapabilirsiniz**. ## Paylaşma -opencode ile yaptığınız görüşmeleri [ekibinizle paylaşabilirsiniz](/docs/share). +OpenCode ile yaptığınız görüşmeleri [ekibinizle paylaşabilirsiniz](/docs/share). ```bash frame="none" /share @@ -342,12 +336,12 @@ Bu, mevcut konuşmaya bir bağlantı oluşturacak ve bunu panonuza kopyalayacakt Konuşmalar varsayılan olarak paylaşılmaz. ::: -İşte opencode'lu bir [örnek konuşma](https://opencode.ai/s/4XP1fce5). +İşte OpenCode ile bir [örnek konuşma](https://opencode.ai/s/4XP1fce5). --- ## Özelleştirme -İşte bu kadar! Artık opencode'u kullanma konusunda profesyonelsiniz. +İşte bu kadar! Artık OpenCode'u kullanma konusunda profesyonelsiniz. -Kendinize göre uyarlamak için [tema seçebilir](/docs/themes), [tuş atamalarını özelleştirebilir](/docs/keybinds), [kod biçimlendirici ayarlayabilir](/docs/formatters), [özel komutlar oluşturabilir](/docs/commands) veya [opencode config](/docs/config) ile oynayabilirsiniz. +Kendinize göre uyarlamak için [tema seçebilir](/docs/themes), [tuş atamalarını özelleştirebilir](/docs/keybinds), [kod biçimlendirici ayarlayabilir](/docs/formatters), [özel komutlar oluşturabilir](/docs/commands) veya [OpenCode config](/docs/config) ile oynayabilirsiniz. diff --git a/packages/web/src/content/docs/tr/keybinds.mdx b/packages/web/src/content/docs/tr/keybinds.mdx index 9a22c329cc7..bea63a35503 100644 --- a/packages/web/src/content/docs/tr/keybinds.mdx +++ b/packages/web/src/content/docs/tr/keybinds.mdx @@ -3,11 +3,11 @@ title: Tuş atamaları description: Tuş bağlantılarınızı özelleştirin. --- -opencode, opencode yapılandırması aracılığıyla özelleştirebileceğiniz bir tuş bağlantıları listesine sahiptir. +opencode, `tui.json` aracılığıyla özelleştirebileceğiniz bir tuş bağlantıları listesine sahiptir. -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "keybinds": { "leader": "ctrl+x", "app_exit": "ctrl+c,ctrl+d,q", @@ -119,9 +119,9 @@ Tuş atamalarınız için lider anahtar kullanmanıza gerek yoktur ancak bunu ya Anahtarı yapılandırmanıza "none" değeriyle ekleyerek bir tuş atamasını devre dışı bırakabilirsiniz. -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "keybinds": { "session_compact": "none" } diff --git a/packages/web/src/content/docs/tr/lsp.mdx b/packages/web/src/content/docs/tr/lsp.mdx index db742113854..52a95d1c3c0 100644 --- a/packages/web/src/content/docs/tr/lsp.mdx +++ b/packages/web/src/content/docs/tr/lsp.mdx @@ -1,15 +1,15 @@ --- title: LSP Sunucuları -description: opencode, LSP sunucularınızla bütünleşir. +description: OpenCode, LSP sunucularınızla bütünleşir. --- -opencode, LLM'in kod tabanınızla etkileşime girmesine yardımcı olmak için Dil Sunucusu Protokolünüzle (LSP) bütünleşir. LLM'ye geri bildirim sağlamak için tanılamayı kullanır. +OpenCode, LLM'in kod tabanınızla etkileşime girmesine yardımcı olmak için Dil Sunucusu Protokolünüzle (LSP) bütünleşir. LLM'ye geri bildirim sağlamak için tanılamayı kullanır. --- ## Yerleşik -opencode, popüler diller için çeşitli yerleşik LSP sunucularıyla birlikte gelir: +OpenCode, popüler diller için çeşitli yerleşik LSP sunucularıyla birlikte gelir: | LSP Sunucu | Uzantılar | Gereksinimler | | ------------------ | ------------------------------------------------------------------- | --------------------------------------------------------------------- | @@ -27,6 +27,7 @@ opencode, popüler diller için çeşitli yerleşik LSP sunucularıyla birlikte | gopls | .go | `go` komutu mevcut | | hls | .hs, .lhs | `haskell-language-server-wrapper` komutu mevcut | | jdtls | .java | `Java SDK (version 21+)` kurulu | +| julials | .jl | `julia` ve `LanguageServer.jl` kurulu | | kotlin-ls | .kt, .kts | Kotlin projeleri için otomatik kurulumlar | | lua-ls | .lua | Lua projeleri için otomatik kurulumlar | | nixd | .nix | `nixd` komutu mevcut | @@ -56,7 +57,7 @@ Yukarıdaki dosya uzantılarından biri tespit edildiğinde ve gereksinimler kar ## Nasıl Çalışır? -Opencode bir dosyayı açtığında: +opencode bir dosyayı açtığında: 1. Dosya uzantısını tüm etkin LSP sunucularına göre kontrol eder. 2. Henüz çalışmıyorsa uygun LSP sunucusunu başlatır. @@ -182,7 +183,7 @@ Komutu ve dosya uzantılarını belirterek özel LSP sunucuları ekleyebilirsini PHP Intelephense, bir lisans anahtarı aracılığıyla premium özellikler sunar. Anahtarı (yalnızca) şu adresteki bir metin dosyasına yerleştirerek bir lisans anahtarı sağlayabilirsiniz: -- MacOS/Linux'ta: `$HOME/intelephense/license.txt` +- macOS/Linux'ta: `$HOME/intelephense/license.txt` - Windows'ta: `%USERPROFILE%/intelephense/license.txt` Dosya, ek içerik olmadan yalnızca lisans anahtarını içermelidir. diff --git a/packages/web/src/content/docs/tr/plugins.mdx b/packages/web/src/content/docs/tr/plugins.mdx index 0b24e48c35e..4926f5f70e5 100644 --- a/packages/web/src/content/docs/tr/plugins.mdx +++ b/packages/web/src/content/docs/tr/plugins.mdx @@ -1,9 +1,9 @@ --- title: Eklentiler -description: opencode'u genişletmek için kendi eklentilerinizi yazın. +description: OpenCode'u genişletmek için kendi eklentilerinizi yazın. --- -Eklentiler, çeşitli olaylara bağlanarak ve davranışı özelleştirerek opencode'u genişletmenize olanak tanır. Yeni özellikler eklemek, harici hizmetlerle entegrasyon sağlamak veya opencode'un varsayılan davranışını değiştirmek için eklentiler oluşturabilirsiniz. +Eklentiler, çeşitli olaylara bağlanarak ve davranışı özelleştirerek OpenCode'u genişletmenize olanak tanır. Yeni özellikler eklemek, harici hizmetlerle entegrasyon sağlamak veya OpenCode'un varsayılan davranışını değiştirmek için eklentiler oluşturabilirsiniz. Örnekler için topluluk tarafından oluşturulan [eklentilere](/docs/ecosystem#plugins) göz atın. @@ -47,7 +47,7 @@ Hem normal hem de kapsamlı npm paketleri desteklenir. **npm eklentileri** başlangıçta Bun kullanılarak otomatik olarak yüklenir. Paketler ve bağımlılıkları `~/.cache/opencode/node_modules/`'da önbelleğe alınır. -**Yerel eklentiler** doğrudan eklenti dizininden yüklenir. Harici paketleri kullanmak için, sisteminizin dizininde bir `package.json` oluşturmanız (bkz. [Bağımlılıklar](#dependencies)) veya eklentiyi npm ve [add it to your config](/docs/config#plugins)'de yayınlamanız gerekir. +**Yerel eklentiler** doğrudan eklenti dizininden yüklenir. Harici paketleri kullanmak için, sisteminizin dizininde bir `package.json` oluşturmanız (bkz. [Bağımlılıklar](#dependencies)) veya eklentiyi npm ve [yapılandırmanıza eklemeniz](/docs/config#plugins) gerekir. --- @@ -66,8 +66,7 @@ Aynı ad ve sürüme sahip yinelenen npm paketleri bir kez yüklenir. Ancak benz ## Eklenti oluşturma -Eklenti, bir veya daha fazla eklentiyi dışa aktaran bir **JavaScript/TypeScript modülüdür** -işlevler. Her işlev bir bağlam nesnesi alır ve bir kanca nesnesi döndürür. +Eklenti, bir veya daha fazla eklenti işlevini dışa aktaran bir **JavaScript/TypeScript modülüdür**. Her işlev bir bağlam nesnesi alır ve bir kanca nesnesi döndürür. --- @@ -83,7 +82,7 @@ Yerel eklentiler ve özel araçlar harici npm paketlerini kullanabilir. İhtiyac } ``` -opencode bunları yüklemek için başlangıçta `bun install` komutunu çalıştırır. Eklentileriniz ve araçlarınız daha sonra bunları içe aktarabilir. +OpenCode bunları yüklemek için başlangıçta `bun install` komutunu çalıştırır. Eklentileriniz ve araçlarınız daha sonra bunları içe aktarabilir. ```ts title=".opencode/plugins/my-plugin.ts" import { escape } from "shescape" @@ -116,9 +115,9 @@ export const MyPlugin = async ({ project, client, $, directory, worktree }) => { Eklenti işlevi şunları alır: - `project`: Mevcut proje bilgisi. -- `directory`: güncel çalışma dizini. +- `directory`: Güncel çalışma dizini. - `worktree`: Git çalışma ağacı yolu. -- `client`: Yapay zeka ile etkileşime geçmek için opencode'lu bir SDK istemcisi. +- `client`: Yapay zeka ile etkileşim kurmak için bir OpenCode SDK istemcisi. - `$`: Bun'un komutları yürütmek için kullandığı [shell API](https://bun.com/docs/runtime/shell). --- @@ -211,7 +210,7 @@ Eklentiler aşağıdaki Örnekler bölümünde görüldüğü gibi etkinliklere ## Örnekler -opencode'u genişletmek için kullanabileceğiniz bazı eklenti örneklerini burada bulabilirsiniz. +OpenCode'u genişletmek için kullanabileceğiniz bazı eklenti örneklerini burada bulabilirsiniz. --- @@ -232,17 +231,17 @@ export const NotificationPlugin = async ({ project, client, $, directory, worktr } ``` -MacOS'ta AppleScript'i çalıştırmak için `osascript` kullanıyoruz. Burada bildirim göndermek için kullanıyoruz. +macOS'ta AppleScript'i çalıştırmak için `osascript` kullanıyoruz. Burada bildirim göndermek için kullanıyoruz. :::note -opencode masaüstü uygulamasını kullanıyorsanız yanıt hazır olduğunda veya oturum hataları oluştuğunda otomatik olarak sistem bildirimleri gönderebilir. +OpenCode masaüstü uygulamasını kullanıyorsanız yanıt hazır olduğunda veya oturum hataları oluştuğunda otomatik olarak sistem bildirimleri gönderebilir. ::: --- ### .env Koruması -opencode'un `.env` dosyalarını okumasını önleyin: +OpenCode'un `.env` dosyalarını okumasını önleyin: ```javascript title=".opencode/plugins/env-protection.js" export const EnvProtection = async ({ project, client, $, directory, worktree }) => { @@ -277,7 +276,7 @@ export const InjectEnvPlugin = async () => { ### Özel araçlar -Eklentiler ayrıca opencode'a özel araçlar da ekleyebilir: +Eklentiler ayrıca OpenCode'a özel araçlar da ekleyebilir: ```ts title=".opencode/plugins/custom-tools.ts" import { type Plugin, tool } from "@opencode-ai/plugin" @@ -300,19 +299,23 @@ export const CustomToolsPlugin: Plugin = async (ctx) => { } ``` -`tool` yardımcısı, opencode'un çağırabileceği özel bir araç oluşturur. Bir Zod şeması işlevini alır ve aşağıdakileri içeren bir araç tanımı döndürür: +`tool` yardımcısı, OpenCode'un çağırabileceği özel bir araç oluşturur. Bir Zod şeması işlevini alır ve aşağıdakileri içeren bir araç tanımı döndürür: - `description`: Araç ne yapar? - `args`: Aracın argümanları için Zod şeması - `execute`: Araç çağrıldığında çalışan fonksiyon -Özel araçlarınız, yerleşik araçların yanı sıra kod açmaya da hazır olacaktır. +Özel araçlarınız, yerleşik araçların yanı sıra OpenCode'a da hazır olacaktır. + +:::note +Eğer bir eklenti aracı yerleşik bir araçla aynı adı kullanırsa, eklenti aracı öncelik kazanır. +::: --- ### Günlüğe kaydetme -Yapılandırılmış günlük kaydı için `client.app.log()` yerine `console.log` kullanın: +Yapılandırılmış günlük kaydı için `console.log` yerine `client.app.log()` kullanın: ```ts title=".opencode/plugins/my-plugin.ts" export const MyPlugin = async ({ client }) => { @@ -327,7 +330,7 @@ export const MyPlugin = async ({ client }) => { } ``` -Seviyeler: `debug`, `info`, `warn`, `error`. Ayrıntılar için [SDK documentation](https://opencode.ai/docs/sdk)'e bakın. +Seviyeler: `debug`, `info`, `warn`, `error`. Ayrıntılar için [SDK belgelerine](https://opencode.ai/docs/sdk) bakın. --- diff --git a/packages/web/src/content/docs/tr/providers.mdx b/packages/web/src/content/docs/tr/providers.mdx index efe5ff9afa4..1ddc65131c9 100644 --- a/packages/web/src/content/docs/tr/providers.mdx +++ b/packages/web/src/content/docs/tr/providers.mdx @@ -84,6 +84,37 @@ opencode'daki diğer sağlayıcılar gibi çalışır ve kullanımı tamamen ist --- +## OpenCode Go + +OpenCode Go, opencode ile iyi çalıştığı test edilmiş ve doğrulanmış, opencode ekibi tarafından sağlanan popüler açık kodlama modellerine güvenilir erişim sağlayan düşük maliyetli bir abonelik planıdır. + +1. TUI'de `/connect` komutunu çalıştırın, `OpenCode Go`'yu seçin ve [opencode.ai/auth](https://opencode.ai/zen) adresine gidin. + + ```txt + /connect + ``` + +2. Oturum açın, fatura ayrıntılarınızı ekleyin ve API anahtarınızı kopyalayın. + +3. API anahtarınızı yapıştırın. + + ```txt + ┌ API key + │ + │ + └ enter + ``` + +4. Önerdiğimiz modellerin listesini görmek için TUI'de `/models` komutunu çalıştırın. + + ```txt + /models + ``` + +opencode'daki diğer sağlayıcılar gibi çalışır ve kullanımı tamamen isteğe bağlıdır. + +--- + ## Dizin Sağlayıcılardan bazılarına ayrıntılı olarak bakalım. Bir sağlayıcı eklemek istiyorsanız @@ -1481,6 +1512,39 @@ SAP AI Core, birleşik bir platform aracılığıyla OpenAI, Anthropic, Google, --- +### STACKIT + +STACKIT AI Model Serving, Llama, Mistral ve Qwen gibi LLM'lere odaklanarak, Avrupa altyapısında maksimum veri egemenliğine sahip AI modelleri için tam olarak yönetilen bağımsız barındırma ortamı sağlar. + +1. [STACKIT Portal](https://portal.stackit.cloud) adresine gidin, **AI Model Serving**'e gidin ve projeniz için bir yetkilendirme belirteci oluşturun. + + :::tip + Yetkilendirme belirteçleri oluşturmadan önce bir STACKIT müşteri hesabına, kullanıcı hesabına ve projesine ihtiyacınız vardır. + ::: + +2. `/connect` komutunu çalıştırın ve **STACKIT**'i arayın. + + ```txt + /connect + ``` + +3. STACKIT AI Model Serving yetkilendirme belirtecinizi girin. + + ```txt + ┌ API key + │ + │ + └ enter + ``` + +4. _Qwen3-VL 235B_ veya _Llama 3.3 70B_ gibi mevcut modellerden seçim yapmak için `/models` komutunu çalıştırın. + + ```txt + /models + ``` + +--- + ### OVHcloud AI Endpoints 1. [OVHcloud panel](https://ovh.com/manager)'a gidin. `Public Cloud` bölümüne gidin, `AI & Machine Learning` > `AI Endpoints` ve `API Keys` sekmesinde **Yeni bir API anahtarı oluştur**'u tıklayın. diff --git a/packages/web/src/content/docs/tr/sdk.mdx b/packages/web/src/content/docs/tr/sdk.mdx index b5ba99f4374..44c910b1cb9 100644 --- a/packages/web/src/content/docs/tr/sdk.mdx +++ b/packages/web/src/content/docs/tr/sdk.mdx @@ -117,6 +117,78 @@ try { --- +## Yapılandırılmış Çıktı + +Bir JSON şeması ile `format` belirterek modelden yapılandırılmış JSON çıktısı isteyebilirsiniz. Model, şemanızla eşleşen doğrulanmış JSON'u döndürmek için bir `StructuredOutput` aracı kullanacaktır. + +### Temel Kullanım + +```typescript +const result = await client.session.prompt({ + path: { id: sessionId }, + body: { + parts: [{ type: "text", text: "Anthropic'i araştırın ve şirket bilgileri sağlayın" }], + format: { + type: "json_schema", + schema: { + type: "object", + properties: { + company: { type: "string", description: "Şirket adı" }, + founded: { type: "number", description: "Kuruluş yılı" }, + products: { + type: "array", + items: { type: "string" }, + description: "Ana ürünler", + }, + }, + required: ["company", "founded"], + }, + }, + }, +}) + +// Yapılandırılmış çıktıya erişin +console.log(result.data.info.structured_output) +// { company: "Anthropic", founded: 2021, products: ["Claude", "Claude API"] } +``` + +### Çıktı Format Türleri + +| Tür | Açıklama | +| ------------- | ------------------------------------------------------------- | +| `text` | Varsayılan. Standart metin yanıtı (yapılandırılmış çıktı yok) | +| `json_schema` | Sağlanan şema ile eşleşen doğrulanmış JSON döndürür | + +### JSON Şema Formatı + +`type: 'json_schema'` kullanırken şunları sağlayın: + +| Alan | Tür | Açıklama | +| ------------ | --------------- | ------------------------------------------------------------- | +| `type` | `'json_schema'` | Gerekli. JSON şema modunu belirtir | +| `schema` | `object` | Gerekli. Çıktı yapısını tanımlayan JSON Şema nesnesi | +| `retryCount` | `number` | İsteğe bağlı. Doğrulama yeniden deneme sayısı (varsayılan: 2) | + +### Hata Yönetimi + +Model, tüm yeniden denemelerden sonra geçerli bir yapılandırılmış çıktı üretemezse, yanıt bir `StructuredOutputError` içerecektir: + +```typescript +if (result.data.info.error?.name === "StructuredOutputError") { + console.error("Yapılandırılmış çıktı üretilemedi:", result.data.info.error.message) + console.error("Denemeler:", result.data.info.error.retries) +} +``` + +### En İyi Uygulamalar + +1. **Açık açıklamalar sağlayın**: Modelin hangi verileri çıkaracağını anlamasına yardımcı olmak için şema özelliklerinde +2. **`required` kullanın**: Hangi alanların mevcut olması gerektiğini belirtmek için +3. **Şemaları odaklı tutun**: Karmaşık iç içe geçmiş şemaların model tarafından doğru doldurulması daha zor olabilir +4. **Uygun `retryCount` ayarlayın**: Karmaşık şemalar için artırın, basit olanlar için azaltın + +--- + ## API'ler SDK, tüm sunucu API'lerini type-safe bir istemci aracılığıyla sunar. @@ -226,27 +298,27 @@ const { providers, default: defaults } = await client.config.providers() ### Oturumlar -| Yöntem | Açıklama | Notlar | -| ---------------------------------------------------------- | ------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `session.list()` | Oturumları listele | Session[] döndürür | -| `session.get({ path })` | Oturum al | Session döndürür | -| `session.children({ path })` | Alt oturumları listele | Session[] döndürür | -| `session.create({ body })` | Oturum oluştur | Session döndürür | -| `session.delete({ path })` | Oturum sil | `boolean` döndürür | -| `session.update({ path, body })` | Oturum özelliklerini güncelle | Session döndürür | -| `session.init({ path, body })` | Uygulamayı analiz et ve `AGENTS.md` oluştur | `boolean` döndürür | -| `session.abort({ path })` | Çalışan bir oturumu iptal et | `boolean` döndürür | -| `session.share({ path })` | Oturum paylaş | Session döndürür | -| `session.unshare({ path })` | Oturum paylaşımını kaldır | Session döndürür | -| `session.summarize({ path, body })` | Oturumu özetle | `boolean` döndürür | -| `session.messages({ path })` | Oturumdaki mesajları listele | `{ info: `Message`, parts: `Part[]`}[]` döndürür | -| `session.message({ path })` | Mesaj ayrıntılarını al | `{ info: `Message`, parts: `Part[]`}` döndürür | -| `session.prompt({ path, body })` | İstem mesajı gönder | `body.noReply: true` UserMessage (yalnızca bağlam) döndürür. Varsayılan olarak AI yanıtıyla AssistantMessage döndürür | -| `session.command({ path, body })` | Oturuma komut gönder | `{ info: `AssistantMessage`, parts: `Part[]`}` döndürür | -| `session.shell({ path, body })` | Bir kabuk komutu çalıştır | AssistantMessage döndürür | -| `session.revert({ path, body })` | Bir mesajı geri al | Session döndürür | -| `session.unrevert({ path })` | Geri alınan mesajları geri yükle | Session döndürür | -| `postSessionByIdPermissionsByPermissionId({ path, body })` | Bir izin isteğine yanıt ver | `boolean` döndürür | +| Yöntem | Açıklama | Notlar | +| ---------------------------------------------------------- | ------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `session.list()` | Oturumları listele | Session[] döndürür | +| `session.get({ path })` | Oturum al | Session döndürür | +| `session.children({ path })` | Alt oturumları listele | Session[] döndürür | +| `session.create({ body })` | Oturum oluştur | Session döndürür | +| `session.delete({ path })` | Oturum sil | `boolean` döndürür | +| `session.update({ path, body })` | Oturum özelliklerini güncelle | Session döndürür | +| `session.init({ path, body })` | Uygulamayı analiz et ve `AGENTS.md` oluştur | `boolean` döndürür | +| `session.abort({ path })` | Çalışan bir oturumu iptal et | `boolean` döndürür | +| `session.share({ path })` | Oturum paylaş | Session döndürür | +| `session.unshare({ path })` | Oturum paylaşımını kaldır | Session döndürür | +| `session.summarize({ path, body })` | Oturumu özetle | `boolean` döndürür | +| `session.messages({ path })` | Oturumdaki mesajları listele | `{ info: `Message`, parts: `Part[]`}[]` döndürür | +| `session.message({ path })` | Mesaj ayrıntılarını al | `{ info: `Message`, parts: `Part[]`}` döndürür | +| `session.prompt({ path, body })` | İstem mesajı gönder | `body.noReply: true` UserMessage (yalnızca bağlam) döndürür. Varsayılan olarak AI yanıtıyla AssistantMessage döndürür. [yapılandırılmış çıktı](#yapılandırılmış-çıktı) için `body.outputFormat` destekler | +| `session.command({ path, body })` | Oturuma komut gönder | `{ info: `AssistantMessage`, parts: `Part[]`}` döndürür | +| `session.shell({ path, body })` | Bir kabuk komutu çalıştır | AssistantMessage döndürür | +| `session.revert({ path, body })` | Bir mesajı geri al | Session döndürür | +| `session.unrevert({ path })` | Geri alınan mesajları geri yükle | Session döndürür | +| `postSessionByIdPermissionsByPermissionId({ path, body })` | Bir izin isteğine yanıt ver | `boolean` döndürür | --- diff --git a/packages/web/src/content/docs/tr/themes.mdx b/packages/web/src/content/docs/tr/themes.mdx index 93911315f26..1511e2b9212 100644 --- a/packages/web/src/content/docs/tr/themes.mdx +++ b/packages/web/src/content/docs/tr/themes.mdx @@ -61,11 +61,11 @@ Sistem teması şu kullanıcılar için idealdir: ## Tema kullanımı -`/theme` komutuyla tema seçicisini açıp tema seçebilirsiniz. İsterseniz [config](/docs/config) dosyanızda da belirtebilirsiniz. +`/theme` komutuyla tema seçicisini açıp tema seçebilirsiniz. İsterseniz `tui.json` içinde de belirtebilirsiniz. -```json title="opencode.json" {3} +```json title="tui.json" {3} { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "theme": "tokyonight" } ``` diff --git a/packages/web/src/content/docs/tr/tui.mdx b/packages/web/src/content/docs/tr/tui.mdx index 2bd5db8f5df..55305f36727 100644 --- a/packages/web/src/content/docs/tr/tui.mdx +++ b/packages/web/src/content/docs/tr/tui.mdx @@ -1,25 +1,25 @@ --- title: TUI -description: opencode terminal arayuzunu kullanin. +description: OpenCode terminal kullanıcı arayüzünü kullanma. --- import { Tabs, TabItem } from "@astrojs/starlight/components" -opencode, projelerinizde LLM ile calismak icin etkilesimli bir terminal arayuzu (TUI) sunar. +OpenCode, projelerinizde LLM ile çalışmak için etkileşimli bir terminal arayüzü (TUI) sunar. -opencode'u calistirdiginizda mevcut dizin icin TUI baslar. +OpenCode'u çalıştırdığınızda mevcut dizin için TUI başlar. ```bash opencode ``` -Isterseniz belirli bir calisma dizini icin de baslatabilirsiniz. +İsterseniz belirli bir çalışma dizini için de başlatabilirsiniz. ```bash opencode /path/to/project ``` -TUI icinde bir mesaj yazarak istem gonderebilirsiniz. +TUI içine girdiğinizde, bir mesajla istem gönderebilirsiniz. ```text Give me a quick summary of the codebase. @@ -27,51 +27,51 @@ Give me a quick summary of the codebase. --- -## Dosya referanslari +## Dosya referansları -Mesajlarinizda `@` kullanarak dosyalara referans verebilirsiniz. Bu, mevcut calisma dizininde bulanik dosya aramasi yapar. +Mesajlarınızda `@` kullanarak dosyalara referans verebilirsiniz. Bu, mevcut çalışma dizininde bulanık dosya araması yapar. :::tip -Mesajlarinizda dosyalara referans vermek icin `@` kullanabilirsiniz. +Mesajlarınızda dosyalara referans vermek için `@` kullanabilirsiniz. ::: ```text "@packages/functions/src/api/index.ts" How is auth handled in @packages/functions/src/api/index.ts? ``` -Dosya icerigi otomatik olarak konusmaya eklenir. +Dosya içeriği otomatik olarak konuşmaya eklenir. --- -## Bash komutlari +## Bash komutları -Bir mesaji `!` ile baslatarak shell komutu calistirin. +Bir mesajı `!` ile başlatarak shell komutu çalıştırın. ```bash frame="none" !ls -la ``` -Komut ciktiisi bir arac sonucu olarak konusmaya eklenir. +Komut çıktısı bir araç sonucu olarak konuşmaya eklenir. --- ## Komutlar -opencode TUI kullanirken `/` ve komut adini yazarak hizli eylemler calistirabilirsiniz. Ornek: +OpenCode TUI kullanırken `/` ve ardından komut adını yazarak hızlı eylemler çalıştırabilirsiniz. Örneğin: ```bash frame="none" /help ``` -Komutlarin cogu, lider tusu `ctrl+x` olan bir kisayola da sahiptir. [Daha fazla bilgi](/docs/keybinds). +Komutların çoğu, lider tuşu `ctrl+x` olan bir kısayola da sahiptir. [Daha fazla bilgi](/docs/keybinds). -Mevcut slash komutlarinin tam listesi: +İşte mevcut slash komutlarının tümü: --- ### connect -opencode'a bir provider ekler. Mevcut provider'lari secip API anahtarlari eklemenizi saglar. +OpenCode'a bir sağlayıcı ekler. Mevcut sağlayıcıları seçip API anahtarları eklemenizi sağlar. ```bash frame="none" /connect @@ -81,172 +81,172 @@ opencode'a bir provider ekler. Mevcut provider'lari secip API anahtarlari ekleme ### compact -Guncel oturumu sikistirir. _Takma ad_: `/summarize` +Mevcut oturumu sıkıştırır. _Takma ad_: `/summarize` ```bash frame="none" /compact ``` -**Kisayol:** `ctrl+x c` +**Kısayol:** `ctrl+x c` --- ### details -Arac calistirma detaylarini goster/gizle yapar. +Araç çalıştırma detaylarını göster/gizle yapar. ```bash frame="none" /details ``` -**Kisayol:** `ctrl+x d` +**Kısayol:** `ctrl+x d` --- ### editor -Mesaj yazmak icin harici editor acar. `EDITOR` ortam degiskeninde ayarli editoru kullanir. [Daha fazla bilgi](#editor-setup). +Mesaj yazmak için harici editör açar. `EDITOR` ortam değişkeninde ayarlı editörü kullanır. [Daha fazla bilgi](#editor-setup). ```bash frame="none" /editor ``` -**Kisayol:** `ctrl+x e` +**Kısayol:** `ctrl+x e` --- ### exit -opencode'dan cikar. _Takma adlar_: `/quit`, `/q` +OpenCode'dan çıkar. _Takma adlar_: `/quit`, `/q` ```bash frame="none" /exit ``` -**Kisayol:** `ctrl+x q` +**Kısayol:** `ctrl+x q` --- ### export -Mevcut konusmayi Markdown olarak disa aktarir ve varsayilan editorunuzde acar. `EDITOR` ortam degiskenindeki editoru kullanir. [Daha fazla bilgi](#editor-setup). +Mevcut konuşmayı Markdown olarak dışa aktarır ve varsayılan editörünüzde açar. `EDITOR` ortam değişkeninde ayarlı editörü kullanır. [Daha fazla bilgi](#editor-setup). ```bash frame="none" /export ``` -**Kisayol:** `ctrl+x x` +**Kısayol:** `ctrl+x x` --- ### help -Yardim penceresini gosterir. +Yardım penceresini gösterir. ```bash frame="none" /help ``` -**Kisayol:** `ctrl+x h` +**Kısayol:** `ctrl+x h` --- ### init -`AGENTS.md` dosyasini olusturur veya gunceller. [Daha fazla bilgi](/docs/rules). +`AGENTS.md` dosyasını oluşturur veya günceller. [Daha fazla bilgi](/docs/rules). ```bash frame="none" /init ``` -**Kisayol:** `ctrl+x i` +**Kısayol:** `ctrl+x i` --- ### models -Kullanilabilir modelleri listeler. +Kullanılabilir modelleri listeler. ```bash frame="none" /models ``` -**Kisayol:** `ctrl+x m` +**Kısayol:** `ctrl+x m` --- ### new -Yeni bir oturum baslatir. _Takma ad_: `/clear` +Yeni bir oturum başlatır. _Takma ad_: `/clear` ```bash frame="none" /new ``` -**Kisayol:** `ctrl+x n` +**Kısayol:** `ctrl+x n` --- ### redo -Geri alinan bir mesaji tekrar uygular. Yalnizca `/undo` kullanildiktan sonra kullanilabilir. +Geri alınan bir mesajı tekrar uygular. Yalnızca `/undo` kullanıldıktan sonra kullanılabilir. :::tip -Dosya degisiklikleri de geri yuklenir. +Herhangi bir dosya değişikliği de geri yüklenir. ::: -Dahilde bu islem dosya degisikliklerini yonetmek icin Git kullanir. Bu nedenle projenizin **bir Git deposu olmasi gerekir**. +Dahili olarak bu işlem dosya değişikliklerini yönetmek için Git kullanır. Bu nedenle projenizin **bir Git deposu olması gerekir**. ```bash frame="none" /redo ``` -**Kisayol:** `ctrl+x r` +**Kısayol:** `ctrl+x r` --- ### sessions -Oturumlari listeler ve aralarinda gecis yapar. _Takma adlar_: `/resume`, `/continue` +Oturumları listeler ve aralarında geçiş yapar. _Takma adlar_: `/resume`, `/continue` ```bash frame="none" /sessions ``` -**Kisayol:** `ctrl+x l` +**Kısayol:** `ctrl+x l` --- ### share -Mevcut oturumu paylasir. [Daha fazla bilgi](/docs/share). +Mevcut oturumu paylaşır. [Daha fazla bilgi](/docs/share). ```bash frame="none" /share ``` -**Kisayol:** `ctrl+x s` +**Kısayol:** `ctrl+x s` --- ### themes -Kullanilabilir temalari listeler. +Kullanılabilir temaları listeler. ```bash frame="none" -/theme +/themes ``` -**Kisayol:** `ctrl+x t` +**Kısayol:** `ctrl+x t` --- ### thinking -Konusmadaki thinking/reasoning bloklarinin gorunurlugunu degistirir. Etkin oldugunda, genisletilmis dusunmeyi destekleyen modellerin akil yurutmelerini gorebilirsiniz. +Konuşmadaki thinking/reasoning bloklarının görünürlüğünü değiştirir. Etkin olduğunda, genişletilmiş düşünmeyi destekleyen modellerin akıl yürütme sürecini görebilirsiniz. :::note -Bu komut sadece thinking bloklarinin **gosterimini** kontrol eder, modelin gercek akil yurutmelerini acip kapatmaz. Gercek akil yurutme yetenegini degistirmek icin `ctrl+t` ile model varyantlari arasinda gecis yapin. +Bu komut sadece thinking bloklarının **gösterilip gösterilmeyeceğini** kontrol eder - modelin akıl yürütme yeteneklerini etkinleştirmez veya devre dışı bırakmaz. Gerçek akıl yürütme yeteneklerini değiştirmek için `ctrl+t` kullanarak model varyantları arasında geçiş yapın. ::: ```bash frame="none" @@ -257,25 +257,25 @@ Bu komut sadece thinking bloklarinin **gosterimini** kontrol eder, modelin gerce ### undo -Konusmadaki son mesaji geri alir. En son kullanici mesaji, sonraki tum yanitlar ve dosya degisiklikleri kaldirilir. +Konuşmadaki son mesajı geri alır. En son kullanıcı mesajını, sonraki tüm yanıtları ve dosya değişikliklerini kaldırır. :::tip -Yapilan dosya degisiklikleri de geri cevrilir. +Yapılan tüm dosya değişiklikleri de geri alınır. ::: -Dahilde bu islem dosya degisikliklerini yonetmek icin Git kullanir. Bu nedenle projenizin **bir Git deposu olmasi gerekir**. +Dahili olarak bu işlem dosya değişikliklerini yönetmek için Git kullanır. Bu nedenle projenizin **bir Git deposu olması gerekir**. ```bash frame="none" /undo ``` -**Kisayol:** `ctrl+x u` +**Kısayol:** `ctrl+x u` --- ### unshare -Mevcut oturumun paylasimini kaldirir. [Daha fazla bilgi](/docs/share#paylasimi-kaldirma). +Mevcut oturumun paylaşımını kaldırır. [Daha fazla bilgi](/docs/share#un-sharing). ```bash frame="none" /unshare @@ -283,9 +283,9 @@ Mevcut oturumun paylasimini kaldirir. [Daha fazla bilgi](/docs/share#paylasimi-k --- -## Editor kurulumu +## Editör kurulumu -`/editor` ve `/export` komutlari, `EDITOR` ortam degiskeninde tanimli editoru kullanir. +Hem `/editor` hem de `/export` komutları, `EDITOR` ortam değişkeninde belirtilen editörü kullanır. @@ -299,7 +299,7 @@ Mevcut oturumun paylasimini kaldirir. [Daha fazla bilgi](/docs/share#paylasimi-k export EDITOR="code --wait" ``` - Kalici yapmak icin bunu kabuk profilinize ekleyin: + Kalıcı yapmak için bunu kabuk profilinize ekleyin; `~/.bashrc`, `~/.zshrc` vb. @@ -313,7 +313,8 @@ Mevcut oturumun paylasimini kaldirir. [Daha fazla bilgi](/docs/share#paylasimi-k set EDITOR=code --wait ``` - Kalici yapmak icin **System Properties** > **Environment Variables** yolunu kullanin. + Kalıcı yapmak için **System Properties** > **Environment + Variables** yolunu kullanın. @@ -326,62 +327,72 @@ Mevcut oturumun paylasimini kaldirir. [Daha fazla bilgi](/docs/share#paylasimi-k $env:EDITOR = "code --wait" ``` - Kalici yapmak icin bunu PowerShell profilinize ekleyin. + Kalıcı yapmak için bunu PowerShell profilinize ekleyin. -Yaygin editor secenekleri: +Popüler editör seçenekleri şunları içerir: - `code` - Visual Studio Code - `cursor` - Cursor - `windsurf` - Windsurf -- `nvim` - Neovim editoru -- `vim` - Vim editoru -- `nano` - Nano editoru +- `nvim` - Neovim editörü +- `vim` - Vim editörü +- `nano` - Nano editörü - `notepad` - Windows Notepad - `subl` - Sublime Text :::note -VS Code gibi bazi editorlerin `--wait` parametresiyle baslatilmasi gerekir. +VS Code gibi bazı editörlerin `--wait` bayrağı ile başlatılması gerekir. ::: -Bazi editorler bloklayici modda calismak icin komut satiri argumanlari ister. `--wait` bayragi editor surecinin kapanana kadar beklemesini saglar. +Bazı editörler bloklama modunda çalışmak için komut satırı argümanlarına ihtiyaç duyar. `--wait` bayrağı, editör süreci kapanana kadar işlemin bloklanmasını sağlar. --- -## Yapilandirin +## Yapılandırma -TUI davranisini opencode config dosyanizdan ozellestirebilirsiniz. +TUI davranışını `tui.json` (veya `tui.jsonc`) aracılığıyla özelleştirebilirsiniz. -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", - "tui": { - "scroll_speed": 3, - "scroll_acceleration": { - "enabled": true - } - } + "$schema": "https://opencode.ai/tui.json", + "theme": "opencode", + "keybinds": { + "leader": "ctrl+x" + }, + "scroll_speed": 3, + "scroll_acceleration": { + "enabled": true + }, + "diff_style": "auto" } ``` -### Secenekler +Bu, sunucu/çalışma zamanı davranışını yapılandıran `opencode.json` dosyasından ayrıdır. -- `scroll_acceleration` - Daha akici ve dogal kaydirma icin macOS tarzı hizlanmayi acar. Etkin oldugunda hizli kaydirma hareketlerinde hiz artar, yavas hareketlerde hassas kalir. **Bu ayar etkin oldugunda `scroll_speed` degerini gecersiz kilar.** -- `scroll_speed` - Kaydirma komutlariyla TUI'nin ne kadar hizli kayacagini belirler (minimum: `1`). Varsayilan `3` degeridir. **Not: `scroll_acceleration.enabled` `true` ise yok sayilir.** +### Seçenekler + +- `theme` - UI temanızı ayarlar. [Daha fazla bilgi](/docs/themes). +- `keybinds` - Klavye kısayollarını özelleştirir. [Daha fazla bilgi](/docs/keybinds). +- `scroll_acceleration.enabled` - Pürüzsüz, doğal kaydırma için macOS tarzı kaydırma ivmesini etkinleştirin. Etkinleştirildiğinde, kaydırma hızı hızlı kaydırma hareketleriyle artar ve daha yavaş hareketler için hassas kalır. **Bu ayar `scroll_speed` ayarından önceliklidir ve etkinleştirildiğinde onu geçersiz kılar.** +- `scroll_speed` - Kaydırma komutlarını kullanırken TUI'nin ne kadar hızlı kaydırılacağını kontrol eder (minimum: `0.001`, ondalık değerleri destekler). Varsayılan değer `3`'tür. **Not: `scroll_acceleration.enabled` `true` olarak ayarlanmışsa bu yok sayılır.** +- `diff_style` - Fark (diff) oluşturmayı kontrol eder. `"auto"` terminal genişliğine uyum sağlar, `"stacked"` her zaman tek sütunlu bir düzen gösterir. + +Özel bir TUI yapılandırma yolu yüklemek için `OPENCODE_TUI_CONFIG` kullanın. --- -## Ozellestirme +## Özelleştirme -TUI gorunumunun cesitli kisimlarini komut paletiyle (`ctrl+x h` veya `/help`) ozellestirebilirsiniz. Bu ayarlar yeniden baslatmalar arasinda korunur. +Komut paletini (`ctrl+x h` veya `/help`) kullanarak TUI görünümünün çeşitli yönlerini özelleştirebilirsiniz. Bu ayarlar yeniden başlatmalar arasında korunur. --- -#### Kullanici adi gorunumu +#### Kullanıcı adı görünümü -Sohbet mesajlarinda kullanici adinizin gosterilip gosterilmeyecegini degistirir. Sunlardan erisebilirsiniz: +Sohbet mesajlarında kullanıcı adınızın görünüp görünmeyeceğini değiştirin. Buna şuradan erişin: -- Komut paleti: "username" veya "hide username" aratin -- Ayar otomatik saklanir ve TUI oturumlarinda hatirlanir +- Komut paleti: "username" veya "hide username" araması yapın +- Ayar otomatik olarak kalıcı hale gelir ve TUI oturumları arasında hatırlanır diff --git a/packages/web/src/content/docs/tr/zen.mdx b/packages/web/src/content/docs/tr/zen.mdx index b9978894193..9582a7b7dc1 100644 --- a/packages/web/src/content/docs/tr/zen.mdx +++ b/packages/web/src/content/docs/tr/zen.mdx @@ -1,61 +1,61 @@ --- title: Zen -description: opencode ekibinin sundugu secili model listesi. +description: opencode tarafından sağlanan seçilmiş modeller listesi. --- import config from "../../../../config.mjs" export const console = config.console export const email = `mailto:${config.email}` -OpenCode Zen, opencode ekibi tarafindan test edilip dogrulanmis modellerin listesidir. +OpenCode Zen, opencode ekibi tarafından test edilip doğrulanmış modellerin bir listesidir. :::note -OpenCode Zen su anda beta asamasindadir. +OpenCode Zen şu anda beta aşamasındadır. ::: -Zen, opencode'daki diger provider'lar gibi calisir. OpenCode Zen'e giris yapar ve API anahtarinizi alirsiniz. -Tamamen istege baglidir; opencode kullanmak icin Zen kullanmak zorunda degilsiniz. +Zen, opencode'daki diğer sağlayıcılar gibi çalışır. OpenCode Zen'e giriş yapar ve API anahtarınızı alırsınız. Tamamen isteğe bağlıdır ve opencode kullanmak için bunu kullanmanıza gerek yoktur. --- ## Arka plan -Piyasada cok sayida model var, ancak bunlarin sadece bir kismi kodlama ajani olarak iyi calisir. Ayrica provider'larin cogu birbirinden cok farkli sekilde ayarlanir; bu da performans ve kaliteyi ciddi bicimde degistirir. +Piyasada çok sayıda model var ancak bu modellerden sadece birkaçı kodlama ajanı olarak iyi çalışır. Ayrıca çoğu sağlayıcı çok farklı şekilde yapılandırılmıştır; bu nedenle çok farklı performans ve kalite elde edersiniz. :::tip -opencode ile iyi calisan belirli model/provider kombinasyonlarini test ettik. +opencode ile iyi çalışan seçkin bir grup model ve sağlayıcıyı test ettik. ::: -Bu nedenle OpenRouter benzeri bir servis uzerinden model kullaniyorsaniz, istediginiz modelin en iyi surumunu alip almadiginizdan her zaman emin olamazsiniz. +Bu nedenle, OpenRouter gibi bir şey üzerinden bir model kullanıyorsanız, istediğiniz modelin en iyi sürümünü alıp almadığınızdan asla emin olamazsınız. -Bunu cozmeye yonelik olarak sunlari yaptik: +Bunu düzeltmek için birkaç şey yaptık: -1. Secili bir model grubunu test ettik ve ekipleriyle en iyi calisma sekli uzerine gorustuk -2. Daha sonra bazi provider'larla bu modellerin dogru sekilde sunuldugunu dogruladik -3. Son olarak model/provider kombinasyonlarini benchmark ederek guvenle onerebilecegimiz bir liste olusturduk +1. Seçkin bir grup modeli test ettik ve ekipleriyle bunları en iyi nasıl çalıştıracakları hakkında konuştuk. +2. Daha sonra bunların doğru şekilde sunulduğundan emin olmak için birkaç sağlayıcıyla çalıştık. +3. Son olarak model/sağlayıcı kombinasyonunu karşılaştırdık ve önermekten memnuniyet duyduğumuz bir liste oluşturduk. -OpenCode Zen, bu modellere erisim saglayan bir AI gateway'dir. +OpenCode Zen, bu modellere erişmenizi sağlayan bir AI ağ geçididir. --- -## Nasil calisir +## Nasıl çalışır -OpenCode Zen, opencode'daki diger provider'lar gibi calisir. +OpenCode Zen, opencode'daki diğer sağlayıcılar gibi çalışır. -1. **OpenCode Zen** hesabina giris yapin, odeme bilgilerinizi ekleyin ve API anahtarinizi kopyalayin -2. TUI'da `/connect` komutunu calistirin, OpenCode Zen'i secin ve API anahtarinizi yapistirin -3. Onerdigimiz model listesini gormek icin TUI'da `/models` calistirin +1. **OpenCode Zen**'de oturum açın, fatura ayrıntılarınızı ekleyin ve API anahtarınızı kopyalayın. +2. TUI'de `/connect` komutunu çalıştırın, OpenCode Zen'i seçin ve API anahtarınızı yapıştırın. +3. Önerdiğimiz modellerin listesini görmek için TUI'de `/models` komutunu çalıştırın. -Ucretlendirme istek basina yapilir ve hesabiniza kredi yukleyebilirsiniz. +İstek başına ücretlendirilirsiniz ve hesabınıza kredi ekleyebilirsiniz. --- -## Endpoint'ler +## Uç Noktalar -Modellerimize asagidaki API endpoint'leri uzerinden de erisebilirsiniz. +Modellerimize aşağıdaki API uç noktaları aracılığıyla da erişebilirsiniz. | Model | Model ID | Endpoint | AI SDK Package | | ------------------ | ------------------ | -------------------------------------------------- | --------------------------- | +| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | @@ -65,35 +65,36 @@ Modellerimize asagidaki API endpoint'leri uzerinden de erisebilirsiniz. | GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | | Gemini 3 Pro | gemini-3-pro | `https://opencode.ai/zen/v1/models/gemini-3-pro` | `@ai-sdk/google` | | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.1 | minimax-m2.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.1 Free | minimax-m2.1-free | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| GLM 5 | glm-5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 4.7 | glm-4.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM 4.7 Free | glm-4.7-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 4.6 | glm-4.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 Free | kimi-k2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2 Thinking | kimi-k2-thinking | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2 | kimi-k2 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Qwen3 Coder 480B | qwen3-coder | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -opencode config'inizdeki [model id](/docs/config/#models), `opencode/` formatini kullanir. -Ornegin GPT 5.2 Codex icin config'te `opencode/gpt-5.2-codex` kullanirsiniz. +opencode yapılandırmanızdaki [model kimliği](/docs/config/#models) `opencode/` biçimini kullanır. Örneğin, GPT 5.2 Codex için yapılandırmanızda `opencode/gpt-5.2-codex` kullanırsınız. --- ### Modeller -Mevcut modellerin tam listesini ve metadatasini su adresten cekebilirsiniz: +Mevcut modellerin tam listesini ve meta verilerini şuradan alabilirsiniz: ``` https://opencode.ai/zen/v1/models @@ -101,36 +102,41 @@ https://opencode.ai/zen/v1/models --- -## Fiyatlandirma +## Fiyatlandırma -Kullandikca ode modelini destekliyoruz. Asagidaki fiyatlar **1M token basina** verilmistir. +Kullandıkça öde modelini destekliyoruz. Aşağıda **1 milyon token başına** fiyatlar verilmiştir. | Model | Input | Output | Cached Read | Cached Write | | --------------------------------- | ------ | ------ | ----------- | ------------ | | Big Pickle | Free | Free | Free | - | -| MiniMax M2.1 Free | Free | Free | Free | - | +| MiniMax M2.5 Free | Free | Free | Free | - | +| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | - | | MiniMax M2.1 | $0.30 | $1.20 | $0.10 | - | -| GLM 4.7 Free | Free | Free | Free | - | +| GLM 5 | $1.00 | $3.20 | $0.20 | - | | GLM 4.7 | $0.60 | $2.20 | $0.10 | - | | GLM 4.6 | $0.60 | $2.20 | $0.10 | - | -| Kimi K2.5 Free | Free | Free | Free | - | | Kimi K2.5 | $0.60 | $3.00 | $0.08 | - | | Kimi K2 Thinking | $0.40 | $2.50 | - | - | | Kimi K2 | $0.40 | $2.50 | - | - | | Qwen3 Coder 480B | $0.45 | $1.50 | - | - | +| Claude Opus 4.6 (≤ 200K tokens) | $5.00 | $25.00 | $0.50 | $6.25 | +| Claude Opus 4.6 (> 200K tokens) | $10.00 | $37.50 | $1.00 | $12.50 | +| Claude Opus 4.5 | $5.00 | $25.00 | $0.50 | $6.25 | +| Claude Opus 4.1 | $15.00 | $75.00 | $1.50 | $18.75 | +| Claude Sonnet 4.6 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 | +| Claude Sonnet 4.6 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 | | Claude Sonnet 4.5 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 | | Claude Sonnet 4.5 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 | | Claude Sonnet 4 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 | | Claude Sonnet 4 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 | | Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 | | Claude Haiku 3.5 | $0.80 | $4.00 | $0.08 | $1.00 | -| Claude Opus 4.6 (≤ 200K tokens) | $5.00 | $25.00 | $0.50 | $6.25 | -| Claude Opus 4.6 (> 200K tokens) | $10.00 | $37.50 | $1.00 | $12.50 | -| Claude Opus 4.5 | $5.00 | $25.00 | $0.50 | $6.25 | -| Claude Opus 4.1 | $15.00 | $75.00 | $1.50 | $18.75 | +| Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | +| Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | +| GPT 5.3 Codex | $1.75 | $14.00 | $0.175 | - | | GPT 5.2 | $1.75 | $14.00 | $0.175 | - | | GPT 5.2 Codex | $1.75 | $14.00 | $0.175 | - | | GPT 5.1 | $1.07 | $8.50 | $0.107 | - | @@ -141,98 +147,94 @@ Kullandikca ode modelini destekliyoruz. Asagidaki fiyatlar **1M token basina** v | GPT 5 Codex | $1.07 | $8.50 | $0.107 | - | | GPT 5 Nano | Free | Free | Free | - | -Kullanim gecmisinizde _Claude Haiku 3.5_ gorebilirsiniz. Bu, oturum basliklarini olusturmak icin kullanilan [dusuk maliyetli bir modeldir](/docs/config/#models). +Kullanım geçmişinizde _Claude Haiku 3.5_ fark edebilirsiniz. Bu, oturumlarınızın başlıklarını oluşturmak için kullanılan [düşük maliyetli bir modeldir](/docs/config/#models). :::note -Kredi karti ucretleri maliyetine yansitilir (islem basina %4.4 + $0.30); bunun disinda ek ucret almiyoruz. +Kredi kartı ücretleri maliyetine yansıtılır (işlem başına %4,4 + 0,30$); bunun ötesinde hiçbir ücret talep etmiyoruz. ::: -Ucretsiz modeller: +Ücretsiz modeller: -- GLM 4.7 Free, sinirli bir sure icin opencode'da ucretsizdir. Ekip bu surede geri bildirim toplayip modeli iyilestiriyor -- Kimi K2.5 Free, sinirli bir sure icin opencode'da ucretsizdir. Ekip bu surede geri bildirim toplayip modeli iyilestiriyor -- MiniMax M2.1 Free, sinirli bir sure icin opencode'da ucretsizdir. Ekip bu surede geri bildirim toplayip modeli iyilestiriyor -- Big Pickle, sinirli bir sure icin opencode'da ucretsiz olan gizli bir modeldir. Ekip bu surede geri bildirim toplayip modeli iyilestiriyor +- MiniMax M2.5 Free, sınırlı bir süre için OpenCode'da ücretsizdir. Ekip bu süreyi geri bildirim toplamak ve modeli iyileştirmek için kullanıyor. +- Big Pickle, sınırlı bir süre için OpenCode'da ücretsiz olan gizli bir modeldir. Ekip bu süreyi geri bildirim toplamak ve modeli iyileştirmek için kullanıyor. -Sorunuz varsa bize ulasin. +Sorularınız varsa bizimle iletişime geçin. --- -### Otomatik yukleme +### Otomatik yükleme -Bakiyeniz $5'in altina dustugunde Zen otomatik olarak $20 yukler. +Bakiyeniz 5$'ın altına düşerse, Zen otomatik olarak 20$ yükler. -Otomatik yukleme tutarini degistirebilir veya bu ozelligi tamamen kapatabilirsiniz. +Otomatik yükleme tutarını değiştirebilirsiniz. Otomatik yüklemeyi tamamen devre dışı da bırakabilirsiniz. --- -### Aylik limitler +### Aylık limitler -Tum calisma alani ve ekip uyeleri icin aylik kullanim limiti belirleyebilirsiniz. +Ayrıca tüm çalışma alanı ve ekibinizin her üyesi için aylık kullanım limiti belirleyebilirsiniz. -Ornegin aylik limiti $20 yaptiysaniz Zen bir ayda $20'den fazla kullandirtmaz. Ancak otomatik yukleme aciksa bakiye $5 altina dustugunde toplam odemeniz $20'nin uzerine cikabilir. +Örneğin, aylık kullanım limitini 20$ olarak ayarladığınızı varsayalım, Zen bir ayda 20$'dan fazla kullanmaz. Ancak otomatik yüklemeyi etkinleştirdiyseniz, bakiyeniz 5$'ın altına düşerse Zen sizden 20$'dan fazla ücret alabilir. --- ## Gizlilik -Tum modellerimiz ABD'de barindiriliyor. Provider'larimiz sifir saklama politikasini izler ve verilerinizi model egitimi icin kullanmaz; asagidaki istisnalar haric: +Tüm modellerimiz ABD'de barındırılmaktadır. Sağlayıcılarımız sıfır saklama politikasını izler ve aşağıdaki istisnalar dışında verilerinizi model eğitimi için kullanmaz: -- Big Pickle: Ucretsiz donemde toplanan veriler modeli iyilestirmek icin kullanilabilir -- GLM 4.7 Free: Ucretsiz donemde toplanan veriler modeli iyilestirmek icin kullanilabilir -- Kimi K2.5 Free: Ucretsiz donemde toplanan veriler modeli iyilestirmek icin kullanilabilir -- MiniMax M2.1 Free: Ucretsiz donemde toplanan veriler modeli iyilestirmek icin kullanilabilir -- OpenAI API'leri: Istekler [OpenAI veri politikalari](https://platform.openai.com/docs/guides/your-data) kapsaminda 30 gun saklanir -- Anthropic API'leri: Istekler [Anthropic veri politikalari](https://docs.anthropic.com/en/docs/claude-code/data-usage) kapsaminda 30 gun saklanir +- Big Pickle: Ücretsiz döneminde toplanan veriler modeli iyileştirmek için kullanılabilir. +- MiniMax M2.5 Free: Ücretsiz döneminde toplanan veriler modeli iyileştirmek için kullanılabilir. +- OpenAI API'leri: İstekler [OpenAI'nin Veri Politikaları](https://platform.openai.com/docs/guides/your-data) uyarınca 30 gün boyunca saklanır. +- Anthropic API'leri: İstekler [Anthropic'in Veri Politikaları](https://docs.anthropic.com/en/docs/claude-code/data-usage) uyarınca 30 gün boyunca saklanır. --- -## Ekipler icin +## Ekipler İçin -Zen ekipler icin de guclu bir cozumdur. Ekip arkadaslarini davet edebilir, roller atayabilir, kullanilacak modelleri yonetebilir ve daha fazlasini yapabilirsiniz. +Zen ekipler için de harika çalışır. Ekip arkadaşlarınızı davet edebilir, roller atayabilir, ekibinizin kullandığı modelleri düzenleyebilir ve daha fazlasını yapabilirsiniz. :::note -Calisma alanlari beta kapsaminda su anda ekipler icin ucretsizdir. +Çalışma alanları şu anda beta'nın bir parçası olarak ekipler için ücretsizdir. ::: -Calisma alani yonetimi su anda beta kapsaminda ucretsizdir. Fiyatlandirma detaylarini yakinda paylasacagiz. +Çalışma alanınızı yönetmek şu anda beta'nın bir parçası olarak ekipler için ücretsizdir. Yakında fiyatlandırma hakkında daha fazla ayrıntı paylaşacağız. --- ### Roller -Calisma alaniniza ekip arkadaslarini davet edip rol atayabilirsiniz: +Ekip arkadaşlarınızı çalışma alanınıza davet edebilir ve roller atayabilirsiniz: -- **Admin**: Modelleri, uyeleri, API anahtarlarini ve faturalandirmayi yonetir -- **Member**: Yalnizca kendi API anahtarlarini yonetir +- **Admin**: Modelleri, üyeleri, API anahtarlarını ve faturalandırmayı yönetin +- **Member**: Yalnızca kendi API anahtarlarını yönetin -Admin'ler maliyet kontrolu icin uye bazinda aylik harcama limitleri de ayarlayabilir. +Yöneticiler, maliyetleri kontrol altında tutmak için her üye için aylık harcama limitleri de belirleyebilir. --- -### Model erisimi +### Model erişimi -Admin'ler calisma alani icin belirli modelleri acip kapatabilir. Devre disi bir modele yapilan istekler hata dondurur. +Yöneticiler çalışma alanı için belirli modelleri etkinleştirebilir veya devre dışı bırakabilir. Devre dışı bırakılmış bir modele yapılan istekler bir hata döndürür. -Bu, veri toplayan bir modelin kullanimini kapatmak istediginiz durumlarda kullanislidir. +Bu, veri toplayan bir modelin kullanımını devre dışı bırakmak istediğiniz durumlarda kullanışlıdır. --- -### Kendi anahtarinizi kullanin +### Kendi anahtarınızı getirin -Zen'deki diger modellere erisirken kendi OpenAI veya Anthropic API anahtarlarinizi da kullanabilirsiniz. +Zen'deki diğer modellere erişmeye devam ederken kendi OpenAI veya Anthropic API anahtarlarınızı kullanabilirsiniz. -Kendi anahtarinizi kullandiginizda token ucreti Zen yerine dogrudan provider tarafindan faturalandirilir. +Kendi anahtarlarınızı kullandığınızda, tokenler Zen tarafından değil, doğrudan sağlayıcı tarafından faturalandırılır. -Ornegin kurulusunuzun zaten OpenAI veya Anthropic anahtari varsa Zen'in sagladigi anahtar yerine onu kullanabilirsiniz. +Örneğin, kuruluşunuzun halihazırda OpenAI veya Anthropic için bir anahtarı olabilir ve Zen'in sağladığı anahtar yerine onu kullanmak isteyebilirsiniz. --- ## Hedefler -OpenCode Zen'i su amaclarla olusturduk: +OpenCode Zen'i şu amaçlarla oluşturduk: -1. Kodlama ajanlari icin en iyi model/provider kombinasyonlarini **benchmark etmek** -2. Performansi dusurmeden veya daha ucuz provider'a yonlendirmeden **en yuksek kaliteye** erismek -3. Maliyetine satarak fiyat dususlerini kullaniciya yansitmak ve yalnizca islem ucretlerini kapsayan pay birakmak -4. Herhangi bir kodlama ajaniyla kullanima izin vererek **kilitlenmeyi onlemek** ve opencode'da diger provider'lari her zaman acik tutmak +1. Kodlama ajanları için en iyi modelleri/sağlayıcıları **kıyaslamak**. +2. **En yüksek kaliteli** seçeneklere erişmek ve performansı düşürmemek veya daha ucuz sağlayıcılara yönlendirmemek. +3. Maliyetine satış yaparak herhangi bir **fiyat düşüşünü** yansıtmak; böylece tek kâr marjı işlem ücretlerimizi karşılamaktır. +4. Başka bir kodlama ajanıyla kullanmanıza izin vererek **kilitlenmeyi önlemek**. Ve her zaman OpenCode ile başka bir sağlayıcıyı kullanmanıza izin vermek. diff --git a/packages/web/src/content/docs/zen.mdx b/packages/web/src/content/docs/zen.mdx index 48c040cf2df..5ed2125cb1e 100644 --- a/packages/web/src/content/docs/zen.mdx +++ b/packages/web/src/content/docs/zen.mdx @@ -121,12 +121,12 @@ We support a pay-as-you-go model. Below are the prices **per 1M tokens**. | --------------------------------- | ------ | ------ | ----------- | ------------ | | Big Pickle | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | -| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | - | +| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.1 | $0.30 | $1.20 | $0.10 | - | | GLM 5 | $1.00 | $3.20 | $0.20 | - | | GLM 4.7 | $0.60 | $2.20 | $0.10 | - | | GLM 4.6 | $0.60 | $2.20 | $0.10 | - | -| Kimi K2.5 | $0.60 | $3.00 | $0.08 | - | +| Kimi K2.5 | $0.60 | $3.00 | $0.10 | - | | Kimi K2 Thinking | $0.40 | $2.50 | - | - | | Kimi K2 | $0.40 | $2.50 | - | - | | Qwen3 Coder 480B | $0.45 | $1.50 | - | - | @@ -192,6 +192,19 @@ charging you more than $20 if your balance goes below $5. --- +### Deprecated models + +| Model | Deprecation date | +| ---------------- | ---------------- | +| Qwen3 Coder 480B | Feb 6, 2026 | +| Kimi K2 Thinking | March 6, 2026 | +| Kimi K2 | March 6, 2026 | +| MiniMax M2.1 | March 15, 2026 | +| GLM 4.7 | March 15, 2026 | +| GLM 4.6 | March 15, 2026 | + +--- + ## Privacy All our models are hosted in the US. Our providers follow a zero-retention policy and do not use your data for model training, with the following exceptions: diff --git a/packages/web/src/content/docs/zh-cn/cli.mdx b/packages/web/src/content/docs/zh-cn/cli.mdx index 490d59ca0be..503207ec488 100644 --- a/packages/web/src/content/docs/zh-cn/cli.mdx +++ b/packages/web/src/content/docs/zh-cn/cli.mdx @@ -558,6 +558,7 @@ OpenCode 可以通过环境变量进行配置。 | `OPENCODE_AUTO_SHARE` | boolean | 自动分享会话 | | `OPENCODE_GIT_BASH_PATH` | string | Windows 上 Git Bash 可执行文件的路径 | | `OPENCODE_CONFIG` | string | 配置文件路径 | +| `OPENCODE_TUI_CONFIG` | string | TUI 配置文件路径 | | `OPENCODE_CONFIG_DIR` | string | 配置目录路径 | | `OPENCODE_CONFIG_CONTENT` | string | 内联 JSON 配置内容 | | `OPENCODE_DISABLE_AUTOUPDATE` | boolean | 禁用自动更新检查 | diff --git a/packages/web/src/content/docs/zh-cn/config.mdx b/packages/web/src/content/docs/zh-cn/config.mdx index 8ed3c8fbee3..c401bcf121f 100644 --- a/packages/web/src/content/docs/zh-cn/config.mdx +++ b/packages/web/src/content/docs/zh-cn/config.mdx @@ -14,10 +14,11 @@ OpenCode 支持 **JSON** 和 **JSONC**(带注释的 JSON)格式。 ```jsonc title="opencode.jsonc" { "$schema": "https://opencode.ai/config.json", - // Theme configuration - "theme": "opencode", "model": "anthropic/claude-sonnet-4-5", "autoupdate": true, + "server": { + "port": 4096, + }, } ``` @@ -33,7 +34,7 @@ OpenCode 支持 **JSON** 和 **JSONC**(带注释的 JSON)格式。 配置文件是合并在一起的,而不是被替换。来自以下配置位置的设置会被合并。后面的配置仅在键冲突时覆盖前面的配置。所有配置中的非冲突设置都会被保留。 -例如,如果您的全局配置设置了 `theme: "opencode"` 和 `autoupdate: true`,而您的项目配置设置了 `model: "anthropic/claude-sonnet-4-5"`,则最终配置将包含所有三个设置。 +例如,如果您的全局配置设置了 `autoupdate: true`,而您的项目配置设置了 `model: "anthropic/claude-sonnet-4-5"`,则最终配置将包含这两个设置。 --- diff --git a/packages/web/src/content/docs/zh-cn/ecosystem.mdx b/packages/web/src/content/docs/zh-cn/ecosystem.mdx index c77cc0542bc..24a5fbe8735 100644 --- a/packages/web/src/content/docs/zh-cn/ecosystem.mdx +++ b/packages/web/src/content/docs/zh-cn/ecosystem.mdx @@ -15,38 +15,39 @@ description: 基于 OpenCode 构建的项目与集成。 ## 插件 -| 名称 | 描述 | -| --------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | -| [opencode-daytona](https://github.com/jamesmurdza/daytona/blob/main/guides/typescript/opencode/README.md) | 在隔离的 Daytona 沙箱中自动运行 OpenCode 会话,支持 git 同步和实时预览 | -| [opencode-helicone-session](https://github.com/H2Shami/opencode-helicone-session) | 自动注入 Helicone 会话头信息,用于请求分组 | -| [opencode-type-inject](https://github.com/nick-vi/opencode-type-inject) | 通过查找工具自动将 TypeScript/Svelte 类型注入到文件读取中 | -| [opencode-openai-codex-auth](https://github.com/numman-ali/opencode-openai-codex-auth) | 使用您的 ChatGPT Plus/Pro 订阅替代 API 额度 | -| [opencode-gemini-auth](https://github.com/jenslys/opencode-gemini-auth) | 使用您现有的 Gemini 套餐替代 API 计费 | -| [opencode-antigravity-auth](https://github.com/NoeFabris/opencode-antigravity-auth) | 使用 Antigravity 的免费模型替代 API 计费 | -| [opencode-devcontainers](https://github.com/athal7/opencode-devcontainers) | 多分支开发容器隔离,支持浅克隆和自动分配端口 | -| [opencode-google-antigravity-auth](https://github.com/shekohex/opencode-google-antigravity-auth) | Google Antigravity OAuth 插件,支持 Google 搜索及更强健的 API 处理 | -| [opencode-dynamic-context-pruning](https://github.com/Tarquinen/opencode-dynamic-context-pruning) | 通过修剪过时的工具输出来优化 Token 使用 | -| [opencode-websearch-cited](https://github.com/ghoulr/opencode-websearch-cited.git) | 为受支持的提供商添加原生网页搜索支持,采用 Google grounded 风格 | -| [opencode-pty](https://github.com/shekohex/opencode-pty.git) | 使 AI 代理能够在 PTY 中运行后台进程,并向其发送交互式输入 | -| [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | 非交互式 shell 命令指令——防止依赖 TTY 的操作导致挂起 | -| [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | 使用 Wakatime 追踪 OpenCode 的使用情况 | -| [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | 清理 LLM 生成的 Markdown 表格 | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | 通过 Morph Fast Apply API 和惰性编辑标记实现 10 倍更快的代码编辑 | -| [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | 后台代理、预构建的 LSP/AST/MCP 工具、精选代理,兼容 Claude Code | -| [opencode-notificator](https://github.com/panta82/opencode-notificator) | OpenCode 会话的桌面通知和声音提醒 | -| [opencode-notifier](https://github.com/mohak34/opencode-notifier) | 针对权限请求、任务完成和错误事件的桌面通知与声音提醒 | -| [opencode-zellij-namer](https://github.com/24601/opencode-zellij-namer) | 基于 OpenCode 上下文的 AI 驱动自动 Zellij 会话命名 | -| [opencode-skillful](https://github.com/zenobi-us/opencode-skillful) | 允许 OpenCode 代理通过技能发现和注入按需延迟加载提示词 | -| [opencode-supermemory](https://github.com/supermemoryai/opencode-supermemory) | 使用 Supermemory 实现跨会话的持久记忆 | -| [@plannotator/opencode](https://github.com/backnotprop/plannotator/tree/main/apps/opencode-plugin) | 支持可视化标注和私有/离线分享的交互式计划审查 | -| [@openspoon/subtask2](https://github.com/spoons-and-mirrors/subtask2) | 将 OpenCode /commands 扩展为具有精细流程控制的强大编排系统 | -| [opencode-scheduler](https://github.com/different-ai/opencode-scheduler) | 使用 cron 语法通过 launchd (Mac) 或 systemd (Linux) 调度周期性任务 | -| [micode](https://github.com/vtemian/micode) | 结构化的头脑风暴 → 计划 → 实现工作流,支持会话连续性 | -| [octto](https://github.com/vtemian/octto) | 用于 AI 头脑风暴的交互式浏览器 UI,支持多问题表单 | -| [opencode-background-agents](https://github.com/kdcokenny/opencode-background-agents) | Claude Code 风格的后台代理,支持异步委托和上下文持久化 | -| [opencode-notify](https://github.com/kdcokenny/opencode-notify) | OpenCode 的原生操作系统通知——随时了解任务完成情况 | -| [opencode-workspace](https://github.com/kdcokenny/opencode-workspace) | 捆绑式多代理编排套件——16 个组件,一次安装 | -| [opencode-worktree](https://github.com/kdcokenny/opencode-worktree) | OpenCode 的零摩擦 git worktree 管理 | +| 名称 | 描述 | +| -------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | +| [opencode-daytona](https://github.com/daytonaio/daytona/tree/main/libs/opencode-plugin) | 在隔离的 Daytona 沙箱中自动运行 OpenCode 会话,支持 git 同步和实时预览 | +| [opencode-helicone-session](https://github.com/H2Shami/opencode-helicone-session) | 自动注入 Helicone 会话头信息,用于请求分组 | +| [opencode-type-inject](https://github.com/nick-vi/opencode-type-inject) | 通过查找工具自动将 TypeScript/Svelte 类型注入到文件读取中 | +| [opencode-openai-codex-auth](https://github.com/numman-ali/opencode-openai-codex-auth) | 使用您的 ChatGPT Plus/Pro 订阅替代 API 额度 | +| [opencode-gemini-auth](https://github.com/jenslys/opencode-gemini-auth) | 使用您现有的 Gemini 套餐替代 API 计费 | +| [opencode-antigravity-auth](https://github.com/NoeFabris/opencode-antigravity-auth) | 使用 Antigravity 的免费模型替代 API 计费 | +| [opencode-devcontainers](https://github.com/athal7/opencode-devcontainers) | 多分支开发容器隔离,支持浅克隆和自动分配端口 | +| [opencode-google-antigravity-auth](https://github.com/shekohex/opencode-google-antigravity-auth) | Google Antigravity OAuth 插件,支持 Google 搜索及更强健的 API 处理 | +| [opencode-dynamic-context-pruning](https://github.com/Tarquinen/opencode-dynamic-context-pruning) | 通过修剪过时的工具输出来优化 Token 使用 | +| [opencode-vibeguard](https://github.com/inkdust2021/opencode-vibeguard) | 在调用 LLM 之前将机密/PII 替换为 VibeGuard 风格的占位符;并在本地恢复 | +| [opencode-websearch-cited](https://github.com/ghoulr/opencode-websearch-cited.git) | 为受支持的提供商添加原生网页搜索支持,采用 Google grounded 风格 | +| [opencode-pty](https://github.com/shekohex/opencode-pty.git) | 使 AI 代理能够在 PTY 中运行后台进程,并向其发送交互式输入 | +| [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | 非交互式 shell 命令指令——防止依赖 TTY 的操作导致挂起 | +| [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | 使用 Wakatime 追踪 OpenCode 的使用情况 | +| [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | 清理 LLM 生成的 Markdown 表格 | +| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | 通过 Morph Fast Apply API 和惰性编辑标记实现 10 倍更快的代码编辑 | +| [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | 后台代理、预构建的 LSP/AST/MCP 工具、精选代理,兼容 Claude Code | +| [opencode-notificator](https://github.com/panta82/opencode-notificator) | OpenCode 会话的桌面通知和声音提醒 | +| [opencode-notifier](https://github.com/mohak34/opencode-notifier) | 针对权限请求、任务完成和错误事件的桌面通知与声音提醒 | +| [opencode-zellij-namer](https://github.com/24601/opencode-zellij-namer) | 基于 OpenCode 上下文的 AI 驱动自动 Zellij 会话命名 | +| [opencode-skillful](https://github.com/zenobi-us/opencode-skillful) | 允许 OpenCode 代理通过技能发现和注入按需延迟加载提示词 | +| [opencode-supermemory](https://github.com/supermemoryai/opencode-supermemory) | 使用 Supermemory 实现跨会话的持久记忆 | +| [@plannotator/opencode](https://github.com/backnotprop/plannotator/tree/main/apps/opencode-plugin) | 支持可视化标注和私有/离线分享的交互式计划审查 | +| [@openspoon/subtask2](https://github.com/spoons-and-mirrors/subtask2) | 将 OpenCode /commands 扩展为具有精细流程控制的强大编排系统 | +| [opencode-scheduler](https://github.com/different-ai/opencode-scheduler) | 使用 cron 语法通过 launchd (Mac) 或 systemd (Linux) 调度周期性任务 | +| [micode](https://github.com/vtemian/micode) | 结构化的头脑风暴 → 计划 → 实现工作流,支持会话连续性 | +| [octto](https://github.com/vtemian/octto) | 用于 AI 头脑风暴的交互式浏览器 UI,支持多问题表单 | +| [opencode-background-agents](https://github.com/kdcokenny/opencode-background-agents) | Claude Code 风格的后台代理,支持异步委托和上下文持久化 | +| [opencode-notify](https://github.com/kdcokenny/opencode-notify) | OpenCode 的原生操作系统通知——随时了解任务完成情况 | +| [opencode-workspace](https://github.com/kdcokenny/opencode-workspace) | 捆绑式多代理编排套件——16 个组件,一次安装 | +| [opencode-worktree](https://github.com/kdcokenny/opencode-worktree) | OpenCode 的零摩擦 git worktree 管理 | --- diff --git a/packages/web/src/content/docs/zh-cn/go.mdx b/packages/web/src/content/docs/zh-cn/go.mdx new file mode 100644 index 00000000000..8bd32a8ad41 --- /dev/null +++ b/packages/web/src/content/docs/zh-cn/go.mdx @@ -0,0 +1,145 @@ +--- +title: Go +description: 低成本的开源编程模型订阅服务。 +--- + +import config from "../../../../config.mjs" +export const console = config.console +export const email = `mailto:${config.email}` + +OpenCode Go 是一项低成本的订阅服务(**$10/月**),为您提供对流行开源编程模型的可靠访问。 + +:::note +OpenCode Go 目前处于测试阶段。 +::: + +Go 的工作方式与 OpenCode 中的其他提供商一样。您订阅 OpenCode Go 并获取 API 密钥。这是**完全可选的**,您不需要它也能使用 OpenCode。 + +它主要为国际用户设计,模型托管在美国、欧盟和新加坡,以确保稳定的全球访问。 + +--- + +## 背景 + +开源模型已经变得非常出色。它们现在在编程任务上的表现接近专有模型。而且因为许多提供商可以竞争性地提供服务,它们通常要便宜得多。 + +然而,获得可靠、低延迟的访问可能很困难。提供商的质量和可用性各不相同。 + +:::tip +我们测试了一组精选的模型和提供商,它们与 OpenCode 配合良好。 +::: + +为了解决这个问题,我们做了一些事情: + +1. 我们测试了一组精选的开源模型,并与他们的团队讨论了如何最好地运行它们。 +2. 然后,我们与几家提供商合作,确保这些模型得到正确的服务。 +3. 最后,我们对模型/提供商的组合进行了基准测试,并得出了一个我们乐于推荐的列表。 + +OpenCode Go 让您可以以 **$10/月** 的价格访问这些模型。 + +--- + +## 工作原理 + +OpenCode Go 的工作方式与 OpenCode 中的其他提供商一样。 + +1. 登录 **OpenCode Zen**,订阅 Go,并复制您的 API 密钥。 +2. 在 TUI 中运行 `/connect` 命令,选择 `OpenCode Go`,然后粘贴您的 API 密钥。 +3. 在 TUI 中运行 `/models` 以查看通过 Go 可用的模型列表。 + +:::note +每个工作区只有一名成员可以订阅 OpenCode Go。 +::: + +目前的模型列表包括: + +- **GLM-5** +- **Kimi K2.5** +- **MiniMax M2.5** + +随着我们测试和添加新模型,模型列表可能会发生变化。 + +--- + +## 使用限制 + +OpenCode Go 包含以下限制: + +- **5 小时限制** — $12 的使用量 +- **每周限制** — $30 的使用量 +- **每月限制** — $60 的使用量 + +限制是以美元价值定义的。这意味着您的实际请求数量取决于您使用的模型。像 MiniMax M2.5 这样更便宜的模型允许更多的请求,而像 GLM-5 这样成本更高的模型允许的请求较少。 + +下表提供了基于典型 Go 使用模式的估计请求数: + +| | GLM-5 | Kimi K2.5 | MiniMax M2.5 | +| --------------- | ----- | --------- | ------------ | +| 每 5 小时请求数 | 1,150 | 1,850 | 30,000 | +| 每周请求数 | 2,880 | 4,630 | 75,000 | +| 每月请求数 | 5,750 | 9,250 | 150,000 | + +估计值基于观察到的平均请求模式: + +- GLM-5 — 每次请求 700 输入,52,000 缓存,150 输出 token +- Kimi K2.5 — 每次请求 870 输入,55,000 缓存,200 输出 token +- MiniMax M2.5 — 每次请求 300 输入,55,000 缓存,125 输出 token + +您可以在 **console** 中跟踪当前的用量。 + +:::tip +如果您达到使用限制,您可以继续使用免费模型。 +::: + +随着我们从早期使用和反馈中学习,使用限制可能会发生变化。 + +--- + +### 定价 + +OpenCode Go 是一个 **$10/月** 的订阅计划。以下是**每 1M token** 的价格。 + +| Model | Input | Output | Cached Read | +| ------------ | ----- | ------ | ----------- | +| GLM-5 | $1.00 | $3.20 | $0.20 | +| Kimi K2.5 | $0.60 | $3.00 | $0.10 | +| MiniMax M2.5 | $0.30 | $1.20 | $0.03 | + +--- + +### 超出限制的使用 + +如果您的 Zen 余额中还有信用点数,您可以在控制台中启用 **Use balance**(使用余额)选项。启用后,当您达到使用限制时,Go 将回退到您的 Zen 余额,而不是阻止请求。 + +--- + +## 端点 + +您也可以通过以下 API 端点访问 Go 模型。 + +| Model | Model ID | Endpoint | AI SDK Package | +| ------------ | ------------ | ------------------------------------------------ | --------------------------- | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | + +OpenCode 配置中的 [model id](/docs/config/#models) 使用 `opencode-go/` 格式。例如,对于 Kimi K2.5,您将在配置中使用 `opencode-go/kimi-k2.5`。 + +--- + +## 隐私 + +该计划主要为国际用户设计,模型托管在美国、欧盟和新加坡,以确保稳定的全球访问。 + +如有任何问题,请 联系我们。 + +--- + +## 目标 + +我们要创建 OpenCode Go 以: + +1. 通过低成本订阅让更多人**获得** AI 编程能力。 +2. 提供对最佳开源编程模型的**可靠**访问。 +3. 策划经过**测试和基准测试**的、适合编程代理使用的模型。 +4. **无锁定**,允许您在 OpenCode 中使用任何其他提供商。 diff --git a/packages/web/src/content/docs/zh-cn/keybinds.mdx b/packages/web/src/content/docs/zh-cn/keybinds.mdx index bb1d2c21a79..5108fdbb519 100644 --- a/packages/web/src/content/docs/zh-cn/keybinds.mdx +++ b/packages/web/src/content/docs/zh-cn/keybinds.mdx @@ -3,11 +3,11 @@ title: 快捷键 description: 自定义您的快捷键。 --- -OpenCode 提供了一系列快捷键,您可以通过 OpenCode 配置进行自定义。 +OpenCode 提供了一系列快捷键,您可以通过 `tui.json` 进行自定义。 -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "keybinds": { "leader": "ctrl+x", "app_exit": "ctrl+c,ctrl+d,q", @@ -117,11 +117,11 @@ OpenCode 的大多数快捷键使用 `leader`(前导键)。这可以避免 ## 禁用快捷键 -您可以通过在配置中将对应的键值设置为 "none" 来禁用某个快捷键。 +您可以通过将键值添加到 `tui.json` 并设置为 "none" 来禁用某个快捷键。 -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "keybinds": { "session_compact": "none" } diff --git a/packages/web/src/content/docs/zh-cn/themes.mdx b/packages/web/src/content/docs/zh-cn/themes.mdx index d1abefed6d6..79386fbe996 100644 --- a/packages/web/src/content/docs/zh-cn/themes.mdx +++ b/packages/web/src/content/docs/zh-cn/themes.mdx @@ -61,11 +61,11 @@ OpenCode 自带多个内置主题。 ## 使用主题 -您可以通过 `/theme` 命令调出主题选择界面来选择主题,也可以在[配置](/docs/config)文件中直接指定。 +您可以通过 `/theme` 命令调出主题选择界面来选择主题,也可以在 `tui.json` 文件中直接指定。 -```json title="opencode.json" {3} +```json title="tui.json" {3} { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "theme": "tokyonight" } ``` diff --git a/packages/web/src/content/docs/zh-cn/zen.mdx b/packages/web/src/content/docs/zh-cn/zen.mdx index e3fe35e8672..0c6c6b9d95d 100644 --- a/packages/web/src/content/docs/zh-cn/zen.mdx +++ b/packages/web/src/content/docs/zh-cn/zen.mdx @@ -55,6 +55,7 @@ OpenCode Zen 的工作方式与 OpenCode 中的任何其他提供商相同。 | 模型 | 模型 ID | 端点 | AI SDK 包 | | ------------------ | ------------------ | -------------------------------------------------- | --------------------------- | +| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | @@ -79,11 +80,9 @@ OpenCode Zen 的工作方式与 OpenCode 中的任何其他提供商相同。 | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.1 | minimax-m2.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 5 | glm-5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM 5 Free | glm-5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 4.7 | glm-4.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 4.6 | glm-4.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 Free | kimi-k2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2 Thinking | kimi-k2-thinking | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2 | kimi-k2 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Qwen3 Coder 480B | qwen3-coder | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -113,11 +112,9 @@ https://opencode.ai/zen/v1/models | MiniMax M2.5 Free | 免费 | 免费 | 免费 | - | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | - | | MiniMax M2.1 | $0.30 | $1.20 | $0.10 | - | -| GLM 5 Free | Free | Free | Free | - | | GLM 5 | $1.00 | $3.20 | $0.20 | - | | GLM 4.7 | $0.60 | $2.20 | $0.10 | - | | GLM 4.6 | $0.60 | $2.20 | $0.10 | - | -| Kimi K2.5 Free | 免费 | 免费 | 免费 | - | | Kimi K2.5 | $0.60 | $3.00 | $0.08 | - | | Kimi K2 Thinking | $0.40 | $2.50 | - | - | | Kimi K2 | $0.40 | $2.50 | - | - | @@ -139,6 +136,7 @@ https://opencode.ai/zen/v1/models | Gemini 3 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | +| GPT 5.3 Codex | $1.75 | $14.00 | $0.175 | - | | GPT 5.2 | $1.75 | $14.00 | $0.175 | - | | GPT 5.2 Codex | $1.75 | $14.00 | $0.175 | - | | GPT 5.1 | $1.07 | $8.50 | $0.107 | - | @@ -157,8 +155,6 @@ https://opencode.ai/zen/v1/models 免费模型说明: -- GLM 5 Free 在 OpenCode 上限时免费提供。团队正在利用这段时间收集反馈并改进模型。 -- Kimi K2.5 Free 在 OpenCode 上限时免费提供。团队正在利用这段时间收集反馈并改进模型。 - MiniMax M2.5 Free 在 OpenCode 上限时免费提供。团队正在利用这段时间收集反馈并改进模型。 - Big Pickle 是一个隐身模型,在 OpenCode 上限时免费提供。团队正在利用这段时间收集反馈并改进模型。 @@ -187,8 +183,6 @@ https://opencode.ai/zen/v1/models 我们所有的模型都托管在美国。我们的提供商遵循零保留政策,不会将你的数据用于模型训练,但以下情况除外: - Big Pickle:在免费期间,收集的数据可能会被用于改进模型。 -- GLM 5 Free:在免费期间,收集的数据可能会被用于改进模型。 -- Kimi K2.5 Free:在免费期间,收集的数据可能会被用于改进模型。 - MiniMax M2.5 Free:在免费期间,收集的数据可能会被用于改进模型。 - OpenAI API:请求会根据 [OpenAI 数据政策](https://platform.openai.com/docs/guides/your-data)保留 30 天。 - Anthropic API:请求会根据 [Anthropic 数据政策](https://docs.anthropic.com/en/docs/claude-code/data-usage)保留 30 天。 diff --git a/packages/web/src/content/docs/zh-tw/cli.mdx b/packages/web/src/content/docs/zh-tw/cli.mdx index f11066dcf56..888740f5bec 100644 --- a/packages/web/src/content/docs/zh-tw/cli.mdx +++ b/packages/web/src/content/docs/zh-tw/cli.mdx @@ -558,6 +558,7 @@ OpenCode 可以透過環境變數進行設定。 | `OPENCODE_AUTO_SHARE` | boolean | 自動分享工作階段 | | `OPENCODE_GIT_BASH_PATH` | string | Windows 上 Git Bash 可執行檔的路徑 | | `OPENCODE_CONFIG` | string | 設定檔路徑 | +| `OPENCODE_TUI_CONFIG` | string | TUI 設定檔路徑 | | `OPENCODE_CONFIG_DIR` | string | 設定目錄路徑 | | `OPENCODE_CONFIG_CONTENT` | string | 內嵌 JSON 設定內容 | | `OPENCODE_DISABLE_AUTOUPDATE` | boolean | 停用自動更新檢查 | diff --git a/packages/web/src/content/docs/zh-tw/config.mdx b/packages/web/src/content/docs/zh-tw/config.mdx index 3715dd0c9f7..a694823a65f 100644 --- a/packages/web/src/content/docs/zh-tw/config.mdx +++ b/packages/web/src/content/docs/zh-tw/config.mdx @@ -14,10 +14,11 @@ OpenCode 支援 **JSON** 和 **JSONC**(帶註解的 JSON)格式。 ```jsonc title="opencode.jsonc" { "$schema": "https://opencode.ai/config.json", - // Theme configuration - "theme": "opencode", "model": "anthropic/claude-sonnet-4-5", "autoupdate": true, + "server": { + "port": 4096, + }, } ``` @@ -33,7 +34,7 @@ OpenCode 支援 **JSON** 和 **JSONC**(帶註解的 JSON)格式。 設定檔是合併在一起的,而不是被替換。來自以下設定位置的設定會被合併。後面的設定僅在鍵衝突時覆寫前面的設定。所有設定中的非衝突設定都會被保留。 -例如,如果您的全域設定設定了 `theme: "opencode"` 和 `autoupdate: true`,而您的專案設定設定了 `model: "anthropic/claude-sonnet-4-5"`,則最終設定將包含所有三個設定。 +例如,如果您的全域設定設定了 `autoupdate: true`,而您的專案設定設定了 `model: "anthropic/claude-sonnet-4-5"`,則最終設定將包含這兩個設定。 --- @@ -171,6 +172,10 @@ opencode run "Hello world" - `scroll_speed` - 自訂捲動速度倍率(預設值:`3`,最小值:`1`)。如果 `scroll_acceleration.enabled` 為 `true`,則忽略此選項。 - `diff_style` - 控制差異呈現方式。`"auto"` 根據終端機寬度自適應,`"stacked"` 始終顯示單列。 +使用 `OPENCODE_TUI_CONFIG` 指向自訂 TUI 設定檔。 + +`opencode.json` 中的舊版 `theme`、`keybinds` 和 `tui` 鍵已被棄用,並將在可能的情況下自動遷移。 + [在此了解更多關於 TUI 的資訊](/docs/tui)。 --- @@ -297,12 +302,12 @@ Bearer Token(`AWS_BEARER_TOKEN_BEDROCK` 或 `/connect`)優先於基於設定 ### 主題 -您可以透過 OpenCode 設定中的 `theme` 選項設定要使用的主題。 +在 `tui.json` 中設定您的 UI 主題。 -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", - "theme": "" + "$schema": "https://opencode.ai/tui.json", + "theme": "tokyonight" } ``` @@ -402,11 +407,11 @@ Bearer Token(`AWS_BEARER_TOKEN_BEDROCK` 或 `/connect`)優先於基於設定 ### 快捷鍵 -您可以透過 `keybinds` 選項自訂快捷鍵。 +在 `tui.json` 中自訂快捷鍵。 -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "keybinds": {} } ``` diff --git a/packages/web/src/content/docs/zh-tw/custom-tools.mdx b/packages/web/src/content/docs/zh-tw/custom-tools.mdx index 3229c6fddfa..86c2b0467f9 100644 --- a/packages/web/src/content/docs/zh-tw/custom-tools.mdx +++ b/packages/web/src/content/docs/zh-tw/custom-tools.mdx @@ -79,6 +79,32 @@ export const multiply = tool({ --- +#### 與內建工具名稱衝突 + +自訂工具以工具名稱作為鍵值。如果自訂工具使用與內建工具相同的名稱,則自訂工具具有優先權。 + +例如,此檔案將替換內建的 `bash` 工具: + +```ts title=".opencode/tools/bash.ts" +import { tool } from "@opencode-ai/plugin" + +export default tool({ + description: "Restricted bash wrapper", + args: { + command: tool.schema.string(), + }, + async execute(args) { + return `blocked: ${args.command}` + }, +}) +``` + +:::note +除非您有意替換內建工具,否則請使用唯一的名稱。如果您想停用內建工具但不覆寫它,請使用[權限](/docs/permissions)。 +::: + +--- + ### 參數 您可以使用 `tool.schema`(即 [Zod](https://zod.dev))來定義參數型別。 diff --git a/packages/web/src/content/docs/zh-tw/ecosystem.mdx b/packages/web/src/content/docs/zh-tw/ecosystem.mdx index 3a867d14305..50871355ecf 100644 --- a/packages/web/src/content/docs/zh-tw/ecosystem.mdx +++ b/packages/web/src/content/docs/zh-tw/ecosystem.mdx @@ -15,38 +15,39 @@ description: 基於 OpenCode 建置的專案與整合。 ## 外掛程式 -| 名稱 | 說明 | -| --------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | -| [opencode-daytona](https://github.com/jamesmurdza/daytona/blob/main/guides/typescript/opencode/README.md) | 在隔離的 Daytona 沙箱中自動執行 OpenCode 工作階段,支援 git 同步和即時預覽 | -| [opencode-helicone-session](https://github.com/H2Shami/opencode-helicone-session) | 自動注入 Helicone 工作階段標頭資訊,用於請求分組 | -| [opencode-type-inject](https://github.com/nick-vi/opencode-type-inject) | 透過搜尋工具自動將 TypeScript/Svelte 型別注入到檔案讀取中 | -| [opencode-openai-codex-auth](https://github.com/numman-ali/opencode-openai-codex-auth) | 使用您的 ChatGPT Plus/Pro 訂閱替代 API 額度 | -| [opencode-gemini-auth](https://github.com/jenslys/opencode-gemini-auth) | 使用您現有的 Gemini 方案替代 API 計費 | -| [opencode-antigravity-auth](https://github.com/NoeFabris/opencode-antigravity-auth) | 使用 Antigravity 的免費模型替代 API 計費 | -| [opencode-devcontainers](https://github.com/athal7/opencode-devcontainers) | 多分支開發容器隔離,支援淺層複製和自動分配連接埠 | -| [opencode-google-antigravity-auth](https://github.com/shekohex/opencode-google-antigravity-auth) | Google Antigravity OAuth 外掛程式,支援 Google 搜尋及更強健的 API 處理 | -| [opencode-dynamic-context-pruning](https://github.com/Tarquinen/opencode-dynamic-context-pruning) | 透過修剪過時的工具輸出來最佳化 Token 使用 | -| [opencode-websearch-cited](https://github.com/ghoulr/opencode-websearch-cited.git) | 為受支援的供應商新增原生網頁搜尋支援,採用 Google grounded 風格 | -| [opencode-pty](https://github.com/shekohex/opencode-pty.git) | 使 AI 代理能夠在 PTY 中執行背景處理程序,並向其傳送互動式輸入 | -| [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | 非互動式 shell 指令說明——防止依賴 TTY 的操作導致卡住 | -| [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | 使用 Wakatime 追蹤 OpenCode 的使用情況 | -| [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | 清理 LLM 生成的 Markdown 表格 | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | 透過 Morph Fast Apply API 和惰性編輯標記實現 10 倍更快的程式碼編輯 | -| [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | 背景代理、預建置的 LSP/AST/MCP 工具、精選代理,相容 Claude Code | -| [opencode-notificator](https://github.com/panta82/opencode-notificator) | OpenCode 工作階段的桌面通知和聲音提醒 | -| [opencode-notifier](https://github.com/mohak34/opencode-notifier) | 針對權限請求、任務完成和錯誤事件的桌面通知與聲音提醒 | -| [opencode-zellij-namer](https://github.com/24601/opencode-zellij-namer) | 基於 OpenCode 上下文的 AI 驅動自動 Zellij 工作階段命名 | -| [opencode-skillful](https://github.com/zenobi-us/opencode-skillful) | 允許 OpenCode 代理透過技能發現和注入按需延遲載入提示詞 | -| [opencode-supermemory](https://github.com/supermemoryai/opencode-supermemory) | 使用 Supermemory 實現跨工作階段的持久記憶 | -| [@plannotator/opencode](https://github.com/backnotprop/plannotator/tree/main/apps/opencode-plugin) | 支援視覺化標註和私有/離線分享的互動式計畫審查 | -| [@openspoon/subtask2](https://github.com/spoons-and-mirrors/subtask2) | 將 OpenCode /commands 擴展為具有精細流程控制的強大編排系統 | -| [opencode-scheduler](https://github.com/different-ai/opencode-scheduler) | 使用 cron 語法透過 launchd (Mac) 或 systemd (Linux) 排程週期性任務 | -| [micode](https://github.com/vtemian/micode) | 結構化的腦力激盪 → 計畫 → 實作工作流程,支援工作階段連續性 | -| [octto](https://github.com/vtemian/octto) | 用於 AI 腦力激盪的互動式瀏覽器 UI,支援多問題表單 | -| [opencode-background-agents](https://github.com/kdcokenny/opencode-background-agents) | Claude Code 風格的背景代理,支援非同步委派和上下文持久化 | -| [opencode-notify](https://github.com/kdcokenny/opencode-notify) | OpenCode 的原生作業系統通知——隨時了解任務完成情況 | -| [opencode-workspace](https://github.com/kdcokenny/opencode-workspace) | 捆綁式多代理編排套件——16 個元件,一次安裝 | -| [opencode-worktree](https://github.com/kdcokenny/opencode-worktree) | OpenCode 的零摩擦 git worktree 管理 | +| 名稱 | 說明 | +| -------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | +| [opencode-daytona](https://github.com/daytonaio/daytona/tree/main/libs/opencode-plugin) | 在隔離的 Daytona 沙箱中自動執行 OpenCode 工作階段,支援 git 同步和即時預覽 | +| [opencode-helicone-session](https://github.com/H2Shami/opencode-helicone-session) | 自動注入 Helicone 工作階段標頭資訊,用於請求分組 | +| [opencode-type-inject](https://github.com/nick-vi/opencode-type-inject) | 透過搜尋工具自動將 TypeScript/Svelte 型別注入到檔案讀取中 | +| [opencode-openai-codex-auth](https://github.com/numman-ali/opencode-openai-codex-auth) | 使用您的 ChatGPT Plus/Pro 訂閱替代 API 額度 | +| [opencode-gemini-auth](https://github.com/jenslys/opencode-gemini-auth) | 使用您現有的 Gemini 方案替代 API 計費 | +| [opencode-antigravity-auth](https://github.com/NoeFabris/opencode-antigravity-auth) | 使用 Antigravity 的免費模型替代 API 計費 | +| [opencode-devcontainers](https://github.com/athal7/opencode-devcontainers) | 多分支開發容器隔離,支援淺層複製和自動分配連接埠 | +| [opencode-google-antigravity-auth](https://github.com/shekohex/opencode-google-antigravity-auth) | Google Antigravity OAuth 外掛程式,支援 Google 搜尋及更強健的 API 處理 | +| [opencode-dynamic-context-pruning](https://github.com/Tarquinen/opencode-dynamic-context-pruning) | 透過修剪過時的工具輸出來最佳化 Token 使用 | +| [opencode-vibeguard](https://github.com/inkdust2021/opencode-vibeguard) | 在呼叫 LLM 之前將秘密/PII 編輯為 VibeGuard 風格的預留位置;並在本地還原 | +| [opencode-websearch-cited](https://github.com/ghoulr/opencode-websearch-cited.git) | 為受支援的供應商新增原生網頁搜尋支援,採用 Google grounded 風格 | +| [opencode-pty](https://github.com/shekohex/opencode-pty.git) | 使 AI 代理能夠在 PTY 中執行背景處理程序,並向其傳送互動式輸入 | +| [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | 非互動式 shell 指令說明——防止依賴 TTY 的操作導致卡住 | +| [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | 使用 Wakatime 追蹤 OpenCode 的使用情況 | +| [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | 清理 LLM 生成的 Markdown 表格 | +| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | 透過 Morph Fast Apply API 和惰性編輯標記實現 10 倍更快的程式碼編輯 | +| [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | 背景代理、預建置的 LSP/AST/MCP 工具、精選代理,相容 Claude Code | +| [opencode-notificator](https://github.com/panta82/opencode-notificator) | OpenCode 工作階段的桌面通知和聲音提醒 | +| [opencode-notifier](https://github.com/mohak34/opencode-notifier) | 針對權限請求、任務完成和錯誤事件的桌面通知與聲音提醒 | +| [opencode-zellij-namer](https://github.com/24601/opencode-zellij-namer) | 基於 OpenCode 上下文的 AI 驅動自動 Zellij 工作階段命名 | +| [opencode-skillful](https://github.com/zenobi-us/opencode-skillful) | 允許 OpenCode 代理透過技能發現和注入按需延遲載入提示詞 | +| [opencode-supermemory](https://github.com/supermemoryai/opencode-supermemory) | 使用 Supermemory 實現跨工作階段的持久記憶 | +| [@plannotator/opencode](https://github.com/backnotprop/plannotator/tree/main/apps/opencode-plugin) | 支援視覺化標註和私有/離線分享的互動式計畫審查 | +| [@openspoon/subtask2](https://github.com/spoons-and-mirrors/subtask2) | 將 OpenCode /commands 擴展為具有精細流程控制的強大編排系統 | +| [opencode-scheduler](https://github.com/different-ai/opencode-scheduler) | 使用 cron 語法透過 launchd (Mac) 或 systemd (Linux) 排程週期性任務 | +| [micode](https://github.com/vtemian/micode) | 結構化的腦力激盪 → 計畫 → 實作工作流程,支援工作階段連續性 | +| [octto](https://github.com/vtemian/octto) | 用於 AI 腦力激盪的互動式瀏覽器 UI,支援多問題表單 | +| [opencode-background-agents](https://github.com/kdcokenny/opencode-background-agents) | Claude Code 風格的背景代理,支援非同步委派和上下文持久化 | +| [opencode-notify](https://github.com/kdcokenny/opencode-notify) | OpenCode 的原生作業系統通知——隨時了解任務完成情況 | +| [opencode-workspace](https://github.com/kdcokenny/opencode-workspace) | 捆綁式多代理編排套件——16 個元件,一次安裝 | +| [opencode-worktree](https://github.com/kdcokenny/opencode-worktree) | OpenCode 的零摩擦 git worktree 管理 | --- diff --git a/packages/web/src/content/docs/zh-tw/go.mdx b/packages/web/src/content/docs/zh-tw/go.mdx new file mode 100644 index 00000000000..0337a3c385a --- /dev/null +++ b/packages/web/src/content/docs/zh-tw/go.mdx @@ -0,0 +1,145 @@ +--- +title: Go +description: 針對開放原始碼程式設計模型的低成本訂閱服務。 +--- + +import config from "../../../../config.mjs" +export const console = config.console +export const email = `mailto:${config.email}` + +OpenCode Go 是一項低成本的 **每月 10 美元** 訂閱服務,讓您可以穩定存取熱門的開放原始碼程式設計模型。 + +:::note +OpenCode Go 目前處於測試階段 (Beta)。 +::: + +Go 的運作方式就像 OpenCode 中的任何其他供應商一樣。您訂閱 OpenCode Go 並取得您的 API key。這是**完全選用**的,您不需要使用它也能使用 OpenCode。 + +主要是為國際使用者設計,模型託管在美國、歐盟和新加坡,以提供穩定的全球存取。 + +--- + +## 背景 + +開放模型已經變得非常優秀。它們現在在程式設計任務上的表現已接近專有模型。而且因為許多供應商都能以具競爭力的方式提供服務,它們通常便宜得多。 + +然而,要獲得穩定且低延遲的存取可能會很困難。供應商的品質和可用性各不相同。 + +:::tip +我們測試了一組精選的模型和供應商,它們與 OpenCode 搭配運作良好。 +::: + +為了解決這個問題,我們做了幾件事: + +1. 我們測試了一組精選的開放模型,並與他們的團隊討論如何最好地運行它們。 +2. 接著我們與幾家供應商合作,確保這些模型能正確地提供服務。 +3. 最後,我們對模型/供應商的組合進行基準測試,並提出了一份我們覺得值得推薦的清單。 + +OpenCode Go 讓您能以 **每月 10 美元** 的價格存取這些模型。 + +--- + +## 運作方式 + +OpenCode Go 的運作方式就像 OpenCode 中的任何其他供應商一樣。 + +1. 您登入 **OpenCode Zen**,訂閱 Go,並複製您的 API key。 +2. 您在 TUI 中執行 `/connect` 指令,選擇 `OpenCode Go`,並貼上您的 API key。 +3. 在 TUI 中執行 `/models` 以查看透過 Go 可用的模型清單。 + +:::note +每個工作區只能有一位成員訂閱 OpenCode Go。 +::: + +目前的模型清單包括: + +- **GLM-5** +- **Kimi K2.5** +- **MiniMax M2.5** + +模型清單可能會隨著我們測試和新增模型而變動。 + +--- + +## 使用限制 + +OpenCode Go 包含以下限制: + +- **5 小時限制** — 12 美元的使用量 +- **每週限制** — 30 美元的使用量 +- **每月限制** — 60 美元的使用量 + +限制是以美元價值定義的。這意味著您的實際請求次數取決於您使用的模型。較便宜的模型(如 MiniMax M2.5)允許更多請求,而較高成本的模型(如 GLM-5)允許較少請求。 + +下表根據典型的 Go 使用模式提供估計的請求次數: + +| | GLM-5 | Kimi K2.5 | MiniMax M2.5 | +| --------------- | ----- | --------- | ------------ | +| 每 5 小時請求數 | 1,150 | 1,850 | 30,000 | +| 每週請求數 | 2,880 | 4,630 | 75,000 | +| 每月請求數 | 5,750 | 9,250 | 150,000 | + +估計值是根據觀察到的平均請求模式: + +- GLM-5 — 700 輸入, 52,000 快取, 150 輸出 token (每個請求) +- Kimi K2.5 — 870 輸入, 55,000 快取, 200 輸出 token (每個請求) +- MiniMax M2.5 — 300 輸入, 55,000 快取, 125 輸出 token (每個請求) + +您可以在 **console** 中追蹤目前的使用量。 + +:::tip +如果您達到使用限制,您可以繼續使用免費模型。 +::: + +使用限制可能會隨著我們從早期使用和回饋中學習而變動。 + +--- + +### 定價 + +OpenCode Go 是一個 **每月 10 美元** 的訂閱方案。以下是 **每 100 萬 token** 的價格。 + +| 模型 | 輸入 | 輸出 | 快取讀取 | +| ------------ | ----- | ----- | -------- | +| GLM-5 | $1.00 | $3.20 | $0.20 | +| Kimi K2.5 | $0.60 | $3.00 | $0.10 | +| MiniMax M2.5 | $0.30 | $1.20 | $0.03 | + +--- + +### 超出限制的使用 + +如果您的 Zen 餘額中也有點數,您可以在 console 中啟用 **Use balance** 選項。啟用後,當您達到使用限制時,Go 將會改用您的 Zen 餘額,而不是封鎖請求。 + +--- + +## 端點 + +您也可以透過以下 API 端點存取 Go 模型。 + +| 模型 | Model ID | 端點 | AI SDK 套件 | +| ------------ | ------------ | ------------------------------------------------ | --------------------------- | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | + +您 OpenCode 設定中的 [model id](/docs/config/#models) 使用 `opencode-go/` 格式。例如,對於 Kimi K2.5,您會在設定中使用 `opencode-go/kimi-k2.5`。 + +--- + +## 隱私權 + +此方案主要是為國際使用者設計,模型託管在美國、歐盟和新加坡,以提供穩定的全球存取。 + +如果您有任何問題,請 聯絡我們。 + +--- + +## 目標 + +我們建立 OpenCode Go 是為了: + +1. 透過低成本訂閱,讓更多人能 **輕易取得** AI 程式設計資源。 +2. 提供對最佳開放程式設計模型的 **可靠** 存取。 +3. 策劃經過 **測試和基準測試** 的模型,以供程式設計代理使用。 +4. **沒有鎖定**,允許您在 OpenCode 中同時使用任何其他供應商。 diff --git a/packages/web/src/content/docs/zh-tw/keybinds.mdx b/packages/web/src/content/docs/zh-tw/keybinds.mdx index d1458dfe8c0..ca085db01d8 100644 --- a/packages/web/src/content/docs/zh-tw/keybinds.mdx +++ b/packages/web/src/content/docs/zh-tw/keybinds.mdx @@ -3,11 +3,11 @@ title: 快捷鍵 description: 自訂您的快捷鍵。 --- -OpenCode 提供了一系列快捷鍵,您可以透過 OpenCode 設定進行自訂。 +OpenCode 提供了一系列快捷鍵,您可以透過 `tui.json` 進行自訂。 -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "keybinds": { "leader": "ctrl+x", "app_exit": "ctrl+c,ctrl+d,q", @@ -117,11 +117,11 @@ OpenCode 的大多數快捷鍵使用 `leader`(前導鍵)。這可以避免 ## 停用快捷鍵 -您可以透過在設定中將對應的鍵值設定為 "none" 來停用某個快捷鍵。 +您可以透過在 `tui.json` 中將對應的鍵值設定為 "none" 來停用某個快捷鍵。 -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "keybinds": { "session_compact": "none" } diff --git a/packages/web/src/content/docs/zh-tw/lsp.mdx b/packages/web/src/content/docs/zh-tw/lsp.mdx index ae419261fff..aa3af328818 100644 --- a/packages/web/src/content/docs/zh-tw/lsp.mdx +++ b/packages/web/src/content/docs/zh-tw/lsp.mdx @@ -27,6 +27,7 @@ OpenCode 內建了多種適用於主流語言的 LSP 伺服器: | gopls | .go | 需要 `go` 指令可用 | | hls | .hs, .lhs | 需要 `haskell-language-server-wrapper` 指令可用 | | jdtls | .java | 需要已安裝 `Java SDK (version 21+)` | +| julials | .jl | 需要已安裝 `julia` 和 `LanguageServer.jl` | | kotlin-ls | .kt, .kts | 為 Kotlin 專案自動安裝 | | lua-ls | .lua | 為 Lua 專案自動安裝 | | nixd | .nix | 需要 `nixd` 指令可用 | diff --git a/packages/web/src/content/docs/zh-tw/plugins.mdx b/packages/web/src/content/docs/zh-tw/plugins.mdx index 5e84f3a3530..8163c291e00 100644 --- a/packages/web/src/content/docs/zh-tw/plugins.mdx +++ b/packages/web/src/content/docs/zh-tw/plugins.mdx @@ -307,6 +307,10 @@ export const CustomToolsPlugin: Plugin = async (ctx) => { 您的自訂工具將與內建工具一起在 OpenCode 中可用。 +:::note +如果外掛工具使用與內建工具相同的名稱,則外掛工具具有優先權。 +::: + --- ### 日誌記錄 diff --git a/packages/web/src/content/docs/zh-tw/providers.mdx b/packages/web/src/content/docs/zh-tw/providers.mdx index 12c4ded4e36..b673b1ade5a 100644 --- a/packages/web/src/content/docs/zh-tw/providers.mdx +++ b/packages/web/src/content/docs/zh-tw/providers.mdx @@ -55,7 +55,7 @@ OpenCode Zen 是由 OpenCode 團隊提供的模型列表,這些模型已經過 如果您是新使用者,我們建議從 OpenCode Zen 開始。 ::: -1. 在 TUI 中執行 `/connect` 指令,選擇 opencode,然後前往 [opencode.ai/auth](https://opencode.ai/auth)。 +1. 在 TUI 中執行 `/connect` 指令,選擇 `OpenCode Zen`,然後前往 [opencode.ai/auth](https://opencode.ai/zen)。 ```txt /connect @@ -82,6 +82,37 @@ OpenCode Zen 是由 OpenCode 團隊提供的模型列表,這些模型已經過 --- +## OpenCode Go + +OpenCode Go 是一個低成本的訂閱計畫,提供對 OpenCode 團隊提供的流行開放編碼模型的可靠存取,這些模型已經過測試和驗證,能夠與 OpenCode 良好配合使用。 + +1. 在 TUI 中執行 `/connect` 指令,選擇 `OpenCode Go`,然後前往 [opencode.ai/auth](https://opencode.ai/zen)。 + + ```txt + /connect + ``` + +2. 登入後新增帳單資訊,然後複製您的 API 金鑰。 + +3. 貼上您的 API 金鑰。 + + ```txt + ┌ API key + │ + │ + └ enter + ``` + +4. 在 TUI 中執行 `/models` 查看我們推薦的模型列表。 + + ```txt + /models + ``` + +它的使用方式與 OpenCode 中的任何其他提供商相同,且完全可選。 + +--- + ## 目錄 下面我們來詳細了解一些提供商。如果您想將某個提供商新增到列表中,歡迎提交 PR。 diff --git a/packages/web/src/content/docs/zh-tw/themes.mdx b/packages/web/src/content/docs/zh-tw/themes.mdx index 513f2c8a774..256b78ccb6d 100644 --- a/packages/web/src/content/docs/zh-tw/themes.mdx +++ b/packages/web/src/content/docs/zh-tw/themes.mdx @@ -61,11 +61,11 @@ OpenCode 自帶多個內建主題。 ## 使用主題 -您可以透過 `/theme` 指令調出主題選擇介面來選擇主題,也可以在[設定](/docs/config)檔案中直接指定。 +您可以透過 `/theme` 指令調出主題選擇介面來選擇主題,也可以在 `tui.json` 中直接指定。 -```json title="opencode.json" {3} +```json title="tui.json" {3} { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "theme": "tokyonight" } ``` diff --git a/packages/web/src/content/docs/zh-tw/tui.mdx b/packages/web/src/content/docs/zh-tw/tui.mdx index 017507f20e2..8f46c4c15bc 100644 --- a/packages/web/src/content/docs/zh-tw/tui.mdx +++ b/packages/web/src/content/docs/zh-tw/tui.mdx @@ -352,24 +352,34 @@ How is auth handled in @packages/functions/src/api/index.ts? ## 設定 -您可以透過 OpenCode 設定檔自訂 TUI 行為。 +您可以透過 `tui.json`(或 `tui.jsonc`)自訂 TUI 行為。 -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", - "tui": { - "scroll_speed": 3, - "scroll_acceleration": { - "enabled": true - } - } + "$schema": "https://opencode.ai/tui.json", + "theme": "opencode", + "keybinds": { + "leader": "ctrl+x" + }, + "scroll_speed": 3, + "scroll_acceleration": { + "enabled": true + }, + "diff_style": "auto" } ``` +這與設定伺服器/執行時行為的 `opencode.json` 是分開的。 + ### 選項 -- `scroll_acceleration` - 啟用 macOS 風格的捲動加速,實現平滑、自然的捲動體驗。啟用後,快速捲動時速度會增加,慢速移動時保持精確。**此設定優先於 `scroll_speed`,啟用時會覆蓋它。** -- `scroll_speed` - 控制使用捲動指令時 TUI 的捲動速度(最小值:`1`)。預設為 `3`。**注意:如果 `scroll_acceleration.enabled` 設定為 `true`,則此設定會被忽略。** +- `theme` - 設定您的 UI 主題。[了解更多](/docs/themes)。 +- `keybinds` - 自訂鍵盤快速鍵。[了解更多](/docs/keybinds)。 +- `scroll_acceleration.enabled` - 啟用 macOS 風格的捲動加速,實現平滑、自然的捲動體驗。啟用後,快速捲動時速度會增加,慢速移動時保持精確。**此設定優先於 `scroll_speed`,啟用時會覆蓋它。** +- `scroll_speed` - 控制使用捲動指令時 TUI 的捲動速度(最小值:`0.001`,支援小數值)。預設為 `3`。**注意:如果 `scroll_acceleration.enabled` 設定為 `true`,則此設定會被忽略。** +- `diff_style` - 控制差異呈現方式。`"auto"` 根據終端機寬度自適應,`"stacked"` 始終顯示單列。 + +使用 `OPENCODE_TUI_CONFIG` 載入自訂 TUI 設定路徑。 --- diff --git a/packages/web/src/content/docs/zh-tw/zen.mdx b/packages/web/src/content/docs/zh-tw/zen.mdx index 5216194a938..c38188280b9 100644 --- a/packages/web/src/content/docs/zh-tw/zen.mdx +++ b/packages/web/src/content/docs/zh-tw/zen.mdx @@ -55,6 +55,7 @@ OpenCode Zen 的工作方式與 OpenCode 中的任何其他供應商相同。 | 模型 | 模型 ID | 端點 | AI SDK 套件 | | ------------------ | ------------------ | -------------------------------------------------- | --------------------------- | +| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | @@ -64,13 +65,15 @@ OpenCode Zen 的工作方式與 OpenCode 中的任何其他供應商相同。 | GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | | Gemini 3 Pro | gemini-3-pro | `https://opencode.ai/zen/v1/models/gemini-3-pro` | `@ai-sdk/google` | | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -80,7 +83,6 @@ OpenCode Zen 的工作方式與 OpenCode 中的任何其他供應商相同。 | GLM 4.7 | glm-4.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 4.6 | glm-4.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 Free | kimi-k2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2 Thinking | kimi-k2-thinking | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2 | kimi-k2 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Qwen3 Coder 480B | qwen3-coder | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -113,24 +115,28 @@ https://opencode.ai/zen/v1/models | GLM 5 | $1.00 | $3.20 | $0.20 | - | | GLM 4.7 | $0.60 | $2.20 | $0.10 | - | | GLM 4.6 | $0.60 | $2.20 | $0.10 | - | -| Kimi K2.5 Free | 免費 | 免費 | 免費 | - | | Kimi K2.5 | $0.60 | $3.00 | $0.08 | - | | Kimi K2 Thinking | $0.40 | $2.50 | - | - | | Kimi K2 | $0.40 | $2.50 | - | - | | Qwen3 Coder 480B | $0.45 | $1.50 | - | - | +| Claude Opus 4.6 (≤ 200K Token) | $5.00 | $25.00 | $0.50 | $6.25 | +| Claude Opus 4.6 (> 200K Token) | $10.00 | $37.50 | $1.00 | $12.50 | +| Claude Opus 4.5 | $5.00 | $25.00 | $0.50 | $6.25 | +| Claude Opus 4.1 | $15.00 | $75.00 | $1.50 | $18.75 | +| Claude Sonnet 4.6 (≤ 200K Token) | $3.00 | $15.00 | $0.30 | $3.75 | +| Claude Sonnet 4.6 (> 200K Token) | $6.00 | $22.50 | $0.60 | $7.50 | | Claude Sonnet 4.5 (≤ 200K Token) | $3.00 | $15.00 | $0.30 | $3.75 | | Claude Sonnet 4.5 (> 200K Token) | $6.00 | $22.50 | $0.60 | $7.50 | | Claude Sonnet 4 (≤ 200K Token) | $3.00 | $15.00 | $0.30 | $3.75 | | Claude Sonnet 4 (> 200K Token) | $6.00 | $22.50 | $0.60 | $7.50 | | Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 | | Claude Haiku 3.5 | $0.80 | $4.00 | $0.08 | $1.00 | -| Claude Opus 4.6 (≤ 200K Token) | $5.00 | $25.00 | $0.50 | $6.25 | -| Claude Opus 4.6 (> 200K Token) | $10.00 | $37.50 | $1.00 | $12.50 | -| Claude Opus 4.5 | $5.00 | $25.00 | $0.50 | $6.25 | -| Claude Opus 4.1 | $15.00 | $75.00 | $1.50 | $18.75 | +| Gemini 3.1 Pro (≤ 200K Token) | $2.00 | $12.00 | $0.20 | - | +| Gemini 3.1 Pro (> 200K Token) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Pro (≤ 200K Token) | $2.00 | $12.00 | $0.20 | - | | Gemini 3 Pro (> 200K Token) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | +| GPT 5.3 Codex | $1.75 | $14.00 | $0.175 | - | | GPT 5.2 | $1.75 | $14.00 | $0.175 | - | | GPT 5.2 Codex | $1.75 | $14.00 | $0.175 | - | | GPT 5.1 | $1.07 | $8.50 | $0.107 | - | @@ -149,7 +155,6 @@ https://opencode.ai/zen/v1/models 免費模型說明: -- Kimi K2.5 Free 在 OpenCode 上限時免費提供。團隊正在利用這段時間收集回饋並改進模型。 - MiniMax M2.5 Free 在 OpenCode 上限時免費提供。團隊正在利用這段時間收集回饋並改進模型。 - Big Pickle 是一個隱身模型,在 OpenCode 上限時免費提供。團隊正在利用這段時間收集回饋並改進模型。 @@ -178,7 +183,6 @@ https://opencode.ai/zen/v1/models 我們所有的模型都託管在美國。我們的供應商遵循零保留政策,不會將你的資料用於模型訓練,但以下情況除外: - Big Pickle:在免費期間,收集的資料可能會被用於改進模型。 -- Kimi K2.5 Free:在免費期間,收集的資料可能會被用於改進模型。 - MiniMax M2.5 Free:在免費期間,收集的資料可能會被用於改進模型。 - OpenAI API:請求會根據 [OpenAI 資料政策](https://platform.openai.com/docs/guides/your-data)保留 30 天。 - Anthropic API:請求會根據 [Anthropic 資料政策](https://docs.anthropic.com/en/docs/claude-code/data-usage)保留 30 天。 diff --git a/script/publish.ts b/script/publish.ts index 334a7349220..3889845fa60 100755 --- a/script/publish.ts +++ b/script/publish.ts @@ -68,6 +68,7 @@ if (Script.release) { } await import(`../packages/desktop/scripts/finalize-latest-json.ts`) + await import(`../packages/desktop-electron/scripts/finalize-latest-yml.ts`) await $`gh release edit v${Script.version} --draft=false --repo ${process.env.GH_REPO}` } diff --git a/sdks/vscode/package.json b/sdks/vscode/package.json index a041b65223d..69c586186ab 100644 --- a/sdks/vscode/package.json +++ b/sdks/vscode/package.json @@ -2,7 +2,7 @@ "name": "opencode", "displayName": "opencode", "description": "opencode for VS Code", - "version": "1.2.15", + "version": "1.2.16", "publisher": "sst-dev", "repository": { "type": "git", diff --git a/sst-env.d.ts b/sst-env.d.ts index fb7a7dc4207..c8622a5a9ac 100644 --- a/sst-env.d.ts +++ b/sst-env.d.ts @@ -145,10 +145,6 @@ declare module "sst" { "type": "sst.cloudflare.StaticSite" "url": string } - "ZEN_BLACK_LIMITS": { - "type": "sst.sst.Secret" - "value": string - } "ZEN_BLACK_PRICE": { "plan100": string "plan20": string @@ -156,7 +152,7 @@ declare module "sst" { "product": string "type": "sst.sst.Linkable" } - "ZEN_LITE_LIMITS": { + "ZEN_LIMITS": { "type": "sst.sst.Secret" "value": string } diff --git a/turbo.json b/turbo.json index ba3d01d3603..4d31bc472bd 100644 --- a/turbo.json +++ b/turbo.json @@ -3,11 +3,9 @@ "globalEnv": ["CI", "OPENCODE_DISABLE_SHARE"], "globalPassThroughEnv": ["CI", "OPENCODE_DISABLE_SHARE"], "tasks": { - "typecheck": { - "dependsOn": ["^build"] - }, + "typecheck": {}, "build": { - "dependsOn": ["^build"], + "dependsOn": [], "outputs": ["dist/**"] }, "opencode#test": {