mirror of
https://github.com/anomalyco/opencode.git
synced 2026-09-28 11:37:37 +00:00
Compare commits
26
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
083ed266e0 | ||
|
|
acb6859b06 | ||
|
|
9f9e73ae0f | ||
|
|
c1919a3477 | ||
|
|
482a1cdf0d | ||
|
|
725e4ba4b8 | ||
|
|
fed9d78791 | ||
|
|
ddbeaa17c0 | ||
|
|
03e67171ab | ||
|
|
d6963bdf24 | ||
|
|
661b7c5548 | ||
|
|
90e65205f6 | ||
|
|
1eacc1bdb9 | ||
|
|
35fc7a776c | ||
|
|
b471c2b449 | ||
|
|
a42f393c85 | ||
|
|
b65de4d694 | ||
|
|
696f41bc8e | ||
|
|
29f07e0c69 | ||
|
|
adee738d1e | ||
|
|
543c686c0d | ||
|
|
34aa427434 | ||
|
|
16c56fe5ec | ||
|
|
6df0d5d951 | ||
|
|
0f549842ee | ||
|
|
0027387dc5 |
@@ -15,12 +15,15 @@ runs:
|
||||
with:
|
||||
node-version: "24"
|
||||
|
||||
- name: Get baseline download URL
|
||||
- name: Get Bun version and baseline download URL
|
||||
id: bun-url
|
||||
shell: bash
|
||||
run: |
|
||||
V=$(node -p "require('./package.json').packageManager.split('@')[1]")
|
||||
# Bun 1.4.2 includes the patched peer-variant fix in oven-sh/bun#33646.
|
||||
if [ "$RUNNER_OS" = "Windows" ]; then V=1.4.2; fi
|
||||
echo "version=$V" >> "$GITHUB_OUTPUT"
|
||||
if [ "$RUNNER_ARCH" = "X64" ]; then
|
||||
V=$(node -p "require('./package.json').packageManager.split('@')[1]")
|
||||
case "$RUNNER_OS" in
|
||||
macOS) OS=darwin ;;
|
||||
Linux) OS=linux ;;
|
||||
@@ -30,9 +33,10 @@ runs:
|
||||
fi
|
||||
|
||||
- name: Setup Bun
|
||||
id: setup-bun
|
||||
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
|
||||
with:
|
||||
bun-version-file: ${{ !steps.bun-url.outputs.url && 'package.json' || '' }}
|
||||
bun-version: ${{ !steps.bun-url.outputs.url && steps.bun-url.outputs.version || '' }}
|
||||
bun-download-url: ${{ steps.bun-url.outputs.url }}
|
||||
|
||||
- name: Get cache directory
|
||||
@@ -45,24 +49,16 @@ runs:
|
||||
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
with:
|
||||
path: ${{ steps.cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }}
|
||||
key: ${{ runner.os }}-bun-${{ steps.setup-bun.outputs.bun-version }}-${{ hashFiles('**/bun.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-bun-
|
||||
${{ runner.os }}-bun-${{ steps.setup-bun.outputs.bun-version }}-
|
||||
|
||||
- name: Install setuptools for distutils compatibility
|
||||
run: python3 -m pip install setuptools || pip install setuptools || true
|
||||
shell: bash
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
# Workaround for patched peer variants
|
||||
# e.g. ./patches/ for standard-openapi
|
||||
# https://github.com/oven-sh/bun/issues/28147
|
||||
if [ "$RUNNER_OS" = "Windows" ]; then
|
||||
bun install --linker hoisted ${{ inputs.install-flags }}
|
||||
else
|
||||
bun install ${{ inputs.install-flags }}
|
||||
fi
|
||||
run: bun install ${{ inputs.install-flags }}
|
||||
shell: bash
|
||||
|
||||
- name: Save Bun dependencies
|
||||
@@ -70,4 +66,4 @@ runs:
|
||||
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
with:
|
||||
path: ${{ steps.cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }}
|
||||
key: ${{ runner.os }}-bun-${{ steps.setup-bun.outputs.bun-version }}-${{ hashFiles('**/bun.lock') }}
|
||||
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
"webfetch": "deny"
|
||||
}
|
||||
run: |
|
||||
opencode run -m opencode/claude-sonnet-4-6 "A new issue has been created:
|
||||
opencode run -m opencode/gpt-6-luna --variant high "A new issue has been created:
|
||||
|
||||
Issue number: ${{ github.event.issue.number }}
|
||||
Issue author association: ${{ github.event.issue.author_association }}
|
||||
@@ -188,7 +188,7 @@ jobs:
|
||||
"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.
|
||||
opencode run -m opencode/gpt-6-luna --variant high "Issue #${{ github.event.issue.number }} was previously flagged as non-compliant and has been edited.
|
||||
Issue author association: ${{ github.event.issue.author_association }}
|
||||
|
||||
Lookup this issue with gh issue view ${{ github.event.issue.number }}.
|
||||
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
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:
|
||||
opencode run --agent triage --variant high "The following issue was just opened, triage it:
|
||||
|
||||
Title: $ISSUE_TITLE
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
mode: primary
|
||||
hidden: true
|
||||
model: opencode/gpt-5.4-mini
|
||||
model: opencode/gpt-6-luna
|
||||
color: "#44BA81"
|
||||
tools:
|
||||
"*": false
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
model: opencode/gpt-5.4
|
||||
model: opencode/gpt-6-luna
|
||||
---
|
||||
|
||||
Create `UPCOMING_CHANGELOG.md` from the structured changelog input below.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
description: translate English to other languages
|
||||
model: opencode/gpt-5.6-sol
|
||||
model: opencode/gpt-6-sol
|
||||
---
|
||||
|
||||
run git diff and translate changed english doc and UI copy files to other international languages. Translate all languages in parallel to save time.
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
},
|
||||
"packages/app": {
|
||||
"name": "@opencode-ai/app",
|
||||
"version": "1.18.32",
|
||||
"version": "1.18.33",
|
||||
"dependencies": {
|
||||
"@corvu/drawer": "catalog:",
|
||||
"@dnd-kit/abstract": "0.5.0",
|
||||
@@ -96,7 +96,7 @@
|
||||
},
|
||||
"packages/cli": {
|
||||
"name": "@opencode-ai/cli",
|
||||
"version": "1.18.32",
|
||||
"version": "1.18.33",
|
||||
"bin": {
|
||||
"lildax": "./bin/lildax.cjs",
|
||||
},
|
||||
@@ -144,7 +144,7 @@
|
||||
},
|
||||
"packages/codemode": {
|
||||
"name": "@opencode-ai/codemode",
|
||||
"version": "1.18.32",
|
||||
"version": "1.18.33",
|
||||
"dependencies": {
|
||||
"acorn": "8.15.0",
|
||||
"effect": "catalog:",
|
||||
@@ -158,7 +158,7 @@
|
||||
},
|
||||
"packages/console/app": {
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.18.32",
|
||||
"version": "1.18.33",
|
||||
"dependencies": {
|
||||
"@cloudflare/vite-plugin": "1.15.2",
|
||||
"@ibm/plex": "6.4.1",
|
||||
@@ -195,7 +195,7 @@
|
||||
},
|
||||
"packages/console/core": {
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.18.32",
|
||||
"version": "1.18.33",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-sts": "3.782.0",
|
||||
"@jsx-email/render": "1.1.1",
|
||||
@@ -222,7 +222,7 @@
|
||||
},
|
||||
"packages/console/function": {
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.18.32",
|
||||
"version": "1.18.33",
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "3.0.82",
|
||||
"@ai-sdk/openai": "3.0.48",
|
||||
@@ -245,7 +245,7 @@
|
||||
},
|
||||
"packages/console/mail": {
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.18.32",
|
||||
"version": "1.18.33",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
@@ -269,7 +269,7 @@
|
||||
},
|
||||
"packages/console/support": {
|
||||
"name": "@opencode-ai/console-support",
|
||||
"version": "1.18.32",
|
||||
"version": "1.18.33",
|
||||
"dependencies": {
|
||||
"@cloudflare/vite-plugin": "1.15.2",
|
||||
"@opencode-ai/console-core": "workspace:*",
|
||||
@@ -289,7 +289,7 @@
|
||||
},
|
||||
"packages/core": {
|
||||
"name": "@opencode-ai/core",
|
||||
"version": "1.18.32",
|
||||
"version": "1.18.33",
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode",
|
||||
},
|
||||
@@ -352,6 +352,7 @@
|
||||
"mime-types": "3.0.2",
|
||||
"minimatch": "10.2.5",
|
||||
"npm-package-arg": "13.0.2",
|
||||
"open": "11.0.4",
|
||||
"semver": "^7.6.3",
|
||||
"turndown": "7.2.0",
|
||||
"venice-ai-sdk-provider": "2.1.1",
|
||||
@@ -383,7 +384,7 @@
|
||||
},
|
||||
"packages/desktop": {
|
||||
"name": "@opencode-ai/desktop",
|
||||
"version": "1.18.32",
|
||||
"version": "1.18.33",
|
||||
"dependencies": {
|
||||
"@zip.js/zip.js": "2.7.62",
|
||||
"drizzle-orm": "catalog:",
|
||||
@@ -437,7 +438,7 @@
|
||||
},
|
||||
"packages/effect-drizzle-sqlite": {
|
||||
"name": "@opencode-ai/effect-drizzle-sqlite",
|
||||
"version": "1.18.32",
|
||||
"version": "1.18.33",
|
||||
"dependencies": {
|
||||
"drizzle-orm": "catalog:",
|
||||
"effect": "catalog:",
|
||||
@@ -451,7 +452,7 @@
|
||||
},
|
||||
"packages/effect-sqlite-node": {
|
||||
"name": "@opencode-ai/effect-sqlite-node",
|
||||
"version": "1.18.32",
|
||||
"version": "1.18.33",
|
||||
"dependencies": {
|
||||
"effect": "catalog:",
|
||||
},
|
||||
@@ -463,7 +464,7 @@
|
||||
},
|
||||
"packages/enterprise": {
|
||||
"name": "@opencode-ai/enterprise",
|
||||
"version": "1.18.32",
|
||||
"version": "1.18.33",
|
||||
"dependencies": {
|
||||
"@hono/standard-validator": "catalog:",
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
@@ -495,7 +496,7 @@
|
||||
},
|
||||
"packages/function": {
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.18.32",
|
||||
"version": "1.18.33",
|
||||
"dependencies": {
|
||||
"@octokit/auth-app": "8.0.1",
|
||||
"@octokit/rest": "catalog:",
|
||||
@@ -511,7 +512,7 @@
|
||||
},
|
||||
"packages/http-recorder": {
|
||||
"name": "@opencode-ai/http-recorder",
|
||||
"version": "1.18.32",
|
||||
"version": "1.18.33",
|
||||
"dependencies": {
|
||||
"@effect/platform-node": "4.0.0-beta.83",
|
||||
"@effect/platform-node-shared": "4.0.0-beta.83",
|
||||
@@ -542,7 +543,7 @@
|
||||
},
|
||||
"packages/llm": {
|
||||
"name": "@opencode-ai/llm",
|
||||
"version": "1.18.32",
|
||||
"version": "1.18.33",
|
||||
"dependencies": {
|
||||
"@opencode-ai/schema": "workspace:*",
|
||||
"@smithy/eventstream-codec": "4.2.14",
|
||||
@@ -561,7 +562,7 @@
|
||||
},
|
||||
"packages/opencode": {
|
||||
"name": "opencode",
|
||||
"version": "1.18.32",
|
||||
"version": "1.18.33",
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode",
|
||||
},
|
||||
@@ -645,7 +646,6 @@
|
||||
"mime-types": "3.0.2",
|
||||
"minimatch": "10.0.3",
|
||||
"npm-package-arg": "13.0.2",
|
||||
"open": "10.1.2",
|
||||
"opencode-gitlab-auth": "2.1.0",
|
||||
"opencode-poe-auth": "0.0.1",
|
||||
"opentui-spinner": "catalog:",
|
||||
@@ -692,7 +692,7 @@
|
||||
},
|
||||
"packages/plugin": {
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "1.18.32",
|
||||
"version": "1.18.33",
|
||||
"dependencies": {
|
||||
"@ai-sdk/provider": "3.0.8",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
@@ -768,7 +768,7 @@
|
||||
},
|
||||
"packages/sdk/js": {
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "1.18.32",
|
||||
"version": "1.18.33",
|
||||
"dependencies": {
|
||||
"cross-spawn": "catalog:",
|
||||
},
|
||||
@@ -783,7 +783,7 @@
|
||||
},
|
||||
"packages/server": {
|
||||
"name": "@opencode-ai/server",
|
||||
"version": "1.18.32",
|
||||
"version": "1.18.33",
|
||||
"dependencies": {
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
"@opencode-ai/protocol": "workspace:*",
|
||||
@@ -798,7 +798,7 @@
|
||||
},
|
||||
"packages/session-ui": {
|
||||
"name": "@opencode-ai/session-ui",
|
||||
"version": "1.18.32",
|
||||
"version": "1.18.33",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/client": "file:../app/vendor/opencode-ai-client-1.17.13-v2.tgz",
|
||||
@@ -838,7 +838,7 @@
|
||||
},
|
||||
"packages/slack": {
|
||||
"name": "@opencode-ai/slack",
|
||||
"version": "1.18.32",
|
||||
"version": "1.18.33",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@slack/bolt": "^3.17.1",
|
||||
@@ -851,7 +851,7 @@
|
||||
},
|
||||
"packages/stats/app": {
|
||||
"name": "@opencode-ai/stats-app",
|
||||
"version": "1.18.32",
|
||||
"version": "1.18.33",
|
||||
"dependencies": {
|
||||
"@ibm/plex": "6.4.1",
|
||||
"@kobalte/core": "catalog:",
|
||||
@@ -878,7 +878,7 @@
|
||||
},
|
||||
"packages/stats/core": {
|
||||
"name": "@opencode-ai/stats-core",
|
||||
"version": "1.18.32",
|
||||
"version": "1.18.33",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-athena": "3.933.0",
|
||||
"@planetscale/database": "1.19.0",
|
||||
@@ -897,7 +897,7 @@
|
||||
},
|
||||
"packages/stats/server": {
|
||||
"name": "@opencode-ai/stats-server",
|
||||
"version": "1.18.32",
|
||||
"version": "1.18.33",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-firehose": "3.933.0",
|
||||
"@effect/platform-node": "catalog:",
|
||||
@@ -939,7 +939,7 @@
|
||||
},
|
||||
"packages/tui": {
|
||||
"name": "@opencode-ai/tui",
|
||||
"version": "1.18.32",
|
||||
"version": "1.18.33",
|
||||
"dependencies": {
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
"@opencode-ai/plugin": "workspace:*",
|
||||
@@ -952,7 +952,6 @@
|
||||
"diff": "catalog:",
|
||||
"effect": "catalog:",
|
||||
"fuzzysort": "catalog:",
|
||||
"open": "10.1.2",
|
||||
"opentui-spinner": "catalog:",
|
||||
"remeda": "catalog:",
|
||||
"solid-js": "catalog:",
|
||||
@@ -966,7 +965,7 @@
|
||||
},
|
||||
"packages/ui": {
|
||||
"name": "@opencode-ai/ui",
|
||||
"version": "1.18.32",
|
||||
"version": "1.18.33",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@pierre/diffs": "catalog:",
|
||||
@@ -1017,7 +1016,7 @@
|
||||
},
|
||||
"packages/web": {
|
||||
"name": "@opencode-ai/web",
|
||||
"version": "1.18.32",
|
||||
"version": "1.18.33",
|
||||
"dependencies": {
|
||||
"@astrojs/cloudflare": "12.6.3",
|
||||
"@astrojs/markdown-remark": "6.3.1",
|
||||
@@ -1086,6 +1085,7 @@
|
||||
"@opentui/solid": "catalog:",
|
||||
"@types/bun": "catalog:",
|
||||
"@types/node": "catalog:",
|
||||
"open": "11.0.4",
|
||||
},
|
||||
"catalog": {
|
||||
"@cloudflare/workers-types": "4.20251008.0",
|
||||
@@ -3456,7 +3456,7 @@
|
||||
|
||||
"deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
|
||||
|
||||
"default-browser": ["default-browser@5.5.0", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw=="],
|
||||
"default-browser": ["default-browser@5.5.1", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-m1pAzaJgZ/gssEqlOhJkPJp8Xly7QyW6xcrkUa2KKcDeDSEMP7X8xipU3snUcfisTQx0w1AGae+9UtJSfVnXGw=="],
|
||||
|
||||
"default-browser-id": ["default-browser-id@5.0.1", "", {}, "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q=="],
|
||||
|
||||
@@ -4592,7 +4592,7 @@
|
||||
|
||||
"oniguruma-to-es": ["oniguruma-to-es@4.3.6", "", { "dependencies": { "oniguruma-parser": "^0.12.2", "regex": "^6.1.0", "regex-recursion": "^6.0.2" } }, "sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA=="],
|
||||
|
||||
"open": ["open@10.1.2", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "is-wsl": "^3.1.0" } }, "sha512-cxN6aIDPz6rm8hbebcP7vrQNhvRcveZoJU72Y7vskh4oIm+BZwBECnx5nTmrlres1Qapvx27Qo1Auukpf8PKXw=="],
|
||||
"open": ["open@11.0.4", "", { "dependencies": { "default-browser": "^5.5.1", "define-lazy-prop": "^3.0.0", "is-in-ssh": "^1.0.0", "is-inside-container": "^1.0.0", "powershell-utils": "^0.2.1", "wsl-utils": "^1.0.0" } }, "sha512-++Zlftm0kVLPmzC06t6epuWmcRMDbI4z5P3NNX979WA/k23+NtSOynEGzsVfZwguKw2mi5umVgnBlJQMwRz4Pg=="],
|
||||
|
||||
"openai": ["openai@5.11.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.23.8" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-+AuTc5pVjlnTuA9zvn8rA/k+1RluPIx9AD4eDcnutv6JNwHHZxIhkFy+tmMKCvmMFDQzfA/r1ujvPWB19DQkYg=="],
|
||||
|
||||
@@ -4758,7 +4758,7 @@
|
||||
|
||||
"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=="],
|
||||
"powershell-utils": ["powershell-utils@0.2.1", "", {}, "sha512-C+y9x90UElAddDZmV4qOx9W53B61PO7cIqWz2dQsWlwswuq4mr8NEwytdGKboYbQlGZ3awrkTeNvcZiZNHnQ8A=="],
|
||||
|
||||
"preact": ["preact@11.0.0-beta.0", "", {}, "sha512-IcODoASASYwJ9kxz7+MJeiJhvLriwSb4y4mHIyxdgaRZp6kPUud7xytrk/6GZw8U3y6EFJaRb5wi9SrEK+8+lg=="],
|
||||
|
||||
@@ -5550,7 +5550,7 @@
|
||||
|
||||
"ws": ["ws@8.21.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g=="],
|
||||
|
||||
"wsl-utils": ["wsl-utils@0.3.1", "", { "dependencies": { "is-wsl": "^3.1.0", "powershell-utils": "^0.1.0" } }, "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg=="],
|
||||
"wsl-utils": ["wsl-utils@1.0.0", "", { "dependencies": { "is-wsl": "^3.1.0", "powershell-utils": "^0.1.0" } }, "sha512-Hl0ZOAs672vg+06kfujwRhoS6/jehvULrlFkuF2dRu6pHgA8U06h3xqNIqNNU1LTXPcedxByAR4GS6pwQK0mgA=="],
|
||||
|
||||
"xdg-basedir": ["xdg-basedir@5.1.0", "", {}, "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ=="],
|
||||
|
||||
@@ -5928,8 +5928,6 @@
|
||||
|
||||
"@hey-api/json-schema-ref-parser/js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
|
||||
|
||||
"@hey-api/openapi-ts/open": ["open@11.0.0", "", { "dependencies": { "default-browser": "^5.4.0", "define-lazy-prop": "^3.0.0", "is-in-ssh": "^1.0.0", "is-inside-container": "^1.0.0", "powershell-utils": "^0.1.0", "wsl-utils": "^0.3.0" } }, "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw=="],
|
||||
|
||||
"@hey-api/openapi-ts/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
|
||||
|
||||
"@img/sharp-wasm32/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="],
|
||||
@@ -6414,8 +6412,6 @@
|
||||
|
||||
"opencode/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
|
||||
|
||||
"opencode-gitlab-auth/open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="],
|
||||
|
||||
"openid-client/jose": ["jose@4.15.9", "", {}, "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA=="],
|
||||
|
||||
"openid-client/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="],
|
||||
@@ -6488,8 +6484,6 @@
|
||||
|
||||
"sst/jose": ["jose@5.2.3", "", {}, "sha512-KUXdbctm1uHVL8BYhnyHkgp3zDX5KW8ZhAKVFEfUbU2P8Alpzjb+48hHvjOdQIyPshoblhzsuqOwEEAbtHVirA=="],
|
||||
|
||||
"storybook/open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="],
|
||||
|
||||
"string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
|
||||
"string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
@@ -6560,6 +6554,8 @@
|
||||
|
||||
"wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"wsl-utils/powershell-utils": ["powershell-utils@0.1.0", "", {}, "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A=="],
|
||||
|
||||
"yaml-language-server/request-light": ["request-light@0.5.8", "", {}, "sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg=="],
|
||||
|
||||
"yaml-language-server/yaml": ["yaml@2.7.1", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ=="],
|
||||
@@ -7172,8 +7168,6 @@
|
||||
|
||||
"motion/framer-motion/motion-utils": ["motion-utils@12.39.0", "", {}, "sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ=="],
|
||||
|
||||
"opencode-gitlab-auth/open/wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="],
|
||||
|
||||
"opencode/@ai-sdk/cerebras/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.54", "", { "dependencies": { "@ai-sdk/provider": "3.0.12", "@ai-sdk/provider-utils": "4.0.33" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-OyXt0zK8y2/ZIyWlbxTv2r1M7AK227S+Gl4BYOEF42q0wz1n5m4fwR8L4Fy/MQ4Ho6xje47MPsFcRdIqIyP6Rw=="],
|
||||
|
||||
"opencode/@ai-sdk/cerebras/@ai-sdk/provider": ["@ai-sdk/provider@3.0.12", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-sj9DWTJ2Ze0WR9qsiOPqoqzNx3OxL6iMxHImbhvoe9qOspekbzxNDMiJ4TIGfYHYh9w4OmBjz3prvqhzTi96+Q=="],
|
||||
@@ -7200,8 +7194,6 @@
|
||||
|
||||
"send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
|
||||
|
||||
"storybook/open/wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="],
|
||||
|
||||
"string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"tw-to-css/tailwindcss/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
|
||||
|
||||
+4
-4
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"nodeModules": {
|
||||
"x86_64-linux": "sha256-yKqw4YLXSzPOxPxXVWEK2W11YpwXQtFR1533Z8/s2rI=",
|
||||
"aarch64-linux": "sha256-xbrn9P8be2XHOVKhH4EM2fiao2kppgJouXcoQSBRfnk=",
|
||||
"aarch64-darwin": "sha256-bs0ulK/Ny3JWVcGF4CRVr2/RsLAK5bglkrshVmGrBrc=",
|
||||
"x86_64-darwin": "sha256-s0j5DImmcA/tCP2kNTaYd6QugG+k8WtcDWVt7Yi2cxU="
|
||||
"x86_64-linux": "sha256-RIzpvDBR6GOfU2vjrtgP3LJ/j+gLxkMnJ9VE5Y65Pfo=",
|
||||
"aarch64-linux": "sha256-seE7v8AqxWCiyxNARMiLJNB1MZv15OwEf6r21k68xqg=",
|
||||
"aarch64-darwin": "sha256-rbyK+3qz9bGOxg3HlGW6yh3qeazCaxFcuPfdGbXHsIY=",
|
||||
"x86_64-darwin": "sha256-chSE5WtSQ+qdbj5vTvGmr4rn9gk8eVp3pVeow4gVqYI="
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -142,7 +142,8 @@
|
||||
"@opentui/keymap": "catalog:",
|
||||
"@opentui/solid": "catalog:",
|
||||
"@types/bun": "catalog:",
|
||||
"@types/node": "catalog:"
|
||||
"@types/node": "catalog:",
|
||||
"open": "11.0.4"
|
||||
},
|
||||
"patchedDependencies": {
|
||||
"@dnd-kit/dom@0.5.0": "patches/@dnd-kit%2Fdom@0.5.0.patch",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/app",
|
||||
"version": "1.18.32",
|
||||
"version": "1.18.33",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/cli",
|
||||
"version": "1.18.32",
|
||||
"version": "1.18.33",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/codemode",
|
||||
"version": "1.18.32",
|
||||
"version": "1.18.33",
|
||||
"description": "Effect-native confined code execution over schema-described tools",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.18.32",
|
||||
"version": "1.18.33",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -4,19 +4,15 @@ export const goModels = [
|
||||
{ id: "qwen3.8-max", name: "Qwen3.8 Max", requests: 160, allowance: 15 },
|
||||
{ id: "grok-4.7", name: "Grok 4.7", requests: 169, allowance: 15 },
|
||||
{ id: "grok-4.6", name: "Grok 4.6", requests: 169, allowance: 15 },
|
||||
{ id: "qwen3.7-max", name: "Qwen3.7 Max", requests: 170, allowance: 30 },
|
||||
{ id: "glm-5.3", name: "GLM-5.3", requests: 220, allowance: 15 },
|
||||
{ id: "glm-5.2", name: "GLM-5.2", requests: 880, allowance: 60 },
|
||||
{ id: "glm-5.1", name: "GLM-5.1", requests: 880, allowance: 60 },
|
||||
{ id: "deepseek-v4-pro", name: "DeepSeek V4 Pro", requests: 1050, allowance: 15 },
|
||||
{ id: "kimi-k2.6", name: "Kimi K2.6", requests: 1150, allowance: 60 },
|
||||
{ id: "kimi-k2.7-code", name: "Kimi K2.7 Code", requests: 1350, allowance: 60, featured: true },
|
||||
{ id: "hy4-preview", name: "Hy4 preview", requests: 1350, allowance: 30 },
|
||||
{ id: "gpt-5.6-luna", name: "GPT 5.6 Luna", requests: 2050, allowance: 15 },
|
||||
{ id: "minimax-m3", name: "MiniMax M3", requests: 3200, allowance: 60, featured: true },
|
||||
{ id: "mimo-v2.6-pro", name: "MiMo-V2.6-Pro", requests: 3250, allowance: 15, fresh: true },
|
||||
{ id: "mimo-v2.5-pro", name: "MiMo-V2.5-Pro", requests: 3250, allowance: 15 },
|
||||
{ id: "qwen3.6-plus", name: "Qwen3.6 Plus", requests: 3300, allowance: 60 },
|
||||
{ id: "minimax-m2.7", name: "MiniMax M2.7", requests: 3400, allowance: 60 },
|
||||
{ id: "deepseek-v4-flash-vision-exp", name: "DeepSeek V4 Flash Vision Exp", requests: 6500, allowance: 15 },
|
||||
{ id: "qwen3.7-plus", name: "Qwen3.7 Plus", requests: 4300, allowance: 60, featured: true },
|
||||
@@ -28,14 +24,12 @@ export const goModels = [
|
||||
id: "deepseek-flash",
|
||||
name: "DeepSeek V4.1 Flash",
|
||||
requests: 26000,
|
||||
baseRequests: 6500,
|
||||
allowance: 60,
|
||||
baseAllowance: 15,
|
||||
bonus: 4,
|
||||
featured: true,
|
||||
fresh: true,
|
||||
},
|
||||
{ id: "longcat-2.0", name: "LongCat-2.0", requests: 11400, allowance: 60 },
|
||||
{ id: "deepseek-v4-flash", name: "DeepSeek V4 Flash", requests: 13000, allowance: 30 },
|
||||
{
|
||||
id: "mimo-v2.6-flash",
|
||||
name: "MiMo-V2.6-Flash",
|
||||
@@ -69,4 +63,52 @@ export const goModels = [
|
||||
fresh: true,
|
||||
limitedTime: true,
|
||||
},
|
||||
{
|
||||
id: "longcat-2.5-preview-free",
|
||||
name: "LongCat 2.5 Preview Free",
|
||||
requests: Infinity,
|
||||
allowance: Infinity,
|
||||
featured: true,
|
||||
fresh: true,
|
||||
limitedTime: true,
|
||||
},
|
||||
].sort((a, b) => a.requests - b.requests)
|
||||
|
||||
// Go Plus limits are published per model in the Console, not a fixed multiplier of Go.
|
||||
const plusLimits: Record<string, [number, number]> = {
|
||||
"kimi-k3": [440, 60],
|
||||
"qwen3.8-max": [640, 60],
|
||||
"grok-4.7": [676, 60],
|
||||
"grok-4.6": [676, 60],
|
||||
"glm-5.3": [1760, 120],
|
||||
"glm-5.2": [2640, 180],
|
||||
"deepseek-v4-pro": [4200, 60],
|
||||
"kimi-k2.7-code": [4050, 180],
|
||||
"hy4-preview": [5400, 120],
|
||||
"gpt-5.6-luna": [8200, 60],
|
||||
"minimax-m3": [9600, 180],
|
||||
"mimo-v2.6-pro": [13000, 60],
|
||||
"mimo-v2.5-pro": [13000, 60],
|
||||
"minimax-m2.7": [13600, 240],
|
||||
"deepseek-v4-flash-vision-exp": [26000, 60],
|
||||
"qwen3.7-plus": [12900, 180],
|
||||
"gpt-6-luna": [16920, 60],
|
||||
hy3: [17200, 240],
|
||||
"qwen3.8-flash": [16200, 90],
|
||||
"glm-5.3-flash": [18960, 180],
|
||||
"deepseek-flash": [52000, 120],
|
||||
"longcat-2.0": [45600, 240],
|
||||
"deepseek-v4-flash": [52000, 120],
|
||||
"mimo-v2.6-flash": [60200, 120],
|
||||
"mimo-v2.5": [60200, 120],
|
||||
"muse-spark-1.3-contributor": [90600, 120],
|
||||
"muse-spark-1.2-contributor": [90600, 120],
|
||||
"space-bunny-free": [Infinity, Infinity],
|
||||
"longcat-2.5-preview-free": [Infinity, Infinity],
|
||||
}
|
||||
|
||||
export const goPlanModels = goModels.map((model) => ({
|
||||
...model,
|
||||
plusRequests: plusLimits[model.id]![0],
|
||||
plusAllowance: plusLimits[model.id]![1],
|
||||
}))
|
||||
|
||||
@@ -0,0 +1,288 @@
|
||||
@import "@opencode-ai/ui/v2/styles/colors.css";
|
||||
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
src: url("@opencode-ai/ui/fonts/Inter.ttf") format("truetype");
|
||||
font-style: normal;
|
||||
font-weight: 100 900;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
[data-page="go"] {
|
||||
--go-console-bg: var(--v2-grey-50);
|
||||
--go-console-surface: var(--v2-grey-100);
|
||||
--go-console-track: var(--v2-grey-200);
|
||||
--go-console-text: var(--v2-grey-1100);
|
||||
--go-console-secondary: var(--v2-grey-700);
|
||||
--go-console-muted: var(--v2-grey-600);
|
||||
--go-console-border: var(--v2-alpha-dark-8);
|
||||
--go-console-icon: var(--v2-grey-600);
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
--go-console-bg: var(--v2-grey-1000);
|
||||
--go-console-surface: var(--v2-grey-900);
|
||||
--go-console-track: var(--v2-grey-800);
|
||||
--go-console-text: var(--v2-grey-200);
|
||||
--go-console-secondary: var(--v2-grey-500);
|
||||
--go-console-muted: var(--v2-grey-600);
|
||||
--go-console-border: var(--v2-alpha-light-8);
|
||||
--go-console-icon: var(--v2-grey-600);
|
||||
}
|
||||
}
|
||||
|
||||
[data-component="go-plan-chart"] {
|
||||
margin: 0;
|
||||
padding: 2rem var(--padding) 3rem;
|
||||
background: var(--go-console-bg);
|
||||
color: var(--go-console-text);
|
||||
font-family: "Inter", sans-serif;
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 440;
|
||||
line-height: 1.25rem;
|
||||
letter-spacing: -0.04px;
|
||||
text-rendering: geometricPrecision;
|
||||
font-variant-numeric: tabular-nums;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
|
||||
[data-slot="heading"] {
|
||||
display: flex;
|
||||
align-items: end;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
h2 {
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 530;
|
||||
line-height: 1.25rem;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
[data-slot="heading"] p {
|
||||
color: var(--go-console-muted);
|
||||
line-height: 1.25rem;
|
||||
}
|
||||
[data-slot="tier-switch"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
[data-slot="tier-switch"] i {
|
||||
width: 3px;
|
||||
height: 12px;
|
||||
border-radius: 1px;
|
||||
}
|
||||
[data-tier="go"] {
|
||||
background: linear-gradient(var(--v2-yellow-500), var(--v2-yellow-600));
|
||||
}
|
||||
[data-tier="plus"] {
|
||||
background: linear-gradient(var(--v2-orange-500), var(--v2-orange-600));
|
||||
}
|
||||
button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
background: none;
|
||||
border: 0;
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
font-weight: 530;
|
||||
padding: 0.375rem 0.5rem;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
[data-slot="tier-switch"] button {
|
||||
color: var(--go-console-secondary);
|
||||
border-radius: 4px;
|
||||
transition:
|
||||
background-color 180ms ease,
|
||||
opacity 180ms ease;
|
||||
}
|
||||
[data-slot="tier-switch"] button[aria-pressed="false"] {
|
||||
opacity: 0.4;
|
||||
font-weight: 440;
|
||||
}
|
||||
[data-slot="tier-switch"] button[aria-pressed="false"]:is(:hover, :focus-visible) {
|
||||
opacity: 0.75;
|
||||
}
|
||||
[data-slot="tier-switch"] button[aria-pressed="true"] {
|
||||
background: var(--go-console-surface);
|
||||
color: var(--go-console-text);
|
||||
}
|
||||
[data-slot="tier-switch"] button:hover {
|
||||
color: var(--go-console-text);
|
||||
}
|
||||
[data-slot="expand"][aria-expanded="true"] svg {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
[data-slot="scroll"] {
|
||||
overflow-x: auto;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
min-width: 48rem;
|
||||
table-layout: fixed;
|
||||
border-spacing: 0;
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
thead {
|
||||
color: var(--go-console-secondary);
|
||||
}
|
||||
thead tr {
|
||||
background: var(--go-console-surface);
|
||||
}
|
||||
thead th {
|
||||
padding: 0.5rem 1rem;
|
||||
border-block: 1px solid var(--go-console-border);
|
||||
font-weight: 440;
|
||||
text-align: end;
|
||||
white-space: nowrap;
|
||||
}
|
||||
thead th:first-child {
|
||||
text-align: start;
|
||||
border-inline-start: 1px solid var(--go-console-border);
|
||||
border-start-start-radius: 6px;
|
||||
border-end-start-radius: 6px;
|
||||
}
|
||||
thead th:last-child {
|
||||
border-inline-end: 1px solid var(--go-console-border);
|
||||
border-start-end-radius: 6px;
|
||||
border-end-end-radius: 6px;
|
||||
}
|
||||
tbody th {
|
||||
padding: 0.5rem 1rem;
|
||||
text-align: start;
|
||||
font-weight: 400;
|
||||
color: var(--go-console-text);
|
||||
}
|
||||
[data-slot="model"] {
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
}
|
||||
[data-slot="model"] small {
|
||||
border: 1px solid var(--go-console-border);
|
||||
padding: 0 0.25rem;
|
||||
font-size: 0.625rem;
|
||||
}
|
||||
[data-slot="model"] a {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
color: var(--go-console-muted);
|
||||
text-decoration: none;
|
||||
}
|
||||
[data-slot="bars"] {
|
||||
position: relative;
|
||||
}
|
||||
[data-slot="gridline"] {
|
||||
position: absolute;
|
||||
inset-block: 0;
|
||||
border-inline-start: 1px dashed var(--go-console-border);
|
||||
transition: inset-inline-start 600ms cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
[data-slot="track"] {
|
||||
position: relative;
|
||||
display: flex;
|
||||
gap: 0.25rem;
|
||||
height: 0.5rem;
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
[data-slot="track"] > [data-tier] {
|
||||
position: relative;
|
||||
border-radius: 2px;
|
||||
background: linear-gradient(var(--v2-yellow-500), var(--v2-yellow-600));
|
||||
transition: width 600ms cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
[data-slot="track"] > [data-tier]::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: inherit;
|
||||
background: linear-gradient(var(--v2-orange-500), var(--v2-orange-600));
|
||||
opacity: 0;
|
||||
transition: opacity 300ms ease;
|
||||
}
|
||||
[data-slot="track"] > [data-tier="plus"]::after {
|
||||
opacity: 1;
|
||||
}
|
||||
[data-slot="remainder"] {
|
||||
flex: 1;
|
||||
border-radius: 2px;
|
||||
background: var(--go-console-track);
|
||||
}
|
||||
[data-slot="number"] {
|
||||
padding: 0.5rem 1rem;
|
||||
text-align: end;
|
||||
white-space: nowrap;
|
||||
}
|
||||
[data-slot="number"] span {
|
||||
display: block;
|
||||
line-height: 1.25rem;
|
||||
}
|
||||
[data-slot="plan-value"] {
|
||||
animation: go-plan-value-in 300ms ease-out both;
|
||||
}
|
||||
[data-slot="axis"] {
|
||||
position: relative;
|
||||
height: 2rem;
|
||||
color: var(--go-console-muted);
|
||||
font-size: 0.6875rem;
|
||||
}
|
||||
[data-slot="axis"] span {
|
||||
position: absolute;
|
||||
top: 0.25rem;
|
||||
transform: translateX(-50%);
|
||||
transition: inset-inline-start 600ms cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
&:dir(rtl) [data-slot="axis"] span {
|
||||
transform: translateX(50%);
|
||||
}
|
||||
figcaption {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
margin-top: 0.75rem;
|
||||
border-top: 1px solid var(--go-console-border);
|
||||
padding-top: 0.75rem;
|
||||
}
|
||||
[data-slot="expand"] {
|
||||
padding-inline: 0;
|
||||
}
|
||||
figcaption a {
|
||||
color: var(--go-console-secondary);
|
||||
}
|
||||
:is(button, a):focus-visible {
|
||||
outline: 2px solid currentColor;
|
||||
outline-offset: 3px;
|
||||
}
|
||||
@media (max-width: 55rem) {
|
||||
[data-slot="heading"] {
|
||||
align-items: start;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
[data-slot="tier-switch"] button,
|
||||
[data-slot="gridline"],
|
||||
[data-slot="track"] > [data-tier],
|
||||
[data-slot="track"] > [data-tier]::after,
|
||||
[data-slot="axis"] span {
|
||||
transition: none;
|
||||
}
|
||||
[data-slot="plan-value"] {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes go-plan-value-in {
|
||||
from {
|
||||
opacity: 0.3;
|
||||
transform: translateY(3px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
import "./go-plan-chart.css"
|
||||
import { createMemo, createSignal, createUniqueId, For, Show } from "solid-js"
|
||||
import { useI18n } from "~/context/i18n"
|
||||
import { useLanguage } from "~/context/language"
|
||||
import { goPlanModels } from "./go-models"
|
||||
|
||||
const ticks = [100, 1000, 10000]
|
||||
|
||||
export function GoPlanChart(props: { href: string }) {
|
||||
const i18n = useI18n()
|
||||
const language = useLanguage()
|
||||
const id = createUniqueId()
|
||||
const [expanded, setExpanded] = createSignal(false)
|
||||
const [tier, setTier] = createSignal<"go" | "go-plus">("go")
|
||||
const models = createMemo(() => goPlanModels.filter((model) => expanded() || model.featured))
|
||||
const max = createMemo(() =>
|
||||
Math.max(
|
||||
...goPlanModels.map((model) => (tier() === "go" ? model.requests : model.plusRequests)).filter(Number.isFinite),
|
||||
),
|
||||
)
|
||||
const position = (requests: number) =>
|
||||
Number.isFinite(requests)
|
||||
? 4 + Math.pow(Math.log10(Math.max(requests / 100, 1)) / Math.log10(max() / 100), 2.2) * 78
|
||||
: 100
|
||||
const format = createMemo(() => new Intl.NumberFormat(language.tag(language.locale())))
|
||||
const currency = createMemo(
|
||||
() =>
|
||||
new Intl.NumberFormat(language.tag(language.locale()), {
|
||||
style: "currency",
|
||||
currency: "USD",
|
||||
currencyDisplay: "narrowSymbol",
|
||||
maximumFractionDigits: 0,
|
||||
}),
|
||||
)
|
||||
|
||||
return (
|
||||
<figure data-component="go-plan-chart" dir={language.dir(language.locale())} aria-labelledby={`${id}-title`}>
|
||||
<div data-slot="heading">
|
||||
<div>
|
||||
<h2 id={`${id}-title`}>{i18n.t("go.plans.limits")}</h2>
|
||||
<p>{i18n.t("go.plans.description")}</p>
|
||||
</div>
|
||||
<div data-slot="tier-switch" role="group" aria-label={i18n.t("go.plans.legend")}>
|
||||
<button type="button" aria-pressed={tier() === "go"} onClick={() => setTier("go")}>
|
||||
<i data-tier="go" />
|
||||
Go
|
||||
</button>
|
||||
<button type="button" aria-pressed={tier() === "go-plus"} onClick={() => setTier("go-plus")}>
|
||||
<i data-tier="plus" />
|
||||
Go Plus
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div data-slot="scroll">
|
||||
<table id={id}>
|
||||
<colgroup>
|
||||
<col style={{ width: "26%" }} />
|
||||
<col style={{ width: "40%" }} />
|
||||
<col style={{ width: "17%" }} />
|
||||
<col style={{ width: "17%" }} />
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">{i18n.t("go.graph.model")}</th>
|
||||
<th scope="col" colspan="2">
|
||||
{i18n.t("go.graph.requests")}
|
||||
</th>
|
||||
<th scope="col">{i18n.t("go.graph.allowance")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<For each={models()}>
|
||||
{(model) => (
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<span data-slot="model">
|
||||
<bdi>{model.name}</bdi>
|
||||
<Show when={model.fresh}>
|
||||
<small>{i18n.t("go.graph.new")}</small>
|
||||
</Show>
|
||||
<Show when={model.limitedTime}>
|
||||
<small>{i18n.t("go.graph.limitedTime")}</small>
|
||||
</Show>
|
||||
<Show when={model.regions}>
|
||||
<a
|
||||
href="https://ai.developer.meta.com/legal/geographic-use-policy"
|
||||
title={i18n.t("go.graph.limitedRegions")}
|
||||
aria-label={`${model.name}: ${i18n.t("go.graph.limitedRegions")}`}
|
||||
>
|
||||
<svg viewBox="0 0 16 16" width="13" height="13" fill="none" aria-hidden="true">
|
||||
<circle cx="8" cy="8" r="6" stroke="currentColor" />
|
||||
<ellipse cx="8" cy="8" rx="2.5" ry="6" stroke="currentColor" />
|
||||
<path d="M2 8h12" stroke="currentColor" />
|
||||
</svg>
|
||||
</a>
|
||||
</Show>
|
||||
</span>
|
||||
</th>
|
||||
<td data-slot="bars" aria-hidden="true">
|
||||
<For each={ticks}>
|
||||
{(tick) => <span data-slot="gridline" style={{ "inset-inline-start": `${position(tick)}%` }} />}
|
||||
</For>
|
||||
<div data-slot="track">
|
||||
<span
|
||||
data-tier={tier() === "go" ? "go" : "plus"}
|
||||
style={{ width: `${position(tier() === "go" ? model.requests : model.plusRequests)}%` }}
|
||||
/>
|
||||
<span data-slot="remainder" />
|
||||
</div>
|
||||
</td>
|
||||
<td data-slot="number">
|
||||
<Show when={tier()} keyed>
|
||||
{(selected) => (
|
||||
<span data-slot="plan-value">
|
||||
{format().format(selected === "go" ? model.requests : model.plusRequests)}
|
||||
</span>
|
||||
)}
|
||||
</Show>
|
||||
</td>
|
||||
<td data-slot="number">
|
||||
<Show when={tier()} keyed>
|
||||
{(selected) => (
|
||||
<span data-slot="plan-value">
|
||||
{Number.isFinite(selected === "go" ? model.allowance : model.plusAllowance)
|
||||
? currency().format(selected === "go" ? model.allowance : model.plusAllowance)
|
||||
: "∞"}
|
||||
</span>
|
||||
)}
|
||||
</Show>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</For>
|
||||
</tbody>
|
||||
<tfoot aria-hidden="true">
|
||||
<tr>
|
||||
<td />
|
||||
<td data-slot="axis">
|
||||
<For each={ticks}>
|
||||
{(tick) => (
|
||||
<span style={{ "inset-inline-start": `${position(tick)}%` }}>
|
||||
{tick < 1000 ? format().format(tick) : `${tick / 1000}K`}
|
||||
</span>
|
||||
)}
|
||||
</For>
|
||||
</td>
|
||||
<td colspan="2" />
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
<figcaption>
|
||||
<button
|
||||
type="button"
|
||||
data-slot="expand"
|
||||
aria-expanded={expanded()}
|
||||
aria-controls={id}
|
||||
onClick={() => setExpanded(!expanded())}
|
||||
>
|
||||
{expanded()
|
||||
? i18n.t("go.graph.showLess")
|
||||
: i18n.t("go.graph.showAll", { count: format().format(goPlanModels.length) })}
|
||||
<svg width="14" height="14" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
||||
<path d="m4 6 4 4 4-4" stroke="currentColor" stroke-width="1.25" />
|
||||
</svg>
|
||||
</button>
|
||||
<a href={props.href}>{i18n.t("go.graph.usageLimits")}</a>
|
||||
</figcaption>
|
||||
</figure>
|
||||
)
|
||||
}
|
||||
@@ -88,9 +88,6 @@ export function LimitsGraph(props: { href: string }) {
|
||||
<Show when={model.fresh}>
|
||||
<span data-slot="badge">{i18n.t("go.graph.new")}</span>
|
||||
</Show>
|
||||
<Show when={model.bonus}>
|
||||
<span data-slot="badge">{i18n.t("go.graph.bonus", { count: model.bonus! })}</span>
|
||||
</Show>
|
||||
<Show when={model.limitedTime}>
|
||||
<span data-slot="badge">{i18n.t("go.graph.limitedTime")}</span>
|
||||
</Show>
|
||||
@@ -117,16 +114,10 @@ export function LimitsGraph(props: { href: string }) {
|
||||
<div data-slot="bar" style={{ "--width": `${position(model.requests)}%` }} />
|
||||
</div>
|
||||
<div data-slot="requests">
|
||||
<Show when={model.baseRequests}>
|
||||
<s>{format().format(model.baseRequests!)}</s>{" "}
|
||||
</Show>
|
||||
<bdi>{format().format(model.requests)}</bdi>
|
||||
</div>
|
||||
</div>
|
||||
<div role="cell" data-slot="allowance" data-high={model.allowance >= 60 ? "" : undefined}>
|
||||
<Show when={model.baseAllowance}>
|
||||
<s>{currency().format(model.baseAllowance!)}</s>{" "}
|
||||
</Show>
|
||||
<bdi>
|
||||
<Show when={Number.isFinite(model.allowance)} fallback="∞">
|
||||
<For each={currency().formatToParts(model.allowance)}>
|
||||
|
||||
@@ -267,6 +267,18 @@ export const dict = {
|
||||
"go.cta.template": "{{text}} {{price}}",
|
||||
"go.cta.text": "اشترك في Go",
|
||||
"go.cta.price": "$10/شهر",
|
||||
"go.plans.month": "/شهر",
|
||||
"go.plans.plus.cta": "الاشتراك في Go Plus",
|
||||
"go.plans.plus.description": "تبلغ تكلفة Go Plus $40/شهر مع حدود استخدام أعلى.",
|
||||
"go.plans.go.feature1": "نماذج مختارة بأسعار معقولة",
|
||||
"go.plans.go.feature2": "مختبرة للبرمجة باستخدام الوكلاء",
|
||||
"go.plans.go.feature3": "حدود سخية ووصول موثوق",
|
||||
"go.plans.plus.feature1": "يشمل كل مزايا Go",
|
||||
"go.plans.plus.feature2": "حدود أعلى لجلسات برمجة أطول وأكثر تركيزًا",
|
||||
"go.plans.plus.feature3": "للمشاريع الأكبر والأكثر تطلبًا",
|
||||
"go.plans.limits": "الحدود",
|
||||
"go.plans.description": "الطلبات المقدرة لكل 5 ساعات وحدود الاستخدام الشهرية لكل نموذج",
|
||||
"go.plans.legend": "الخطط",
|
||||
"go.pricing.body": "استخدمه مع أي وكيل. $10/شهر. قم بزيادة الرصيد إذا لزم الأمر. الإلغاء في أي وقت.",
|
||||
"go.graph.free": "مجاني",
|
||||
"go.graph.freePill": "Big Pickle ونماذج مجانية",
|
||||
@@ -375,7 +387,7 @@ export const dict = {
|
||||
|
||||
"go.faq.q9": "ما الفرق بين النماذج المجانية وGo؟",
|
||||
"go.faq.a9":
|
||||
"تشمل النماذج المجانية Big Pickle بالإضافة إلى النماذج الترويجية المتاحة في ذلك الوقت، مع حصة قدرها 200 طلب/يوم. يقدّم Go مجموعة منسقة من النماذج مع حصص طلبات أعلى مطبقة عبر نوافذ متجددة (5 ساعات، وأسبوعية، وشهرية)، تعادل الحصص الأساسية فيها تقريبًا $12 لكل 5 ساعات و$30 في الأسبوع و$60 في الشهر؛ وقد تختلف الحصص حسب النموذج (تختلف أعداد الطلبات الفعلية حسب النموذج والاستخدام).",
|
||||
"تشمل النماذج المجانية Big Pickle بالإضافة إلى النماذج الترويجية المتاحة في ذلك الوقت، مع حصة قدرها 200 طلب/يوم. يقدّم Go مجموعة منسقة من النماذج مع حصص طلبات أعلى عبر نوافذ متجددة: 20% من الحصة الشهرية لكل 5 ساعات، و50% لكل أسبوع، و100% لكل شهر. وقد تختلف الحصص حسب النموذج (تختلف أعداد الطلبات الفعلية حسب النموذج والاستخدام).",
|
||||
"go.faq.q10": "هل يمكنني استرداد أموالي؟",
|
||||
"go.faq.a10":
|
||||
"قد تكون مؤهلًا لاسترداد أموالك إذا تم الخصم خلال آخر 14 يومًا ولم تستخدم مخصصات Go خلال فترة الفوترة تلك. {{contact}} لطلب استرداد الأموال.",
|
||||
|
||||
@@ -272,6 +272,18 @@ export const dict = {
|
||||
"go.cta.template": "{{text}} {{price}}",
|
||||
"go.cta.text": "Assinar o Go",
|
||||
"go.cta.price": "$10/mês",
|
||||
"go.plans.month": "/mês",
|
||||
"go.plans.plus.cta": "Assinar Go Plus",
|
||||
"go.plans.plus.description": "O Go Plus custa $40/mês e oferece limites maiores.",
|
||||
"go.plans.go.feature1": "Modelos selecionados e acessíveis",
|
||||
"go.plans.go.feature2": "Testados para programação com agentes",
|
||||
"go.plans.go.feature3": "Limites generosos e acesso confiável",
|
||||
"go.plans.plus.feature1": "Tudo o que está incluído no Go",
|
||||
"go.plans.plus.feature2": "Limites maiores para sessões de programação mais longas e focadas",
|
||||
"go.plans.plus.feature3": "Para projetos maiores e exigentes",
|
||||
"go.plans.limits": "Limites",
|
||||
"go.plans.description": "Solicitações estimadas a cada 5 horas e limites mensais por modelo",
|
||||
"go.plans.legend": "Planos",
|
||||
"go.pricing.body":
|
||||
"Use com qualquer agente. $10/mês. Recarregue o crédito se necessário. Cancele a qualquer momento.",
|
||||
"go.graph.free": "Grátis",
|
||||
@@ -385,7 +397,7 @@ export const dict = {
|
||||
|
||||
"go.faq.q9": "Qual a diferença entre os modelos gratuitos e o Go?",
|
||||
"go.faq.a9":
|
||||
"Os modelos gratuitos incluem Big Pickle e modelos promocionais disponíveis no momento, com uma cota de 200 requisições/dia. O Go oferece uma seleção de modelos com cotas de requisição mais altas aplicadas em janelas móveis (5 horas, semanal e mensal), aproximadamente equivalentes a cotas básicas de $12 por 5 horas, $30 por semana e $60 por mês; as cotas específicas podem variar por modelo (as contagens reais de requisições variam de acordo com o modelo e o uso).",
|
||||
"Os modelos gratuitos incluem Big Pickle e modelos promocionais disponíveis no momento, com uma cota de 200 requisições/dia. O Go oferece uma seleção de modelos com cotas de requisição mais altas em janelas móveis: 20% da cota mensal a cada 5 horas, 50% por semana e 100% por mês. As cotas específicas podem variar por modelo (as contagens reais de requisições variam de acordo com o modelo e o uso).",
|
||||
"go.faq.q10": "Posso receber um reembolso?",
|
||||
"go.faq.a10":
|
||||
"Você pode ter direito a um reembolso se a cobrança foi feita nos últimos 14 dias e você não usou sua cota do Go durante esse período de faturamento. {{contact}} para solicitar um reembolso.",
|
||||
|
||||
@@ -270,6 +270,18 @@ export const dict = {
|
||||
"go.cta.template": "{{text}} {{price}}",
|
||||
"go.cta.text": "Abonner på Go",
|
||||
"go.cta.price": "$10/måned",
|
||||
"go.plans.month": "/måned",
|
||||
"go.plans.plus.cta": "Abonnér på Go Plus",
|
||||
"go.plans.plus.description": "Go Plus koster $40/måned og giver højere brugsgrænser.",
|
||||
"go.plans.go.feature1": "Udvalgte, prisvenlige modeller",
|
||||
"go.plans.go.feature2": "Testet til agentbaseret kodning",
|
||||
"go.plans.go.feature3": "Generøse grænser og pålidelig adgang",
|
||||
"go.plans.plus.feature1": "Alt i Go er inkluderet",
|
||||
"go.plans.plus.feature2": "Højere grænser til længere, fokuserede kodningsforløb",
|
||||
"go.plans.plus.feature3": "Til større, krævende projekter",
|
||||
"go.plans.limits": "Grænser",
|
||||
"go.plans.description": "Anslåede anmodninger pr. 5 timer og månedlige grænser pr. model",
|
||||
"go.plans.legend": "Abonnementer",
|
||||
"go.pricing.body": "Brug med enhver agent. $10/måned. Tank op med kredit efter behov. Afmeld når som helst.",
|
||||
"go.graph.free": "Gratis",
|
||||
"go.graph.freePill": "Big Pickle og gratis modeller",
|
||||
@@ -381,7 +393,7 @@ export const dict = {
|
||||
|
||||
"go.faq.q9": "Hvad er forskellen på gratis modeller og Go?",
|
||||
"go.faq.a9":
|
||||
"Gratis modeller inkluderer Big Pickle plus kampagnemodeller, der er tilgængelige på det pågældende tidspunkt, med en kvote på 200 forespørgsler/dag. Go tilbyder et kurateret modeludvalg med højere forespørgselskvoter håndhævet over rullende perioder (5 timer, ugentligt og månedligt), omtrent svarende til basiskvoter på $12 pr. 5 timer, $30 pr. uge og $60 pr. måned; modelspecifikke kvoter kan variere (det faktiske antal forespørgsler varierer efter model og brug).",
|
||||
"Gratis modeller inkluderer Big Pickle plus kampagnemodeller, der er tilgængelige på det pågældende tidspunkt, med en kvote på 200 forespørgsler/dag. Go tilbyder et kurateret modeludvalg med højere forespørgselskvoter i rullende perioder: 20 % af den månedlige kvote pr. 5 timer, 50 % pr. uge og 100 % pr. måned. Modelspecifikke kvoter kan variere (det faktiske antal forespørgsler varierer efter model og brug).",
|
||||
"go.faq.q10": "Kan jeg få en refusion?",
|
||||
"go.faq.a10":
|
||||
"Du kan muligvis få en refusion, hvis opkrævningen blev foretaget inden for de seneste 14 dage, og du ikke har brugt din Go-kvote i den pågældende faktureringsperiode. {{contact}} for at anmode om en refusion.",
|
||||
|
||||
@@ -272,6 +272,18 @@ export const dict = {
|
||||
"go.cta.template": "{{text}} {{price}}",
|
||||
"go.cta.text": "Go abonnieren",
|
||||
"go.cta.price": "$10/Monat",
|
||||
"go.plans.month": "/Monat",
|
||||
"go.plans.plus.cta": "Go Plus abonnieren",
|
||||
"go.plans.plus.description": "Go Plus kostet $40/Monat und bietet höhere Nutzungslimits.",
|
||||
"go.plans.go.feature1": "Ausgewählte, erschwingliche Modelle",
|
||||
"go.plans.go.feature2": "Für agentisches Programmieren getestet",
|
||||
"go.plans.go.feature3": "Großzügige Limits und zuverlässiger Zugriff",
|
||||
"go.plans.plus.feature1": "Alle Vorteile von Go",
|
||||
"go.plans.plus.feature2": "Höhere Limits für längere, konzentrierte Coding-Sessions",
|
||||
"go.plans.plus.feature3": "Für größere, anspruchsvolle Projekte",
|
||||
"go.plans.limits": "Limits",
|
||||
"go.plans.description": "Geschätzte Anfragen pro 5 Stunden und monatliche Nutzungslimits je Modell",
|
||||
"go.plans.legend": "Tarife",
|
||||
"go.pricing.body": "Mit jedem Agenten nutzbar. $10/Monat. Guthaben bei Bedarf aufladen. Jederzeit kündbar.",
|
||||
"go.graph.free": "Kostenlos",
|
||||
"go.graph.freePill": "Big Pickle und kostenlose Modelle",
|
||||
@@ -383,7 +395,7 @@ export const dict = {
|
||||
|
||||
"go.faq.q9": "Was ist der Unterschied zwischen kostenlosen Modellen und Go?",
|
||||
"go.faq.a9":
|
||||
"Kostenlose Modelle beinhalten Big Pickle sowie Werbemodelle, die zum jeweiligen Zeitpunkt verfügbar sind, mit einem Kontingent von 200 Anfragen/Tag. Go bietet eine kuratierte Modellauswahl mit höheren Anfragekontingenten, die über rollierende Zeitfenster (5 Stunden, wöchentlich und monatlich) durchgesetzt werden, grob äquivalent zu Basiskontingenten von $12 pro 5 Stunden, $30 pro Woche und $60 pro Monat; modellspezifische Kontingente können abweichen (tatsächliche Anfragezahlen variieren je nach Modell und Nutzung).",
|
||||
"Kostenlose Modelle beinhalten Big Pickle sowie Werbemodelle, die zum jeweiligen Zeitpunkt verfügbar sind, mit einem Kontingent von 200 Anfragen/Tag. Go bietet eine kuratierte Modellauswahl mit höheren Anfragekontingenten in rollierenden Zeitfenstern: 20 % des monatlichen Kontingents pro 5 Stunden, 50 % pro Woche und 100 % pro Monat. Modellspezifische Kontingente können abweichen (tatsächliche Anfragezahlen variieren je nach Modell und Nutzung).",
|
||||
"go.faq.q10": "Kann ich eine Rückerstattung erhalten?",
|
||||
"go.faq.a10":
|
||||
"Du hast möglicherweise Anspruch auf eine Rückerstattung, wenn die Zahlung innerhalb der letzten 14 Tage erfolgt ist und du dein Go-Kontingent in diesem Abrechnungszeitraum nicht genutzt hast. {{contact}}, um eine Rückerstattung anzufordern.",
|
||||
|
||||
@@ -267,6 +267,18 @@ export const dict = {
|
||||
"go.cta.template": "{{text}} {{price}}",
|
||||
"go.cta.text": "Subscribe to Go",
|
||||
"go.cta.price": "$10/month",
|
||||
"go.plans.month": "/month",
|
||||
"go.plans.plus.cta": "Subscribe to Go Plus",
|
||||
"go.plans.plus.description": "Go Plus costs $40/month with higher limits.",
|
||||
"go.plans.go.feature1": "Curated, affordable models",
|
||||
"go.plans.go.feature2": "Tested for agentic coding",
|
||||
"go.plans.go.feature3": "Generous limits and reliable access",
|
||||
"go.plans.plus.feature1": "Everything included in Go",
|
||||
"go.plans.plus.feature2": "Higher limits and fewer interruptions",
|
||||
"go.plans.plus.feature3": "Built for larger, demanding projects",
|
||||
"go.plans.limits": "Limits",
|
||||
"go.plans.description": "Estimated requests per 5 hours and monthly usage limits by model",
|
||||
"go.plans.legend": "Plans",
|
||||
"go.pricing.body": "Use with any agent. $10/month. Top up credit if needed. Cancel any time.",
|
||||
"go.graph.free": "Free",
|
||||
"go.graph.freePill": "Big Pickle and free models",
|
||||
@@ -379,7 +391,7 @@ export const dict = {
|
||||
|
||||
"go.faq.q9": "What is the difference between free models and Go?",
|
||||
"go.faq.a9":
|
||||
"Free models include Big Pickle plus promotional models available at the time, with a quota of 200 requests/day. Go offers a curated model lineup with higher request quotas enforced across rolling windows (5-hour, weekly, and monthly), roughly equivalent to base allowances of $12 per 5 hours, $30 per week, and $60 per month; model-specific allowances may differ (actual request counts vary by model and usage).",
|
||||
"Free models include Big Pickle plus promotional models available at the time, with a quota of 200 requests/day. Go offers a curated model lineup with higher request quotas enforced across rolling windows: 20% of the monthly allowance per 5 hours, 50% per week, and 100% per month. Model-specific allowances may differ (actual request counts vary by model and usage).",
|
||||
"go.faq.q10": "Can I get a refund?",
|
||||
"go.faq.a10":
|
||||
"You may qualify for a refund if the charge was made within the last 14 days and you have not used your Go allowance during that billing period. {{contact}} to request a refund.",
|
||||
|
||||
@@ -273,6 +273,18 @@ export const dict = {
|
||||
"go.cta.template": "{{text}} {{price}}",
|
||||
"go.cta.text": "Suscribirse a Go",
|
||||
"go.cta.price": "10 $/mes",
|
||||
"go.plans.month": "/mes",
|
||||
"go.plans.plus.cta": "Suscribirse a Go Plus",
|
||||
"go.plans.plus.description": "Go Plus cuesta 40 $/mes y ofrece límites más altos.",
|
||||
"go.plans.go.feature1": "Modelos seleccionados y asequibles",
|
||||
"go.plans.go.feature2": "Probados para programación con agentes",
|
||||
"go.plans.go.feature3": "Límites generosos y acceso fiable",
|
||||
"go.plans.plus.feature1": "Todo lo incluido en Go",
|
||||
"go.plans.plus.feature2": "Límites más altos para programar durante más tiempo con concentración",
|
||||
"go.plans.plus.feature3": "Para proyectos más grandes y exigentes",
|
||||
"go.plans.limits": "Límites",
|
||||
"go.plans.description": "Solicitudes estimadas cada 5 horas y límites mensuales por modelo",
|
||||
"go.plans.legend": "Planes",
|
||||
"go.pricing.body":
|
||||
"Úsalo con cualquier agente. 10 $/mes. Recarga crédito si es necesario. Cancela en cualquier momento.",
|
||||
"go.graph.free": "Gratis",
|
||||
@@ -386,7 +398,7 @@ export const dict = {
|
||||
|
||||
"go.faq.q9": "¿Cuál es la diferencia entre los modelos gratuitos y Go?",
|
||||
"go.faq.a9":
|
||||
"Los modelos gratuitos incluyen Big Pickle y los modelos promocionales disponibles en ese momento, con una cuota de 200 solicitudes/día. Go ofrece una selección de modelos con cuotas de solicitudes más altas aplicadas en ventanas móviles (de 5 horas, semanales y mensuales), aproximadamente equivalentes a cuotas base de 12 $ por 5 horas, 30 $ por semana y 60 $ por mes; las cuotas específicas pueden variar según el modelo (la cantidad real de solicitudes varía según el modelo y el uso).",
|
||||
"Los modelos gratuitos incluyen Big Pickle y los modelos promocionales disponibles en ese momento, con una cuota de 200 solicitudes/día. Go ofrece una selección de modelos con cuotas de solicitudes más altas en ventanas móviles: el 20 % de la cuota mensual cada 5 horas, el 50 % por semana y el 100 % por mes. Las cuotas específicas pueden variar según el modelo (la cantidad real de solicitudes varía según el modelo y el uso).",
|
||||
"go.faq.q10": "¿Puedo obtener un reembolso?",
|
||||
"go.faq.a10":
|
||||
"Podrías tener derecho a un reembolso si el cargo se realizó en los últimos 14 días y no has utilizado tu cuota de Go durante ese periodo de facturación. {{contact}} para solicitar un reembolso.",
|
||||
|
||||
@@ -274,6 +274,18 @@ export const dict = {
|
||||
"go.cta.template": "{{text}} {{price}}",
|
||||
"go.cta.text": "S'abonner à Go",
|
||||
"go.cta.price": "10 $/mois",
|
||||
"go.plans.month": "/mois",
|
||||
"go.plans.plus.cta": "S'abonner à Go Plus",
|
||||
"go.plans.plus.description": "Go Plus coûte 40 $/mois et offre des limites plus élevées.",
|
||||
"go.plans.go.feature1": "Modèles sélectionnés et abordables",
|
||||
"go.plans.go.feature2": "Testés pour le codage agentique",
|
||||
"go.plans.go.feature3": "Limites généreuses et accès fiable",
|
||||
"go.plans.plus.feature1": "Tous les avantages de Go",
|
||||
"go.plans.plus.feature2": "Des limites plus élevées pour coder plus longtemps sans perdre le fil",
|
||||
"go.plans.plus.feature3": "Pour les projets plus vastes et exigeants",
|
||||
"go.plans.limits": "Limites",
|
||||
"go.plans.description": "Requêtes estimées par 5 heures et limites mensuelles par modèle",
|
||||
"go.plans.legend": "Offres",
|
||||
"go.pricing.body":
|
||||
"Utilisez-le avec n'importe quel agent. 10 $/mois. Rechargez du crédit si nécessaire. Annulez à tout moment.",
|
||||
"go.graph.free": "Gratuit",
|
||||
@@ -386,7 +398,7 @@ export const dict = {
|
||||
"Oui, vous pouvez utiliser Go avec n'importe quel agent. Suivez les instructions de configuration dans votre agent de code préféré.",
|
||||
"go.faq.q9": "Quelle est la différence entre les modèles gratuits et Go ?",
|
||||
"go.faq.a9":
|
||||
"Les modèles gratuits incluent Big Pickle ainsi que les modèles promotionnels disponibles à ce moment-là, avec un quota de 200 requêtes/jour. Go propose une sélection de modèles avec des quotas de requêtes plus élevés appliqués sur des fenêtres glissantes (5 heures, hebdomadaire et mensuelle), à peu près équivalents à des quotas de base de 12 $ par 5 heures, 30 $ par semaine et 60 $ par mois ; les quotas propres à chaque modèle peuvent varier (le nombre réel de requêtes varie selon le modèle et l'utilisation).",
|
||||
"Les modèles gratuits incluent Big Pickle ainsi que les modèles promotionnels disponibles à ce moment-là, avec un quota de 200 requêtes/jour. Go propose une sélection de modèles avec des quotas de requêtes plus élevés sur des fenêtres glissantes : 20 % du quota mensuel par 5 heures, 50 % par semaine et 100 % par mois. Les quotas propres à chaque modèle peuvent varier (le nombre réel de requêtes varie selon le modèle et l'utilisation).",
|
||||
"go.faq.q10": "Puis-je obtenir un remboursement ?",
|
||||
"go.faq.a10":
|
||||
"Vous pourriez avoir droit à un remboursement si le paiement a été effectué au cours des 14 derniers jours et que vous n’avez pas utilisé votre quota Go pendant cette période de facturation. {{contact}} pour demander un remboursement.",
|
||||
|
||||
@@ -270,6 +270,18 @@ export const dict = {
|
||||
"go.cta.template": "{{text}} {{price}}",
|
||||
"go.cta.text": "Abbonati a Go",
|
||||
"go.cta.price": "$10/mese",
|
||||
"go.plans.month": "/mese",
|
||||
"go.plans.plus.cta": "Abbonati a Go Plus",
|
||||
"go.plans.plus.description": "Go Plus costa $40/mese e offre limiti più alti.",
|
||||
"go.plans.go.feature1": "Modelli selezionati e convenienti",
|
||||
"go.plans.go.feature2": "Testati per la programmazione agentica",
|
||||
"go.plans.go.feature3": "Limiti generosi e accesso affidabile",
|
||||
"go.plans.plus.feature1": "Tutto ciò che include Go",
|
||||
"go.plans.plus.feature2": "Limiti più alti per sessioni di coding più lunghe e concentrate",
|
||||
"go.plans.plus.feature3": "Per progetti più grandi e impegnativi",
|
||||
"go.plans.limits": "Limiti",
|
||||
"go.plans.description": "Richieste stimate ogni 5 ore e limiti mensili per modello",
|
||||
"go.plans.legend": "Piani",
|
||||
"go.pricing.body":
|
||||
"Usalo con qualsiasi agente. $10/mese. Ricarica il credito se necessario. Annulla in qualsiasi momento.",
|
||||
"go.graph.free": "Gratis",
|
||||
@@ -382,7 +394,7 @@ export const dict = {
|
||||
|
||||
"go.faq.q9": "Qual è la differenza tra i modelli gratuiti e Go?",
|
||||
"go.faq.a9":
|
||||
"I modelli gratuiti includono Big Pickle più i modelli promozionali disponibili al momento, con una quota di 200 richieste/giorno. Go offre una selezione curata di modelli con quote di richiesta più elevate applicate su finestre mobili (5 ore, settimanale e mensile), approssimativamente equivalenti a quote base di $12 ogni 5 ore, $30 a settimana e $60 al mese; le quote specifiche possono variare in base al modello (il conteggio effettivo delle richieste varia in base al modello e all'utilizzo).",
|
||||
"I modelli gratuiti includono Big Pickle più i modelli promozionali disponibili al momento, con una quota di 200 richieste/giorno. Go offre una selezione curata di modelli con quote di richiesta più elevate su finestre mobili: il 20% della quota mensile ogni 5 ore, il 50% a settimana e il 100% al mese. Le quote specifiche possono variare in base al modello (il conteggio effettivo delle richieste varia in base al modello e all'utilizzo).",
|
||||
"go.faq.q10": "Posso ottenere un rimborso?",
|
||||
"go.faq.a10":
|
||||
"Potresti avere diritto a un rimborso se l'addebito è stato effettuato negli ultimi 14 giorni e non hai utilizzato la tua quota Go durante quel periodo di fatturazione. {{contact}} per richiedere un rimborso.",
|
||||
|
||||
@@ -269,6 +269,18 @@ export const dict = {
|
||||
"go.cta.template": "{{text}} {{price}}",
|
||||
"go.cta.text": "Goを購読する",
|
||||
"go.cta.price": "$10/月",
|
||||
"go.plans.month": "/月",
|
||||
"go.plans.plus.cta": "Go Plus に登録",
|
||||
"go.plans.plus.description": "Go Plus は月額$40で、より高い利用枠を提供します。",
|
||||
"go.plans.go.feature1": "厳選された手頃なモデル",
|
||||
"go.plans.go.feature2": "エージェント型コーディングで検証済み",
|
||||
"go.plans.go.feature3": "十分な利用枠と安定したアクセス",
|
||||
"go.plans.plus.feature1": "Go のすべての特典を含む",
|
||||
"go.plans.plus.feature2": "より高い利用枠で、長時間のコーディングに集中",
|
||||
"go.plans.plus.feature3": "大規模で高度なプロジェクト向け",
|
||||
"go.plans.limits": "利用枠",
|
||||
"go.plans.description": "モデル別の5時間あたりの推定リクエスト数と月間利用枠",
|
||||
"go.plans.legend": "プラン",
|
||||
"go.pricing.body":
|
||||
"どのエージェントでも使えます。月額$10。必要に応じてクレジットを追加。いつでもキャンセルできます。",
|
||||
"go.graph.free": "無料",
|
||||
@@ -380,7 +392,7 @@ export const dict = {
|
||||
|
||||
"go.faq.q9": "無料モデルとGoの違いは何ですか?",
|
||||
"go.faq.a9":
|
||||
"無料モデルにはBig Pickleと、その時点で利用可能なプロモーションモデルが含まれ、1日200リクエストの制限があります。Goでは厳選されたモデルラインナップを利用でき、ローリングウィンドウ(5時間、週間、月間)全体でより高いリクエスト制限が適用されます。基本利用枠では概算で5時間あたり$12、週間$30、月間$60相当ですが、モデル別の利用枠は異なる場合があります(実際のリクエスト数はモデルと使用状況により異なります)。",
|
||||
"無料モデルにはBig Pickleと、その時点で利用可能なプロモーションモデルが含まれ、1日200リクエストの制限があります。Goでは厳選されたモデルラインナップを利用でき、ローリングウィンドウで月間利用枠の20%を5時間ごと、50%を週ごと、100%を月ごとの上限として適用します。モデル別の利用枠は異なる場合があります(実際のリクエスト数はモデルと使用状況により異なります)。",
|
||||
"go.faq.q10": "返金を受けられますか?",
|
||||
"go.faq.a10":
|
||||
"請求から14日以内で、その請求期間中にGoの利用枠を一切使用していない場合、返金の対象となる可能性があります。返金を希望する場合は、{{contact}}ください。",
|
||||
|
||||
@@ -265,6 +265,18 @@ export const dict = {
|
||||
"go.cta.template": "{{text}} {{price}}",
|
||||
"go.cta.text": "Go 구독하기",
|
||||
"go.cta.price": "$10/월",
|
||||
"go.plans.month": "/월",
|
||||
"go.plans.plus.cta": "Go Plus 구독하기",
|
||||
"go.plans.plus.description": "Go Plus는 월 $40로 더 높은 사용 한도를 제공합니다.",
|
||||
"go.plans.go.feature1": "엄선된 합리적인 가격의 모델",
|
||||
"go.plans.go.feature2": "에이전트 코딩에 최적화된 테스트",
|
||||
"go.plans.go.feature3": "넉넉한 한도와 안정적인 접근",
|
||||
"go.plans.plus.feature1": "Go의 모든 혜택 포함",
|
||||
"go.plans.plus.feature2": "더 높은 한도로 장시간 코딩에 집중",
|
||||
"go.plans.plus.feature3": "더 크고 복잡한 프로젝트에 적합",
|
||||
"go.plans.limits": "사용 한도",
|
||||
"go.plans.description": "모델별 5시간당 예상 요청 횟수 및 월간 사용 한도",
|
||||
"go.plans.legend": "요금제",
|
||||
"go.pricing.body":
|
||||
"어떤 에이전트와도 사용할 수 있습니다. 월 $10. 필요하면 크레딧을 충전하세요. 언제든지 취소할 수 있습니다.",
|
||||
"go.graph.free": "무료",
|
||||
@@ -374,7 +386,7 @@ export const dict = {
|
||||
|
||||
"go.faq.q9": "무료 모델과 Go의 차이점은 무엇인가요?",
|
||||
"go.faq.a9":
|
||||
"무료 모델에는 Big Pickle과 당시 사용 가능한 프로모션 모델이 포함되며, 하루 200회 요청 할당량이 적용됩니다. Go는 엄선된 모델 라인업을 제공하며, 롤링 윈도우(5시간, 주간, 월간)에 걸쳐 더 높은 요청 할당량을 적용합니다. 기본 할당량은 대략 5시간당 $12, 주당 $30, 월 $60에 해당하며 모델별 할당량은 다를 수 있습니다(실제 요청 수는 모델 및 사용량에 따라 다름).",
|
||||
"무료 모델에는 Big Pickle과 당시 사용 가능한 프로모션 모델이 포함되며, 하루 200회 요청 할당량이 적용됩니다. Go는 엄선된 모델 라인업을 제공하며, 이동 기간별 요청 한도는 5시간당 월간 한도의 20%, 주간 50%, 월간 100%입니다. 모델별 한도는 다를 수 있습니다(실제 요청 수는 모델 및 사용량에 따라 다름).",
|
||||
"go.faq.q10": "환불받을 수 있나요?",
|
||||
"go.faq.a10":
|
||||
"결제일로부터 14일 이내이며 해당 결제 기간에 Go 사용 한도를 전혀 사용하지 않은 경우 환불 대상이 될 수 있습니다. 환불을 요청하려면 {{contact}}를 선택해 주세요.",
|
||||
|
||||
@@ -270,6 +270,18 @@ export const dict = {
|
||||
"go.cta.template": "{{text}} {{price}}",
|
||||
"go.cta.text": "Abonner på Go",
|
||||
"go.cta.price": "$10/måned",
|
||||
"go.plans.month": "/måned",
|
||||
"go.plans.plus.cta": "Abonner på Go Plus",
|
||||
"go.plans.plus.description": "Go Plus koster $40/måned og gir høyere bruksgrenser.",
|
||||
"go.plans.go.feature1": "Utvalgte, rimelige modeller",
|
||||
"go.plans.go.feature2": "Testet for agentbasert koding",
|
||||
"go.plans.go.feature3": "Romslige grenser og pålitelig tilgang",
|
||||
"go.plans.plus.feature1": "Alt i Go er inkludert",
|
||||
"go.plans.plus.feature2": "Høyere grenser for lengre, fokuserte kodeøkter",
|
||||
"go.plans.plus.feature3": "For større, krevende prosjekter",
|
||||
"go.plans.limits": "Grenser",
|
||||
"go.plans.description": "Anslåtte forespørsler per 5 timer og månedlige grenser per modell",
|
||||
"go.plans.legend": "Abonnementer",
|
||||
"go.pricing.body": "Bruk med hvilken som helst agent. $10/måned. Fyll på kreditt ved behov. Avslutt når som helst.",
|
||||
"go.graph.free": "Gratis",
|
||||
"go.graph.freePill": "Big Pickle og gratis modeller",
|
||||
@@ -382,7 +394,7 @@ export const dict = {
|
||||
|
||||
"go.faq.q9": "Hva er forskjellen mellom gratis modeller og Go?",
|
||||
"go.faq.a9":
|
||||
"Gratis modeller inkluderer Big Pickle pluss kampanjemodeller som er tilgjengelige på det tidspunktet, med en kvote på 200 forespørsler/dag. Go tilbyr et kuratert modellutvalg med høyere forespørselskvoter som håndheves over rullerende vinduer (5 timer, ukentlig og månedlig), omtrent tilsvarende basiskvoter på $12 per 5 timer, $30 per uke og $60 per måned; modellspesifikke kvoter kan variere (faktiske forespørselsantall varierer etter modell og bruk).",
|
||||
"Gratis modeller inkluderer Big Pickle pluss kampanjemodeller som er tilgjengelige på det tidspunktet, med en kvote på 200 forespørsler/dag. Go tilbyr et kuratert modellutvalg med høyere forespørselskvoter i rullerende perioder: 20 % av den månedlige kvoten per 5 timer, 50 % per uke og 100 % per måned. Modellspesifikke kvoter kan variere (faktisk antall forespørsler varierer etter modell og bruk).",
|
||||
"go.faq.q10": "Kan jeg få refusjon?",
|
||||
"go.faq.a10":
|
||||
"Du kan ha rett på refusjon hvis belastningen ble gjort i løpet av de siste 14 dagene og du ikke har brukt noe av Go-kvoten i den faktureringsperioden. {{contact}} for å be om refusjon.",
|
||||
|
||||
@@ -271,6 +271,18 @@ export const dict = {
|
||||
"go.cta.template": "{{text}} {{price}}",
|
||||
"go.cta.text": "Zasubskrybuj Go",
|
||||
"go.cta.price": "$10/miesiąc",
|
||||
"go.plans.month": "/miesiąc",
|
||||
"go.plans.plus.cta": "Subskrybuj Go Plus",
|
||||
"go.plans.plus.description": "Go Plus kosztuje $40/miesiąc i oferuje wyższe limity.",
|
||||
"go.plans.go.feature1": "Wyselekcjonowane, przystępne modele",
|
||||
"go.plans.go.feature2": "Przetestowane do kodowania agentowego",
|
||||
"go.plans.go.feature3": "Hojne limity i niezawodny dostęp",
|
||||
"go.plans.plus.feature1": "Wszystko, co obejmuje Go",
|
||||
"go.plans.plus.feature2": "Wyższe limity na dłuższe sesje kodowania w skupieniu",
|
||||
"go.plans.plus.feature3": "Do większych, wymagających projektów",
|
||||
"go.plans.limits": "Limity",
|
||||
"go.plans.description": "Szacowana liczba żądań na 5 godzin i miesięczne limity dla modeli",
|
||||
"go.plans.legend": "Plany",
|
||||
"go.pricing.body":
|
||||
"Używaj z dowolnym agentem. $10/miesiąc. Doładuj konto w razie potrzeby. Anuluj w dowolnym momencie.",
|
||||
"go.graph.free": "Darmowe",
|
||||
@@ -383,7 +395,7 @@ export const dict = {
|
||||
|
||||
"go.faq.q9": "Jaka jest różnica między darmowymi modelami a Go?",
|
||||
"go.faq.a9":
|
||||
"Darmowe modele obejmują Big Pickle oraz modele promocyjne dostępne w danym momencie, z limitem 200 zapytań/dzień. Go oferuje starannie dobrany zestaw modeli z wyższymi limitami zapytań egzekwowanymi w oknach kroczących (5-godzinnych, tygodniowych i miesięcznych), odpowiadającymi w przybliżeniu bazowym limitom $12 na 5 godzin, $30 tygodniowo i $60 miesięcznie; limity mogą się różnić zależnie od modelu (rzeczywista liczba zapytań zależy od modelu i użycia).",
|
||||
"Darmowe modele obejmują Big Pickle oraz modele promocyjne dostępne w danym momencie, z limitem 200 zapytań/dzień. Go oferuje starannie dobrany zestaw modeli z wyższymi limitami zapytań w oknach kroczących: 20% miesięcznego limitu na 5 godzin, 50% na tydzień i 100% na miesiąc. Limity mogą się różnić zależnie od modelu (rzeczywista liczba zapytań zależy od modelu i użycia).",
|
||||
"go.faq.q10": "Czy mogę otrzymać zwrot pieniędzy?",
|
||||
"go.faq.a10":
|
||||
"Możesz kwalifikować się do zwrotu, jeśli opłata została pobrana w ciągu ostatnich 14 dni i nie wykorzystano żadnej części limitu Go w tym okresie rozliczeniowym. {{contact}}, aby poprosić o zwrot.",
|
||||
|
||||
@@ -274,6 +274,18 @@ export const dict = {
|
||||
"go.cta.template": "{{text}} {{price}}",
|
||||
"go.cta.text": "Подписаться на Go",
|
||||
"go.cta.price": "$10/месяц",
|
||||
"go.plans.month": "/мес.",
|
||||
"go.plans.plus.cta": "Подписаться на Go Plus",
|
||||
"go.plans.plus.description": "Go Plus стоит $40/месяц и предлагает более высокие лимиты.",
|
||||
"go.plans.go.feature1": "Отобранные доступные модели",
|
||||
"go.plans.go.feature2": "Проверены для агентного программирования",
|
||||
"go.plans.go.feature3": "Щедрые лимиты и надёжный доступ",
|
||||
"go.plans.plus.feature1": "Все возможности Go",
|
||||
"go.plans.plus.feature2": "Повышенные лимиты для длительной сосредоточенной работы над кодом",
|
||||
"go.plans.plus.feature3": "Для крупных и сложных проектов",
|
||||
"go.plans.limits": "Лимиты",
|
||||
"go.plans.description": "Примерное число запросов за 5 часов и месячные лимиты по моделям",
|
||||
"go.plans.legend": "Тарифы",
|
||||
"go.pricing.body":
|
||||
"Используйте с любым агентом. $10/месяц. Пополняйте баланс при необходимости. Отменить можно в любое время.",
|
||||
"go.graph.free": "Бесплатно",
|
||||
@@ -388,7 +400,7 @@ export const dict = {
|
||||
|
||||
"go.faq.q9": "В чем разница между бесплатными моделями и Go?",
|
||||
"go.faq.a9":
|
||||
"Бесплатные модели включают Big Pickle и доступные на данный момент промо-модели с квотой 200 запросов/день. Go предлагает набор отобранных моделей с более высокими квотами запросов, применяемыми в скользящих окнах (5 часов, неделя и месяц), что примерно эквивалентно базовым лимитам $12 за 5 часов, $30 в неделю и $60 в месяц; лимиты для отдельных моделей могут отличаться (фактическое количество запросов зависит от модели и использования).",
|
||||
"Бесплатные модели включают Big Pickle и доступные на данный момент промо-модели с квотой 200 запросов/день. Go предлагает набор отобранных моделей с более высокими квотами запросов в скользящих окнах: 20% месячного лимита за 5 часов, 50% за неделю и 100% за месяц. Лимиты для отдельных моделей могут отличаться (фактическое количество запросов зависит от модели и использования).",
|
||||
"go.faq.q10": "Могу ли я получить возврат средств?",
|
||||
"go.faq.a10":
|
||||
"Вы можете претендовать на возврат, если списание произошло в течение последних 14 дней и вы не использовали лимит Go в этом расчетном периоде. {{contact}}, чтобы запросить возврат.",
|
||||
|
||||
@@ -269,6 +269,18 @@ export const dict = {
|
||||
"go.cta.template": "{{text}} {{price}}",
|
||||
"go.cta.text": "สมัครสมาชิก Go",
|
||||
"go.cta.price": "$10/เดือน",
|
||||
"go.plans.month": "/เดือน",
|
||||
"go.plans.plus.cta": "สมัคร Go Plus",
|
||||
"go.plans.plus.description": "Go Plus ราคา $40/เดือน พร้อมขีดจำกัดการใช้งานที่สูงขึ้น",
|
||||
"go.plans.go.feature1": "โมเดลคัดสรรในราคาคุ้มค่า",
|
||||
"go.plans.go.feature2": "ทดสอบสำหรับการเขียนโค้ดด้วยเอเจนต์",
|
||||
"go.plans.go.feature3": "ขีดจำกัดสูงและเข้าถึงได้อย่างเสถียร",
|
||||
"go.plans.plus.feature1": "รวมทุกสิ่งที่มีใน Go",
|
||||
"go.plans.plus.feature2": "ขีดจำกัดสูงขึ้น เพื่อเขียนโค้ดอย่างต่อเนื่องและมีสมาธิได้นานขึ้น",
|
||||
"go.plans.plus.feature3": "เหมาะสำหรับโปรเจกต์ขนาดใหญ่ที่ซับซ้อน",
|
||||
"go.plans.limits": "ขีดจำกัด",
|
||||
"go.plans.description": "จำนวนคำขอโดยประมาณต่อ 5 ชั่วโมงและขีดจำกัดรายเดือนแยกตามโมเดล",
|
||||
"go.plans.legend": "แพ็กเกจ",
|
||||
"go.pricing.body": "ใช้กับเอเจนต์ใดก็ได้ $10/เดือน เติมเครดิตหากจำเป็น ยกเลิกได้ตลอดเวลา",
|
||||
"go.graph.free": "ฟรี",
|
||||
"go.graph.freePill": "Big Pickle และโมเดลฟรี",
|
||||
@@ -379,7 +391,7 @@ export const dict = {
|
||||
|
||||
"go.faq.q9": "ความแตกต่างระหว่างโมเดลฟรีและ Go คืออะไร?",
|
||||
"go.faq.a9":
|
||||
"โมเดลฟรีประกอบด้วย Big Pickle และโมเดลโปรโมชันที่มีให้บริการในขณะนั้น โดยมีโควตา 200 คำขอ/วัน Go นำเสนอชุดโมเดลที่คัดสรร พร้อมโควตาคำขอที่สูงกว่าซึ่งบังคับใช้ตามกรอบเวลาแบบต่อเนื่อง (5 ชั่วโมง, รายสัปดาห์ และรายเดือน) เทียบเท่าโควตาพื้นฐานประมาณ $12 ต่อ 5 ชั่วโมง, $30 ต่อสัปดาห์ และ $60 ต่อเดือน โดยโควตาเฉพาะอาจแตกต่างกันไปตามโมเดล (จำนวนคำขอจริงแตกต่างกันไปตามโมเดลและการใช้งาน)",
|
||||
"โมเดลฟรีประกอบด้วย Big Pickle และโมเดลโปรโมชันที่มีให้บริการในขณะนั้น โดยมีโควตา 200 คำขอ/วัน Go นำเสนอชุดโมเดลที่คัดสรร พร้อมโควตาคำขอที่สูงกว่าในกรอบเวลาแบบต่อเนื่อง ได้แก่ 20% ของโควตารายเดือนต่อ 5 ชั่วโมง 50% ต่อสัปดาห์ และ 100% ต่อเดือน โควตาเฉพาะอาจแตกต่างกันไปตามโมเดล (จำนวนคำขอจริงแตกต่างกันไปตามโมเดลและการใช้งาน)",
|
||||
"go.faq.q10": "ฉันขอเงินคืนได้หรือไม่?",
|
||||
"go.faq.a10":
|
||||
"หากมีการเรียกเก็บเงินภายใน 14 วันที่ผ่านมาและคุณยังไม่ได้ใช้สิทธิ์การใช้งาน Go เลยในรอบการเรียกเก็บเงินนั้น คุณอาจมีสิทธิ์ได้รับเงินคืน {{contact}}เพื่อขอเงินคืน",
|
||||
|
||||
@@ -272,6 +272,18 @@ export const dict = {
|
||||
"go.cta.template": "{{text}} {{price}}",
|
||||
"go.cta.text": "Go'ya abone ol",
|
||||
"go.cta.price": "Ayda 10$",
|
||||
"go.plans.month": "/ay",
|
||||
"go.plans.plus.cta": "Go Plus'a abone ol",
|
||||
"go.plans.plus.description": "Go Plus ayda 40$'dır ve daha yüksek limitler sunar.",
|
||||
"go.plans.go.feature1": "Seçilmiş, uygun fiyatlı modeller",
|
||||
"go.plans.go.feature2": "Ajan destekli kodlama için test edildi",
|
||||
"go.plans.go.feature3": "Cömert limitler ve güvenilir erişim",
|
||||
"go.plans.plus.feature1": "Go'daki her şey dahil",
|
||||
"go.plans.plus.feature2": "Daha uzun, odaklı kodlama oturumları için daha yüksek limitler",
|
||||
"go.plans.plus.feature3": "Daha büyük ve zorlu projeler için",
|
||||
"go.plans.limits": "Limitler",
|
||||
"go.plans.description": "Model başına 5 saatlik tahmini istek ve aylık kullanım limitleri",
|
||||
"go.plans.legend": "Planlar",
|
||||
"go.pricing.body": "Herhangi bir ajanla kullanın. Ayda 10$. Gerekirse kredi yükleyin. İstediğiniz zaman iptal edin.",
|
||||
"go.graph.free": "Ücretsiz",
|
||||
"go.graph.freePill": "Big Pickle ve ücretsiz modeller",
|
||||
@@ -385,7 +397,7 @@ export const dict = {
|
||||
|
||||
"go.faq.q9": "Ücretsiz modeller ve Go arasındaki fark nedir?",
|
||||
"go.faq.a9":
|
||||
"Ücretsiz modeller, günlük 200 istek kotasıyla Big Pickle'ı ve o sırada mevcut olan promosyonel modelleri içerir. Go ise kayan zaman aralıklarında (5 saatlik, haftalık ve aylık) uygulanan daha yüksek istek kotalarıyla özenle seçilmiş model seçenekleri sunar. Bu kotalar kabaca her 5 saatte 12$, haftada 30$ ve ayda 60$ değerindeki temel kullanım haklarına eşdeğerdir; modele özgü kullanım hakları farklılık gösterebilir (gerçek istek sayıları modele ve kullanıma göre değişir).",
|
||||
"Ücretsiz modeller, günlük 200 istek kotasıyla Big Pickle'ı ve o sırada mevcut olan promosyonel modelleri içerir. Go ise kayan zaman aralıklarında daha yüksek istek kotalarıyla özenle seçilmiş model seçenekleri sunar: aylık kotanın %20'si her 5 saatte, %50'si haftada ve %100'ü ayda kullanılabilir. Modele özgü kullanım hakları farklılık gösterebilir (gerçek istek sayıları modele ve kullanıma göre değişir).",
|
||||
"go.faq.q10": "Para iadesi alabilir miyim?",
|
||||
"go.faq.a10":
|
||||
"Ücret son 14 gün içinde tahsil edildiyse ve ilgili faturalandırma döneminde Go kullanım hakkınızı hiç kullanmadıysanız para iadesine hak kazanabilirsiniz. {{contact}} ve para iadesi talep edin.",
|
||||
|
||||
@@ -270,6 +270,18 @@ export const dict = {
|
||||
"go.cta.template": "{{text}} {{price}}",
|
||||
"go.cta.text": "Підписатися на Go",
|
||||
"go.cta.price": "$10/місяць",
|
||||
"go.plans.month": "/місяць",
|
||||
"go.plans.plus.cta": "Підписатися на Go Plus",
|
||||
"go.plans.plus.description": "Go Plus коштує $40/місяць і пропонує вищі ліміти.",
|
||||
"go.plans.go.feature1": "Відібрані доступні моделі",
|
||||
"go.plans.go.feature2": "Перевірені для агентного програмування",
|
||||
"go.plans.go.feature3": "Щедрі ліміти й надійний доступ",
|
||||
"go.plans.plus.feature1": "Усе, що входить до Go",
|
||||
"go.plans.plus.feature2": "Вищі ліміти для тривалішої зосередженої роботи над кодом",
|
||||
"go.plans.plus.feature3": "Для більших і складніших проєктів",
|
||||
"go.plans.limits": "Ліміти",
|
||||
"go.plans.description": "Орієнтовні запити за 5 годин і місячні ліміти за моделями",
|
||||
"go.plans.legend": "Плани",
|
||||
"go.pricing.body": "Використовуйте з будь-яким агентом. $10/місяць. Поповнюйте за потреби. Скасуйте в будь-який час.",
|
||||
"go.graph.free": "Безкоштовно",
|
||||
"go.graph.freePill": "Big Pickle та безкоштовні моделі",
|
||||
@@ -380,7 +392,7 @@ export const dict = {
|
||||
|
||||
"go.faq.q9": "Яка різниця між безкоштовними моделями та Go?",
|
||||
"go.faq.a9":
|
||||
"Безкоштовні моделі включають Big Pickle та доступні на той момент акційні моделі з квотою 200 запитів/день. Go пропонує добірку моделей із вищими квотами запитів, що застосовуються протягом ковзних періодів (5 годин, тижня та місяця), приблизно еквівалентними базовим лімітам $12 за 5 годин, $30 на тиждень і $60 на місяць; ліміти для окремих моделей можуть відрізнятися (фактична кількість запитів залежить від моделі та використання).",
|
||||
"Безкоштовні моделі включають Big Pickle та доступні на той момент акційні моделі з квотою 200 запитів/день. Go пропонує добірку моделей із вищими квотами запитів протягом ковзних періодів: 20% місячного ліміту за 5 годин, 50% за тиждень і 100% за місяць. Ліміти для окремих моделей можуть відрізнятися (фактична кількість запитів залежить від моделі та використання).",
|
||||
"go.faq.q10": "Чи можу я отримати повернення коштів?",
|
||||
"go.faq.a10":
|
||||
"Ви можете претендувати на повернення, якщо кошти було списано протягом останніх 14 днів і ви не використали ліміт Go протягом цього розрахункового періоду. {{contact}}, щоб подати запит на повернення.",
|
||||
|
||||
@@ -259,6 +259,18 @@ export const dict = {
|
||||
"go.cta.template": "{{text}} {{price}}",
|
||||
"go.cta.text": "订阅 Go",
|
||||
"go.cta.price": "$10/月",
|
||||
"go.plans.month": "/月",
|
||||
"go.plans.plus.cta": "订阅 Go Plus",
|
||||
"go.plans.plus.description": "Go Plus 每月 $40,提供更高的使用额度。",
|
||||
"go.plans.go.feature1": "精选实惠的模型",
|
||||
"go.plans.go.feature2": "专为智能体编程测试",
|
||||
"go.plans.go.feature3": "充足额度与稳定访问",
|
||||
"go.plans.plus.feature1": "包含 Go 的全部权益",
|
||||
"go.plans.plus.feature2": "更高额度,支持更长时间的专注编码",
|
||||
"go.plans.plus.feature3": "适合更大型、更复杂的项目",
|
||||
"go.plans.limits": "使用额度",
|
||||
"go.plans.description": "各模型每 5 小时的预计请求次数和每月使用额度",
|
||||
"go.plans.legend": "套餐",
|
||||
"go.pricing.body": "可配合任何代理使用。每月 $10。如有需要可充值。随时取消。",
|
||||
"go.graph.free": "免费",
|
||||
"go.graph.freePill": "Big Pickle 和免费模型",
|
||||
@@ -361,7 +373,7 @@ export const dict = {
|
||||
|
||||
"go.faq.q9": "免费模型和 Go 之间的区别是什么?",
|
||||
"go.faq.a9":
|
||||
"免费模型包含 Big Pickle 加上当时可用的促销模型,每天有 200 次请求的配额。Go 提供精选模型阵容,并在滚动窗口(5 小时、每周和每月)内执行更高的请求配额,大致相当于每 5 小时 $12、每周 $30 和每月 $60 的基础额度;具体额度可能因模型而异(实际请求计数因模型和使用情况而异)。",
|
||||
"免费模型包含 Big Pickle 加上当时可用的促销模型,每天有 200 次请求的配额。Go 提供精选模型阵容,并在滚动窗口内执行更高的请求配额:每 5 小时为月额度的 20%,每周为 50%,每月为 100%。具体额度可能因模型而异(实际请求次数因模型和使用情况而异)。",
|
||||
"go.faq.q10": "我可以退款吗?",
|
||||
"go.faq.a10":
|
||||
"如果扣款发生在过去 14 天内,并且您在该计费周期内尚未使用任何 Go 额度,您可能符合退款条件。请{{contact}}申请退款。",
|
||||
|
||||
@@ -258,6 +258,18 @@ export const dict = {
|
||||
"go.cta.template": "{{text}} {{price}}",
|
||||
"go.cta.text": "訂閱 Go",
|
||||
"go.cta.price": "$10/月",
|
||||
"go.plans.month": "/月",
|
||||
"go.plans.plus.cta": "訂閱 Go Plus",
|
||||
"go.plans.plus.description": "Go Plus 每月 $40,提供更高的使用額度。",
|
||||
"go.plans.go.feature1": "精選實惠的模型",
|
||||
"go.plans.go.feature2": "專為代理程式編碼測試",
|
||||
"go.plans.go.feature3": "充足額度與穩定存取",
|
||||
"go.plans.plus.feature1": "包含 Go 的所有權益",
|
||||
"go.plans.plus.feature2": "更高額度,支援更長時間的專注編碼",
|
||||
"go.plans.plus.feature3": "適合更大型、更複雜的專案",
|
||||
"go.plans.limits": "使用額度",
|
||||
"go.plans.description": "各模型每 5 小時的預估請求次數及每月使用額度",
|
||||
"go.plans.legend": "方案",
|
||||
"go.pricing.body": "可搭配任何代理使用。每月 $10。如有需要可儲值。隨時取消。",
|
||||
"go.graph.free": "免費",
|
||||
"go.graph.freePill": "Big Pickle 與免費模型",
|
||||
@@ -360,7 +372,7 @@ export const dict = {
|
||||
|
||||
"go.faq.q9": "免費模型與 Go 有什麼區別?",
|
||||
"go.faq.a9":
|
||||
"免費模型包括 Big Pickle 以及當時可用的促銷模型,配額為 200 次請求/天。Go 提供精選模型陣容,並在滾動視窗(5 小時、每週和每月)內提供更高的請求配額,大約相當於每 5 小時 $12、每週 $30 和每月 $60 的基礎額度;具體額度可能因模型而異(實際請求數因模型和使用情況而異)。",
|
||||
"免費模型包括 Big Pickle 以及當時可用的促銷模型,配額為每天 200 次請求。Go 提供精選模型陣容,並在滾動視窗內執行較高的請求配額:每 5 小時為每月額度的 20%、每週 50%、每月 100%。具體額度可能因模型而異(實際請求次數因模型和使用情況而異)。",
|
||||
"go.faq.q10": "我可以退款嗎?",
|
||||
"go.faq.a10":
|
||||
"若扣款發生在過去 14 天內,且你在該計費期間尚未使用任何 Go 額度,你可能符合退款資格。請{{contact}}申請退款。",
|
||||
|
||||
@@ -1069,6 +1069,101 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="plans"] {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 0.75rem;
|
||||
margin-top: 2rem;
|
||||
font-family: "Inter", sans-serif;
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 440;
|
||||
line-height: 1.25rem;
|
||||
letter-spacing: -0.04px;
|
||||
text-rendering: geometricPrecision;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
|
||||
[data-slot="plan"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
gap: 1.75rem;
|
||||
padding: 1rem;
|
||||
border: 1px solid var(--go-console-border);
|
||||
border-radius: 6px;
|
||||
background: var(--go-console-surface);
|
||||
color: var(--go-console-text);
|
||||
}
|
||||
[data-slot="name"] {
|
||||
color: var(--go-console-secondary);
|
||||
line-height: 1.25rem;
|
||||
}
|
||||
[data-slot="price"] {
|
||||
margin-top: 1rem;
|
||||
font-size: 1.625rem;
|
||||
font-weight: 610;
|
||||
line-height: 2rem;
|
||||
letter-spacing: -0.5px;
|
||||
span {
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 440;
|
||||
letter-spacing: -0.04px;
|
||||
color: var(--go-console-secondary);
|
||||
}
|
||||
}
|
||||
ul {
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
margin-top: 1.25rem;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
color: var(--go-console-secondary);
|
||||
}
|
||||
li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
li span {
|
||||
display: inline-flex;
|
||||
flex: none;
|
||||
color: var(--go-console-icon);
|
||||
}
|
||||
a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 2rem;
|
||||
padding: 0 1rem;
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 530;
|
||||
border: 1px solid var(--go-console-border);
|
||||
border-radius: 6px;
|
||||
background: var(--go-console-bg);
|
||||
color: var(--go-console-text);
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
}
|
||||
a:hover {
|
||||
background: var(--go-console-track);
|
||||
}
|
||||
a:focus-visible {
|
||||
outline: 2px solid var(--go-console-text);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
@media (max-width: 42rem) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
[data-component="comparison"] {
|
||||
background: var(--go-console-bg);
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
[data-component="hero"] {
|
||||
padding-bottom: 2rem;
|
||||
}
|
||||
|
||||
[data-slot="section-title"] {
|
||||
margin-bottom: 24px;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import "./index.css"
|
||||
import { createAsync, query } from "@solidjs/router"
|
||||
import { Title, Meta } from "@solidjs/meta"
|
||||
import { For, createMemo } from "solid-js"
|
||||
import { For } from "solid-js"
|
||||
//import { HttpHeader } from "@solidjs/start"
|
||||
import goLogoLight from "../../asset/go-ornate-light.svg"
|
||||
import goLogoDark from "../../asset/go-ornate-dark.svg"
|
||||
@@ -10,7 +10,7 @@ import { Faq } from "~/component/faq"
|
||||
import { Legal } from "~/component/legal"
|
||||
import { Footer } from "~/component/footer"
|
||||
import { Header } from "~/component/header"
|
||||
import { LimitsGraph } from "~/component/limits-graph"
|
||||
import { GoPlanChart } from "~/component/go-plan-chart"
|
||||
import { config } from "~/config"
|
||||
import { getLastSeenWorkspaceID } from "../workspace/common"
|
||||
import { IconMiniMax, IconMiMo, IconZai, IconAlibaba, IconDeepSeek } from "~/component/icon"
|
||||
@@ -26,6 +26,7 @@ const checkLoggedIn = query(async () => {
|
||||
|
||||
const models = [
|
||||
{ name: "Space Bunny Free", training: "go.faq.a5.notUsed", retention: "go.faq.a5.retention0" },
|
||||
{ name: "LongCat 2.5 Preview Free", training: "go.faq.a5.notUsed", retention: "go.faq.a5.retention0" },
|
||||
{ name: "Grok 4.7", training: "go.faq.a5.notUsed", retention: "go.faq.a5.retention30" },
|
||||
{ name: "Grok 4.6", training: "go.faq.a5.notUsed", retention: "go.faq.a5.retention30" },
|
||||
{ name: "GPT 6 Luna", training: "go.faq.a5.notUsed", retention: "go.faq.a5.retention30" },
|
||||
@@ -33,10 +34,8 @@ const models = [
|
||||
{ name: "GLM-5.3-Flash", training: "go.faq.a5.notUsed", retention: "go.faq.a5.retention0" },
|
||||
{ name: "GLM-5.3", training: "go.faq.a5.notUsed", retention: "go.faq.a5.retention0" },
|
||||
{ name: "GLM-5.2", training: "go.faq.a5.notUsed", retention: "go.faq.a5.retention0" },
|
||||
{ name: "GLM-5.1", training: "go.faq.a5.notUsed", retention: "go.faq.a5.retention0" },
|
||||
{ name: "Kimi K3", training: "go.faq.a5.notUsed", retention: "go.faq.a5.retention0" },
|
||||
{ name: "Kimi K2.7 Code", training: "go.faq.a5.notUsed", retention: "go.faq.a5.retention0" },
|
||||
{ name: "Kimi K2.6", training: "go.faq.a5.notUsed", retention: "go.faq.a5.retention0" },
|
||||
{ name: "LongCat-2.0", training: "go.faq.a5.notUsed", retention: "go.faq.a5.retention0" },
|
||||
{ name: "MiMo-V2.6-Pro", training: "go.faq.a5.notUsed", retention: "go.faq.a5.retention0" },
|
||||
{ name: "MiMo-V2.6-Flash", training: "go.faq.a5.notUsed", retention: "go.faq.a5.retention0" },
|
||||
@@ -44,9 +43,7 @@ const models = [
|
||||
{ name: "MiMo-V2.5", training: "go.faq.a5.notUsed", retention: "go.faq.a5.retention0" },
|
||||
{ name: "Qwen3.8 Max", training: "go.faq.a5.notUsed", retention: "go.faq.a5.retention0" },
|
||||
{ name: "Qwen3.8 Flash", training: "go.faq.a5.notUsed", retention: "go.faq.a5.retention0" },
|
||||
{ name: "Qwen3.7 Max", training: "go.faq.a5.notUsed", retention: "go.faq.a5.retention0" },
|
||||
{ name: "Qwen3.7 Plus", training: "go.faq.a5.notUsed", retention: "go.faq.a5.retention0" },
|
||||
{ name: "Qwen3.6 Plus", training: "go.faq.a5.notUsed", retention: "go.faq.a5.retention0" },
|
||||
{ name: "MiniMax M3", training: "go.faq.a5.notUsed", retention: "go.faq.a5.retention0" },
|
||||
{ name: "MiniMax M2.7", training: "go.faq.a5.notUsed", retention: "go.faq.a5.retention0" },
|
||||
{ name: "Muse Spark 1.3 Contributor", training: "go.faq.a5.used", retention: "go.faq.a5.notZdr" },
|
||||
@@ -61,23 +58,29 @@ const models = [
|
||||
|
||||
export default function Home() {
|
||||
const workspaceID = createAsync(() => checkLoggedIn())
|
||||
const subscribeUrl = createMemo(() => "/console/go")
|
||||
const subscribeUrl = "https://opencode.ai/console/go"
|
||||
const i18n = useI18n()
|
||||
const language = useLanguage()
|
||||
return (
|
||||
<main data-page="go">
|
||||
{/*<HttpHeader name="Cache-Control" value="public, max-age=1, s-maxage=3600, stale-while-revalidate=86400" />*/}
|
||||
<Title>{i18n.t("go.title")}</Title>
|
||||
<Meta name="description" content={i18n.t("go.meta.description")} />
|
||||
<Meta name="description" content={`${i18n.t("go.meta.description")} ${i18n.t("go.plans.plus.description")}`} />
|
||||
<LocaleLinks path="/go" />
|
||||
<Meta property="og:type" content="website" />
|
||||
<Meta property="og:url" content={`${config.baseUrl}${language.route("/go")}`} />
|
||||
<Meta property="og:title" content={i18n.t("go.title")} />
|
||||
<Meta property="og:description" content={i18n.t("go.meta.description")} />
|
||||
<Meta
|
||||
property="og:description"
|
||||
content={`${i18n.t("go.meta.description")} ${i18n.t("go.plans.plus.description")}`}
|
||||
/>
|
||||
<Meta property="og:image" content="/social-share-black.png" />
|
||||
<Meta name="twitter:card" content="summary_large_image" />
|
||||
<Meta name="twitter:title" content={i18n.t("go.title")} />
|
||||
<Meta name="twitter:description" content={i18n.t("go.meta.description")} />
|
||||
<Meta
|
||||
name="twitter:description"
|
||||
content={`${i18n.t("go.meta.description")} ${i18n.t("go.plans.plus.description")}`}
|
||||
/>
|
||||
<Meta name="twitter:image" content="/social-share-black.png" />
|
||||
<Meta name="opencode:auth" content={workspaceID() ? "true" : "false"} />
|
||||
|
||||
@@ -181,46 +184,74 @@ export default function Home() {
|
||||
</div>
|
||||
*/}
|
||||
</div>
|
||||
<a href={subscribeUrl()}>
|
||||
<span>
|
||||
<For
|
||||
each={i18n
|
||||
.t("go.cta.template")
|
||||
.split(/(\{\{text\}\}|\{\{price\}\})/g)
|
||||
.filter(Boolean)}
|
||||
>
|
||||
{(part) => {
|
||||
if (part === "{{text}}") return <span>{i18n.t("go.cta.text")}</span>
|
||||
if (part === "{{price}}") {
|
||||
return <span data-slot="cta-price">{i18n.t("go.cta.price")}</span>
|
||||
}
|
||||
return part
|
||||
}}
|
||||
</For>
|
||||
</span>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M6.5 12L17 12M13 16.5L17.5 12L13 7.5"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="square"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
<div data-slot="pricing-copy">
|
||||
<p>{i18n.t("go.pricing.body")}</p>
|
||||
<div data-slot="plans">
|
||||
<For
|
||||
each={
|
||||
[
|
||||
{
|
||||
name: "Go",
|
||||
price: "$10",
|
||||
cta: "go.cta.text",
|
||||
features: ["go.plans.go.feature1", "go.plans.go.feature2", "go.plans.go.feature3"],
|
||||
icon: "✓",
|
||||
},
|
||||
{
|
||||
name: "Go Plus",
|
||||
price: "$40",
|
||||
cta: "go.plans.plus.cta",
|
||||
features: ["go.plans.plus.feature1", "go.plans.plus.feature2", "go.plans.plus.feature3"],
|
||||
icon: "+",
|
||||
},
|
||||
] as const
|
||||
}
|
||||
>
|
||||
{(plan) => (
|
||||
<div data-slot="plan">
|
||||
<div>
|
||||
<p data-slot="name">{plan.name}</p>
|
||||
<p data-slot="price">
|
||||
{plan.price} <span>{i18n.t("go.plans.month")}</span>
|
||||
</p>
|
||||
<ul>
|
||||
<For each={plan.features}>
|
||||
{(feature) => (
|
||||
<li>
|
||||
<span aria-hidden="true">
|
||||
<svg width="14" height="14" viewBox="0 0 16 16" fill="none">
|
||||
<path
|
||||
d={
|
||||
plan.icon === "✓"
|
||||
? "M3.53613 8.17857L6.39328 11.75L12.4647 4.25"
|
||||
: "M8 2.88867V13.1109M2.88867 8H13.1109"
|
||||
}
|
||||
stroke="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
{i18n.t(feature)}
|
||||
</li>
|
||||
)}
|
||||
</For>
|
||||
</ul>
|
||||
</div>
|
||||
<a href={subscribeUrl}>{i18n.t(plan.cta)}</a>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section data-component="comparison">
|
||||
<LimitsGraph href={goUsageLimits(language.locale())} />
|
||||
<GoPlanChart href={goUsageLimits(language.locale())} />
|
||||
</section>
|
||||
|
||||
<section data-component="problem">
|
||||
<div data-slot="section-title">
|
||||
<h3>{i18n.t("go.problem.title")}</h3>
|
||||
<p>{i18n.t("go.problem.body")}</p>
|
||||
<p>
|
||||
{i18n.t("go.problem.body")} {i18n.t("go.plans.plus.description")}
|
||||
</p>
|
||||
</div>
|
||||
<p>{i18n.t("go.problem.subtitle")}</p>
|
||||
<ul>
|
||||
@@ -242,7 +273,9 @@ export default function Home() {
|
||||
<section data-component="how">
|
||||
<div data-slot="section-title">
|
||||
<h3>{i18n.t("go.how.title")}</h3>
|
||||
<p>{i18n.t("go.how.body")}</p>
|
||||
<p>
|
||||
{i18n.t("go.how.body")} {i18n.t("go.plans.plus.description")}
|
||||
</p>
|
||||
</div>
|
||||
<ul>
|
||||
<li>
|
||||
@@ -259,7 +292,7 @@ export default function Home() {
|
||||
<div>
|
||||
<strong>{i18n.t("go.how.step2.title")}</strong> -{" "}
|
||||
<a href={language.route("/docs/go/#pricing")}>{i18n.t("go.how.step2.link")}</a>{" "}
|
||||
{i18n.t("go.how.step2.afterLink")}
|
||||
{i18n.t("go.how.step2.afterLink")} · {i18n.t("go.plans.plus.description")}
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
@@ -291,14 +324,17 @@ export default function Home() {
|
||||
<Faq question={i18n.t("go.faq.q9")}>{i18n.t("go.faq.a9")}</Faq>
|
||||
</li>
|
||||
<li>
|
||||
<Faq question={i18n.t("go.faq.q3")}>{i18n.t("go.faq.a3")}</Faq>
|
||||
<Faq question={i18n.t("go.faq.q3")}>
|
||||
{i18n.t("go.faq.a3")} {i18n.t("go.plans.plus.description")}
|
||||
</Faq>
|
||||
</li>
|
||||
<li>
|
||||
<Faq question={i18n.t("go.faq.q4")}>
|
||||
{i18n.t("go.faq.a4.p1.beforePricing")}{" "}
|
||||
<a href={language.route("/docs/go/#pricing")}>{i18n.t("go.faq.a4.p1.pricingLink")}</a>{" "}
|
||||
{i18n.t("go.faq.a4.p1.afterPricing")} {i18n.t("go.faq.a4.p2.beforeAccount")}{" "}
|
||||
<a href={subscribeUrl()}>{i18n.t("go.faq.a4.p2.accountLink")}</a>. {i18n.t("go.faq.a4.p3")}
|
||||
{i18n.t("go.faq.a4.p1.afterPricing")} {i18n.t("go.plans.plus.description")}{" "}
|
||||
{i18n.t("go.faq.a4.p2.beforeAccount")} <a href={subscribeUrl}>{i18n.t("go.faq.a4.p2.accountLink")}</a>
|
||||
. {i18n.t("go.faq.a4.p3")}
|
||||
</Faq>
|
||||
</li>
|
||||
<li>
|
||||
|
||||
@@ -662,7 +662,7 @@ export function LiteSection(props: { lite: LiteSubscription | undefined }) {
|
||||
<li>Qwen3.7 Max</li>
|
||||
<li>Qwen3.7 Plus</li>
|
||||
<li>Qwen3.6 Plus</li>
|
||||
<li>DeepSeek V4.1 Flash — {i18n.t("go.graph.bonus", { count: 4 })}</li>
|
||||
<li>DeepSeek V4.1 Flash</li>
|
||||
<li>DeepSeek V4 Pro</li>
|
||||
<li>DeepSeek V4 Flash</li>
|
||||
<li>DeepSeek V4 Flash Vision Exp</li>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.18.32",
|
||||
"version": "1.18.33",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.18.32",
|
||||
"version": "1.18.33",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.18.32",
|
||||
"version": "1.18.33",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-support",
|
||||
"version": "1.18.32",
|
||||
"version": "1.18.33",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.18.32",
|
||||
"version": "1.18.33",
|
||||
"name": "@opencode-ai/core",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
@@ -119,6 +119,7 @@
|
||||
"mime-types": "3.0.2",
|
||||
"minimatch": "10.2.5",
|
||||
"npm-package-arg": "13.0.2",
|
||||
"open": "11.0.4",
|
||||
"semver": "^7.6.3",
|
||||
"turndown": "7.2.0",
|
||||
"venice-ai-sdk-provider": "2.1.1",
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import open from "open"
|
||||
|
||||
export function openUrl(input: string) {
|
||||
const url = URL.canParse(input) ? new URL(input) : undefined
|
||||
if (!url || (url.protocol !== "http:" && url.protocol !== "https:"))
|
||||
return Promise.reject(new Error(`Only http and https links can be opened in the browser: ${input}`))
|
||||
return open(url.href)
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { openUrl } from "@opencode-ai/core/open"
|
||||
|
||||
describe("openUrl", () => {
|
||||
test("rejects values that are not URLs", async () => {
|
||||
await expect(openUrl("not a url")).rejects.toThrow("Only http and https links")
|
||||
await expect(openUrl("")).rejects.toThrow("Only http and https links")
|
||||
})
|
||||
|
||||
test("rejects non-http schemes", async () => {
|
||||
await expect(openUrl("file:///etc/hosts")).rejects.toThrow("Only http and https links")
|
||||
await expect(openUrl("javascript:alert(1)")).rejects.toThrow("Only http and https links")
|
||||
await expect(openUrl("ms-msdt:/id PCWDiagnostic")).rejects.toThrow("Only http and https links")
|
||||
await expect(openUrl("\\\\server\\share\\file.html")).rejects.toThrow("Only http and https links")
|
||||
})
|
||||
})
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@opencode-ai/desktop",
|
||||
"private": true,
|
||||
"version": "1.18.32",
|
||||
"version": "1.18.33",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"homepage": "https://opencode.ai",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.18.32",
|
||||
"version": "1.18.33",
|
||||
"name": "@opencode-ai/effect-drizzle-sqlite",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.18.32",
|
||||
"version": "1.18.33",
|
||||
"name": "@opencode-ai/effect-sqlite-node",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/enterprise",
|
||||
"version": "1.18.32",
|
||||
"version": "1.18.33",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.18.32",
|
||||
"version": "1.18.33",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.18.32",
|
||||
"version": "1.18.33",
|
||||
"name": "@opencode-ai/http-recorder",
|
||||
"description": "Record and replay Effect HTTP client traffic with deterministic cassettes",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.18.32",
|
||||
"version": "1.18.33",
|
||||
"name": "@opencode-ai/llm",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.18.32",
|
||||
"version": "1.18.33",
|
||||
"name": "opencode",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
@@ -131,7 +131,6 @@
|
||||
"mime-types": "3.0.2",
|
||||
"minimatch": "10.0.3",
|
||||
"npm-package-arg": "13.0.2",
|
||||
"open": "10.1.2",
|
||||
"opencode-gitlab-auth": "2.1.0",
|
||||
"opencode-poe-auth": "0.0.1",
|
||||
"opentui-spinner": "catalog:",
|
||||
|
||||
@@ -5,9 +5,9 @@ import { Account } from "@/account/account"
|
||||
import { AccountID, OrgID, PollExpired, type PollResult, type AccountError } from "@/account/schema"
|
||||
import { effectCmd } from "../effect-cmd"
|
||||
import * as Prompt from "../effect/prompt"
|
||||
import open from "open"
|
||||
import { openUrl } from "@opencode-ai/core/open"
|
||||
|
||||
const openBrowser = (url: string) => Effect.promise(() => open(url).catch(() => undefined))
|
||||
const openBrowser = (url: string) => Effect.promise(() => openUrl(url).catch(() => undefined))
|
||||
|
||||
const println = (msg: string) => Effect.sync(() => UI.println(msg))
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { UI } from "../ui"
|
||||
import { effectCmd } from "../effect-cmd"
|
||||
import { withNetworkOptions, resolveNetworkOptions } from "../network"
|
||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||
import open from "open"
|
||||
import { openUrl } from "@opencode-ai/core/open"
|
||||
import { networkInterfaces } from "os"
|
||||
|
||||
function getNetworkIPs() {
|
||||
@@ -72,11 +72,11 @@ export const WebCommand = effectCmd({
|
||||
}
|
||||
|
||||
// Open localhost in browser
|
||||
open(localhostUrl).catch(() => {})
|
||||
openUrl(localhostUrl).catch(() => {})
|
||||
} else {
|
||||
const displayUrl = server.url.toString()
|
||||
UI.println(UI.Style.TEXT_INFO_BOLD + " Web interface: ", UI.Style.TEXT_NORMAL, displayUrl)
|
||||
open(displayUrl).catch(() => {})
|
||||
openUrl(displayUrl).catch(() => {})
|
||||
}
|
||||
|
||||
yield* Effect.never
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { openUrl } from "@opencode-ai/core/open"
|
||||
import { Context, Effect, Layer } from "effect"
|
||||
import open from "open"
|
||||
|
||||
export interface Interface {
|
||||
readonly open: (url: string) => Effect.Effect<void, Error>
|
||||
@@ -13,7 +13,7 @@ const layer = Layer.succeed(
|
||||
Service.of({
|
||||
open: Effect.fn("McpBrowser.open")(function* (url: string) {
|
||||
const subprocess = yield* Effect.tryPromise({
|
||||
try: () => open(url),
|
||||
try: () => openUrl(url),
|
||||
catch: (error) => (error instanceof Error ? error : new Error(String(error))),
|
||||
})
|
||||
yield* Effect.callback<void, Error>((resume) => {
|
||||
@@ -22,11 +22,14 @@ const layer = Layer.succeed(
|
||||
clearTimeout(timer)
|
||||
resume(Effect.fail(error))
|
||||
})
|
||||
subprocess.on("exit", (code) => {
|
||||
const onExit = (code: number | null) => {
|
||||
if (code === null || code === 0) return
|
||||
clearTimeout(timer)
|
||||
resume(Effect.fail(new Error(`Browser open failed with exit code ${code}`)))
|
||||
})
|
||||
}
|
||||
subprocess.on("exit", onExit)
|
||||
// On Windows and WSL, open() can return only after the launcher has exited.
|
||||
onExit(subprocess.exitCode)
|
||||
})
|
||||
}),
|
||||
}),
|
||||
|
||||
@@ -125,6 +125,10 @@ export class McpOAuthProvider implements OAuthClientProvider {
|
||||
}
|
||||
|
||||
async redirectToAuthorization(authorizationUrl: URL): Promise<void> {
|
||||
if (authorizationUrl.protocol !== "http:" && authorizationUrl.protocol !== "https:")
|
||||
throw new Error(
|
||||
`MCP server "${this.serverUrl}" returned a ${authorizationUrl.protocol} authorization URL; only http and https are supported`,
|
||||
)
|
||||
await this.callbacks.onRedirect(authorizationUrl)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { Model } from "@opencode-ai/sdk/v2"
|
||||
import { InstallationVersion } from "@opencode-ai/core/installation/version"
|
||||
import { OauthCallbackPage } from "@opencode-ai/core/oauth/page"
|
||||
import { createServer } from "http"
|
||||
import open from "open"
|
||||
import { openUrl } from "@opencode-ai/core/open"
|
||||
|
||||
const DO_OAUTH_CLIENT_ID = "b1a6c5158156caac821fd1b30253ca8acb52454a48fa744420e41889cb589f82"
|
||||
const DO_AUTHORIZE_URL = "https://cloud.digitalocean.com/v1/oauth/authorize"
|
||||
@@ -279,7 +279,7 @@ export async function DigitalOceanAuthPlugin(input: PluginInput): Promise<Hooks>
|
||||
const state = generateState()
|
||||
const callbackPromise = waitForOAuthCallback(state)
|
||||
const url = buildAuthorizeUrl(state)
|
||||
await open(url).catch(() => undefined)
|
||||
await openUrl(url).catch(() => undefined)
|
||||
return {
|
||||
url,
|
||||
instructions:
|
||||
|
||||
@@ -79,17 +79,6 @@ type CopilotModel = Omit<Model, "api"> & {
|
||||
const decodeModels = Schema.decodeUnknownSync(schema)
|
||||
const decodeItem = Schema.decodeUnknownOption(item)
|
||||
|
||||
// Copilot serves these families only on /responses, so route them there when the
|
||||
// live model metadata does not advertise supported_endpoints. Older chat-only
|
||||
// families (gpt-5-mini, gpt-4*, Claude) stay on chat.
|
||||
export function fallbackEndpoint(modelID: string): "responses" | undefined {
|
||||
const match = /^gpt-(\d+)/.exec(modelID)
|
||||
if (match && Number(match[1]) >= 5 && !modelID.startsWith("gpt-5-mini")) return "responses"
|
||||
if (modelID.startsWith("grok-") || modelID.startsWith("gemini-") || modelID.startsWith("mai-code-"))
|
||||
return "responses"
|
||||
return undefined
|
||||
}
|
||||
|
||||
function build(key: string, remote: SelectableItem, url: string, prev?: Model): Model {
|
||||
const reasoning =
|
||||
!!remote.capabilities.supports.adaptive_thinking ||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { OAUTH_DUMMY_KEY } from "../auth"
|
||||
import { InstallationVersion } from "@opencode-ai/core/installation/version"
|
||||
import { OauthCallbackPage } from "@opencode-ai/core/oauth/page"
|
||||
import { createServer } from "http"
|
||||
import open from "open"
|
||||
import { openUrl } from "@opencode-ai/core/open"
|
||||
|
||||
const OAUTH_CLIENT_ID = "LOCAL_APPLICATION"
|
||||
const OAUTH_CALLBACK_HOST = "127.0.0.1"
|
||||
@@ -470,7 +470,7 @@ export async function SnowflakeCortexAuthPlugin(_input: PluginInput): Promise<Ho
|
||||
const role = (inputs.role || "").trim() || undefined
|
||||
const url = buildAuthorizeUrl(account, role, state, pkce)
|
||||
const callbackPromise = waitForOAuthCallback(account, pkce, state)
|
||||
await open(url).catch(() => undefined)
|
||||
await openUrl(url).catch(() => undefined)
|
||||
|
||||
return {
|
||||
url,
|
||||
|
||||
@@ -8,7 +8,6 @@ import { NoSuchModelError, type Provider as SDK } from "ai"
|
||||
import { Npm } from "@opencode-ai/core/npm"
|
||||
import { Hash } from "@opencode-ai/core/util/hash"
|
||||
import { Plugin } from "../plugin"
|
||||
import { CopilotModels } from "../plugin/github-copilot/models"
|
||||
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||
import { type LanguageModelV3 } from "@ai-sdk/provider"
|
||||
import { ModelsDev } from "@opencode-ai/core/models-dev"
|
||||
@@ -92,6 +91,41 @@ function timeoutController(ms: number) {
|
||||
}
|
||||
}
|
||||
|
||||
// Applies the `headerTimeout`, `chunkTimeout` (SSE idle) and `timeout` provider options at the
|
||||
// fetch layer, on top of `options.fetch` when a custom fetch is configured.
|
||||
function timeoutFetch(options: Record<string, any>) {
|
||||
const customFetch = options["fetch"]
|
||||
const chunkTimeout = options["chunkTimeout"] ?? 300_000
|
||||
const headerTimeout = options["headerTimeout"] ?? 300_000
|
||||
const timeout = options["timeout"]
|
||||
|
||||
return async (input: any, init?: BunFetchRequestInit) => {
|
||||
const fetchFn = customFetch ?? fetch
|
||||
const opts = init ?? {}
|
||||
const chunkAbortCtl = typeof chunkTimeout === "number" && chunkTimeout > 0 ? new AbortController() : undefined
|
||||
const headerTimeoutMs = headerTimeout === false ? undefined : headerTimeout
|
||||
const headerTimeoutCtl = typeof headerTimeoutMs === "number" ? timeoutController(headerTimeoutMs) : undefined
|
||||
const signals: AbortSignal[] = []
|
||||
|
||||
if (opts.signal) signals.push(opts.signal)
|
||||
if (chunkAbortCtl) signals.push(chunkAbortCtl.signal)
|
||||
if (headerTimeoutCtl) signals.push(headerTimeoutCtl.signal)
|
||||
if (timeout !== undefined && timeout !== null && timeout !== false) signals.push(AbortSignal.timeout(timeout))
|
||||
|
||||
const combined = signals.length === 0 ? null : signals.length === 1 ? signals[0] : AbortSignal.any(signals)
|
||||
if (combined) opts.signal = combined
|
||||
|
||||
const res = await fetchFn(input, {
|
||||
...opts,
|
||||
// @ts-ignore see here: https://github.com/oven-sh/bun/issues/16682
|
||||
timeout: false,
|
||||
}).finally(() => headerTimeoutCtl?.clear())
|
||||
|
||||
if (!chunkAbortCtl) return res
|
||||
return wrapSSE(res, chunkTimeout, chunkAbortCtl)
|
||||
}
|
||||
}
|
||||
|
||||
function googleVertexAnthropicBaseURL(project: string | undefined, location: string | undefined) {
|
||||
if (!project) return
|
||||
if (location !== "eu" && location !== "us") return
|
||||
@@ -238,7 +272,8 @@ function custom(dep: CustomDep): Record<string, CustomLoader> {
|
||||
if (model.api.endpoint === "responses" && sdk.responses) return sdk.responses(modelID)
|
||||
if (model.api.endpoint === "chat" && sdk.chat) return sdk.chat(modelID)
|
||||
}
|
||||
if (CopilotModels.fallbackEndpoint(modelID) === "responses") return sdk.responses(modelID)
|
||||
const match = /^gpt-(\d+)/.exec(modelID)
|
||||
if (match && Number(match[1]) >= 5 && !modelID.startsWith("gpt-5-mini")) return sdk.responses(modelID)
|
||||
return sdk.chat(modelID)
|
||||
},
|
||||
options: {},
|
||||
@@ -810,7 +845,7 @@ function custom(dep: CustomDep): Record<string, CustomLoader> {
|
||||
)
|
||||
}
|
||||
|
||||
const { createAiGateway } = yield* Effect.promise(() => import("ai-gateway-provider"))
|
||||
const { createAiGateway, parseAiGatewayOptions } = yield* Effect.promise(() => import("ai-gateway-provider"))
|
||||
const { createUnified } = yield* Effect.promise(() => import("ai-gateway-provider/providers/unified"))
|
||||
const { createOpenAI } = yield* Effect.promise(() => import("ai-gateway-provider/providers/openai"))
|
||||
const { createAnthropic } = yield* Effect.promise(() => import("ai-gateway-provider/providers/anthropic"))
|
||||
@@ -835,15 +870,30 @@ function custom(dep: CustomDep): Record<string, CustomLoader> {
|
||||
},
|
||||
}
|
||||
|
||||
const aigateway = createAiGateway({
|
||||
accountId,
|
||||
gateway,
|
||||
apiKey: apiToken,
|
||||
...(Object.values(opts).some((v) => v !== undefined) ? { options: opts } : {}),
|
||||
})
|
||||
return {
|
||||
autoload: true,
|
||||
async getModel(_sdk: any, modelID: string, _options?: Record<string, any>) {
|
||||
async getModel(_sdk: any, modelID: string, options?: Record<string, any>) {
|
||||
// This loader builds its own clients instead of using the SDK from resolveSDK, so the
|
||||
// timeout options have to be applied to the requests it makes explicitly.
|
||||
const gatewayFetch = timeoutFetch(options ?? {})
|
||||
// ai-gateway-provider's REST path always calls the global fetch. Its binding path hands
|
||||
// the request to `run`, so send the same request the REST path would (options as
|
||||
// request-level cf-aig-* headers) through the timeout-aware fetch instead.
|
||||
const aigateway = createAiGateway({
|
||||
binding: {
|
||||
run(body, init) {
|
||||
const headers = parseAiGatewayOptions(opts)
|
||||
headers.set("Content-Type", "application/json")
|
||||
headers.set("cf-aig-authorization", `Bearer ${apiToken}`)
|
||||
return gatewayFetch(`https://gateway.ai.cloudflare.com/v1/${accountId}/${gateway}`, {
|
||||
body: JSON.stringify(body),
|
||||
headers,
|
||||
method: "POST",
|
||||
signal: init?.signal,
|
||||
})
|
||||
},
|
||||
},
|
||||
})
|
||||
// Model IDs use Unified API format: provider/model (e.g., "anthropic/claude-sonnet-4-5").
|
||||
// OpenAI and Anthropic ride their native passthrough routes so agents get the Responses
|
||||
// and Messages APIs; new OpenAI models reject tools+reasoning_effort on chat completions.
|
||||
@@ -877,6 +927,7 @@ function custom(dep: CustomDep): Record<string, CustomLoader> {
|
||||
baseURL: `https://api.cloudflare.com/client/v4/accounts/${accountId}/ai/v1`,
|
||||
apiKey: apiToken,
|
||||
headers: { "cf-aig-gateway-id": gateway },
|
||||
fetch: gatewayFetch as typeof fetch,
|
||||
})(modelID)
|
||||
},
|
||||
options: {},
|
||||
@@ -1497,15 +1548,11 @@ const layer = Layer.effect(
|
||||
model.provider?.npm ??
|
||||
provider.npm ??
|
||||
existingModel?.api.npm ??
|
||||
// Provider-specific SDKs that must not inherit the models.dev catalog
|
||||
// default: GitHub Copilot needs its bundled SDK, and gateway models
|
||||
// bypass fromModelsDevModel so they resolve their native passthrough npm
|
||||
// before falling back to the catalog default.
|
||||
(providerID === ProviderV2.ID.githubCopilot
|
||||
? "@ai-sdk/github-copilot"
|
||||
: (cloudflareGatewayNpm(providerID, apiID) ??
|
||||
modelsDev[providerID]?.npm ??
|
||||
"@ai-sdk/openai-compatible"))
|
||||
// Config-defined gateway models bypass fromModelsDevModel, so resolve the
|
||||
// native passthrough npm here before falling back to the catalog default.
|
||||
cloudflareGatewayNpm(providerID, apiID) ??
|
||||
modelsDev[providerID]?.npm ??
|
||||
"@ai-sdk/openai-compatible"
|
||||
const name = iife(() => {
|
||||
if (model.name) return model.name
|
||||
if (model.id && model.id !== modelID) return modelID
|
||||
@@ -1799,39 +1846,10 @@ const layer = Layer.effect(
|
||||
const existing = s.sdk.get(key)
|
||||
if (existing) return existing
|
||||
|
||||
const customFetch = options["fetch"]
|
||||
const chunkTimeout = options["chunkTimeout"] ?? 300_000
|
||||
const headerTimeout = options["headerTimeout"] ?? 300_000
|
||||
options["fetch"] = timeoutFetch(options)
|
||||
delete options["chunkTimeout"]
|
||||
delete options["headerTimeout"]
|
||||
|
||||
options["fetch"] = async (input: any, init?: BunFetchRequestInit) => {
|
||||
const fetchFn = customFetch ?? fetch
|
||||
const opts = init ?? {}
|
||||
const chunkAbortCtl = typeof chunkTimeout === "number" && chunkTimeout > 0 ? new AbortController() : undefined
|
||||
const headerTimeoutMs = headerTimeout === false ? undefined : headerTimeout
|
||||
const headerTimeoutCtl = typeof headerTimeoutMs === "number" ? timeoutController(headerTimeoutMs) : undefined
|
||||
const signals: AbortSignal[] = []
|
||||
|
||||
if (opts.signal) signals.push(opts.signal)
|
||||
if (chunkAbortCtl) signals.push(chunkAbortCtl.signal)
|
||||
if (headerTimeoutCtl) signals.push(headerTimeoutCtl.signal)
|
||||
if (options["timeout"] !== undefined && options["timeout"] !== null && options["timeout"] !== false)
|
||||
signals.push(AbortSignal.timeout(options["timeout"]))
|
||||
|
||||
const combined = signals.length === 0 ? null : signals.length === 1 ? signals[0] : AbortSignal.any(signals)
|
||||
if (combined) opts.signal = combined
|
||||
|
||||
const res = await fetchFn(input, {
|
||||
...opts,
|
||||
// @ts-ignore see here: https://github.com/oven-sh/bun/issues/16682
|
||||
timeout: false,
|
||||
}).finally(() => headerTimeoutCtl?.clear())
|
||||
|
||||
if (!chunkAbortCtl) return res
|
||||
return wrapSSE(res, chunkTimeout, chunkAbortCtl)
|
||||
}
|
||||
|
||||
const bundledLoader = BUNDLED_PROVIDERS[model.api.npm]
|
||||
if (bundledLoader) {
|
||||
const factory = await bundledLoader()
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { expect, mock, test } from "bun:test"
|
||||
import { spawn } from "node:child_process"
|
||||
import { once } from "node:events"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { Effect } from "effect"
|
||||
|
||||
await mock.module("@opencode-ai/core/open", () => ({
|
||||
openUrl: async (url: string) => {
|
||||
const code = new URL(url).pathname === "/successful" ? 0 : 23
|
||||
const subprocess = spawn(process.execPath, ["-e", `process.exit(${code})`], { stdio: "ignore" })
|
||||
await once(subprocess, "close")
|
||||
return subprocess
|
||||
},
|
||||
}))
|
||||
|
||||
const { McpBrowser } = await import("../../src/mcp/browser")
|
||||
|
||||
test("reports a browser launcher that exited before openUrl returned", async () => {
|
||||
const error = await Effect.runPromise(
|
||||
Effect.gen(function* () {
|
||||
const browser = yield* McpBrowser.Service
|
||||
return yield* Effect.flip(browser.open("https://example.com/authorize"))
|
||||
}).pipe(Effect.provide(LayerNode.compile(McpBrowser.node))),
|
||||
)
|
||||
|
||||
expect(error.message).toBe("Browser open failed with exit code 23")
|
||||
})
|
||||
|
||||
test("accepts a browser launcher that exited successfully before openUrl returned", async () => {
|
||||
await Effect.runPromise(
|
||||
Effect.gen(function* () {
|
||||
const browser = yield* McpBrowser.Service
|
||||
yield* browser.open("https://example.com/successful")
|
||||
}).pipe(Effect.provide(LayerNode.compile(McpBrowser.node))),
|
||||
)
|
||||
})
|
||||
@@ -34,6 +34,37 @@ describe("McpOAuthProvider.redirectUrl", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("McpOAuthProvider.redirectToAuthorization", () => {
|
||||
test("forwards http and https authorization URLs", async () => {
|
||||
const seen: string[] = []
|
||||
const provider = new McpOAuthProvider(
|
||||
"test-server",
|
||||
"https://mcp.example.com/mcp",
|
||||
{},
|
||||
{ onRedirect: (url) => void seen.push(url.href) },
|
||||
stubAuth,
|
||||
)
|
||||
await provider.redirectToAuthorization(new URL("https://auth.example.com/authorize?state=1"))
|
||||
await provider.redirectToAuthorization(new URL("http://127.0.0.1:8080/authorize"))
|
||||
expect(seen).toEqual(["https://auth.example.com/authorize?state=1", "http://127.0.0.1:8080/authorize"])
|
||||
})
|
||||
|
||||
test("rejects authorization URLs that are not http or https", async () => {
|
||||
const seen: string[] = []
|
||||
const provider = new McpOAuthProvider(
|
||||
"test-server",
|
||||
"https://mcp.example.com/mcp",
|
||||
{},
|
||||
{ onRedirect: (url) => void seen.push(url.href) },
|
||||
stubAuth,
|
||||
)
|
||||
await expect(provider.redirectToAuthorization(new URL("file:///tmp/authorize"))).rejects.toThrow(
|
||||
"returned a file: authorization URL",
|
||||
)
|
||||
expect(seen).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe("McpOAuthProvider.clientMetadata", () => {
|
||||
test("includes redirect_uris from redirectUrl", () => {
|
||||
const provider = makeProvider({ callbackPort: 6620 })
|
||||
|
||||
@@ -490,14 +490,3 @@ test("remaps fallback oauth model urls to the enterprise host", async () => {
|
||||
expect(models.claude.api.url).toBe("https://copilot-api.ghe.example.com")
|
||||
expect(models.claude.api.npm).toBe("@ai-sdk/github-copilot")
|
||||
})
|
||||
|
||||
test("falls back to responses for Copilot families that require it", () => {
|
||||
expect(CopilotModels.fallbackEndpoint("gpt-5.6-sol")).toBe("responses")
|
||||
expect(CopilotModels.fallbackEndpoint("grok-4.5")).toBe("responses")
|
||||
expect(CopilotModels.fallbackEndpoint("grok-4.6")).toBe("responses")
|
||||
expect(CopilotModels.fallbackEndpoint("gemini-3.5-flash")).toBe("responses")
|
||||
expect(CopilotModels.fallbackEndpoint("mai-code-1.1-flash")).toBe("responses")
|
||||
expect(CopilotModels.fallbackEndpoint("gpt-5-mini")).toBeUndefined()
|
||||
expect(CopilotModels.fallbackEndpoint("gpt-4o")).toBeUndefined()
|
||||
expect(CopilotModels.fallbackEndpoint("claude-sonnet-4.6")).toBeUndefined()
|
||||
})
|
||||
|
||||
@@ -244,6 +244,112 @@ it.live("OpenAI API auth gets default headerTimeout", () =>
|
||||
}),
|
||||
)
|
||||
|
||||
const gatewayModels = {
|
||||
"native passthrough": "anthropic/claude-sonnet-4-6",
|
||||
"REST catalog": "google/gemini-2.5-flash",
|
||||
}
|
||||
|
||||
for (const [route, modelID] of Object.entries(gatewayModels)) {
|
||||
it.live(`cloudflare-ai-gateway ${route} applies chunkTimeout when the SSE body stalls`, () =>
|
||||
Effect.gen(function* () {
|
||||
yield* provideTmpdirInstance(
|
||||
() =>
|
||||
Effect.gen(function* () {
|
||||
const urls = yield* setupGateway(() =>
|
||||
Promise.resolve(new Response(new ReadableStream(), { headers: { "content-type": "text/event-stream" } })),
|
||||
)
|
||||
const provider = yield* Provider.Service
|
||||
const model = yield* provider.getModel(
|
||||
ProviderV2.ID.make("cloudflare-ai-gateway"),
|
||||
ModelV2.ID.make(modelID),
|
||||
)
|
||||
const result = streamText({
|
||||
model: yield* provider.getLanguage(model),
|
||||
onError() {},
|
||||
messages: [{ role: "user", content: "hello" }],
|
||||
})
|
||||
|
||||
const error = yield* Effect.promise(() => firstStreamError(result.fullStream))
|
||||
expect(urls).toHaveLength(1)
|
||||
expect(error).toBeInstanceOf(ProviderError.ResponseStreamError)
|
||||
}),
|
||||
{ config: gatewayConfig({ chunkTimeout: 50 }) },
|
||||
)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
it.live("cloudflare-ai-gateway applies headerTimeout when response headers do not arrive", () =>
|
||||
Effect.gen(function* () {
|
||||
yield* provideTmpdirInstance(
|
||||
() =>
|
||||
Effect.gen(function* () {
|
||||
const urls = yield* setupGateway(
|
||||
(init) =>
|
||||
new Promise((_, reject) => init?.signal?.addEventListener("abort", () => reject(init.signal?.reason))),
|
||||
)
|
||||
const provider = yield* Provider.Service
|
||||
const model = yield* provider.getModel(
|
||||
ProviderV2.ID.make("cloudflare-ai-gateway"),
|
||||
ModelV2.ID.make(gatewayModels["native passthrough"]),
|
||||
)
|
||||
const result = streamText({
|
||||
model: yield* provider.getLanguage(model),
|
||||
onError() {},
|
||||
messages: [{ role: "user", content: "hello" }],
|
||||
})
|
||||
|
||||
const error = yield* Effect.promise(() => firstStreamError(result.fullStream))
|
||||
expect(urls).toEqual(["https://gateway.ai.cloudflare.com/v1/test-account/test-gateway"])
|
||||
expect(String(error)).toContain("response headers timed out")
|
||||
}),
|
||||
{ config: gatewayConfig({ headerTimeout: 50 }) },
|
||||
)
|
||||
}),
|
||||
)
|
||||
|
||||
// Routes the gateway provider's requests to `respond` through the configured custom fetch, which
|
||||
// the timeout wrapper calls instead of the global fetch. Returns the requested URLs.
|
||||
function setupGateway(respond: (init?: RequestInit) => Promise<Response>) {
|
||||
return Effect.gen(function* () {
|
||||
yield* Env.use.set("CLOUDFLARE_ACCOUNT_ID", "test-account")
|
||||
yield* Env.use.set("CLOUDFLARE_GATEWAY_ID", "test-gateway")
|
||||
yield* Env.use.set("CLOUDFLARE_API_TOKEN", "test-token")
|
||||
const provider = yield* Provider.Service
|
||||
const configured = yield* provider.getProvider(ProviderV2.ID.make("cloudflare-ai-gateway"))
|
||||
const urls: string[] = []
|
||||
configured.options.fetch = (input: string, init?: RequestInit) => {
|
||||
urls.push(input)
|
||||
return respond(init)
|
||||
}
|
||||
return urls
|
||||
})
|
||||
}
|
||||
|
||||
function gatewayConfig(options: Record<string, unknown>) {
|
||||
return {
|
||||
provider: {
|
||||
"cloudflare-ai-gateway": {
|
||||
options,
|
||||
models: {
|
||||
// The gateway loader builds its own client; a bundled npm keeps resolveSDK off the network.
|
||||
[gatewayModels["REST catalog"]]: { name: "Gemini 2.5 Flash", provider: { npm: "@ai-sdk/openai-compatible" } },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async function firstStreamError(stream: AsyncIterable<{ type: string; error?: unknown }>) {
|
||||
try {
|
||||
for await (const part of stream) {
|
||||
if (part.type === "error") return part.error
|
||||
}
|
||||
} catch (error) {
|
||||
return error
|
||||
}
|
||||
}
|
||||
|
||||
function providerConfig(url: string, options: Record<string, unknown> = {}) {
|
||||
const config = testProviderConfig(url)
|
||||
return {
|
||||
|
||||
@@ -1348,31 +1348,6 @@ it.instance(
|
||||
},
|
||||
)
|
||||
|
||||
it.instance(
|
||||
"custom github-copilot models use the bundled Copilot SDK",
|
||||
Effect.gen(function* () {
|
||||
yield* set("GITHUB_TOKEN", "test-token")
|
||||
const providers = yield* list
|
||||
const model = providers[ProviderV2.ID.githubCopilot].models["copilot-grok-4.6"]
|
||||
expect(model).toBeDefined()
|
||||
expect(model.api.npm).toBe("@ai-sdk/github-copilot")
|
||||
}),
|
||||
{
|
||||
config: {
|
||||
provider: {
|
||||
"github-copilot": {
|
||||
models: {
|
||||
"copilot-grok-4.6": {
|
||||
name: "Copilot Grok 4.6",
|
||||
limit: { context: 128000, output: 64000 },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
it.instance(
|
||||
"custom model inherits api.url from models.dev provider",
|
||||
Effect.gen(function* () {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "1.18.32",
|
||||
"version": "1.18.33",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "1.18.32",
|
||||
"version": "1.18.33",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/server",
|
||||
"version": "1.18.32",
|
||||
"version": "1.18.33",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/session-ui",
|
||||
"version": "1.18.32",
|
||||
"version": "1.18.33",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/slack",
|
||||
"version": "1.18.32",
|
||||
"version": "1.18.33",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/stats-app",
|
||||
"version": "1.18.32",
|
||||
"version": "1.18.33",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -80,6 +80,7 @@ const en = {
|
||||
"range.2W": "2 Weeks",
|
||||
"range.1M": "1 Month",
|
||||
"range.2M": "2 Months",
|
||||
"chart.weekly": "Weekly",
|
||||
"chart.daily": "Daily",
|
||||
"chart.input": "Input",
|
||||
"chart.output": "Output",
|
||||
|
||||
@@ -61,6 +61,7 @@ export const dict = {
|
||||
"range.2W": "أسبوعان",
|
||||
"range.1M": "شهر واحد",
|
||||
"range.2M": "شهران",
|
||||
"chart.weekly": "أسبوعي",
|
||||
"chart.daily": "يومي",
|
||||
"chart.input": "الإدخال",
|
||||
"chart.output": "الإخراج",
|
||||
|
||||
@@ -61,6 +61,7 @@ export const dict = {
|
||||
"range.2W": "2 semanas",
|
||||
"range.1M": "1 mês",
|
||||
"range.2M": "2 meses",
|
||||
"chart.weekly": "Semanal",
|
||||
"chart.daily": "Diário",
|
||||
"chart.input": "Entrada",
|
||||
"chart.output": "Saída",
|
||||
|
||||
@@ -61,6 +61,7 @@ export const dict = {
|
||||
"range.2W": "2 uger",
|
||||
"range.1M": "1 måned",
|
||||
"range.2M": "2 måneder",
|
||||
"chart.weekly": "Ugentligt",
|
||||
"chart.daily": "Dagligt",
|
||||
"chart.input": "Input",
|
||||
"chart.output": "Output",
|
||||
|
||||
@@ -61,6 +61,7 @@ export const dict = {
|
||||
"range.2W": "2 Wochen",
|
||||
"range.1M": "1 Monat",
|
||||
"range.2M": "2 Monate",
|
||||
"chart.weekly": "Wöchentlich",
|
||||
"chart.daily": "Täglich",
|
||||
"chart.input": "Eingabe",
|
||||
"chart.output": "Ausgabe",
|
||||
|
||||
@@ -61,6 +61,7 @@ export const dict = {
|
||||
"range.2W": "2 semanas",
|
||||
"range.1M": "1 mes",
|
||||
"range.2M": "2 meses",
|
||||
"chart.weekly": "Semanal",
|
||||
"chart.daily": "Diario",
|
||||
"chart.input": "Entrada",
|
||||
"chart.output": "Salida",
|
||||
|
||||
@@ -61,6 +61,7 @@ export const dict = {
|
||||
"range.2W": "2 semaines",
|
||||
"range.1M": "1 mois",
|
||||
"range.2M": "2 mois",
|
||||
"chart.weekly": "Hebdomadaire",
|
||||
"chart.daily": "Quotidien",
|
||||
"chart.input": "Entrée",
|
||||
"chart.output": "Sortie",
|
||||
|
||||
@@ -61,6 +61,7 @@ export const dict = {
|
||||
"range.2W": "2 settimane",
|
||||
"range.1M": "1 mese",
|
||||
"range.2M": "2 mesi",
|
||||
"chart.weekly": "Settimanale",
|
||||
"chart.daily": "Giornaliero",
|
||||
"chart.input": "Input",
|
||||
"chart.output": "Output",
|
||||
|
||||
@@ -63,6 +63,7 @@ export const dict = {
|
||||
"range.2W": "2週間",
|
||||
"range.1M": "1か月",
|
||||
"range.2M": "2か月",
|
||||
"chart.weekly": "週次",
|
||||
"chart.daily": "日次",
|
||||
"chart.input": "入力",
|
||||
"chart.output": "出力",
|
||||
|
||||
@@ -63,6 +63,7 @@ export const dict = {
|
||||
"range.2W": "2주",
|
||||
"range.1M": "1개월",
|
||||
"range.2M": "2개월",
|
||||
"chart.weekly": "주간",
|
||||
"chart.daily": "일간",
|
||||
"chart.input": "입력",
|
||||
"chart.output": "출력",
|
||||
|
||||
@@ -61,6 +61,7 @@ export const dict = {
|
||||
"range.2W": "2 uker",
|
||||
"range.1M": "1 måned",
|
||||
"range.2M": "2 måneder",
|
||||
"chart.weekly": "Ukentlig",
|
||||
"chart.daily": "Daglig",
|
||||
"chart.input": "Inndata",
|
||||
"chart.output": "Utdata",
|
||||
|
||||
@@ -61,6 +61,7 @@ export const dict = {
|
||||
"range.2W": "2 tygodnie",
|
||||
"range.1M": "1 miesiąc",
|
||||
"range.2M": "2 miesiące",
|
||||
"chart.weekly": "Tygodniowo",
|
||||
"chart.daily": "Dziennie",
|
||||
"chart.input": "Wejście",
|
||||
"chart.output": "Wyjście",
|
||||
|
||||
@@ -61,6 +61,7 @@ export const dict = {
|
||||
"range.2W": "2 недели",
|
||||
"range.1M": "1 месяц",
|
||||
"range.2M": "2 месяца",
|
||||
"chart.weekly": "Еженедельно",
|
||||
"chart.daily": "Ежедневно",
|
||||
"chart.input": "Ввод",
|
||||
"chart.output": "Вывод",
|
||||
|
||||
@@ -63,6 +63,7 @@ export const dict = {
|
||||
"range.2W": "2 สัปดาห์",
|
||||
"range.1M": "1 เดือน",
|
||||
"range.2M": "2 เดือน",
|
||||
"chart.weekly": "รายสัปดาห์",
|
||||
"chart.daily": "รายวัน",
|
||||
"chart.input": "อินพุต",
|
||||
"chart.output": "เอาต์พุต",
|
||||
|
||||
@@ -61,6 +61,7 @@ export const dict = {
|
||||
"range.2W": "2 Hafta",
|
||||
"range.1M": "1 Ay",
|
||||
"range.2M": "2 Ay",
|
||||
"chart.weekly": "Haftalık",
|
||||
"chart.daily": "Günlük",
|
||||
"chart.input": "Girdi",
|
||||
"chart.output": "Çıktı",
|
||||
|
||||
@@ -61,6 +61,7 @@ export const dict = {
|
||||
"range.2W": "2 тижні",
|
||||
"range.1M": "1 місяць",
|
||||
"range.2M": "2 місяці",
|
||||
"chart.weekly": "Щотижня",
|
||||
"chart.daily": "Щоденно",
|
||||
"chart.input": "Вхід",
|
||||
"chart.output": "Вихід",
|
||||
|
||||
@@ -63,6 +63,7 @@ export const dict = {
|
||||
"range.2W": "2 周",
|
||||
"range.1M": "1 个月",
|
||||
"range.2M": "2 个月",
|
||||
"chart.weekly": "每周",
|
||||
"chart.daily": "每日",
|
||||
"chart.input": "输入",
|
||||
"chart.output": "输出",
|
||||
|
||||
@@ -63,6 +63,7 @@ export const dict = {
|
||||
"range.2W": "2 週",
|
||||
"range.1M": "1 個月",
|
||||
"range.2M": "2 個月",
|
||||
"chart.weekly": "每週",
|
||||
"chart.daily": "每日",
|
||||
"chart.input": "輸入",
|
||||
"chart.output": "輸出",
|
||||
|
||||
@@ -22,6 +22,12 @@ type RadarAxis = {
|
||||
label: string
|
||||
description: string
|
||||
score: (model: ModelCatalogEntry) => number | undefined
|
||||
capability?: "reasoning" | "toolCall"
|
||||
}
|
||||
|
||||
type RadarScore = {
|
||||
value: number
|
||||
fallback?: string
|
||||
}
|
||||
|
||||
type RadarPoint = {
|
||||
@@ -37,7 +43,7 @@ export function ComparisonRadar(props: ComparisonRadarProps) {
|
||||
name: model.name,
|
||||
labName: model.labName,
|
||||
color: radarColors[index % radarColors.length],
|
||||
scores: axes().map((axis) => (model.catalog ? axis.score(model.catalog) : undefined)),
|
||||
scores: axes().map((axis) => resolveRadarScore(axis, model.catalog)),
|
||||
})),
|
||||
)
|
||||
const accessibleDescription = createMemo(() =>
|
||||
@@ -62,6 +68,9 @@ export function ComparisonRadar(props: ComparisonRadarProps) {
|
||||
<span>
|
||||
<strong>{model.name}</strong>
|
||||
<Show when={model.labName}>{(name) => <small>{name()}</small>}</Show>
|
||||
<Show when={model.scores.some((score) => score.fallback)}>
|
||||
<small data-slot="compare-radar-coverage">Hollow points use fallbacks · hover for details</small>
|
||||
</Show>
|
||||
</span>
|
||||
</li>
|
||||
)}
|
||||
@@ -89,10 +98,16 @@ export function ComparisonRadar(props: ComparisonRadarProps) {
|
||||
<polygon data-slot="compare-radar-area" points={radarSeriesPolygon(model.scores)} />
|
||||
<For each={model.scores}>
|
||||
{(score, index) => {
|
||||
const point = () => radarPoint(index(), axes().length, score ?? 0)
|
||||
const point = () => radarPoint(index(), axes().length, score.value)
|
||||
return (
|
||||
<>
|
||||
<circle data-slot="compare-radar-point" cx={point().x} cy={point().y} r="0.95" />
|
||||
<circle
|
||||
data-slot="compare-radar-point"
|
||||
data-fallback={score.fallback ? "true" : undefined}
|
||||
cx={point().x}
|
||||
cy={point().y}
|
||||
r="0.95"
|
||||
/>
|
||||
<circle
|
||||
data-slot="compare-radar-point-hit"
|
||||
cx={point().x}
|
||||
@@ -138,6 +153,13 @@ export function ComparisonRadar(props: ComparisonRadarProps) {
|
||||
>
|
||||
<strong>{axes()[activeAxis() ?? 0]?.label}</strong>
|
||||
<p>{axes()[activeAxis() ?? 0]?.description}</p>
|
||||
<For each={series()}>
|
||||
{(model) => (
|
||||
<p>
|
||||
{model.name}: {formatRadarScore(model.scores[activeAxis() ?? 0])}
|
||||
</p>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
@@ -166,7 +188,7 @@ export function ComparisonRadar(props: ComparisonRadarProps) {
|
||||
)
|
||||
}
|
||||
|
||||
function buildRadarAxes(catalogModels: readonly ModelCatalogEntry[]): RadarAxis[] {
|
||||
export function buildRadarAxes(catalogModels: readonly ModelCatalogEntry[]): RadarAxis[] {
|
||||
const benchmarks = benchmarkScoreGroups(catalogModels)
|
||||
const toolUseBenchmarks = benchmarkScoreGroups(catalogModels, true)
|
||||
const costs = catalogModels.flatMap((model) => {
|
||||
@@ -180,9 +202,10 @@ function buildRadarAxes(catalogModels: readonly ModelCatalogEntry[]): RadarAxis[
|
||||
return [
|
||||
{
|
||||
label: "Reasoning",
|
||||
description: "Ability to solve complex, multi-step problems. Based on reasoning benchmarks when available.",
|
||||
score: (model) =>
|
||||
benchmarkPercentile(model, benchmarks, reasoningBenchmarkPattern) ?? (model.reasoning ? 100 : 0),
|
||||
capability: "reasoning",
|
||||
description:
|
||||
"Ability to solve complex, multi-step problems. Benchmarks take priority; reasoning support defaults to 50/100.",
|
||||
score: (model) => benchmarkPercentile(model, benchmarks, reasoningBenchmarkPattern),
|
||||
},
|
||||
{
|
||||
label: "Coding",
|
||||
@@ -218,7 +241,8 @@ function buildRadarAxes(catalogModels: readonly ModelCatalogEntry[]): RadarAxis[
|
||||
},
|
||||
{
|
||||
label: "Tool use",
|
||||
description: "Performance on agent benchmarks including Terminal-Bench, Tau3, and Claw-Eval.",
|
||||
capability: "toolCall",
|
||||
description: "Agent benchmark performance. Benchmarks take priority; tool calling support defaults to 50/100.",
|
||||
score: (model) =>
|
||||
benchmarkPercentile(model, toolUseBenchmarks, toolUseBenchmarkPattern, {
|
||||
aggregate: "average",
|
||||
@@ -324,9 +348,9 @@ function radarPolygonPoints(count: number, score: number) {
|
||||
.join(" ")
|
||||
}
|
||||
|
||||
function radarSeriesPolygon(scores: (number | undefined)[]) {
|
||||
function radarSeriesPolygon(scores: RadarScore[]) {
|
||||
return scores
|
||||
.map((score, index) => radarPoint(index, scores.length, score ?? 0))
|
||||
.map((score, index) => radarPoint(index, scores.length, score.value))
|
||||
.map((point) => `${point.x},${point.y}`)
|
||||
.join(" ")
|
||||
}
|
||||
@@ -355,6 +379,17 @@ function roundRadarCoordinate(value: number) {
|
||||
return Math.round(value * 1000) / 1000
|
||||
}
|
||||
|
||||
function formatRadarScore(score: number | undefined) {
|
||||
return score === undefined ? "No data" : `${Math.round(score)}/100`
|
||||
function formatRadarScore(score: RadarScore) {
|
||||
return `${Math.round(score.value)}/100${score.fallback ? ` — ${score.fallback}` : ""}`
|
||||
}
|
||||
|
||||
export function resolveRadarScore(axis: RadarAxis, model: ModelCatalogEntry | null): RadarScore {
|
||||
const score = model ? axis.score(model) : undefined
|
||||
if (score !== undefined) return { value: score }
|
||||
const supported = axis.capability ? model?.[axis.capability] : undefined
|
||||
if (supported === undefined) return { value: 50, fallback: "No data; neutral placeholder" }
|
||||
const capability = axis.capability === "toolCall" ? "Tool calling" : "Reasoning"
|
||||
return supported
|
||||
? { value: 50, fallback: `${capability} supported; no comparable benchmark` }
|
||||
: { value: 0, fallback: `${capability} not supported` }
|
||||
}
|
||||
|
||||
@@ -1545,6 +1545,25 @@ body {
|
||||
background: var(--stats-layer-2);
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="leaderboard-period"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
margin-top: 24px;
|
||||
color: var(--stats-muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="leaderboard-period"] [data-component="usage-filter"] button {
|
||||
width: auto;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="leaderboard-period"] + [data-component="leaderboard"] {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-component="chart-tooltip"] {
|
||||
position: absolute;
|
||||
right: auto;
|
||||
@@ -6291,8 +6310,7 @@ body {
|
||||
vector-effect: non-scaling-stroke;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="compare-radar-area"],
|
||||
[data-page="stats"] [data-slot="compare-radar-line"] {
|
||||
[data-page="stats"] [data-slot="compare-radar-area"] {
|
||||
stroke: currentColor;
|
||||
stroke-width: 1.5px;
|
||||
stroke-linejoin: round;
|
||||
@@ -6304,10 +6322,6 @@ body {
|
||||
fill-opacity: 0.09;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="compare-radar-line"] {
|
||||
fill: none;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="compare-radar-point"] {
|
||||
fill: currentColor;
|
||||
stroke: currentColor;
|
||||
@@ -6315,6 +6329,10 @@ body {
|
||||
vector-effect: non-scaling-stroke;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="compare-radar-point"][data-fallback="true"] {
|
||||
fill: var(--stats-bg);
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="compare-radar-point-hit"] {
|
||||
fill: transparent;
|
||||
cursor: pointer;
|
||||
|
||||
@@ -65,7 +65,7 @@ type StatsHomePageData = {
|
||||
updatedAt: string | null
|
||||
usage: UsagePoint[]
|
||||
users: UsagePoint[]
|
||||
leaderboard: LeaderboardEntry[]
|
||||
leaderboard: { daily: LeaderboardEntry[]; weekly: LeaderboardEntry[] }
|
||||
market: MarketDay[]
|
||||
tokenCost: TokenCostEntry[]
|
||||
cacheRatio: CacheRatioEntry[]
|
||||
@@ -86,7 +86,7 @@ const getData = query(async () => {
|
||||
updatedAt: stats.updatedAt,
|
||||
usage: stats.usage.Go["2M"],
|
||||
users: stats.users.Go["2M"],
|
||||
leaderboard: stats.leaderboard.Go["2M"],
|
||||
leaderboard: { daily: stats.leaderboard.Go["1D"], weekly: stats.leaderboard.Go["1W"] },
|
||||
market: stats.market["2M"],
|
||||
tokenCost: priceTokenCostFromCatalog(stats.tokenCost.Go, catalog),
|
||||
cacheRatio: stats.cacheRatio.Go,
|
||||
@@ -162,7 +162,7 @@ export default function StatsHome() {
|
||||
<MarketShareSection data={stats().market} />
|
||||
<GeoBreakdownSection data={stats().country} />
|
||||
<ComparisonCardsSection
|
||||
pairs={homeComparisonPairs(stats().leaderboard, stats().catalogLabs)}
|
||||
pairs={homeComparisonPairs(stats().leaderboard.weekly, stats().catalogLabs)}
|
||||
title="Model Comparisons"
|
||||
description="Popular model pairs from the leaderboard."
|
||||
variant="featured"
|
||||
@@ -376,11 +376,13 @@ function formatUpdatedAtLabel(value: { date: string; time: string }) {
|
||||
|
||||
function TopModelsSection(props: {
|
||||
data: UsagePoint[]
|
||||
leaderboard: LeaderboardEntry[]
|
||||
leaderboard: StatsHomePageData["leaderboard"]
|
||||
catalogLabs: readonly string[]
|
||||
}) {
|
||||
const i18n = useI18n()
|
||||
const [activeModel, setActiveModel] = createSignal<string>()
|
||||
const [period, setPeriod] = createSignal<"weekly" | "daily">("weekly")
|
||||
const leaderboard = createMemo(() => props.leaderboard[period()])
|
||||
|
||||
return (
|
||||
<section id="top-models" data-section="top-models">
|
||||
@@ -397,14 +399,41 @@ function TopModelsSection(props: {
|
||||
>
|
||||
<TopModelsChart data={props.data} range="2M" activeModel={activeModel()} onActiveModelChange={setActiveModel} />
|
||||
</Show>
|
||||
<div data-slot="leaderboard-period" role="group" aria-label={i18n.t("nav.leaderboard")}>
|
||||
<div data-component="usage-filter" data-variant="range">
|
||||
<button
|
||||
type="button"
|
||||
data-active={period() === "weekly" ? "true" : undefined}
|
||||
aria-pressed={period() === "weekly"}
|
||||
onClick={() => {
|
||||
setPeriod("weekly")
|
||||
setActiveModel(undefined)
|
||||
}}
|
||||
>
|
||||
{i18n.t("chart.weekly")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-active={period() === "daily" ? "true" : undefined}
|
||||
aria-pressed={period() === "daily"}
|
||||
onClick={() => {
|
||||
setPeriod("daily")
|
||||
setActiveModel(undefined)
|
||||
}}
|
||||
>
|
||||
{i18n.t("chart.daily")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<Show
|
||||
when={props.leaderboard.length > 0}
|
||||
when={leaderboard().length > 0}
|
||||
fallback={
|
||||
<EmptyState title={i18n.t("home.noLeaderboardTitle")} description={i18n.t("home.noLeaderboardDescription")} />
|
||||
}
|
||||
>
|
||||
<Leaderboard
|
||||
data={props.leaderboard}
|
||||
data={leaderboard()}
|
||||
showChange={period() === "weekly"}
|
||||
activeModel={activeModel()}
|
||||
onActiveModelChange={setActiveModel}
|
||||
catalogLabs={props.catalogLabs}
|
||||
@@ -819,6 +848,7 @@ function formatUsers(value: number) {
|
||||
|
||||
function Leaderboard(props: {
|
||||
data: LeaderboardEntry[]
|
||||
showChange: boolean
|
||||
activeModel: string | undefined
|
||||
onActiveModelChange: (model: string | undefined) => void
|
||||
catalogLabs: readonly string[]
|
||||
@@ -833,6 +863,7 @@ function Leaderboard(props: {
|
||||
{(entry) => (
|
||||
<LeaderboardCard
|
||||
entry={entry}
|
||||
showChange={props.showChange}
|
||||
size="featured"
|
||||
active={props.activeModel === entry.model}
|
||||
onActiveModelChange={props.onActiveModelChange}
|
||||
@@ -847,6 +878,7 @@ function Leaderboard(props: {
|
||||
{(entry) => (
|
||||
<LeaderboardCard
|
||||
entry={entry}
|
||||
showChange={props.showChange}
|
||||
size="compact"
|
||||
active={props.activeModel === entry.model}
|
||||
onActiveModelChange={props.onActiveModelChange}
|
||||
@@ -860,6 +892,7 @@ function Leaderboard(props: {
|
||||
{(entry) => (
|
||||
<LeaderboardCard
|
||||
entry={entry}
|
||||
showChange={props.showChange}
|
||||
size="featured"
|
||||
active={props.activeModel === entry.model}
|
||||
onActiveModelChange={props.onActiveModelChange}
|
||||
@@ -874,6 +907,7 @@ function Leaderboard(props: {
|
||||
|
||||
function LeaderboardCard(props: {
|
||||
entry: LeaderboardEntry
|
||||
showChange: boolean
|
||||
size: "featured" | "compact"
|
||||
active: boolean
|
||||
onActiveModelChange: (model: string | undefined) => void
|
||||
@@ -882,6 +916,7 @@ function LeaderboardCard(props: {
|
||||
const i18n = useI18n()
|
||||
const language = useLanguage()
|
||||
const hasProvider = () => isKnownCatalogLab(props.entry.provider, props.catalogLabs)
|
||||
const icon = () => (hasProvider() ? getProviderIconId(props.entry.author) : "synthetic")
|
||||
return (
|
||||
<a
|
||||
data-component="leader-card"
|
||||
@@ -900,13 +935,9 @@ function LeaderboardCard(props: {
|
||||
onClick={() => props.onActiveModelChange(props.entry.model)}
|
||||
>
|
||||
<span data-slot="rank">{String(props.entry.rank).padStart(2, "0")}</span>
|
||||
<Show when={hasProvider()}>
|
||||
<ProviderIcon data-slot="leader-watermark" aria-hidden="true" id={getProviderIconId(props.entry.author)} />
|
||||
</Show>
|
||||
<ProviderIcon data-slot="leader-watermark" aria-hidden="true" id={icon()} />
|
||||
<div data-slot="leader-body">
|
||||
<Show when={hasProvider()}>
|
||||
<ProviderIcon data-slot="leader-avatar" aria-hidden="true" id={getProviderIconId(props.entry.author)} />
|
||||
</Show>
|
||||
<ProviderIcon data-slot="leader-avatar" aria-hidden="true" id={icon()} />
|
||||
<div data-slot="leader-copy">
|
||||
<div>
|
||||
<strong>{props.entry.model}</strong>
|
||||
@@ -916,13 +947,15 @@ function LeaderboardCard(props: {
|
||||
<Show when={hasProvider()} fallback={<span />}>
|
||||
<span>{props.entry.author}</span>
|
||||
</Show>
|
||||
<span
|
||||
data-slot="delta"
|
||||
data-new={props.entry.change === null ? "true" : undefined}
|
||||
data-negative={props.entry.change !== null && props.entry.change < 0 ? "true" : undefined}
|
||||
>
|
||||
{formatChange(props.entry.change, i18n)}
|
||||
</span>
|
||||
<Show when={props.showChange || props.entry.change === null}>
|
||||
<span
|
||||
data-slot="delta"
|
||||
data-new={props.entry.change === null ? "true" : undefined}
|
||||
data-negative={props.entry.change !== null && props.entry.change < 0 ? "true" : undefined}
|
||||
>
|
||||
{formatChange(props.entry.change, i18n)}
|
||||
</span>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -25,8 +25,8 @@ export type ModelCatalogEntry = {
|
||||
limit?: { context?: number; output?: number }
|
||||
modalities: { input: string[]; output: string[] }
|
||||
openWeights: boolean
|
||||
reasoning: boolean
|
||||
toolCall: boolean
|
||||
reasoning?: boolean
|
||||
toolCall?: boolean
|
||||
attachment: boolean
|
||||
temperature: boolean
|
||||
cost?: ModelCatalogCost
|
||||
@@ -54,6 +54,7 @@ export type ModelCatalogLab = {
|
||||
|
||||
export type ModelCatalog = {
|
||||
models: ModelCatalogEntry[]
|
||||
aliases?: ModelCatalogEntry[]
|
||||
labs: ModelCatalogLab[]
|
||||
}
|
||||
|
||||
@@ -80,7 +81,8 @@ export function findModelCatalogEntry(catalog: ModelCatalog, model: string, lab?
|
||||
return (
|
||||
catalog.models.find((entry) => entry.id.toLowerCase() === normalizedId) ??
|
||||
catalog.models.find((entry) => (lab ? entry.lab === catalogLabSlug(lab) : true) && entry.slug === leaf) ??
|
||||
catalog.models.find((entry) => entry.slug === leaf)
|
||||
catalog.models.find((entry) => entry.slug === leaf) ??
|
||||
catalog.aliases?.find((entry) => (lab ? entry.lab === catalogLabSlug(lab) : true) && entry.slug === leaf)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -133,7 +135,7 @@ export function catalogSlug(value: string) {
|
||||
.replace(/-{2,}/g, "-")
|
||||
}
|
||||
|
||||
function buildModelCatalog(payload: unknown, pricingPayload?: unknown, labPayload?: unknown): ModelCatalog {
|
||||
export function buildModelCatalog(payload: unknown, pricingPayload?: unknown, labPayload?: unknown): ModelCatalog {
|
||||
const costs = readCatalogCosts(pricingPayload)
|
||||
const labDescriptions = readCatalogLabDescriptions(payload, pricingPayload, labPayload)
|
||||
const models = readCatalogModels(payload)
|
||||
@@ -149,6 +151,25 @@ function buildModelCatalog(payload: unknown, pricingPayload?: unknown, labPayloa
|
||||
.toSorted((a, b) => a.lab.localeCompare(b.lab) || displayDateTime(b.releaseDate) - displayDateTime(a.releaseDate))
|
||||
return {
|
||||
models,
|
||||
// Contributor is a serving tier of these Muse models, with its own pricing.
|
||||
// Keep aliases out of the model population used to normalize benchmark scores.
|
||||
aliases: ["meta/muse-spark-1.2", "meta/muse-spark-1.3"].flatMap((id) => {
|
||||
const model = models.find((entry) => entry.id === id)
|
||||
if (!model) return []
|
||||
const alias = `${id}-contributor`
|
||||
return [
|
||||
{
|
||||
...model,
|
||||
id: alias,
|
||||
slug: `${model.slug}-contributor`,
|
||||
name: `${model.name} Contributor`,
|
||||
cost:
|
||||
costs.get(catalogIdKey(alias)) ??
|
||||
costs.get(`${model.lab}/${model.slug}-contributor`) ??
|
||||
costs.get(`${model.slug}-contributor`),
|
||||
},
|
||||
]
|
||||
}),
|
||||
labs: Object.values(
|
||||
models.reduce<Record<string, ModelCatalogLab>>((result, model) => {
|
||||
result[model.lab] = {
|
||||
@@ -184,8 +205,8 @@ function readModelCatalogEntry(value: unknown): ModelCatalogEntry[] {
|
||||
limit: readCatalogLimit(value.limit),
|
||||
modalities: readCatalogModalities(value.modalities),
|
||||
openWeights: booleanValue(value.open_weights),
|
||||
reasoning: booleanValue(value.reasoning),
|
||||
toolCall: booleanValue(value.tool_call),
|
||||
reasoning: typeof value.reasoning === "boolean" ? value.reasoning : undefined,
|
||||
toolCall: typeof value.tool_call === "boolean" ? value.tool_call : undefined,
|
||||
attachment: booleanValue(value.attachment),
|
||||
temperature: booleanValue(value.temperature),
|
||||
cost: readCatalogCost(value.cost),
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { buildRadarAxes, resolveRadarScore } from "../src/routes/compare-radar"
|
||||
import type { ModelCatalogEntry } from "../src/routes/model-catalog"
|
||||
|
||||
const model: ModelCatalogEntry = {
|
||||
id: "meta/muse-spark-1.3",
|
||||
lab: "meta",
|
||||
slug: "muse-spark-1-3",
|
||||
name: "Muse Spark 1.3",
|
||||
modalities: { input: ["text", "image"], output: ["text"] },
|
||||
reasoning: true,
|
||||
toolCall: true,
|
||||
openWeights: false,
|
||||
attachment: true,
|
||||
temperature: true,
|
||||
weights: [],
|
||||
benchmarks: [],
|
||||
}
|
||||
|
||||
function scores(entry: ModelCatalogEntry | null, catalog = [model]) {
|
||||
return Object.fromEntries(buildRadarAxes(catalog).map((axis) => [axis.label, resolveRadarScore(axis, entry)]))
|
||||
}
|
||||
|
||||
describe("radar capability fallbacks", () => {
|
||||
test("supported capabilities have visible baselines without benchmarks", () => {
|
||||
const result = scores(model)
|
||||
expect(result["Tool use"]).toEqual({ value: 50, fallback: "Tool calling supported; no comparable benchmark" })
|
||||
expect(result.Reasoning).toEqual({ value: 50, fallback: "Reasoning supported; no comparable benchmark" })
|
||||
expect(result.Coding).toEqual({ value: 50, fallback: "No data; neutral placeholder" })
|
||||
})
|
||||
|
||||
test("explicitly unsupported capabilities remain zero", () => {
|
||||
const result = scores({ ...model, reasoning: false, toolCall: false })
|
||||
expect(result["Tool use"]).toEqual({ value: 0, fallback: "Tool calling not supported" })
|
||||
expect(result.Reasoning).toEqual({ value: 0, fallback: "Reasoning not supported" })
|
||||
})
|
||||
|
||||
test("unknown capabilities and unmatched models use neutral placeholders", () => {
|
||||
const result = scores({ ...model, reasoning: undefined, toolCall: undefined })
|
||||
expect(result["Tool use"]).toEqual({ value: 50, fallback: "No data; neutral placeholder" })
|
||||
expect(result.Reasoning).toEqual({ value: 50, fallback: "No data; neutral placeholder" })
|
||||
expect(Object.values(scores(null))).toEqual(Array(6).fill({ value: 50, fallback: "No data; neutral placeholder" }))
|
||||
})
|
||||
|
||||
test("measured benchmark percentiles override fallbacks, including zero", () => {
|
||||
const low = {
|
||||
...model,
|
||||
benchmarks: [
|
||||
{ name: "Tau3", score: 20 },
|
||||
{ name: "GPQA", score: 40 },
|
||||
],
|
||||
}
|
||||
const high = {
|
||||
...model,
|
||||
id: "other/model",
|
||||
benchmarks: [
|
||||
{ name: "Tau3", score: 80 },
|
||||
{ name: "GPQA", score: 90 },
|
||||
],
|
||||
}
|
||||
expect(scores(low, [low, high])["Tool use"]).toEqual({ value: 0 })
|
||||
expect(scores(low, [low, high]).Reasoning).toEqual({ value: 0 })
|
||||
expect(scores(high, [low, high])["Tool use"]).toEqual({ value: 100 })
|
||||
expect(scores(high, [low, high]).Reasoning).toEqual({ value: 100 })
|
||||
})
|
||||
|
||||
test("a benchmark without comparison peers retains the capability baseline", () => {
|
||||
const entry = { ...model, benchmarks: [{ name: "Tau3", score: 90 }] }
|
||||
expect(scores(entry, [entry])["Tool use"]).toEqual({
|
||||
value: 50,
|
||||
fallback: "Tool calling supported; no comparable benchmark",
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/stats-core",
|
||||
"version": "1.18.32",
|
||||
"version": "1.18.33",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { catalogIdentity } from "./catalog-identity"
|
||||
|
||||
describe("stats catalog identity", () => {
|
||||
test("resolves OpenCode offerings to canonical labs", () => {
|
||||
const identity = catalogIdentity({
|
||||
models: { "meituan/longcat-2.5-preview": {} },
|
||||
providers: {
|
||||
opencode: { models: { "longcat-2.5-preview-free": { canonical_model_id: "meituan/longcat-2.5-preview" } } },
|
||||
"opencode-go": {
|
||||
models: { "longcat-2.5-preview-free": { canonical_model_id: "meituan/longcat-2.5-preview" } },
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(identity.offerings.get("opencode/longcat-2.5-preview-free")).toBe("meituan")
|
||||
expect(identity.offerings.get("opencode-go/longcat-2.5-preview-free")).toBe("meituan")
|
||||
expect(identity.models.get("longcat-2.5-preview")).toBe("meituan")
|
||||
})
|
||||
|
||||
test("does not guess a lab for a name shared by different canonical models", () => {
|
||||
const identity = catalogIdentity({
|
||||
models: { "lab-a/model": {}, "lab-b/model": {} },
|
||||
providers: {
|
||||
opencode: { models: { "model-free": { canonical_model_id: "lab-a/model" } } },
|
||||
"opencode-go": { models: { model: { canonical_model_id: "lab-b/model" } } },
|
||||
},
|
||||
})
|
||||
|
||||
expect(identity.offerings.get("opencode/model-free")).toBe("lab-a")
|
||||
expect(identity.offerings.get("opencode-go/model")).toBe("lab-b")
|
||||
expect(identity.models.has("model")).toBe(false)
|
||||
})
|
||||
|
||||
test("accepts provider IDs that are already canonical catalog IDs", () => {
|
||||
const identity = catalogIdentity({
|
||||
models: { "opencode/direct-model": {} },
|
||||
providers: { opencode: { models: { "direct-model": {} } } },
|
||||
})
|
||||
|
||||
expect(identity.offerings.get("opencode/direct-model")).toBe("opencode")
|
||||
expect(identity.models.get("direct-model")).toBe("opencode")
|
||||
})
|
||||
|
||||
test("rejects a catalog without published canonical identities", () => {
|
||||
expect(() => catalogIdentity({ models: {}, providers: { opencode: { models: { model: {} } } } })).toThrow(
|
||||
"Model catalog has no canonical OpenCode offerings",
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,70 @@
|
||||
import { statModel } from "./model-normalization"
|
||||
|
||||
const CATALOG_URL = "https://models.opencode.ai/catalog.json"
|
||||
const STATS_PROVIDERS = ["opencode", "opencode-go"] as const
|
||||
const cache: { value?: CatalogIdentity; expiresAt?: number } = {}
|
||||
|
||||
export type CatalogIdentity = {
|
||||
offerings: ReadonlyMap<string, string>
|
||||
models: ReadonlyMap<string, string>
|
||||
}
|
||||
|
||||
export async function loadCatalogIdentity() {
|
||||
if (cache.value && (cache.expiresAt ?? 0) > Date.now()) return { catalog: cache.value, stale: false }
|
||||
return fetch(CATALOG_URL, { signal: AbortSignal.timeout(10_000) })
|
||||
.then(async (response) => {
|
||||
if (!response.ok) throw new Error(`Model catalog returned ${response.status}`)
|
||||
return catalogIdentity(await response.json())
|
||||
})
|
||||
.then((value) => {
|
||||
cache.value = value
|
||||
cache.expiresAt = Date.now() + 5 * 60_000
|
||||
return { catalog: value, stale: false }
|
||||
})
|
||||
.catch(() => ({ catalog: cache.value, stale: true }))
|
||||
}
|
||||
|
||||
export function catalogIdentity(value: unknown): CatalogIdentity {
|
||||
if (!record(value) || !record(value.models) || !record(value.providers)) throw new Error("Invalid model catalog")
|
||||
const models = value.models
|
||||
const providers = value.providers
|
||||
|
||||
const offerings = new Map<string, string>()
|
||||
const candidates = new Map<string, Set<string>>()
|
||||
STATS_PROVIDERS.forEach((providerID) => {
|
||||
const provider = providers[providerID]
|
||||
if (!record(provider) || !record(provider.models)) return
|
||||
Object.entries(provider.models).forEach(([modelID, model]) => {
|
||||
if (!record(model)) return
|
||||
const canonicalID =
|
||||
typeof model.canonical_model_id === "string"
|
||||
? model.canonical_model_id
|
||||
: modelID in models
|
||||
? modelID
|
||||
: `${providerID}/${modelID}` in models
|
||||
? `${providerID}/${modelID}`
|
||||
: undefined
|
||||
if (!canonicalID || !(canonicalID in models)) return
|
||||
const [lab, ...parts] = canonicalID.split("/")
|
||||
if (!lab || parts.length === 0) return
|
||||
const canonicalModel = parts.join("/")
|
||||
offerings.set(`${providerID}/${modelID.toLowerCase()}`, lab)
|
||||
;[modelID, canonicalModel].forEach((name) => {
|
||||
const normalized = statModel(name, undefined)
|
||||
candidates.set(normalized, (candidates.get(normalized) ?? new Set()).add(lab))
|
||||
})
|
||||
})
|
||||
})
|
||||
if (offerings.size === 0) throw new Error("Model catalog has no canonical OpenCode offerings")
|
||||
|
||||
return {
|
||||
offerings,
|
||||
models: new Map(
|
||||
[...candidates.entries()].flatMap(([model, labs]) => (labs.size === 1 ? [[model, [...labs][0]!]] : [])),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
function record(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value)
|
||||
}
|
||||
@@ -56,6 +56,7 @@ export declare namespace GeoStatRepo {
|
||||
}) => Effect.Effect<GeoStatRow[], DatabaseError>
|
||||
readonly upsert: (rows: GeoStatRow[]) => Effect.Effect<void, DatabaseError>
|
||||
readonly deleteRetiredDimensions: (rows: GeoStatRow[]) => Effect.Effect<void, DatabaseError>
|
||||
readonly deleteUnknownDimensions: (rows: GeoStatRow[]) => Effect.Effect<void, DatabaseError>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,14 +208,64 @@ export class GeoStatRepo extends Context.Service<GeoStatRepo, GeoStatRepo.Servic
|
||||
inArray(geoStat.dataset, scope.datasets),
|
||||
inArray(geoStat.client, scope.clients),
|
||||
inArray(geoStat.source, scope.sources),
|
||||
or(inArray(geoStat.provider, RETIRED_STAT_PROVIDERS), inArray(geoStat.model, RETIRED_STAT_MODELS)),
|
||||
or(
|
||||
inArray(geoStat.provider, RETIRED_STAT_PROVIDERS),
|
||||
inArray(geoStat.model, RETIRED_STAT_MODELS),
|
||||
and(eq(geoStat.provider, "unknown"), eq(geoStat.model, "hy4-preview")),
|
||||
),
|
||||
),
|
||||
),
|
||||
catch: (cause) => DatabaseError.make({ cause }),
|
||||
})
|
||||
})
|
||||
|
||||
return GeoStatRepo.of({ listDaily, listByPeriod, upsert, deleteRetiredDimensions })
|
||||
const deleteUnknownDimensions = Effect.fn("GeoStatRepo.deleteUnknownDimensions")(function* (rows: GeoStatRow[]) {
|
||||
const scope = statRowScope(rows)
|
||||
if (!scope) return
|
||||
const replacements = new Set(rows.map((row) => [statPeriodKey(row), row.model, row.country].join("\u0000")))
|
||||
const stale = yield* Effect.tryPromise({
|
||||
try: () =>
|
||||
db
|
||||
.select({
|
||||
id: geoStat.id,
|
||||
grain: geoStat.grain,
|
||||
period_key: geoStat.period_key,
|
||||
dataset: geoStat.dataset,
|
||||
tier: geoStat.tier,
|
||||
client: geoStat.client,
|
||||
source: geoStat.source,
|
||||
model: geoStat.model,
|
||||
country: geoStat.country,
|
||||
})
|
||||
.from(geoStat)
|
||||
.where(
|
||||
and(
|
||||
eq(geoStat.provider, "unknown"),
|
||||
inArray(geoStat.grain, scope.grains),
|
||||
inArray(geoStat.period_key, scope.periodKeys),
|
||||
inArray(geoStat.dataset, scope.datasets),
|
||||
inArray(geoStat.client, scope.clients),
|
||||
inArray(geoStat.source, scope.sources),
|
||||
inArray(geoStat.model, [...new Set(rows.map((row) => row.model ?? "all"))]),
|
||||
),
|
||||
),
|
||||
catch: (cause) => DatabaseError.make({ cause }),
|
||||
})
|
||||
const ids = stale
|
||||
.filter((row) => replacements.has([statPeriodKey(row), row.model, row.country].join("\u0000")))
|
||||
.map((row) => row.id)
|
||||
yield* Effect.forEach(
|
||||
chunks(ids, UPSERT_CHUNK_SIZE),
|
||||
(chunk) =>
|
||||
Effect.tryPromise({
|
||||
try: () => db.delete(geoStat).where(and(eq(geoStat.provider, "unknown"), inArray(geoStat.id, chunk))),
|
||||
catch: (cause) => DatabaseError.make({ cause }),
|
||||
}),
|
||||
{ discard: true },
|
||||
)
|
||||
})
|
||||
|
||||
return GeoStatRepo.of({ listDaily, listByPeriod, upsert, deleteRetiredDimensions, deleteUnknownDimensions })
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,10 +1,84 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import type { ModelStatMetric } from "./model"
|
||||
import type { RetentionMetricRow } from "./home"
|
||||
|
||||
process.env.SST_RESOURCE_App = JSON.stringify({ name: "opencode", stage: "test" })
|
||||
process.env.SST_RESOURCE_StatsDatabase = JSON.stringify({ url: "mysql://localhost/stats" })
|
||||
|
||||
const { buildRetentionEntries } = await import("./home")
|
||||
const { buildRetentionEntries, buildStatsHomeData, normalizeStatRows } = await import("./home")
|
||||
|
||||
test("daily rankings use the latest day while weekly rankings retain seven days and their previous-period change", () => {
|
||||
const rows = Array.from({ length: 14 }, (_, index) =>
|
||||
[
|
||||
{ model: "model-a", totalTokens: index === 13 ? 3_000_000_000 : 1_000_000_000 },
|
||||
{ model: "model-b", totalTokens: index === 13 ? 2_000_000_000 : 4_000_000_000 },
|
||||
].map(
|
||||
(item) =>
|
||||
({
|
||||
periodKey: `2026-09-${String(index + 1).padStart(2, "0")}`,
|
||||
updatedAt: new Date(Date.UTC(2026, 8, index + 1)),
|
||||
tier: "Go",
|
||||
provider: "test",
|
||||
model: item.model,
|
||||
sessions: 1,
|
||||
uniqueUsers: 1,
|
||||
inputTokens: item.totalTokens,
|
||||
outputTokens: 0,
|
||||
reasoningTokens: 0,
|
||||
cacheReadTokens: 0,
|
||||
totalTokens: item.totalTokens,
|
||||
inputCostMicrocents: 0,
|
||||
outputCostMicrocents: 0,
|
||||
totalCostMicrocents: 0,
|
||||
}) satisfies ModelStatMetric,
|
||||
),
|
||||
).flat()
|
||||
const rankings = buildStatsHomeData(rows, [], []).leaderboard.Go
|
||||
|
||||
expect(rankings["1D"].map((item) => [item.model, item.tokens, item.change])).toEqual([
|
||||
["model-a", 3, 200],
|
||||
["model-b", 2, -50],
|
||||
])
|
||||
expect(rankings["1W"].map((item) => [item.model, item.tokens])).toEqual([
|
||||
["model-b", 26],
|
||||
["model-a", 9],
|
||||
])
|
||||
expect(rankings["2M"]).toEqual(rankings["1W"])
|
||||
})
|
||||
|
||||
describe("model usage attribution", () => {
|
||||
const row = {
|
||||
periodKey: "2026-09-27",
|
||||
updatedAt: new Date("2026-09-28T00:00:00.000Z"),
|
||||
tier: "Go",
|
||||
provider: "unknown",
|
||||
model: "longcat-2.5-preview",
|
||||
sessions: 1,
|
||||
uniqueUsers: 1,
|
||||
inputTokens: 50,
|
||||
outputTokens: 50,
|
||||
reasoningTokens: 0,
|
||||
cacheReadTokens: 0,
|
||||
totalTokens: 100,
|
||||
inputCostMicrocents: 0,
|
||||
outputCostMicrocents: 0,
|
||||
totalCostMicrocents: 0,
|
||||
}
|
||||
|
||||
test("shows historical LongCat usage under Meituan", () => {
|
||||
expect(normalizeStatRows([row])).toMatchObject([{ provider: "meituan", model: "longcat-2.5-preview" }])
|
||||
})
|
||||
|
||||
test("prefers recomputed rows over stale unknown dimensions", () => {
|
||||
expect(normalizeStatRows([row, { ...row, provider: "meituan" }])).toMatchObject([
|
||||
{ provider: "meituan", totalTokens: 100 },
|
||||
])
|
||||
})
|
||||
|
||||
test("keeps unknown usage when a different lab has the same model name", () => {
|
||||
expect(normalizeStatRows([row, { ...row, provider: "another-lab" }])).toHaveLength(2)
|
||||
})
|
||||
})
|
||||
|
||||
describe("retention aggregates", () => {
|
||||
test("pools the latest seven weekly cohorts and ranks models above the sample floor", () => {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user