mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-15 17:08:21 -04:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b1710249f2 |
@@ -34,48 +34,10 @@ jobs:
|
|||||||
|
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const twoHours = 2 * 60 * 60 * 1000;
|
const twoHours = 2 * 60 * 60 * 1000;
|
||||||
const orgMemberAssociations = new Set(['OWNER', 'MEMBER']);
|
|
||||||
const agentLogin = 'opencode-agent[bot]';
|
|
||||||
const { data: file } = await github.rest.repos.getContent({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
path: '.github/TEAM_MEMBERS',
|
|
||||||
ref: 'dev',
|
|
||||||
});
|
|
||||||
const teamMembers = new Set(
|
|
||||||
Buffer.from(file.content, 'base64')
|
|
||||||
.toString()
|
|
||||||
.split('\n')
|
|
||||||
.map((line) => line.trim().toLowerCase())
|
|
||||||
.filter(Boolean)
|
|
||||||
);
|
|
||||||
|
|
||||||
function isExempt(item) {
|
|
||||||
const login = item.user?.login?.toLowerCase();
|
|
||||||
return (
|
|
||||||
login === agentLogin ||
|
|
||||||
orgMemberAssociations.has(item.author_association) ||
|
|
||||||
(login && teamMembers.has(login))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
const isPR = !!item.pull_request;
|
const isPR = !!item.pull_request;
|
||||||
const kind = isPR ? 'PR' : 'issue';
|
const kind = isPR ? 'PR' : 'issue';
|
||||||
const login = item.user?.login;
|
|
||||||
|
|
||||||
if (isExempt(item)) {
|
|
||||||
core.info(`Skipping ${kind} #${item.number}; author ${login || 'unknown'} is exempt`);
|
|
||||||
try {
|
|
||||||
await github.rest.issues.removeLabel({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
issue_number: item.number,
|
|
||||||
name: 'needs:compliance',
|
|
||||||
});
|
|
||||||
} catch (e) {}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { data: comments } = await github.rest.issues.listComments({
|
const { data: comments } = await github.rest.issues.listComments({
|
||||||
owner: context.repo.owner,
|
owner: context.repo.owner,
|
||||||
|
|||||||
@@ -17,31 +17,12 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
|
|
||||||
- name: Check exempt issue author
|
|
||||||
id: author
|
|
||||||
run: |
|
|
||||||
LOGIN="${{ github.event.issue.user.login }}"
|
|
||||||
ASSOCIATION="${{ github.event.issue.author_association }}"
|
|
||||||
|
|
||||||
if [ "$LOGIN" = "opencode-agent[bot]" ] ||
|
|
||||||
[ "$ASSOCIATION" = "OWNER" ] ||
|
|
||||||
[ "$ASSOCIATION" = "MEMBER" ] ||
|
|
||||||
grep -qxiF "$LOGIN" .github/TEAM_MEMBERS; then
|
|
||||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "Skipping issue automation for exempt author: $LOGIN ($ASSOCIATION)"
|
|
||||||
else
|
|
||||||
echo "skip=false" >> "$GITHUB_OUTPUT"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- uses: ./.github/actions/setup-bun
|
- uses: ./.github/actions/setup-bun
|
||||||
if: steps.author.outputs.skip != 'true'
|
|
||||||
|
|
||||||
- name: Install opencode
|
- name: Install opencode
|
||||||
if: steps.author.outputs.skip != 'true'
|
|
||||||
run: curl -fsSL https://opencode.ai/install | bash
|
run: curl -fsSL https://opencode.ai/install | bash
|
||||||
|
|
||||||
- name: Check duplicates and compliance
|
- name: Check duplicates and compliance
|
||||||
if: steps.author.outputs.skip != 'true'
|
|
||||||
env:
|
env:
|
||||||
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@@ -57,7 +38,6 @@ jobs:
|
|||||||
opencode run -m opencode/claude-sonnet-4-6 "A new issue has been created:
|
opencode run -m opencode/claude-sonnet-4-6 "A new issue has been created:
|
||||||
|
|
||||||
Issue number: ${{ github.event.issue.number }}
|
Issue number: ${{ github.event.issue.number }}
|
||||||
Issue author association: ${{ github.event.issue.author_association }}
|
|
||||||
|
|
||||||
Lookup this issue with gh issue view ${{ github.event.issue.number }}.
|
Lookup this issue with gh issue view ${{ github.event.issue.number }}.
|
||||||
|
|
||||||
@@ -69,8 +49,6 @@ jobs:
|
|||||||
|
|
||||||
Check whether the issue follows our contributing guidelines and issue templates.
|
Check whether the issue follows our contributing guidelines and issue templates.
|
||||||
|
|
||||||
If the issue author association is OWNER or MEMBER, skip this compliance check. Do not add the needs:compliance label for organization-owned issues.
|
|
||||||
|
|
||||||
This project has three issue templates that every issue MUST use one of:
|
This project has three issue templates that every issue MUST use one of:
|
||||||
|
|
||||||
1. Bug Report - requires a Description field with real content
|
1. Bug Report - requires a Description field with real content
|
||||||
@@ -105,7 +83,7 @@ jobs:
|
|||||||
|
|
||||||
Based on your findings, post a SINGLE comment on issue #${{ github.event.issue.number }}. Build the comment as follows:
|
Based on your findings, post a SINGLE comment on issue #${{ github.event.issue.number }}. Build the comment as follows:
|
||||||
|
|
||||||
If the issue is NOT compliant and the author association is not OWNER or MEMBER, start the comment with:
|
If the issue is NOT compliant, start the comment with:
|
||||||
<!-- issue-compliance -->
|
<!-- issue-compliance -->
|
||||||
Then explain what needs to be fixed and that they have 2 hours to edit the issue before it is automatically closed. Also add the label needs:compliance to the issue using: gh issue edit ${{ github.event.issue.number }} --add-label needs:compliance
|
Then explain what needs to be fixed and that they have 2 hours to edit the issue before it is automatically closed. Also add the label needs:compliance to the issue using: gh issue edit ${{ github.event.issue.number }} --add-label needs:compliance
|
||||||
|
|
||||||
@@ -151,31 +129,12 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
|
|
||||||
- name: Check exempt issue author
|
|
||||||
id: author
|
|
||||||
run: |
|
|
||||||
LOGIN="${{ github.event.issue.user.login }}"
|
|
||||||
ASSOCIATION="${{ github.event.issue.author_association }}"
|
|
||||||
|
|
||||||
if [ "$LOGIN" = "opencode-agent[bot]" ] ||
|
|
||||||
[ "$ASSOCIATION" = "OWNER" ] ||
|
|
||||||
[ "$ASSOCIATION" = "MEMBER" ] ||
|
|
||||||
grep -qxiF "$LOGIN" .github/TEAM_MEMBERS; then
|
|
||||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "Skipping issue automation for exempt author: $LOGIN ($ASSOCIATION)"
|
|
||||||
else
|
|
||||||
echo "skip=false" >> "$GITHUB_OUTPUT"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- uses: ./.github/actions/setup-bun
|
- uses: ./.github/actions/setup-bun
|
||||||
if: steps.author.outputs.skip != 'true'
|
|
||||||
|
|
||||||
- name: Install opencode
|
- name: Install opencode
|
||||||
if: steps.author.outputs.skip != 'true'
|
|
||||||
run: curl -fsSL https://opencode.ai/install | bash
|
run: curl -fsSL https://opencode.ai/install | bash
|
||||||
|
|
||||||
- name: Recheck compliance
|
- name: Recheck compliance
|
||||||
if: steps.author.outputs.skip != 'true'
|
|
||||||
env:
|
env:
|
||||||
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@@ -189,12 +148,9 @@ jobs:
|
|||||||
}
|
}
|
||||||
run: |
|
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/claude-sonnet-4-6 "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 }}.
|
Lookup this issue with gh issue view ${{ github.event.issue.number }}.
|
||||||
|
|
||||||
If the issue author association is OWNER or MEMBER, remove the needs:compliance label if present, delete the previous compliance comment if present, and do not post a new comment.
|
|
||||||
|
|
||||||
Re-check whether the issue now follows our contributing guidelines and issue templates.
|
Re-check whether the issue now follows our contributing guidelines and issue templates.
|
||||||
|
|
||||||
This project has three issue templates that every issue MUST use one of:
|
This project has three issue templates that every issue MUST use one of:
|
||||||
|
|||||||
@@ -16,32 +16,13 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
|
|
||||||
- name: Check exempt issue author
|
|
||||||
id: author
|
|
||||||
run: |
|
|
||||||
LOGIN="${{ github.event.issue.user.login }}"
|
|
||||||
ASSOCIATION="${{ github.event.issue.author_association }}"
|
|
||||||
|
|
||||||
if [ "$LOGIN" = "opencode-agent[bot]" ] ||
|
|
||||||
[ "$ASSOCIATION" = "OWNER" ] ||
|
|
||||||
[ "$ASSOCIATION" = "MEMBER" ] ||
|
|
||||||
grep -qxiF "$LOGIN" .github/TEAM_MEMBERS; then
|
|
||||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "Skipping issue automation for exempt author: $LOGIN ($ASSOCIATION)"
|
|
||||||
else
|
|
||||||
echo "skip=false" >> "$GITHUB_OUTPUT"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Setup Bun
|
- name: Setup Bun
|
||||||
if: steps.author.outputs.skip != 'true'
|
|
||||||
uses: ./.github/actions/setup-bun
|
uses: ./.github/actions/setup-bun
|
||||||
|
|
||||||
- name: Install opencode
|
- name: Install opencode
|
||||||
if: steps.author.outputs.skip != 'true'
|
|
||||||
run: curl -fsSL https://opencode.ai/install | bash
|
run: curl -fsSL https://opencode.ai/install | bash
|
||||||
|
|
||||||
- name: Triage issue
|
- name: Triage issue
|
||||||
if: steps.author.outputs.skip != 'true'
|
|
||||||
env:
|
env:
|
||||||
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
},
|
},
|
||||||
"packages/app": {
|
"packages/app": {
|
||||||
"name": "@opencode-ai/app",
|
"name": "@opencode-ai/app",
|
||||||
"version": "1.17.12",
|
"version": "1.17.11",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@dnd-kit/abstract": "0.5.0",
|
"@dnd-kit/abstract": "0.5.0",
|
||||||
"@dnd-kit/dom": "0.5.0",
|
"@dnd-kit/dom": "0.5.0",
|
||||||
@@ -92,7 +92,7 @@
|
|||||||
},
|
},
|
||||||
"packages/cli": {
|
"packages/cli": {
|
||||||
"name": "@opencode-ai/cli",
|
"name": "@opencode-ai/cli",
|
||||||
"version": "1.17.12",
|
"version": "1.17.11",
|
||||||
"bin": {
|
"bin": {
|
||||||
"lildax": "./bin/lildax.cjs",
|
"lildax": "./bin/lildax.cjs",
|
||||||
},
|
},
|
||||||
@@ -140,7 +140,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/app": {
|
"packages/console/app": {
|
||||||
"name": "@opencode-ai/console-app",
|
"name": "@opencode-ai/console-app",
|
||||||
"version": "1.17.12",
|
"version": "1.17.11",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cloudflare/vite-plugin": "1.15.2",
|
"@cloudflare/vite-plugin": "1.15.2",
|
||||||
"@ibm/plex": "6.4.1",
|
"@ibm/plex": "6.4.1",
|
||||||
@@ -176,7 +176,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/core": {
|
"packages/console/core": {
|
||||||
"name": "@opencode-ai/console-core",
|
"name": "@opencode-ai/console-core",
|
||||||
"version": "1.17.12",
|
"version": "1.17.11",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-sts": "3.782.0",
|
"@aws-sdk/client-sts": "3.782.0",
|
||||||
"@jsx-email/render": "1.1.1",
|
"@jsx-email/render": "1.1.1",
|
||||||
@@ -203,7 +203,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/function": {
|
"packages/console/function": {
|
||||||
"name": "@opencode-ai/console-function",
|
"name": "@opencode-ai/console-function",
|
||||||
"version": "1.17.12",
|
"version": "1.17.11",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/anthropic": "3.0.82",
|
"@ai-sdk/anthropic": "3.0.82",
|
||||||
"@ai-sdk/openai": "3.0.48",
|
"@ai-sdk/openai": "3.0.48",
|
||||||
@@ -225,7 +225,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/mail": {
|
"packages/console/mail": {
|
||||||
"name": "@opencode-ai/console-mail",
|
"name": "@opencode-ai/console-mail",
|
||||||
"version": "1.17.12",
|
"version": "1.17.11",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jsx-email/all": "2.2.3",
|
"@jsx-email/all": "2.2.3",
|
||||||
"@jsx-email/cli": "1.4.3",
|
"@jsx-email/cli": "1.4.3",
|
||||||
@@ -249,7 +249,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/support": {
|
"packages/console/support": {
|
||||||
"name": "@opencode-ai/console-support",
|
"name": "@opencode-ai/console-support",
|
||||||
"version": "1.17.12",
|
"version": "1.17.11",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cloudflare/vite-plugin": "1.15.2",
|
"@cloudflare/vite-plugin": "1.15.2",
|
||||||
"@opencode-ai/console-core": "workspace:*",
|
"@opencode-ai/console-core": "workspace:*",
|
||||||
@@ -269,7 +269,7 @@
|
|||||||
},
|
},
|
||||||
"packages/core": {
|
"packages/core": {
|
||||||
"name": "@opencode-ai/core",
|
"name": "@opencode-ai/core",
|
||||||
"version": "1.17.12",
|
"version": "1.17.11",
|
||||||
"bin": {
|
"bin": {
|
||||||
"opencode": "./bin/opencode",
|
"opencode": "./bin/opencode",
|
||||||
},
|
},
|
||||||
@@ -334,7 +334,7 @@
|
|||||||
"npm-package-arg": "13.0.2",
|
"npm-package-arg": "13.0.2",
|
||||||
"semver": "^7.6.3",
|
"semver": "^7.6.3",
|
||||||
"turndown": "7.2.0",
|
"turndown": "7.2.0",
|
||||||
"venice-ai-sdk-provider": "2.1.1",
|
"venice-ai-sdk-provider": "2.0.2",
|
||||||
"which": "6.0.1",
|
"which": "6.0.1",
|
||||||
"xdg-basedir": "5.1.0",
|
"xdg-basedir": "5.1.0",
|
||||||
"zod": "catalog:",
|
"zod": "catalog:",
|
||||||
@@ -363,7 +363,7 @@
|
|||||||
},
|
},
|
||||||
"packages/desktop": {
|
"packages/desktop": {
|
||||||
"name": "@opencode-ai/desktop",
|
"name": "@opencode-ai/desktop",
|
||||||
"version": "1.17.12",
|
"version": "1.17.11",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@zip.js/zip.js": "2.7.62",
|
"@zip.js/zip.js": "2.7.62",
|
||||||
"effect": "catalog:",
|
"effect": "catalog:",
|
||||||
@@ -417,7 +417,7 @@
|
|||||||
},
|
},
|
||||||
"packages/effect-drizzle-sqlite": {
|
"packages/effect-drizzle-sqlite": {
|
||||||
"name": "@opencode-ai/effect-drizzle-sqlite",
|
"name": "@opencode-ai/effect-drizzle-sqlite",
|
||||||
"version": "1.17.12",
|
"version": "1.17.11",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"drizzle-orm": "catalog:",
|
"drizzle-orm": "catalog:",
|
||||||
"effect": "catalog:",
|
"effect": "catalog:",
|
||||||
@@ -431,7 +431,7 @@
|
|||||||
},
|
},
|
||||||
"packages/effect-sqlite-node": {
|
"packages/effect-sqlite-node": {
|
||||||
"name": "@opencode-ai/effect-sqlite-node",
|
"name": "@opencode-ai/effect-sqlite-node",
|
||||||
"version": "1.17.12",
|
"version": "1.17.11",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"effect": "catalog:",
|
"effect": "catalog:",
|
||||||
},
|
},
|
||||||
@@ -443,7 +443,7 @@
|
|||||||
},
|
},
|
||||||
"packages/enterprise": {
|
"packages/enterprise": {
|
||||||
"name": "@opencode-ai/enterprise",
|
"name": "@opencode-ai/enterprise",
|
||||||
"version": "1.17.12",
|
"version": "1.17.11",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hono/standard-validator": "catalog:",
|
"@hono/standard-validator": "catalog:",
|
||||||
"@opencode-ai/core": "workspace:*",
|
"@opencode-ai/core": "workspace:*",
|
||||||
@@ -475,7 +475,7 @@
|
|||||||
},
|
},
|
||||||
"packages/function": {
|
"packages/function": {
|
||||||
"name": "@opencode-ai/function",
|
"name": "@opencode-ai/function",
|
||||||
"version": "1.17.12",
|
"version": "1.17.11",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/auth-app": "8.0.1",
|
"@octokit/auth-app": "8.0.1",
|
||||||
"@octokit/rest": "catalog:",
|
"@octokit/rest": "catalog:",
|
||||||
@@ -491,7 +491,7 @@
|
|||||||
},
|
},
|
||||||
"packages/http-recorder": {
|
"packages/http-recorder": {
|
||||||
"name": "@opencode-ai/http-recorder",
|
"name": "@opencode-ai/http-recorder",
|
||||||
"version": "1.17.12",
|
"version": "1.17.11",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@effect/platform-node": "4.0.0-beta.83",
|
"@effect/platform-node": "4.0.0-beta.83",
|
||||||
"@effect/platform-node-shared": "4.0.0-beta.83",
|
"@effect/platform-node-shared": "4.0.0-beta.83",
|
||||||
@@ -522,7 +522,7 @@
|
|||||||
},
|
},
|
||||||
"packages/llm": {
|
"packages/llm": {
|
||||||
"name": "@opencode-ai/llm",
|
"name": "@opencode-ai/llm",
|
||||||
"version": "1.17.12",
|
"version": "1.17.11",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/schema": "workspace:*",
|
"@opencode-ai/schema": "workspace:*",
|
||||||
"@smithy/eventstream-codec": "4.2.14",
|
"@smithy/eventstream-codec": "4.2.14",
|
||||||
@@ -541,7 +541,7 @@
|
|||||||
},
|
},
|
||||||
"packages/opencode": {
|
"packages/opencode": {
|
||||||
"name": "opencode",
|
"name": "opencode",
|
||||||
"version": "1.17.12",
|
"version": "1.17.11",
|
||||||
"bin": {
|
"bin": {
|
||||||
"opencode": "./bin/opencode",
|
"opencode": "./bin/opencode",
|
||||||
},
|
},
|
||||||
@@ -637,7 +637,7 @@
|
|||||||
"tree-sitter-powershell": "0.25.10",
|
"tree-sitter-powershell": "0.25.10",
|
||||||
"turndown": "7.2.0",
|
"turndown": "7.2.0",
|
||||||
"ulid": "catalog:",
|
"ulid": "catalog:",
|
||||||
"venice-ai-sdk-provider": "2.1.1",
|
"venice-ai-sdk-provider": "2.0.2",
|
||||||
"vscode-jsonrpc": "8.2.1",
|
"vscode-jsonrpc": "8.2.1",
|
||||||
"web-tree-sitter": "0.25.10",
|
"web-tree-sitter": "0.25.10",
|
||||||
"ws": "8.21.0",
|
"ws": "8.21.0",
|
||||||
@@ -671,7 +671,7 @@
|
|||||||
},
|
},
|
||||||
"packages/plugin": {
|
"packages/plugin": {
|
||||||
"name": "@opencode-ai/plugin",
|
"name": "@opencode-ai/plugin",
|
||||||
"version": "1.17.12",
|
"version": "1.17.11",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/provider": "3.0.8",
|
"@ai-sdk/provider": "3.0.8",
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
@@ -747,7 +747,7 @@
|
|||||||
},
|
},
|
||||||
"packages/sdk/js": {
|
"packages/sdk/js": {
|
||||||
"name": "@opencode-ai/sdk",
|
"name": "@opencode-ai/sdk",
|
||||||
"version": "1.17.12",
|
"version": "1.17.11",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cross-spawn": "catalog:",
|
"cross-spawn": "catalog:",
|
||||||
},
|
},
|
||||||
@@ -762,7 +762,7 @@
|
|||||||
},
|
},
|
||||||
"packages/server": {
|
"packages/server": {
|
||||||
"name": "@opencode-ai/server",
|
"name": "@opencode-ai/server",
|
||||||
"version": "1.17.12",
|
"version": "1.17.11",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/core": "workspace:*",
|
"@opencode-ai/core": "workspace:*",
|
||||||
"@opencode-ai/protocol": "workspace:*",
|
"@opencode-ai/protocol": "workspace:*",
|
||||||
@@ -777,7 +777,7 @@
|
|||||||
},
|
},
|
||||||
"packages/session-ui": {
|
"packages/session-ui": {
|
||||||
"name": "@opencode-ai/session-ui",
|
"name": "@opencode-ai/session-ui",
|
||||||
"version": "1.17.12",
|
"version": "1.17.11",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kobalte/core": "catalog:",
|
"@kobalte/core": "catalog:",
|
||||||
"@opencode-ai/core": "workspace:*",
|
"@opencode-ai/core": "workspace:*",
|
||||||
@@ -821,7 +821,7 @@
|
|||||||
},
|
},
|
||||||
"packages/slack": {
|
"packages/slack": {
|
||||||
"name": "@opencode-ai/slack",
|
"name": "@opencode-ai/slack",
|
||||||
"version": "1.17.12",
|
"version": "1.17.11",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
"@slack/bolt": "^3.17.1",
|
"@slack/bolt": "^3.17.1",
|
||||||
@@ -834,7 +834,7 @@
|
|||||||
},
|
},
|
||||||
"packages/stats/app": {
|
"packages/stats/app": {
|
||||||
"name": "@opencode-ai/stats-app",
|
"name": "@opencode-ai/stats-app",
|
||||||
"version": "1.17.12",
|
"version": "1.17.11",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ibm/plex": "6.4.1",
|
"@ibm/plex": "6.4.1",
|
||||||
"@opencode-ai/stats-core": "workspace:*",
|
"@opencode-ai/stats-core": "workspace:*",
|
||||||
@@ -867,7 +867,7 @@
|
|||||||
},
|
},
|
||||||
"packages/stats/core": {
|
"packages/stats/core": {
|
||||||
"name": "@opencode-ai/stats-core",
|
"name": "@opencode-ai/stats-core",
|
||||||
"version": "1.17.12",
|
"version": "1.17.11",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-athena": "3.933.0",
|
"@aws-sdk/client-athena": "3.933.0",
|
||||||
"@planetscale/database": "1.19.0",
|
"@planetscale/database": "1.19.0",
|
||||||
@@ -886,7 +886,7 @@
|
|||||||
},
|
},
|
||||||
"packages/stats/server": {
|
"packages/stats/server": {
|
||||||
"name": "@opencode-ai/stats-server",
|
"name": "@opencode-ai/stats-server",
|
||||||
"version": "1.17.12",
|
"version": "1.17.11",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-firehose": "3.933.0",
|
"@aws-sdk/client-firehose": "3.933.0",
|
||||||
"@effect/platform-node": "catalog:",
|
"@effect/platform-node": "catalog:",
|
||||||
@@ -927,7 +927,7 @@
|
|||||||
},
|
},
|
||||||
"packages/tui": {
|
"packages/tui": {
|
||||||
"name": "@opencode-ai/tui",
|
"name": "@opencode-ai/tui",
|
||||||
"version": "1.17.12",
|
"version": "1.17.11",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/core": "workspace:*",
|
"@opencode-ai/core": "workspace:*",
|
||||||
"@opencode-ai/plugin": "workspace:*",
|
"@opencode-ai/plugin": "workspace:*",
|
||||||
@@ -954,7 +954,7 @@
|
|||||||
},
|
},
|
||||||
"packages/ui": {
|
"packages/ui": {
|
||||||
"name": "@opencode-ai/ui",
|
"name": "@opencode-ai/ui",
|
||||||
"version": "1.17.12",
|
"version": "1.17.11",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kobalte/core": "catalog:",
|
"@kobalte/core": "catalog:",
|
||||||
"@pierre/diffs": "catalog:",
|
"@pierre/diffs": "catalog:",
|
||||||
@@ -1005,7 +1005,7 @@
|
|||||||
},
|
},
|
||||||
"packages/web": {
|
"packages/web": {
|
||||||
"name": "@opencode-ai/web",
|
"name": "@opencode-ai/web",
|
||||||
"version": "1.17.12",
|
"version": "1.17.11",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/cloudflare": "12.6.3",
|
"@astrojs/cloudflare": "12.6.3",
|
||||||
"@astrojs/markdown-remark": "6.3.1",
|
"@astrojs/markdown-remark": "6.3.1",
|
||||||
@@ -5429,7 +5429,7 @@
|
|||||||
|
|
||||||
"vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
|
"vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
|
||||||
|
|
||||||
"venice-ai-sdk-provider": ["venice-ai-sdk-provider@2.1.1", "", { "dependencies": { "@ai-sdk/openai-compatible": "^2.0.51", "@ai-sdk/provider": "^3.0.10", "@ai-sdk/provider-utils": "^4.0.30" }, "peerDependencies": { "ai": "^6.0.90" } }, "sha512-w3OHkuzzKZ3r2TOxER6myBYzZJNoDqol+DUHu3NnfBN/GETnUVxecZJab0CHQQ8GZc0jjzpFymepjcLDPS4SQg=="],
|
"venice-ai-sdk-provider": ["venice-ai-sdk-provider@2.0.2", "", { "dependencies": { "@ai-sdk/openai-compatible": "^2.0.47", "@ai-sdk/provider": "^3.0.10", "@ai-sdk/provider-utils": "^4.0.27" }, "peerDependencies": { "ai": "^6.0.90" } }, "sha512-aoa05nI3BTK5aGbjBflq+Gfln2AHAkwNbWuGGvCzUIsOfp5Y3iPD4O4PUGDAEiWVJWbjpPn0KfDa0H/HebwsaA=="],
|
||||||
|
|
||||||
"verror": ["verror@1.10.1", "", { "dependencies": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", "extsprintf": "^1.2.0" } }, "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg=="],
|
"verror": ["verror@1.10.1", "", { "dependencies": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", "extsprintf": "^1.2.0" } }, "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg=="],
|
||||||
|
|
||||||
@@ -6449,11 +6449,11 @@
|
|||||||
|
|
||||||
"unzipper/fs-extra": ["fs-extra@11.3.5", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg=="],
|
"unzipper/fs-extra": ["fs-extra@11.3.5", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg=="],
|
||||||
|
|
||||||
"venice-ai-sdk-provider/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.53", "", { "dependencies": { "@ai-sdk/provider": "3.0.12", "@ai-sdk/provider-utils": "4.0.32" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-SoPSkrL5cbNQnAljRsJ7pOzJ2FmWgnhC0lfFOda873ycCdFJL1A+h3Ib7mX2spcv3XnNaO13y/45/0RyqNWlIQ=="],
|
"venice-ai-sdk-provider/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.47", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Enm5UlL0zUCrW3792opk5h7hRWxZOZzDe6eQYVFqX9LUOGGCe1h8MZWAGim765nwzgnjlpeYOsuzZmLtRsTPlg=="],
|
||||||
|
|
||||||
"venice-ai-sdk-provider/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="],
|
"venice-ai-sdk-provider/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="],
|
||||||
|
|
||||||
"venice-ai-sdk-provider/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.32", "", { "dependencies": { "@ai-sdk/provider": "3.0.12", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Kwj499fTcN9bP/AfGoPU7JWIXeP6VZqKI6omsH062c9E2G4gdjeJczkz4z/tYSkzYjLE2AI3DtZbMfs6D7vn2Q=="],
|
"venice-ai-sdk-provider/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="],
|
||||||
|
|
||||||
"verror/core-util-is": ["core-util-is@1.0.2", "", {}, "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ=="],
|
"verror/core-util-is": ["core-util-is@1.0.2", "", {}, "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ=="],
|
||||||
|
|
||||||
@@ -7073,10 +7073,6 @@
|
|||||||
|
|
||||||
"unzipper/fs-extra/jsonfile": ["jsonfile@6.2.1", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q=="],
|
"unzipper/fs-extra/jsonfile": ["jsonfile@6.2.1", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q=="],
|
||||||
|
|
||||||
"venice-ai-sdk-provider/@ai-sdk/openai-compatible/@ai-sdk/provider": ["@ai-sdk/provider@3.0.12", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-sj9DWTJ2Ze0WR9qsiOPqoqzNx3OxL6iMxHImbhvoe9qOspekbzxNDMiJ4TIGfYHYh9w4OmBjz3prvqhzTi96+Q=="],
|
|
||||||
|
|
||||||
"venice-ai-sdk-provider/@ai-sdk/provider-utils/@ai-sdk/provider": ["@ai-sdk/provider@3.0.12", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-sj9DWTJ2Ze0WR9qsiOPqoqzNx3OxL6iMxHImbhvoe9qOspekbzxNDMiJ4TIGfYHYh9w4OmBjz3prvqhzTi96+Q=="],
|
|
||||||
|
|
||||||
"venice-ai-sdk-provider/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
"venice-ai-sdk-provider/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||||
|
|
||||||
"vitest/@vitest/expect/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
"vitest/@vitest/expect/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||||
|
|||||||
+4
-4
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"nodeModules": {
|
"nodeModules": {
|
||||||
"x86_64-linux": "sha256-Ulflihjr8JDRVyNxSchoqaey6z/12256zs1FOiw+4vo=",
|
"x86_64-linux": "sha256-JXQ9PAqqRlJtHa8T3ZxqdRRyxC+0ip+2wSnehvKXUbI=",
|
||||||
"aarch64-linux": "sha256-wP6p30a9f7s3tua5qbIq/10OHdbJb8xUni5KxE3F+/k=",
|
"aarch64-linux": "sha256-nI+RaxDXmAcjhSjCtIvyi292xBEg0E5NXaoyGrJE69s=",
|
||||||
"aarch64-darwin": "sha256-7+4skFiJ+tLSjNG6WR53bT/IektA5B7gEcWLYpkK4z8=",
|
"aarch64-darwin": "sha256-UleKpm7Khf3kibm4BqZJ9bmu0N2kOjNd77g1Bd2tmfw=",
|
||||||
"x86_64-darwin": "sha256-NL945zD4J6HomH5SF0XIO8ZFCbIh2MTsJFtTyuxm2Q0="
|
"x86_64-darwin": "sha256-V4AQH868dOfVhEj1mKTnZlhpZwFqBYsHS28Tx9VXHkE="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/app",
|
"name": "@opencode-ai/app",
|
||||||
"version": "1.17.12",
|
"version": "1.17.11",
|
||||||
"description": "",
|
"description": "",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import {
|
|||||||
Show,
|
Show,
|
||||||
} from "solid-js"
|
} from "solid-js"
|
||||||
import { Dynamic } from "solid-js/web"
|
import { Dynamic } from "solid-js/web"
|
||||||
import { CommandProvider, useCommand, type CommandOption } from "@/context/command"
|
import { CommandProvider } from "@/context/command"
|
||||||
import { CommentsProvider } from "@/context/comments"
|
import { CommentsProvider } from "@/context/comments"
|
||||||
import { FileProvider } from "@/context/file"
|
import { FileProvider } from "@/context/file"
|
||||||
import { ServerSDKProvider, useServerSDK } from "@/context/server-sdk"
|
import { ServerSDKProvider, useServerSDK } from "@/context/server-sdk"
|
||||||
@@ -40,7 +40,6 @@ import { LayoutProvider } from "@/context/layout"
|
|||||||
import { ModelsProvider } from "@/context/models"
|
import { ModelsProvider } from "@/context/models"
|
||||||
import { NotificationProvider, useNotification } from "@/context/notification"
|
import { NotificationProvider, useNotification } from "@/context/notification"
|
||||||
import { PermissionProvider } from "@/context/permission"
|
import { PermissionProvider } from "@/context/permission"
|
||||||
import { usePlatform } from "@/context/platform"
|
|
||||||
import { PromptProvider } from "@/context/prompt"
|
import { PromptProvider } from "@/context/prompt"
|
||||||
import { ServerConnection, ServerProvider, serverName, useServer } from "@/context/server"
|
import { ServerConnection, ServerProvider, serverName, useServer } from "@/context/server"
|
||||||
import { SettingsProvider, useSettings } from "@/context/settings"
|
import { SettingsProvider, useSettings } from "@/context/settings"
|
||||||
@@ -301,36 +300,12 @@ function SharedProviders(props: ParentProps) {
|
|||||||
<>
|
<>
|
||||||
<BodyDesignClass />
|
<BodyDesignClass />
|
||||||
<CommandProvider>
|
<CommandProvider>
|
||||||
<DesktopCommands />
|
|
||||||
<HighlightsProvider>{props.children}</HighlightsProvider>
|
<HighlightsProvider>{props.children}</HighlightsProvider>
|
||||||
</CommandProvider>
|
</CommandProvider>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function DesktopCommands() {
|
|
||||||
const command = useCommand()
|
|
||||||
const language = useLanguage()
|
|
||||||
const platform = usePlatform()
|
|
||||||
|
|
||||||
command.register("desktop", () => {
|
|
||||||
const commands: CommandOption[] = []
|
|
||||||
if (platform.platform === "desktop" && platform.exportDebugLogs) {
|
|
||||||
commands.push({
|
|
||||||
id: "logs.export",
|
|
||||||
title: "Export logs",
|
|
||||||
category: language.t("command.category.settings"),
|
|
||||||
onSelect: () => {
|
|
||||||
void platform.exportDebugLogs?.()
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return commands
|
|
||||||
})
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
// Server-scoped providers shared by the legacy shell and the top-level new shell.
|
// Server-scoped providers shared by the legacy shell and the top-level new shell.
|
||||||
type ServerScopedShellProps = ParentProps<{
|
type ServerScopedShellProps = ParentProps<{
|
||||||
directory?: () => string | undefined
|
directory?: () => string | undefined
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ const ModelList: Component<{
|
|||||||
class="w-full"
|
class="w-full"
|
||||||
placement="right-start"
|
placement="right-start"
|
||||||
gutter={12}
|
gutter={12}
|
||||||
|
openDelay={0}
|
||||||
value={<ModelTooltip model={item} latest={item.latest} free={isFree(item.provider.id, item.cost)} />}
|
value={<ModelTooltip model={item} latest={item.latest} free={isFree(item.provider.id, item.cost)} />}
|
||||||
>
|
>
|
||||||
{node}
|
{node}
|
||||||
|
|||||||
@@ -68,7 +68,6 @@ import { promptPlaceholder } from "./prompt-input/placeholder"
|
|||||||
import { createPromptInputTransientState } from "./prompt-input/transient-state"
|
import { createPromptInputTransientState } from "./prompt-input/transient-state"
|
||||||
import { showToast } from "@/utils/toast"
|
import { showToast } from "@/utils/toast"
|
||||||
import { ImagePreview } from "@opencode-ai/ui/image-preview"
|
import { ImagePreview } from "@opencode-ai/ui/image-preview"
|
||||||
import type { ReferenceInfo } from "@opencode-ai/sdk/v2/client"
|
|
||||||
|
|
||||||
export type PromptInputState = ReturnType<typeof usePrompt>
|
export type PromptInputState = ReturnType<typeof usePrompt>
|
||||||
|
|
||||||
@@ -215,7 +214,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
let fileInputRef: HTMLInputElement | undefined
|
let fileInputRef: HTMLInputElement | undefined
|
||||||
let scrollRef!: HTMLDivElement
|
let scrollRef!: HTMLDivElement
|
||||||
let slashPopoverRef!: HTMLDivElement
|
let slashPopoverRef!: HTMLDivElement
|
||||||
let restoreEndOnFocus = true
|
|
||||||
|
|
||||||
const mirror = { input: false }
|
const mirror = { input: false }
|
||||||
const inset = 56
|
const inset = 56
|
||||||
@@ -595,16 +593,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleFocus = () => {
|
|
||||||
if (!restoreEndOnFocus) return
|
|
||||||
restoreEndOnFocus = false
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
if (document.activeElement !== editorRef) return
|
|
||||||
setCursorPosition(editorRef, prompt.cursor() ?? promptLength(prompt.current()))
|
|
||||||
queueScroll()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const renderEditorWithCursor = (parts: Prompt) => {
|
const renderEditorWithCursor = (parts: Prompt) => {
|
||||||
const cursor = currentCursor()
|
const cursor = currentCursor()
|
||||||
renderEditor(parts)
|
renderEditor(parts)
|
||||||
@@ -641,89 +629,24 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const referenceDescription = (reference: ReferenceInfo) =>
|
|
||||||
reference.source.type === "git" ? reference.source.repository : reference.source.path
|
|
||||||
|
|
||||||
const referenceList = createMemo(() =>
|
|
||||||
sync()
|
|
||||||
.data.reference.filter((reference) => !reference.hidden)
|
|
||||||
.map(
|
|
||||||
(reference): AtOption => ({
|
|
||||||
type: "reference",
|
|
||||||
name: reference.name,
|
|
||||||
path: reference.path,
|
|
||||||
display: reference.name,
|
|
||||||
description: reference.description ?? referenceDescription(reference),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
const agentList = createMemo(() =>
|
const agentList = createMemo(() =>
|
||||||
props.controls.agents.available
|
props.controls.agents.available
|
||||||
.filter((agent) => !agent.hidden && agent.mode !== "primary")
|
.filter((agent) => !agent.hidden && agent.mode !== "primary")
|
||||||
.map((agent): AtOption => ({ type: "agent", name: agent.name, display: agent.name })),
|
.map((agent): AtOption => ({ type: "agent", name: agent.name, display: agent.name })),
|
||||||
)
|
)
|
||||||
|
|
||||||
const mcpResourceList = createMemo(() =>
|
|
||||||
Object.values(sync().data.mcp_resource).map(
|
|
||||||
(resource): AtOption => ({
|
|
||||||
type: "resource",
|
|
||||||
name: resource.name,
|
|
||||||
uri: resource.uri,
|
|
||||||
client: resource.client,
|
|
||||||
display: resource.name,
|
|
||||||
description: resource.description,
|
|
||||||
mime: resource.mimeType,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
const handleAtSelect = (option: AtOption | undefined) => {
|
const handleAtSelect = (option: AtOption | undefined) => {
|
||||||
if (!option) return
|
if (!option) return
|
||||||
if (option.type === "agent") {
|
if (option.type === "agent") {
|
||||||
addPart({ type: "agent", name: option.name, content: "@" + option.name, start: 0, end: 0 })
|
addPart({ type: "agent", name: option.name, content: "@" + option.name, start: 0, end: 0 })
|
||||||
return
|
} else {
|
||||||
|
addPart({ type: "file", path: option.path, content: "@" + option.path, start: 0, end: 0 })
|
||||||
}
|
}
|
||||||
if (option.type === "reference") {
|
|
||||||
addPart({
|
|
||||||
type: "file",
|
|
||||||
path: option.path,
|
|
||||||
content: "@" + option.name,
|
|
||||||
start: 0,
|
|
||||||
end: 0,
|
|
||||||
mime: "application/x-directory",
|
|
||||||
filename: option.name,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (option.type === "resource") {
|
|
||||||
addPart({
|
|
||||||
type: "file",
|
|
||||||
path: option.uri,
|
|
||||||
content: "@" + option.name,
|
|
||||||
start: 0,
|
|
||||||
end: 0,
|
|
||||||
mime: option.mime ?? "text/plain",
|
|
||||||
filename: option.name,
|
|
||||||
url: option.uri,
|
|
||||||
source: {
|
|
||||||
type: "resource",
|
|
||||||
text: { value: "@" + option.name, start: 0, end: 0 },
|
|
||||||
clientName: option.client,
|
|
||||||
uri: option.uri,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
addPart({ type: "file", path: option.path, content: "@" + option.path, start: 0, end: 0 })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const atKey = (x: AtOption | undefined) => {
|
const atKey = (x: AtOption | undefined) => {
|
||||||
if (!x) return ""
|
if (!x) return ""
|
||||||
if (x.type === "agent") return `agent:${x.name}`
|
return x.type === "agent" ? `agent:${x.name}` : `file:${x.path}`
|
||||||
if (x.type === "reference") return `reference:${x.name}`
|
|
||||||
if (x.type === "resource") return `resource:${x.client}:${x.uri}`
|
|
||||||
return `file:${x.path}`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -734,36 +657,30 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
onKeyDown: atOnKeyDown,
|
onKeyDown: atOnKeyDown,
|
||||||
} = useFilteredList<AtOption>({
|
} = useFilteredList<AtOption>({
|
||||||
items: async (query) => {
|
items: async (query) => {
|
||||||
const references = referenceList()
|
|
||||||
const agents = agentList()
|
const agents = agentList()
|
||||||
const mcpResources = mcpResourceList()
|
|
||||||
const open = recent()
|
const open = recent()
|
||||||
const seen = new Set(open)
|
const seen = new Set(open)
|
||||||
const pinned: AtOption[] = open.map((path) => ({ type: "file", path, display: path, recent: true }))
|
const pinned: AtOption[] = open.map((path) => ({ type: "file", path, display: path, recent: true }))
|
||||||
if (!query.trim()) return [...references, ...agents, ...mcpResources, ...pinned]
|
if (!query.trim()) return [...agents, ...pinned]
|
||||||
const paths = await files.searchFilesAndDirectories(query)
|
const paths = await files.searchFilesAndDirectories(query)
|
||||||
const fileOptions: AtOption[] = paths
|
const fileOptions: AtOption[] = paths
|
||||||
.filter((path) => !seen.has(path))
|
.filter((path) => !seen.has(path))
|
||||||
.map((path) => ({ type: "file", path, display: path }))
|
.map((path) => ({ type: "file", path, display: path }))
|
||||||
return [...references, ...agents, ...mcpResources, ...pinned, ...fileOptions]
|
return [...agents, ...pinned, ...fileOptions]
|
||||||
},
|
},
|
||||||
key: atKey,
|
key: atKey,
|
||||||
filterKeys: ["display"],
|
filterKeys: ["display"],
|
||||||
skipFilter: (item) => item.type === "file" && !item.recent,
|
skipFilter: (item) => item.type === "file" && !item.recent,
|
||||||
groupBy: (item) => {
|
groupBy: (item) => {
|
||||||
if (item.type === "reference") return "reference"
|
|
||||||
if (item.type === "agent") return "agent"
|
if (item.type === "agent") return "agent"
|
||||||
if (item.type === "resource") return "resource"
|
|
||||||
if (item.recent) return "recent"
|
if (item.recent) return "recent"
|
||||||
return "file"
|
return "file"
|
||||||
},
|
},
|
||||||
sortGroupsBy: (a, b) => {
|
sortGroupsBy: (a, b) => {
|
||||||
const rank = (category: string) => {
|
const rank = (category: string) => {
|
||||||
if (category === "reference") return 0
|
if (category === "agent") return 0
|
||||||
if (category === "agent") return 1
|
if (category === "recent") return 1
|
||||||
if (category === "resource") return 2
|
return 2
|
||||||
if (category === "recent") return 3
|
|
||||||
return 4
|
|
||||||
}
|
}
|
||||||
return rank(a.category) - rank(b.category)
|
return rank(a.category) - rank(b.category)
|
||||||
},
|
},
|
||||||
@@ -829,17 +746,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
const pill = document.createElement("span")
|
const pill = document.createElement("span")
|
||||||
pill.textContent = part.content
|
pill.textContent = part.content
|
||||||
pill.setAttribute("data-type", part.type)
|
pill.setAttribute("data-type", part.type)
|
||||||
if (part.type === "file") {
|
if (part.type === "file") pill.setAttribute("data-path", part.path)
|
||||||
pill.setAttribute("data-path", part.path)
|
|
||||||
if (part.mime) pill.setAttribute("data-mime", part.mime)
|
|
||||||
if (part.filename) pill.setAttribute("data-filename", part.filename)
|
|
||||||
if (part.url) pill.setAttribute("data-url", part.url)
|
|
||||||
if (part.source?.type === "resource") {
|
|
||||||
pill.setAttribute("data-source-type", part.source.type)
|
|
||||||
pill.setAttribute("data-source-client-name", part.source.clientName)
|
|
||||||
pill.setAttribute("data-source-uri", part.source.uri)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (part.type === "agent") pill.setAttribute("data-name", part.name)
|
if (part.type === "agent") pill.setAttribute("data-name", part.name)
|
||||||
pill.setAttribute("contenteditable", "false")
|
pill.setAttribute("contenteditable", "false")
|
||||||
pill.style.userSelect = "text"
|
pill.style.userSelect = "text"
|
||||||
@@ -954,29 +861,12 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
|
|
||||||
const pushFile = (file: HTMLElement) => {
|
const pushFile = (file: HTMLElement) => {
|
||||||
const content = file.textContent ?? ""
|
const content = file.textContent ?? ""
|
||||||
const source =
|
|
||||||
file.dataset.sourceType === "resource" && file.dataset.sourceClientName && file.dataset.sourceUri
|
|
||||||
? {
|
|
||||||
type: "resource" as const,
|
|
||||||
text: {
|
|
||||||
value: content,
|
|
||||||
start: position,
|
|
||||||
end: position + content.length,
|
|
||||||
},
|
|
||||||
clientName: file.dataset.sourceClientName,
|
|
||||||
uri: file.dataset.sourceUri,
|
|
||||||
}
|
|
||||||
: undefined
|
|
||||||
parts.push({
|
parts.push({
|
||||||
type: "file",
|
type: "file",
|
||||||
path: file.dataset.path!,
|
path: file.dataset.path!,
|
||||||
content,
|
content,
|
||||||
start: position,
|
start: position,
|
||||||
end: position + content.length,
|
end: position + content.length,
|
||||||
...(file.dataset.mime ? { mime: file.dataset.mime } : {}),
|
|
||||||
...(file.dataset.filename ? { filename: file.dataset.filename } : {}),
|
|
||||||
...(file.dataset.url ? { url: file.dataset.url } : {}),
|
|
||||||
...(source ? { source } : {}),
|
|
||||||
})
|
})
|
||||||
position += content.length
|
position += content.length
|
||||||
}
|
}
|
||||||
@@ -1490,11 +1380,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
const newSession = () => props.variant === "new-session"
|
const newSession = () => props.variant === "new-session"
|
||||||
const bindEditorRef = (el: HTMLDivElement) => {
|
|
||||||
editorRef = el
|
|
||||||
restoreEndOnFocus = true
|
|
||||||
props.ref?.(el)
|
|
||||||
}
|
|
||||||
const showAgentControl = createMemo(() => props.controls.agents.visible && props.controls.agents.options.length > 0)
|
const showAgentControl = createMemo(() => props.controls.agents.visible && props.controls.agents.options.length > 0)
|
||||||
const agentControlState = createMemo<ComposerAgentControlState>(() => ({
|
const agentControlState = createMemo<ComposerAgentControlState>(() => ({
|
||||||
title: language.t("command.agent.cycle"),
|
title: language.t("command.agent.cycle"),
|
||||||
@@ -1578,7 +1463,10 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
<div class="relative max-h-[180px] overflow-y-auto no-scrollbar" ref={(el) => (scrollRef = el)}>
|
<div class="relative max-h-[180px] overflow-y-auto no-scrollbar" ref={(el) => (scrollRef = el)}>
|
||||||
<div
|
<div
|
||||||
data-component="prompt-input"
|
data-component="prompt-input"
|
||||||
ref={bindEditorRef}
|
ref={(el) => {
|
||||||
|
editorRef = el
|
||||||
|
props.ref?.(el)
|
||||||
|
}}
|
||||||
role="textbox"
|
role="textbox"
|
||||||
aria-multiline="true"
|
aria-multiline="true"
|
||||||
aria-label={designPlaceholder()}
|
aria-label={designPlaceholder()}
|
||||||
@@ -1593,7 +1481,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
onPaste={handlePaste}
|
onPaste={handlePaste}
|
||||||
onCompositionStart={handleCompositionStart}
|
onCompositionStart={handleCompositionStart}
|
||||||
onCompositionEnd={handleCompositionEnd}
|
onCompositionEnd={handleCompositionEnd}
|
||||||
onFocus={handleFocus}
|
|
||||||
onBlur={handleBlur}
|
onBlur={handleBlur}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
classList={{
|
classList={{
|
||||||
@@ -1614,7 +1501,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex h-11 items-center px-2">
|
<div class="flex h-11 items-center px-2">
|
||||||
<div class="flex min-w-0 flex-1 items-center gap-1">
|
<div class="flex min-w-0 flex-1 items-center gap-0">
|
||||||
{fileAttachmentInput()}
|
{fileAttachmentInput()}
|
||||||
<TooltipV2
|
<TooltipV2
|
||||||
placement="top"
|
placement="top"
|
||||||
@@ -1757,7 +1644,10 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
data-component="prompt-input"
|
data-component="prompt-input"
|
||||||
ref={bindEditorRef}
|
ref={(el) => {
|
||||||
|
editorRef = el
|
||||||
|
props.ref?.(el)
|
||||||
|
}}
|
||||||
role="textbox"
|
role="textbox"
|
||||||
aria-multiline="true"
|
aria-multiline="true"
|
||||||
aria-label={placeholder()}
|
aria-label={placeholder()}
|
||||||
@@ -1772,7 +1662,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
onPaste={handlePaste}
|
onPaste={handlePaste}
|
||||||
onCompositionStart={handleCompositionStart}
|
onCompositionStart={handleCompositionStart}
|
||||||
onCompositionEnd={handleCompositionEnd}
|
onCompositionEnd={handleCompositionEnd}
|
||||||
onFocus={handleFocus}
|
|
||||||
onBlur={handleBlur}
|
onBlur={handleBlur}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
classList={{
|
classList={{
|
||||||
|
|||||||
@@ -100,41 +100,6 @@ describe("buildRequestParts", () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test("preserves reference aliases as directory file parts", () => {
|
|
||||||
const result = buildRequestParts({
|
|
||||||
prompt: [
|
|
||||||
{
|
|
||||||
type: "file",
|
|
||||||
path: "/repo/../docs",
|
|
||||||
content: "@docs",
|
|
||||||
start: 0,
|
|
||||||
end: 5,
|
|
||||||
mime: "application/x-directory",
|
|
||||||
filename: "docs",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
context: [],
|
|
||||||
images: [],
|
|
||||||
text: "@docs",
|
|
||||||
messageID: "msg_reference",
|
|
||||||
sessionID: "ses_reference",
|
|
||||||
sessionDirectory: "/repo/app",
|
|
||||||
})
|
|
||||||
|
|
||||||
const filePart = result.requestParts.find((part) => part.type === "file")
|
|
||||||
expect(filePart).toBeDefined()
|
|
||||||
if (filePart?.type === "file") {
|
|
||||||
expect(filePart.mime).toBe("application/x-directory")
|
|
||||||
expect(filePart.filename).toBe("docs")
|
|
||||||
expect(filePart.url).toBe("file:///repo/../docs")
|
|
||||||
expect(filePart.source?.type).toBe("file")
|
|
||||||
if (filePart.source?.type === "file") {
|
|
||||||
expect(filePart.source.path).toBe("/repo/../docs")
|
|
||||||
expect(filePart.source.text.value).toBe("@docs")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
test("deduplicates context files when prompt already includes same path", () => {
|
test("deduplicates context files when prompt already includes same path", () => {
|
||||||
const prompt: Prompt = [{ type: "file", path: "src/foo.ts", content: "@src/foo.ts", start: 0, end: 11 }]
|
const prompt: Prompt = [{ type: "file", path: "src/foo.ts", content: "@src/foo.ts", start: 0, end: 11 }]
|
||||||
|
|
||||||
|
|||||||
@@ -99,31 +99,21 @@ export function buildRequestParts(input: BuildRequestPartsInput) {
|
|||||||
|
|
||||||
const files = input.prompt.filter(isFileAttachment).map((attachment) => {
|
const files = input.prompt.filter(isFileAttachment).map((attachment) => {
|
||||||
const path = absolute(input.sessionDirectory, attachment.path)
|
const path = absolute(input.sessionDirectory, attachment.path)
|
||||||
const source = attachment.source
|
|
||||||
? {
|
|
||||||
...attachment.source,
|
|
||||||
text: {
|
|
||||||
value: attachment.content,
|
|
||||||
start: attachment.start,
|
|
||||||
end: attachment.end,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
type: "file" as const,
|
|
||||||
text: {
|
|
||||||
value: attachment.content,
|
|
||||||
start: attachment.start,
|
|
||||||
end: attachment.end,
|
|
||||||
},
|
|
||||||
path,
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
id: Identifier.ascending("part"),
|
id: Identifier.ascending("part"),
|
||||||
type: "file",
|
type: "file",
|
||||||
mime: attachment.mime ?? "text/plain",
|
mime: "text/plain",
|
||||||
url: attachment.url ?? `file://${encodeFilePath(path)}${fileQuery(attachment.selection)}`,
|
url: `file://${encodeFilePath(path)}${fileQuery(attachment.selection)}`,
|
||||||
filename: attachment.filename ?? getFilename(attachment.path),
|
filename: getFilename(attachment.path),
|
||||||
source,
|
source: {
|
||||||
|
type: "file",
|
||||||
|
text: {
|
||||||
|
value: attachment.content,
|
||||||
|
start: attachment.start,
|
||||||
|
end: attachment.end,
|
||||||
|
},
|
||||||
|
path,
|
||||||
|
},
|
||||||
} satisfies PromptRequestPart
|
} satisfies PromptRequestPart
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export const PromptContextItems: Component<ContextItemsProps> = (props) => {
|
|||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
placement="top"
|
placement="top"
|
||||||
openDelay={800}
|
openDelay={2000}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
classList={{
|
classList={{
|
||||||
|
|||||||
@@ -7,16 +7,6 @@ import { getDirectory, getFilename } from "@opencode-ai/core/util/path"
|
|||||||
|
|
||||||
export type AtOption =
|
export type AtOption =
|
||||||
| { type: "agent"; name: string; display: string }
|
| { type: "agent"; name: string; display: string }
|
||||||
| {
|
|
||||||
type: "resource"
|
|
||||||
name: string
|
|
||||||
uri: string
|
|
||||||
client: string
|
|
||||||
display: string
|
|
||||||
description?: string
|
|
||||||
mime?: string
|
|
||||||
}
|
|
||||||
| { type: "reference"; name: string; path: string; display: string; description: string }
|
|
||||||
| { type: "file"; path: string; display: string; recent?: boolean }
|
| { type: "file"; path: string; display: string; recent?: boolean }
|
||||||
|
|
||||||
export interface SlashCommand {
|
export interface SlashCommand {
|
||||||
@@ -113,94 +103,6 @@ export const PromptPopover: Component<PromptPopoverProps> = (props) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.type === "resource") {
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
class="w-full flex items-center gap-x-2 px-2 py-0.5"
|
|
||||||
classList={{
|
|
||||||
"rounded-[4px]": props.newLayoutDesigns,
|
|
||||||
"rounded-md": !props.newLayoutDesigns,
|
|
||||||
"bg-v2-overlay-simple-overlay-hover": props.newLayoutDesigns && props.atActive === key,
|
|
||||||
"bg-surface-raised-base-hover": !props.newLayoutDesigns && props.atActive === key,
|
|
||||||
}}
|
|
||||||
onClick={() => props.onAtSelect(item)}
|
|
||||||
onPointerMove={() => props.setAtActive(key)}
|
|
||||||
>
|
|
||||||
<FileIcon node={{ path: item.uri, type: "file" }} class="shrink-0 size-4" />
|
|
||||||
<div
|
|
||||||
class="flex items-center min-w-0"
|
|
||||||
classList={{
|
|
||||||
"text-[13px] leading-[calc(var(--font-size-base)*1.8)] tracking-[-0.04px] [font-weight:440]":
|
|
||||||
props.newLayoutDesigns,
|
|
||||||
"text-14-regular": !props.newLayoutDesigns,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="text-text-strong whitespace-nowrap"
|
|
||||||
classList={{ "text-v2-text-text-base": props.newLayoutDesigns }}
|
|
||||||
>
|
|
||||||
@{item.name}
|
|
||||||
</span>
|
|
||||||
<Show when={item.description}>
|
|
||||||
{(description) => (
|
|
||||||
<span
|
|
||||||
class="whitespace-nowrap truncate min-w-0 ml-2"
|
|
||||||
classList={{
|
|
||||||
"text-v2-text-text-muted": props.newLayoutDesigns,
|
|
||||||
"text-text-weak": !props.newLayoutDesigns,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{description()}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.type === "reference") {
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
class="w-full flex items-center gap-x-2 px-2 py-0.5"
|
|
||||||
classList={{
|
|
||||||
"rounded-[4px]": props.newLayoutDesigns,
|
|
||||||
"rounded-md": !props.newLayoutDesigns,
|
|
||||||
"bg-v2-overlay-simple-overlay-hover": props.newLayoutDesigns && props.atActive === key,
|
|
||||||
"bg-surface-raised-base-hover": !props.newLayoutDesigns && props.atActive === key,
|
|
||||||
}}
|
|
||||||
onClick={() => props.onAtSelect(item)}
|
|
||||||
onPointerMove={() => props.setAtActive(key)}
|
|
||||||
>
|
|
||||||
<FileIcon node={{ path: item.path, type: "directory" }} class="shrink-0 size-4" />
|
|
||||||
<div
|
|
||||||
class="flex items-center min-w-0"
|
|
||||||
classList={{
|
|
||||||
"text-[13px] leading-[calc(var(--font-size-base)*1.8)] tracking-[-0.04px] [font-weight:440]":
|
|
||||||
props.newLayoutDesigns,
|
|
||||||
"text-14-regular": !props.newLayoutDesigns,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="text-text-strong whitespace-nowrap"
|
|
||||||
classList={{ "text-v2-text-text-base": props.newLayoutDesigns }}
|
|
||||||
>
|
|
||||||
@{item.name}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="whitespace-nowrap truncate min-w-0 ml-2"
|
|
||||||
classList={{
|
|
||||||
"text-v2-text-text-muted": props.newLayoutDesigns,
|
|
||||||
"text-text-weak": !props.newLayoutDesigns,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{item.description}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const isDirectory = item.path.endsWith("/")
|
const isDirectory = item.path.endsWith("/")
|
||||||
const directory = isDirectory ? item.path : getDirectory(item.path)
|
const directory = isDirectory ? item.path : getDirectory(item.path)
|
||||||
const filename = isDirectory ? "" : getFilename(item.path)
|
const filename = isDirectory ? "" : getFilename(item.path)
|
||||||
|
|||||||
@@ -92,17 +92,11 @@ export function PromptWorkspaceSelector(props: {
|
|||||||
</MenuV2.Content>
|
</MenuV2.Content>
|
||||||
</MenuV2.Portal>
|
</MenuV2.Portal>
|
||||||
</MenuV2>
|
</MenuV2>
|
||||||
<Show when={props.branch}>
|
<span class="hidden select-none opacity-50 sm:inline mx-1">/</span>
|
||||||
{(branch) => (
|
<div class="flex h-7 min-w-0 max-w-[220px] items-center gap-1.5 px-2 text-[13px] font-[440] leading-5 tracking-[-0.04px]">
|
||||||
<>
|
<Icon name="branch" size="small" class="shrink-0 text-v2-icon-icon-muted" />
|
||||||
<span class="hidden select-none opacity-50 sm:inline mx-1">/</span>
|
<span class="min-w-0 truncate">{props.branch || "main"}</span>
|
||||||
<div class="flex h-7 min-w-0 max-w-[220px] items-center gap-1.5 px-2 text-[13px] font-[440] leading-5 tracking-[-0.04px]">
|
</div>
|
||||||
<Icon name="branch" size="small" class="shrink-0 text-v2-icon-icon-muted" />
|
|
||||||
<span class="min-w-0 truncate">{branch()}</span>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Show>
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ function openSessionContext(args: {
|
|||||||
layout: ReturnType<typeof useLayout>
|
layout: ReturnType<typeof useLayout>
|
||||||
tabs: ReturnType<ReturnType<typeof useLayout>["tabs"]>
|
tabs: ReturnType<ReturnType<typeof useLayout>["tabs"]>
|
||||||
}) {
|
}) {
|
||||||
args.view.reviewPanel.open(args.view.reviewPanel.opened() ? "other" : "context-button")
|
if (!args.view.reviewPanel.opened()) args.view.reviewPanel.open()
|
||||||
if (args.layout.fileTree.opened() && args.layout.fileTree.tab() !== "all") args.layout.fileTree.setTab("all")
|
if (args.layout.fileTree.opened() && args.layout.fileTree.tab() !== "all") args.layout.fileTree.setTab("all")
|
||||||
void args.tabs.open("context")
|
void args.tabs.open("context")
|
||||||
args.tabs.setActive("context")
|
args.tabs.setActive("context")
|
||||||
@@ -64,25 +64,16 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
|
|||||||
const cost = createMemo(() => {
|
const cost = createMemo(() => {
|
||||||
return usd().format(info()?.cost ?? 0)
|
return usd().format(info()?.cost ?? 0)
|
||||||
})
|
})
|
||||||
const contextVisible = createMemo(() => view().reviewPanel.opened() && tabState.activeTab() === "context")
|
|
||||||
const hasOtherTabs = createMemo(() =>
|
|
||||||
tabs()
|
|
||||||
.all()
|
|
||||||
.some((tab) => tab !== "context" && tab !== "review"),
|
|
||||||
)
|
|
||||||
|
|
||||||
const openContext = () => {
|
const openContext = () => {
|
||||||
if (!params.id) return
|
if (!params.id) return
|
||||||
|
|
||||||
const sessionView = view()
|
if (tabState.activeTab() === "context") {
|
||||||
if (contextVisible()) {
|
|
||||||
tabs().close("context")
|
tabs().close("context")
|
||||||
if (sessionView.reviewPanel.source() === "context-button" && !hasOtherTabs()) sessionView.reviewPanel.close()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
openSessionContext({
|
openSessionContext({
|
||||||
view: sessionView,
|
view: view(),
|
||||||
layout,
|
layout,
|
||||||
tabs: tabs(),
|
tabs: tabs(),
|
||||||
})
|
})
|
||||||
@@ -90,20 +81,7 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
|
|||||||
|
|
||||||
const circle = () => (
|
const circle = () => (
|
||||||
<div class="flex items-center justify-center">
|
<div class="flex items-center justify-center">
|
||||||
<ProgressCircle
|
<ProgressCircle size={16} strokeWidth={2} percentage={context()?.usage ?? 0} />
|
||||||
size={16}
|
|
||||||
strokeWidth={2}
|
|
||||||
percentage={context()?.usage ?? 0}
|
|
||||||
style={
|
|
||||||
variant() === "indicator"
|
|
||||||
? {
|
|
||||||
"--progress-circle-background": "var(--v2-background-bg-layer-04, var(--border-weak-base))",
|
|
||||||
"--progress-circle-background-overlay": "var(--v2-overlay-simple-overlay-pressed, transparent)",
|
|
||||||
"--progress-circle-progress": "var(--v2-icon-icon-base, var(--icon-base))",
|
|
||||||
}
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
const circleV2 = () => (
|
const circleV2 = () => (
|
||||||
@@ -141,10 +119,10 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Show when={params.id}>
|
<Show when={params.id}>
|
||||||
<Switch>
|
<Tooltip value={tooltipValue()} placement={props.placement ?? "top"}>
|
||||||
<Match when={variant() === "indicator"}>{circle()}</Match>
|
<Switch>
|
||||||
<Match when={buttonAppearance() === "v2"}>
|
<Match when={variant() === "indicator"}>{circle()}</Match>
|
||||||
<Tooltip value={tooltipValue()} placement={props.placement ?? "top"}>
|
<Match when={buttonAppearance() === "v2"}>
|
||||||
<IconButtonV2
|
<IconButtonV2
|
||||||
type="button"
|
type="button"
|
||||||
variant="ghost-muted"
|
variant="ghost-muted"
|
||||||
@@ -153,10 +131,8 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
|
|||||||
onClick={openContext}
|
onClick={openContext}
|
||||||
aria-label={language.t("context.usage.view")}
|
aria-label={language.t("context.usage.view")}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Match>
|
||||||
</Match>
|
<Match when={true}>
|
||||||
<Match when={true}>
|
|
||||||
<Tooltip value={tooltipValue()} placement={props.placement ?? "top"}>
|
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@@ -166,9 +142,9 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
|
|||||||
>
|
>
|
||||||
{circle()}
|
{circle()}
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Match>
|
||||||
</Match>
|
</Switch>
|
||||||
</Switch>
|
</Tooltip>
|
||||||
</Show>
|
</Show>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -541,7 +541,6 @@ function SessionHeaderV2Actions(props: { state: SessionHeaderV2ActionsState }) {
|
|||||||
</Show>
|
</Show>
|
||||||
<Show when={props.state.reviewVisible}>
|
<Show when={props.state.reviewVisible}>
|
||||||
<TooltipV2
|
<TooltipV2
|
||||||
class="shrink-0"
|
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
value={
|
value={
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-titlebar-tab-slot]:not(:first-child):not([data-active="true"])::before {
|
[data-titlebar-tab-slot]:not(:first-child)::before {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 8px;
|
top: 8px;
|
||||||
@@ -32,10 +32,6 @@
|
|||||||
background: var(--v2-background-bg-layer-02);
|
background: var(--v2-background-bg-layer-02);
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-titlebar-tab-slot][data-active="true"] + [data-titlebar-tab-slot]::before {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-titlebar-tab] [data-slot="tab-close"]::before {
|
[data-titlebar-tab] [data-slot="tab-close"]::before {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
@@ -13,9 +13,6 @@ import type { Session } from "@opencode-ai/sdk/v2"
|
|||||||
import { canOpenTabRename, forwardTabRef } from "./titlebar-tab-gesture"
|
import { canOpenTabRename, forwardTabRef } from "./titlebar-tab-gesture"
|
||||||
import "./titlebar-tab-nav.css"
|
import "./titlebar-tab-nav.css"
|
||||||
|
|
||||||
// MouseEvent.button uses 1 for the middle/wheel button.
|
|
||||||
const MIDDLE_MOUSE_BUTTON = 1
|
|
||||||
|
|
||||||
export function TabNavItem(props: {
|
export function TabNavItem(props: {
|
||||||
ref?: Ref<HTMLDivElement>
|
ref?: Ref<HTMLDivElement>
|
||||||
href: string
|
href: string
|
||||||
@@ -187,12 +184,7 @@ export function TabNavItem(props: {
|
|||||||
data-dragging={props.dragging}
|
data-dragging={props.dragging}
|
||||||
data-pressed={props.pressed}
|
data-pressed={props.pressed}
|
||||||
onMouseDown={(event) => {
|
onMouseDown={(event) => {
|
||||||
if (event.button !== MIDDLE_MOUSE_BUTTON) return
|
if (event.button !== 1) return
|
||||||
event.preventDefault()
|
|
||||||
event.stopPropagation()
|
|
||||||
}}
|
|
||||||
onAuxClick={(event) => {
|
|
||||||
if (event.button !== MIDDLE_MOUSE_BUTTON) return
|
|
||||||
closeTab(event)
|
closeTab(event)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -310,12 +302,7 @@ export function DraftTabItem(props: {
|
|||||||
class="group relative flex h-7 w-full min-w-0 flex-row items-center gap-1.5 overflow-hidden rounded-[6px] bg-[var(--tab-bg)] px-1.5 [container-type:inline-size] whitespace-nowrap [--tab-bg:var(--v2-background-bg-deep)] hover:[--tab-bg:var(--v2-background-bg-layer-02)] has-[>a:focus-visible]:[--tab-bg:var(--v2-background-bg-layer-02)] data-[active='true']:[--tab-bg:var(--v2-background-bg-layer-02)] data-[dragging='true']:[--tab-bg:var(--v2-background-bg-layer-02)] data-[pressed='true']:[--tab-bg:var(--v2-background-bg-layer-02)] data-[editing='true']:[--tab-bg:var(--v2-background-bg-layer-02)]"
|
class="group relative flex h-7 w-full min-w-0 flex-row items-center gap-1.5 overflow-hidden rounded-[6px] bg-[var(--tab-bg)] px-1.5 [container-type:inline-size] whitespace-nowrap [--tab-bg:var(--v2-background-bg-deep)] hover:[--tab-bg:var(--v2-background-bg-layer-02)] has-[>a:focus-visible]:[--tab-bg:var(--v2-background-bg-layer-02)] data-[active='true']:[--tab-bg:var(--v2-background-bg-layer-02)] data-[dragging='true']:[--tab-bg:var(--v2-background-bg-layer-02)] data-[pressed='true']:[--tab-bg:var(--v2-background-bg-layer-02)] data-[editing='true']:[--tab-bg:var(--v2-background-bg-layer-02)]"
|
||||||
classList={{ invisible: props.hidden }}
|
classList={{ invisible: props.hidden }}
|
||||||
onMouseDown={(event) => {
|
onMouseDown={(event) => {
|
||||||
if (event.button !== MIDDLE_MOUSE_BUTTON) return
|
if (event.button !== 1) return
|
||||||
event.preventDefault()
|
|
||||||
event.stopPropagation()
|
|
||||||
}}
|
|
||||||
onAuxClick={(event) => {
|
|
||||||
if (event.button !== MIDDLE_MOUSE_BUTTON) return
|
|
||||||
closeTab(event)
|
closeTab(event)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -85,7 +85,6 @@ function SessionTabSlot(props: {
|
|||||||
ref={sortable.ref}
|
ref={sortable.ref}
|
||||||
data-titlebar-tab-slot
|
data-titlebar-tab-slot
|
||||||
data-tab-key={props.id}
|
data-tab-key={props.id}
|
||||||
data-active={props.active()}
|
|
||||||
class="relative flex w-56 min-w-7 max-w-56 flex-shrink"
|
class="relative flex w-56 min-w-7 max-w-56 flex-shrink"
|
||||||
classList={{ hidden: !session() }}
|
classList={{ hidden: !session() }}
|
||||||
>
|
>
|
||||||
@@ -141,7 +140,6 @@ function DraftTabSlot(props: {
|
|||||||
ref={sortable.ref}
|
ref={sortable.ref}
|
||||||
data-titlebar-tab-slot
|
data-titlebar-tab-slot
|
||||||
data-tab-key={props.id}
|
data-tab-key={props.id}
|
||||||
data-active={props.active()}
|
|
||||||
class="relative flex w-56 min-w-7 max-w-56 flex-shrink"
|
class="relative flex w-56 min-w-7 max-w-56 flex-shrink"
|
||||||
>
|
>
|
||||||
<DraftTabItem
|
<DraftTabItem
|
||||||
|
|||||||
@@ -229,8 +229,7 @@ export function Titlebar(props: { update?: TitlebarUpdate }) {
|
|||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
"min-height": minHeight(),
|
"min-height": minHeight(),
|
||||||
// Keep native macOS traffic lights clear even when the desktop window is narrow.
|
"padding-left": mac() && !mobile() ? `${84 / zoom()}px` : 0,
|
||||||
"padding-left": mac() ? `${84 / zoom()}px` : 0,
|
|
||||||
width: electronWindows() ? `env(titlebar-area-width, calc(100vw - ${windowsControlsWidth()}))` : undefined,
|
width: electronWindows() ? `env(titlebar-area-width, calc(100vw - ${windowsControlsWidth()}))` : undefined,
|
||||||
"max-width": electronWindows()
|
"max-width": electronWindows()
|
||||||
? `env(titlebar-area-width, calc(100vw - ${windowsControlsWidth()}))`
|
? `env(titlebar-area-width, calc(100vw - ${windowsControlsWidth()}))`
|
||||||
@@ -572,7 +571,7 @@ export function Titlebar(props: { update?: TitlebarUpdate }) {
|
|||||||
placement="bottom"
|
placement="bottom"
|
||||||
title={language.t("command.session.new")}
|
title={language.t("command.session.new")}
|
||||||
keybind={command.keybind("session.new")}
|
keybind={command.keybind("session.new")}
|
||||||
openDelay={800}
|
openDelay={2000}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@@ -602,7 +601,7 @@ export function Titlebar(props: { update?: TitlebarUpdate }) {
|
|||||||
>
|
>
|
||||||
<Show when={hasProjects() && nav()}>
|
<Show when={hasProjects() && nav()}>
|
||||||
<div class="flex items-center gap-0 transition-transform">
|
<div class="flex items-center gap-0 transition-transform">
|
||||||
<Tooltip placement="bottom" value={language.t("common.goBack")} openDelay={800}>
|
<Tooltip placement="bottom" value={language.t("common.goBack")} openDelay={2000}>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
icon="chevron-left"
|
icon="chevron-left"
|
||||||
@@ -612,7 +611,7 @@ export function Titlebar(props: { update?: TitlebarUpdate }) {
|
|||||||
aria-label={language.t("common.goBack")}
|
aria-label={language.t("common.goBack")}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip placement="bottom" value={language.t("common.goForward")} openDelay={800}>
|
<Tooltip placement="bottom" value={language.t("common.goForward")} openDelay={2000}>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
icon="chevron-right"
|
icon="chevron-right"
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ describe("bootstrapDirectory", () => {
|
|||||||
status: "loading",
|
status: "loading",
|
||||||
agent: [],
|
agent: [],
|
||||||
command: [],
|
command: [],
|
||||||
reference: [],
|
|
||||||
project: "",
|
project: "",
|
||||||
projectMeta: undefined,
|
projectMeta: undefined,
|
||||||
icon: undefined,
|
icon: undefined,
|
||||||
@@ -36,7 +35,6 @@ describe("bootstrapDirectory", () => {
|
|||||||
question: {},
|
question: {},
|
||||||
mcp_ready: true,
|
mcp_ready: true,
|
||||||
mcp: {},
|
mcp: {},
|
||||||
mcp_resource: {},
|
|
||||||
lsp_ready: true,
|
lsp_ready: true,
|
||||||
lsp: [],
|
lsp: [],
|
||||||
vcs: undefined,
|
vcs: undefined,
|
||||||
@@ -69,7 +67,6 @@ describe("bootstrapDirectory", () => {
|
|||||||
},
|
},
|
||||||
permission: { list: async () => ({ data: [] }) },
|
permission: { list: async () => ({ data: [] }) },
|
||||||
question: { list: async () => ({ data: [] }) },
|
question: { list: async () => ({ data: [] }) },
|
||||||
v2: { reference: { list: async () => ({ data: { data: [] } }) } },
|
|
||||||
mcp: {
|
mcp: {
|
||||||
status: async () => {
|
status: async () => {
|
||||||
mcpReads.push("status")
|
mcpReads.push("status")
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import type {
|
|||||||
Project,
|
Project,
|
||||||
ProviderAuthResponse,
|
ProviderAuthResponse,
|
||||||
QuestionRequest,
|
QuestionRequest,
|
||||||
ReferenceInfo,
|
|
||||||
Session,
|
Session,
|
||||||
} from "@opencode-ai/sdk/v2/client"
|
} from "@opencode-ai/sdk/v2/client"
|
||||||
import { showToast } from "@/utils/toast"
|
import { showToast } from "@/utils/toast"
|
||||||
@@ -19,7 +18,7 @@ import type { ServerSession } from "../server-session"
|
|||||||
import { cmp, normalizeAgentList, normalizeProviderList } from "./utils"
|
import { cmp, normalizeAgentList, normalizeProviderList } from "./utils"
|
||||||
import { formatServerError } from "@/utils/server-errors"
|
import { formatServerError } from "@/utils/server-errors"
|
||||||
import { QueryClient, queryOptions } from "@tanstack/solid-query"
|
import { QueryClient, queryOptions } from "@tanstack/solid-query"
|
||||||
import { loadMcpQuery, loadMcpResourcesQuery } from "../server-sync"
|
import { loadMcpQuery } from "../server-sync"
|
||||||
import { NormalizedProviderListResponse } from "@opencode-ai/session-ui/context"
|
import { NormalizedProviderListResponse } from "@opencode-ai/session-ui/context"
|
||||||
import { ScopedKey, type ServerScope } from "@/utils/server-scope"
|
import { ScopedKey, type ServerScope } from "@/utils/server-scope"
|
||||||
|
|
||||||
@@ -196,13 +195,6 @@ export const loadPathQuery = (scope: ServerScope, directory: string | null, sdk:
|
|||||||
queryFn: () => retry(() => sdk.path.get().then((x) => x.data!)),
|
queryFn: () => retry(() => sdk.path.get().then((x) => x.data!)),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const loadReferencesQuery = (scope: ServerScope, directory: string, sdk: OpencodeClient) =>
|
|
||||||
queryOptions<ReferenceInfo[]>({
|
|
||||||
queryKey: [scope, directory, "references"] as const,
|
|
||||||
queryFn: () => retry(() => sdk.v2.reference.list().then((x) => x.data?.data ?? [])).catch(() => []),
|
|
||||||
placeholderData: [],
|
|
||||||
})
|
|
||||||
|
|
||||||
export async function bootstrapDirectory(input: {
|
export async function bootstrapDirectory(input: {
|
||||||
directory: string
|
directory: string
|
||||||
scope: ServerScope
|
scope: ServerScope
|
||||||
@@ -285,7 +277,6 @@ export async function bootstrapDirectory(input: {
|
|||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
input.mcp && (() => retry(() => input.sdk.command.list().then((x) => input.setStore("command", x.data ?? [])))),
|
input.mcp && (() => retry(() => input.sdk.command.list().then((x) => input.setStore("command", x.data ?? [])))),
|
||||||
() => input.queryClient.fetchQuery(loadReferencesQuery(input.scope, input.directory, input.sdk)),
|
|
||||||
() =>
|
() =>
|
||||||
retry(() =>
|
retry(() =>
|
||||||
input.sdk.permission.list().then((x) => {
|
input.sdk.permission.list().then((x) => {
|
||||||
@@ -348,7 +339,6 @@ export async function bootstrapDirectory(input: {
|
|||||||
),
|
),
|
||||||
() => Promise.resolve(input.loadSessions(input.directory)),
|
() => Promise.resolve(input.loadSessions(input.directory)),
|
||||||
input.mcp && (() => input.queryClient.fetchQuery(loadMcpQuery(input.scope, input.directory, input.sdk))),
|
input.mcp && (() => input.queryClient.fetchQuery(loadMcpQuery(input.scope, input.directory, input.sdk))),
|
||||||
input.mcp && (() => input.queryClient.fetchQuery(loadMcpResourcesQuery(input.scope, input.directory, input.sdk))),
|
|
||||||
() =>
|
() =>
|
||||||
input.queryClient.fetchQuery(loadProvidersQuery(input.scope, input.directory, input.sdk)).catch((err) => {
|
input.queryClient.fetchQuery(loadProvidersQuery(input.scope, input.directory, input.sdk)).catch((err) => {
|
||||||
const project = getFilename(input.directory)
|
const project = getFilename(input.directory)
|
||||||
|
|||||||
@@ -34,9 +34,7 @@ const queryOptionsApi = {
|
|||||||
}),
|
}),
|
||||||
agents: (directory: string) => ({ queryKey: [directory, "agents"], queryFn: async () => [] }),
|
agents: (directory: string) => ({ queryKey: [directory, "agents"], queryFn: async () => [] }),
|
||||||
mcp: (directory: string) => ({ queryKey: [directory, "mcp"], queryFn: async () => ({}) }),
|
mcp: (directory: string) => ({ queryKey: [directory, "mcp"], queryFn: async () => ({}) }),
|
||||||
mcpResources: (directory: string) => ({ queryKey: [directory, "mcpResources"], queryFn: async () => ({}) }),
|
|
||||||
lsp: (directory: string) => ({ queryKey: [directory, "lsp"], queryFn: async () => [] }),
|
lsp: (directory: string) => ({ queryKey: [directory, "lsp"], queryFn: async () => [] }),
|
||||||
references: (directory: string) => ({ queryKey: [directory, "references"], queryFn: async () => [] }),
|
|
||||||
sessions: (directory: string) => ({ queryKey: [directory, "loadSessions"] as const }),
|
sessions: (directory: string) => ({ queryKey: [directory, "loadSessions"] as const }),
|
||||||
} as unknown as QueryOptionsApi
|
} as unknown as QueryOptionsApi
|
||||||
|
|
||||||
@@ -199,18 +197,14 @@ describe("createChildStoreManager", () => {
|
|||||||
try {
|
try {
|
||||||
if (!manager) throw new Error("manager required")
|
if (!manager) throw new Error("manager required")
|
||||||
const [store, setStore] = manager.child("/project", { bootstrap: false })
|
const [store, setStore] = manager.child("/project", { bootstrap: false })
|
||||||
expect(querySingles.length - offset).toBe(6)
|
expect(querySingles.length - offset).toBe(4)
|
||||||
const query = querySingles[offset + 1]
|
const query = querySingles[offset + 1]
|
||||||
const resourceQuery = querySingles[offset + 2]
|
|
||||||
if (!query) throw new Error("query required")
|
if (!query) throw new Error("query required")
|
||||||
if (!resourceQuery) throw new Error("resource query required")
|
|
||||||
expect(query().enabled).toBe(false)
|
expect(query().enabled).toBe(false)
|
||||||
expect(resourceQuery().enabled).toBe(false)
|
|
||||||
|
|
||||||
setStore("status", "complete")
|
setStore("status", "complete")
|
||||||
manager.child("/project", { bootstrap: false, mcp: true })
|
manager.child("/project", { bootstrap: false, mcp: true })
|
||||||
expect(query().enabled).toBe(true)
|
expect(query().enabled).toBe(true)
|
||||||
expect(resourceQuery().enabled).toBe(true)
|
|
||||||
expect(store.mcp).toEqual({ demo: { status: "disabled" } })
|
expect(store.mcp).toEqual({ demo: { status: "disabled" } })
|
||||||
expect(mcpLoads).toEqual(["/project"])
|
expect(mcpLoads).toEqual(["/project"])
|
||||||
|
|
||||||
|
|||||||
@@ -185,10 +185,8 @@ export function createChildStoreManager(input: {
|
|||||||
|
|
||||||
const pathQuery = useQuery(() => input.queryOptions.path(key))
|
const pathQuery = useQuery(() => input.queryOptions.path(key))
|
||||||
const mcpQuery = useQuery(() => ({ ...input.queryOptions.mcp(key), enabled: mcpEnabled() }))
|
const mcpQuery = useQuery(() => ({ ...input.queryOptions.mcp(key), enabled: mcpEnabled() }))
|
||||||
const mcpResourceQuery = useQuery(() => ({ ...input.queryOptions.mcpResources(key), enabled: mcpEnabled() }))
|
|
||||||
const lspQuery = useQuery(() => input.queryOptions.lsp(key))
|
const lspQuery = useQuery(() => input.queryOptions.lsp(key))
|
||||||
const providerQuery = useQuery(() => input.queryOptions.providers(key))
|
const providerQuery = useQuery(() => input.queryOptions.providers(key))
|
||||||
const referenceQuery = useQuery(() => input.queryOptions.references(key))
|
|
||||||
|
|
||||||
const child = createStore<State>({
|
const child = createStore<State>({
|
||||||
project: "",
|
project: "",
|
||||||
@@ -212,9 +210,6 @@ export function createChildStoreManager(input: {
|
|||||||
status: "loading" as const,
|
status: "loading" as const,
|
||||||
agent: [],
|
agent: [],
|
||||||
command: [],
|
command: [],
|
||||||
get reference() {
|
|
||||||
return referenceQuery.isLoading ? [] : (referenceQuery.data ?? [])
|
|
||||||
},
|
|
||||||
session: [],
|
session: [],
|
||||||
sessionTotal: 0,
|
sessionTotal: 0,
|
||||||
session_status: {},
|
session_status: {},
|
||||||
@@ -232,9 +227,6 @@ export function createChildStoreManager(input: {
|
|||||||
get mcp() {
|
get mcp() {
|
||||||
return mcpQuery.isLoading ? {} : (mcpQuery.data ?? {})
|
return mcpQuery.isLoading ? {} : (mcpQuery.data ?? {})
|
||||||
},
|
},
|
||||||
get mcp_resource() {
|
|
||||||
return mcpResourceQuery.isLoading ? {} : (mcpResourceQuery.data ?? {})
|
|
||||||
},
|
|
||||||
get lsp_ready() {
|
get lsp_ready() {
|
||||||
return !lspQuery.isLoading
|
return !lspQuery.isLoading
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -112,7 +112,6 @@ export function applyDirectoryEvent(input: {
|
|||||||
push: (directory: string) => void
|
push: (directory: string) => void
|
||||||
directory: string
|
directory: string
|
||||||
loadLsp: () => void
|
loadLsp: () => void
|
||||||
loadReferences?: () => void
|
|
||||||
vcsCache?: VcsCache
|
vcsCache?: VcsCache
|
||||||
setSessionTodo?: (sessionID: string, todos: Todo[] | undefined) => void
|
setSessionTodo?: (sessionID: string, todos: Todo[] | undefined) => void
|
||||||
retainedLimit?: number
|
retainedLimit?: number
|
||||||
@@ -405,9 +404,5 @@ export function applyDirectoryEvent(input: {
|
|||||||
input.loadLsp()
|
input.loadLsp()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case "reference.updated": {
|
|
||||||
input.loadReferences?.()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,12 @@ import type {
|
|||||||
Command,
|
Command,
|
||||||
Config,
|
Config,
|
||||||
LspStatus,
|
LspStatus,
|
||||||
McpResource,
|
|
||||||
McpStatus,
|
McpStatus,
|
||||||
Message,
|
Message,
|
||||||
Part,
|
Part,
|
||||||
Path,
|
Path,
|
||||||
PermissionRequest,
|
PermissionRequest,
|
||||||
QuestionRequest,
|
QuestionRequest,
|
||||||
ReferenceInfo,
|
|
||||||
Session,
|
Session,
|
||||||
SessionStatus,
|
SessionStatus,
|
||||||
SnapshotFileDiff,
|
SnapshotFileDiff,
|
||||||
@@ -36,7 +34,6 @@ export type State = {
|
|||||||
status: "loading" | "partial" | "complete"
|
status: "loading" | "partial" | "complete"
|
||||||
agent: Agent[]
|
agent: Agent[]
|
||||||
command: Command[]
|
command: Command[]
|
||||||
reference: ReferenceInfo[]
|
|
||||||
project: string
|
project: string
|
||||||
projectMeta: ProjectMeta | undefined
|
projectMeta: ProjectMeta | undefined
|
||||||
icon: string | undefined
|
icon: string | undefined
|
||||||
@@ -66,9 +63,6 @@ export type State = {
|
|||||||
mcp: {
|
mcp: {
|
||||||
[name: string]: McpStatus
|
[name: string]: McpStatus
|
||||||
}
|
}
|
||||||
mcp_resource: {
|
|
||||||
[key: string]: McpResource
|
|
||||||
}
|
|
||||||
lsp_ready: boolean
|
lsp_ready: boolean
|
||||||
lsp: LspStatus[]
|
lsp: LspStatus[]
|
||||||
vcs: VcsInfo | undefined
|
vcs: VcsInfo | undefined
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ const DEFAULT_SIDEBAR_WIDTH = 344
|
|||||||
const DEFAULT_FILE_TREE_WIDTH = 200
|
const DEFAULT_FILE_TREE_WIDTH = 200
|
||||||
const DEFAULT_SESSION_WIDTH = 600
|
const DEFAULT_SESSION_WIDTH = 600
|
||||||
const DEFAULT_TERMINAL_HEIGHT = 280
|
const DEFAULT_TERMINAL_HEIGHT = 280
|
||||||
const DEFAULT_REVIEW_PANEL_OPENED = false
|
|
||||||
export type AvatarColorKey = (typeof AVATAR_COLOR_KEYS)[number]
|
export type AvatarColorKey = (typeof AVATAR_COLOR_KEYS)[number]
|
||||||
|
|
||||||
export function getAvatarColors(key?: string) {
|
export function getAvatarColors(key?: string) {
|
||||||
@@ -78,7 +77,6 @@ export type LocalProject = Partial<Project> & { worktree: string; expanded: bool
|
|||||||
export type HomeProjectSelection = { server: ServerConnection.Key; directory?: string }
|
export type HomeProjectSelection = { server: ServerConnection.Key; directory?: string }
|
||||||
|
|
||||||
export type ReviewDiffStyle = "unified" | "split"
|
export type ReviewDiffStyle = "unified" | "split"
|
||||||
export type ReviewPanelSource = "context-button" | "other"
|
|
||||||
|
|
||||||
export type LayoutRoute =
|
export type LayoutRoute =
|
||||||
| { type: "home" }
|
| { type: "home" }
|
||||||
@@ -212,8 +210,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
|||||||
if (!isRecord(review)) return review
|
if (!isRecord(review)) return review
|
||||||
if (typeof review.panelOpened === "boolean") return review
|
if (typeof review.panelOpened === "boolean") return review
|
||||||
|
|
||||||
const opened =
|
const opened = isRecord(fileTree) && typeof fileTree.opened === "boolean" ? fileTree.opened : true
|
||||||
isRecord(fileTree) && typeof fileTree.opened === "boolean" ? fileTree.opened : DEFAULT_REVIEW_PANEL_OPENED
|
|
||||||
return {
|
return {
|
||||||
...review,
|
...review,
|
||||||
panelOpened: opened,
|
panelOpened: opened,
|
||||||
@@ -282,7 +279,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
|||||||
},
|
},
|
||||||
review: {
|
review: {
|
||||||
diffStyle: "split" as ReviewDiffStyle,
|
diffStyle: "split" as ReviewDiffStyle,
|
||||||
panelOpened: DEFAULT_REVIEW_PANEL_OPENED,
|
panelOpened: true,
|
||||||
},
|
},
|
||||||
fileTree: {
|
fileTree: {
|
||||||
opened: false,
|
opened: false,
|
||||||
@@ -305,9 +302,6 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
const [ephemeral, setEphemeral] = createStore({
|
|
||||||
reviewPanelSource: "other" as ReviewPanelSource,
|
|
||||||
})
|
|
||||||
|
|
||||||
const MAX_SESSION_KEYS = 50
|
const MAX_SESSION_KEYS = 50
|
||||||
const PENDING_MESSAGE_TTL_MS = 2 * 60 * 1000
|
const PENDING_MESSAGE_TTL_MS = 2 * 60 * 1000
|
||||||
@@ -668,7 +662,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
|||||||
diffStyle: createMemo(() => store.review?.diffStyle ?? "split"),
|
diffStyle: createMemo(() => store.review?.diffStyle ?? "split"),
|
||||||
setDiffStyle(diffStyle: ReviewDiffStyle) {
|
setDiffStyle(diffStyle: ReviewDiffStyle) {
|
||||||
if (!store.review) {
|
if (!store.review) {
|
||||||
setStore("review", { diffStyle, panelOpened: DEFAULT_REVIEW_PANEL_OPENED })
|
setStore("review", { diffStyle, panelOpened: true })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setStore("review", "diffStyle", diffStyle)
|
setStore("review", "diffStyle", diffStyle)
|
||||||
@@ -783,8 +777,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
|||||||
const key = createSessionKeyReader(sessionKey, ensureKey)
|
const key = createSessionKeyReader(sessionKey, ensureKey)
|
||||||
const s = createMemo(() => store.sessionView[key()] ?? { scroll: {} })
|
const s = createMemo(() => store.sessionView[key()] ?? { scroll: {} })
|
||||||
const terminalOpened = createMemo(() => store.terminal?.opened ?? false)
|
const terminalOpened = createMemo(() => store.terminal?.opened ?? false)
|
||||||
const reviewPanelOpened = createMemo(() => store.review?.panelOpened ?? DEFAULT_REVIEW_PANEL_OPENED)
|
const reviewPanelOpened = createMemo(() => store.review?.panelOpened ?? true)
|
||||||
const reviewPanelSource = createMemo(() => (reviewPanelOpened() ? ephemeral.reviewPanelSource : "other"))
|
|
||||||
|
|
||||||
function setTerminalOpened(next: boolean) {
|
function setTerminalOpened(next: boolean) {
|
||||||
const current = store.terminal
|
const current = store.terminal
|
||||||
@@ -798,26 +791,16 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
|||||||
setStore("terminal", "opened", next)
|
setStore("terminal", "opened", next)
|
||||||
}
|
}
|
||||||
|
|
||||||
function setReviewPanelOpened(next: boolean, source: ReviewPanelSource) {
|
function setReviewPanelOpened(next: boolean) {
|
||||||
const nextSource = next ? source : "other"
|
|
||||||
const current = store.review
|
const current = store.review
|
||||||
if (!current) {
|
if (!current) {
|
||||||
batch(() => {
|
setStore("review", { diffStyle: "split" as ReviewDiffStyle, panelOpened: next })
|
||||||
setStore("review", { diffStyle: "split" as ReviewDiffStyle, panelOpened: next })
|
|
||||||
setEphemeral("reviewPanelSource", nextSource)
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const value = current.panelOpened ?? DEFAULT_REVIEW_PANEL_OPENED
|
const value = current.panelOpened ?? true
|
||||||
if (value === next) {
|
if (value === next) return
|
||||||
if (ephemeral.reviewPanelSource !== nextSource) setEphemeral("reviewPanelSource", nextSource)
|
setStore("review", "panelOpened", next)
|
||||||
return
|
|
||||||
}
|
|
||||||
batch(() => {
|
|
||||||
setStore("review", "panelOpened", next)
|
|
||||||
setEphemeral("reviewPanelSource", nextSource)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -853,15 +836,14 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
|||||||
},
|
},
|
||||||
reviewPanel: {
|
reviewPanel: {
|
||||||
opened: reviewPanelOpened,
|
opened: reviewPanelOpened,
|
||||||
source: reviewPanelSource,
|
open() {
|
||||||
open(source: ReviewPanelSource = "other") {
|
setReviewPanelOpened(true)
|
||||||
setReviewPanelOpened(true, source)
|
|
||||||
},
|
},
|
||||||
close() {
|
close() {
|
||||||
setReviewPanelOpened(false, "other")
|
setReviewPanelOpened(false)
|
||||||
},
|
},
|
||||||
toggle() {
|
toggle() {
|
||||||
setReviewPanelOpened(!reviewPanelOpened(), "other")
|
setReviewPanelOpened(!reviewPanelOpened())
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
review: {
|
review: {
|
||||||
|
|||||||
@@ -80,7 +80,6 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
|||||||
const [store, setStore] = createStore<{
|
const [store, setStore] = createStore<{
|
||||||
current?: string
|
current?: string
|
||||||
draft?: State
|
draft?: State
|
||||||
promoting?: State
|
|
||||||
last?: {
|
last?: {
|
||||||
type: "agent" | "model" | "variant"
|
type: "agent" | "model" | "variant"
|
||||||
agent?: string
|
agent?: string
|
||||||
@@ -124,7 +123,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
|||||||
|
|
||||||
const scope = createMemo<State | undefined>(() => {
|
const scope = createMemo<State | undefined>(() => {
|
||||||
const session = id()
|
const session = id()
|
||||||
if (!session) return store.draft ?? store.promoting
|
if (!session) return store.draft
|
||||||
return saved.session[session] ?? handoff.get(handoffKey(serverSDK().scope, sdk().directory, session))
|
return saved.session[session] ?? handoff.get(handoffKey(serverSDK().scope, sdk().directory, session))
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -137,13 +136,11 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
|||||||
if (!next) return
|
if (!next) return
|
||||||
if (saved.session[session] !== undefined) {
|
if (saved.session[session] !== undefined) {
|
||||||
handoff.delete(key)
|
handoff.delete(key)
|
||||||
setStore("promoting", undefined)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
setSaved("session", session, clone(next))
|
setSaved("session", session, clone(next))
|
||||||
handoff.delete(key)
|
handoff.delete(key)
|
||||||
setStore("promoting", undefined)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const configuredModel = () => {
|
const configuredModel = () => {
|
||||||
@@ -376,19 +373,19 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
|||||||
agent,
|
agent,
|
||||||
session: {
|
session: {
|
||||||
reset() {
|
reset() {
|
||||||
setStore({ draft: undefined, promoting: undefined })
|
setStore("draft", undefined)
|
||||||
},
|
},
|
||||||
promote(dir: string, session: string) {
|
promote(dir: string, session: string) {
|
||||||
const next = clone(snapshot())
|
const next = clone(snapshot())
|
||||||
if (!next) return
|
if (!next) return
|
||||||
const key = handoffKey(serverSDK().scope, dir, session)
|
|
||||||
handoff.set(key, next)
|
|
||||||
|
|
||||||
if (dir === sdk().directory) {
|
if (dir === sdk().directory) {
|
||||||
setSaved("session", session, next)
|
setSaved("session", session, next)
|
||||||
|
setStore("draft", undefined)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
setStore("promoting", next)
|
handoff.set(handoffKey(serverSDK().scope, dir, session), next)
|
||||||
setStore("draft", undefined)
|
setStore("draft", undefined)
|
||||||
},
|
},
|
||||||
restore(msg: { sessionID: string; agent: string; model: ModelKey }) {
|
restore(msg: { sessionID: string; agent: string; model: ModelKey }) {
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import { useTabs, type Tab } from "./tabs"
|
|||||||
import { ServerConnection } from "./server"
|
import { ServerConnection } from "./server"
|
||||||
import { requireServerKey } from "@/utils/session-route"
|
import { requireServerKey } from "@/utils/session-route"
|
||||||
import { useSettings } from "./settings"
|
import { useSettings } from "./settings"
|
||||||
import type { FilePartSource } from "@opencode-ai/sdk/v2/client"
|
|
||||||
|
|
||||||
interface PartBase {
|
interface PartBase {
|
||||||
content: string
|
content: string
|
||||||
@@ -28,10 +27,6 @@ export interface FileAttachmentPart extends PartBase {
|
|||||||
type: "file"
|
type: "file"
|
||||||
path: string
|
path: string
|
||||||
selection?: FileSelection
|
selection?: FileSelection
|
||||||
mime?: string
|
|
||||||
filename?: string
|
|
||||||
url?: string
|
|
||||||
source?: FilePartSource
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AgentPart extends PartBase {
|
export interface AgentPart extends PartBase {
|
||||||
@@ -78,13 +73,7 @@ function isPartEqual(partA: ContentPart, partB: ContentPart) {
|
|||||||
case "text":
|
case "text":
|
||||||
return partB.type === "text" && partA.content === partB.content
|
return partB.type === "text" && partA.content === partB.content
|
||||||
case "file":
|
case "file":
|
||||||
return (
|
return partB.type === "file" && partA.path === partB.path && isSelectionEqual(partA.selection, partB.selection)
|
||||||
partB.type === "file" &&
|
|
||||||
partA.path === partB.path &&
|
|
||||||
partA.mime === partB.mime &&
|
|
||||||
partA.filename === partB.filename &&
|
|
||||||
isSelectionEqual(partA.selection, partB.selection)
|
|
||||||
)
|
|
||||||
case "agent":
|
case "agent":
|
||||||
return partB.type === "agent" && partA.name === partB.name
|
return partB.type === "agent" && partA.name === partB.name
|
||||||
case "image":
|
case "image":
|
||||||
|
|||||||
@@ -1,11 +1,4 @@
|
|||||||
import type {
|
import type { Config, OpencodeClient, Path, Project, ProviderAuthResponse } from "@opencode-ai/sdk/v2/client"
|
||||||
Config,
|
|
||||||
McpResource,
|
|
||||||
OpencodeClient,
|
|
||||||
Path,
|
|
||||||
Project,
|
|
||||||
ProviderAuthResponse,
|
|
||||||
} from "@opencode-ai/sdk/v2/client"
|
|
||||||
import { showToast } from "@/utils/toast"
|
import { showToast } from "@/utils/toast"
|
||||||
import { getFilename } from "@opencode-ai/core/util/path"
|
import { getFilename } from "@opencode-ai/core/util/path"
|
||||||
import { type Accessor, batch, createMemo, getOwner, onCleanup, onMount, untrack } from "solid-js"
|
import { type Accessor, batch, createMemo, getOwner, onCleanup, onMount, untrack } from "solid-js"
|
||||||
@@ -22,7 +15,6 @@ import {
|
|||||||
loadPathQuery,
|
loadPathQuery,
|
||||||
loadProjectsQuery,
|
loadProjectsQuery,
|
||||||
loadProvidersQuery,
|
loadProvidersQuery,
|
||||||
loadReferencesQuery,
|
|
||||||
} from "./global-sync/bootstrap"
|
} from "./global-sync/bootstrap"
|
||||||
import { createChildStoreManager } from "./global-sync/child-store"
|
import { createChildStoreManager } from "./global-sync/child-store"
|
||||||
import { applyDirectoryEvent, applyGlobalEvent } from "./global-sync/event-reducer"
|
import { applyDirectoryEvent, applyGlobalEvent } from "./global-sync/event-reducer"
|
||||||
@@ -64,13 +56,6 @@ export const loadMcpQuery = (scope: ServerScope, directory: string, sdk: Opencod
|
|||||||
queryFn: () => sdk.mcp.status().then((r) => r.data ?? {}),
|
queryFn: () => sdk.mcp.status().then((r) => r.data ?? {}),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const loadMcpResourcesQuery = (scope: ServerScope, directory: string, sdk: OpencodeClient) =>
|
|
||||||
queryOptions<Record<string, McpResource>>({
|
|
||||||
queryKey: [scope, directory, "mcpResources"] as const,
|
|
||||||
queryFn: () => sdk.experimental.resource.list().then((r) => r.data ?? {}),
|
|
||||||
placeholderData: {},
|
|
||||||
})
|
|
||||||
|
|
||||||
export const loadLspQuery = (scope: ServerScope, directory: string, sdk: OpencodeClient) =>
|
export const loadLspQuery = (scope: ServerScope, directory: string, sdk: OpencodeClient) =>
|
||||||
queryOptions({
|
queryOptions({
|
||||||
queryKey: [scope, directory, "lsp"] as const,
|
queryKey: [scope, directory, "lsp"] as const,
|
||||||
@@ -90,9 +75,7 @@ function makeQueryOptionsApi(
|
|||||||
path: (directory: PathKey | null) =>
|
path: (directory: PathKey | null) =>
|
||||||
loadPathQuery(scope, directory, directory === null ? serverSDK() : sdkFor(directory)),
|
loadPathQuery(scope, directory, directory === null ? serverSDK() : sdkFor(directory)),
|
||||||
agents: (directory: PathKey) => loadAgentsQuery(scope, directory, sdkFor(directory)),
|
agents: (directory: PathKey) => loadAgentsQuery(scope, directory, sdkFor(directory)),
|
||||||
references: (directory: PathKey) => loadReferencesQuery(scope, directory, sdkFor(directory)),
|
|
||||||
mcp: (directory: PathKey) => loadMcpQuery(scope, directory, sdkFor(directory)),
|
mcp: (directory: PathKey) => loadMcpQuery(scope, directory, sdkFor(directory)),
|
||||||
mcpResources: (directory: PathKey) => loadMcpResourcesQuery(scope, directory, sdkFor(directory)),
|
|
||||||
lsp: (directory: PathKey) => loadLspQuery(scope, directory, sdkFor(directory)),
|
lsp: (directory: PathKey) => loadLspQuery(scope, directory, sdkFor(directory)),
|
||||||
sessions: (directory: PathKey) => ({ queryKey: [scope, directory, "loadSessions"] as const }),
|
sessions: (directory: PathKey) => ({ queryKey: [scope, directory, "loadSessions"] as const }),
|
||||||
}
|
}
|
||||||
@@ -413,9 +396,6 @@ export function createServerSyncContextInner(serverSDK: ServerSDK) {
|
|||||||
loadLsp: () => {
|
loadLsp: () => {
|
||||||
void queryClient.fetchQuery(queryOptionsApi.lsp(key))
|
void queryClient.fetchQuery(queryOptionsApi.lsp(key))
|
||||||
},
|
},
|
||||||
loadReferences: () => {
|
|
||||||
void queryClient.fetchQuery(queryOptionsApi.references(key))
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -504,7 +484,6 @@ export function createServerSyncContextInner(serverSDK: ServerSDK) {
|
|||||||
},
|
},
|
||||||
refresh: async () => {
|
refresh: async () => {
|
||||||
await queryClient.refetchQueries(queryOptionsApi.mcp(key))
|
await queryClient.refetchQueries(queryOptionsApi.mcp(key))
|
||||||
await queryClient.refetchQueries(queryOptionsApi.mcpResources(key))
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
import { describe, expect, test } from "bun:test"
|
|
||||||
import { DESKTOP_MENU } from "./desktop-menu"
|
|
||||||
|
|
||||||
describe("desktop menu", () => {
|
|
||||||
test("exports logs through the desktop command registry", () => {
|
|
||||||
const items = DESKTOP_MENU.flatMap((menu) => menu.items ?? []).filter(
|
|
||||||
(item) => item.type === "item" && item.label === "Export Logs...",
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(items).toHaveLength(2)
|
|
||||||
expect(items.every((item) => item.type === "item" && item.command === "logs.export" && !item.action)).toBe(true)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -276,10 +276,7 @@ export const ErrorPage: Component<ErrorPageProps> = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div class="relative flex-1 h-screen w-screen min-h-0 flex flex-col items-center justify-center bg-background-base font-sans">
|
||||||
class="relative flex-1 h-screen w-screen min-h-0 flex flex-col items-center justify-center bg-background-base font-sans"
|
|
||||||
data-tauri-drag-region
|
|
||||||
>
|
|
||||||
<div class="w-2/3 max-w-3xl flex flex-col items-center justify-center gap-8">
|
<div class="w-2/3 max-w-3xl flex flex-col items-center justify-center gap-8">
|
||||||
<Logo class="w-58.5 opacity-12 shrink-0" />
|
<Logo class="w-58.5 opacity-12 shrink-0" />
|
||||||
<div class="flex flex-col items-center gap-2 text-center">
|
<div class="flex flex-col items-center gap-2 text-center">
|
||||||
|
|||||||
@@ -1006,8 +1006,8 @@ function HomeSessionLeading(props: {
|
|||||||
<Show when={hasOpenTab()}>
|
<Show when={hasOpenTab()}>
|
||||||
<span
|
<span
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
class="pointer-events-none absolute top-1/2 h-3 w-0.5 -translate-y-1/2 rounded-[2px] bg-v2-background-bg-layer-04"
|
class="pointer-events-none absolute top-1/2 h-[7px] w-[3px] -translate-y-1/2 rounded-[2px] bg-v2-background-bg-layer-04"
|
||||||
style={{ right: "calc(100% + 4px)" }}
|
style={{ right: "calc(100% + 5px)" }}
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
<SessionTabAvatar
|
<SessionTabAvatar
|
||||||
@@ -1163,7 +1163,13 @@ function HomeSessionSearch(props: {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
<label class="relative z-20 flex h-9 w-full items-center gap-2 rounded-[6px] bg-v2-background-bg-layer-02/60 py-1 pl-3 pr-2 text-v2-icon-icon-muted transition-[background-color,box-shadow] duration-[120ms] ease-in-out hover:bg-v2-background-bg-layer-02">
|
<label
|
||||||
|
class="relative z-20 flex h-9 w-full items-center gap-2 rounded-[6px] bg-v2-background-bg-layer-02 py-1 pl-3 pr-2 text-v2-icon-icon-muted transition-[background-color,box-shadow] duration-[120ms] ease-in-out"
|
||||||
|
classList={{
|
||||||
|
"focus-within:shadow-[0_0_0_0.5px_var(--v2-border-border-focus),var(--v2-elevation-raised)]": !props.open,
|
||||||
|
"shadow-[0_0_0_0.5px_var(--v2-border-border-focus)]": props.open,
|
||||||
|
}}
|
||||||
|
>
|
||||||
<IconV2 name="magnifying-glass" />
|
<IconV2 name="magnifying-glass" />
|
||||||
<input
|
<input
|
||||||
ref={input}
|
ref={input}
|
||||||
|
|||||||
@@ -943,6 +943,18 @@ export default function LegacyLayout(props: ParentProps) {
|
|||||||
keybind: "mod+comma",
|
keybind: "mod+comma",
|
||||||
onSelect: () => openSettings(),
|
onSelect: () => openSettings(),
|
||||||
},
|
},
|
||||||
|
...(platform.platform === "desktop" && platform.exportDebugLogs
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
id: "logs.export",
|
||||||
|
title: "Export logs",
|
||||||
|
category: language.t("command.category.settings"),
|
||||||
|
onSelect: () => {
|
||||||
|
void platform.exportDebugLogs?.()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
{
|
{
|
||||||
id: "session.previous",
|
id: "session.previous",
|
||||||
title: language.t("command.session.previous"),
|
title: language.t("command.session.previous"),
|
||||||
|
|||||||
@@ -736,9 +736,8 @@ export function MessageTimeline(props: {
|
|||||||
if (!sessionID() || parentID()) return
|
if (!sessionID() || parentID()) return
|
||||||
setTitle({ editing: true, draft: titleLabel() ?? "" })
|
setTitle({ editing: true, draft: titleLabel() ?? "" })
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
if (!titleRef) return
|
titleRef?.focus()
|
||||||
titleRef.focus()
|
titleRef?.select()
|
||||||
titleRef.select()
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1358,7 +1357,7 @@ export function MessageTimeline(props: {
|
|||||||
"w-full": true,
|
"w-full": true,
|
||||||
"pb-4": true,
|
"pb-4": true,
|
||||||
"pr-3": true,
|
"pr-3": true,
|
||||||
"pl-2": settings.general.newLayoutDesigns(),
|
"pl-4": settings.general.newLayoutDesigns(),
|
||||||
"pl-2 md:pl-4": !settings.general.newLayoutDesigns(),
|
"pl-2 md:pl-4": !settings.general.newLayoutDesigns(),
|
||||||
"md:max-w-200 md:mx-auto 2xl:max-w-[1000px]": props.centered && !settings.general.newLayoutDesigns(),
|
"md:max-w-200 md:mx-auto 2xl:max-w-[1000px]": props.centered && !settings.general.newLayoutDesigns(),
|
||||||
}}
|
}}
|
||||||
@@ -1370,7 +1369,7 @@ export function MessageTimeline(props: {
|
|||||||
"pr-3": !settings.general.newLayoutDesigns(),
|
"pr-3": !settings.general.newLayoutDesigns(),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div class="flex items-center min-w-0 flex-1 w-full">
|
<div class="flex items-center min-w-0 grow-1">
|
||||||
<Show when={parentID()}>
|
<Show when={parentID()}>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -1394,13 +1393,8 @@ export function MessageTimeline(props: {
|
|||||||
fallback={
|
fallback={
|
||||||
<h1
|
<h1
|
||||||
data-slot="session-title-child"
|
data-slot="session-title-child"
|
||||||
classList={{
|
class="text-14-medium text-text-strong truncate grow-1 min-w-0"
|
||||||
"text-14-medium text-text-strong truncate": true,
|
onDblClick={openTitleEditor}
|
||||||
"w-fit rounded-[6px] px-2 py-1 hover:bg-v2-overlay-simple-overlay-hover":
|
|
||||||
settings.general.newLayoutDesigns(),
|
|
||||||
"grow-1 min-w-0": !settings.general.newLayoutDesigns(),
|
|
||||||
}}
|
|
||||||
onClick={openTitleEditor}
|
|
||||||
>
|
>
|
||||||
{childTitle()}
|
{childTitle()}
|
||||||
</h1>
|
</h1>
|
||||||
@@ -1414,10 +1408,10 @@ export function MessageTimeline(props: {
|
|||||||
value={title.draft}
|
value={title.draft}
|
||||||
disabled={titleMutation.isPending}
|
disabled={titleMutation.isPending}
|
||||||
classList={{
|
classList={{
|
||||||
"text-14-medium text-text-strong block": true,
|
"text-14-medium text-text-strong grow-1 min-w-0 pl-1 -ml-1": true,
|
||||||
"w-full flex-1 grow-1 min-w-0 pl-1 -ml-1 rounded-[6px]": !settings.general.newLayoutDesigns(),
|
"h-6 leading-4 rounded-[3px] focus:shadow-none focus:outline focus:outline-1 focus:outline-offset-[-1px] focus:outline-v2-border-border-focus":
|
||||||
"field-sizing-content self-start rounded-[6px] px-2 py-1 ":
|
|
||||||
settings.general.newLayoutDesigns(),
|
settings.general.newLayoutDesigns(),
|
||||||
|
"rounded-[6px]": !settings.general.newLayoutDesigns(),
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
"--inline-input-shadow": settings.general.newLayoutDesigns()
|
"--inline-input-shadow": settings.general.newLayoutDesigns()
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"name": "@opencode-ai/cli",
|
"name": "@opencode-ai/cli",
|
||||||
"version": "1.17.12",
|
"version": "1.17.11",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import { NodeHttpServer } from "@effect/platform-node"
|
import { NodeHttpServer } from "@effect/platform-node"
|
||||||
import { Credential } from "@opencode-ai/core/credential"
|
import { Credential } from "@opencode-ai/core/credential"
|
||||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
|
||||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
|
||||||
import { PermissionSaved } from "@opencode-ai/core/permission/saved"
|
import { PermissionSaved } from "@opencode-ai/core/permission/saved"
|
||||||
import { Context, Layer, Option } from "effect"
|
import { Context, Layer, Option } from "effect"
|
||||||
import * as Effect from "effect/Effect"
|
import * as Effect from "effect/Effect"
|
||||||
@@ -40,7 +38,8 @@ function bind(hostname: string, port: number, password: string) {
|
|||||||
return Layer.build(
|
return Layer.build(
|
||||||
HttpRouter.serve(createRoutes(password), { disableListenLog: true, disableLogger: true }).pipe(
|
HttpRouter.serve(createRoutes(password), { disableListenLog: true, disableLogger: true }).pipe(
|
||||||
Layer.provideMerge(NodeHttpServer.layer(() => createServer(), { port, host: hostname })),
|
Layer.provideMerge(NodeHttpServer.layer(() => createServer(), { port, host: hostname })),
|
||||||
Layer.provide(AppNodeBuilder.build(LayerNode.group([Credential.node, PermissionSaved.node]))),
|
Layer.provide(Credential.defaultLayer),
|
||||||
|
Layer.provide(PermissionSaved.defaultLayer),
|
||||||
),
|
),
|
||||||
).pipe(Effect.map((context) => Context.get(context, HttpServer.HttpServer).address))
|
).pipe(Effect.map((context) => Context.get(context, HttpServer.HttpServer).address))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const Handlers = Runtime.handlers(Commands, {
|
|||||||
})
|
})
|
||||||
|
|
||||||
Runtime.run(Commands, Handlers, { version: "local" }).pipe(
|
Runtime.run(Commands, Handlers, { version: "local" }).pipe(
|
||||||
Effect.provide(Daemon.layer),
|
Effect.provide(Daemon.defaultLayer),
|
||||||
Effect.provide(NodeServices.layer),
|
Effect.provide(NodeServices.layer),
|
||||||
Effect.scoped,
|
Effect.scoped,
|
||||||
NodeRuntime.runMain,
|
NodeRuntime.runMain,
|
||||||
|
|||||||
@@ -189,4 +189,6 @@ export const layer = Layer.effect(
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
export const defaultLayer = layer
|
||||||
|
|
||||||
export * as Daemon from "./daemon"
|
export * as Daemon from "./daemon"
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { run } from "@opencode-ai/tui"
|
import { run } from "@opencode-ai/tui"
|
||||||
import { TuiConfig } from "@opencode-ai/tui/config"
|
import { TuiConfig } from "@opencode-ai/tui/config"
|
||||||
import { Effect } from "effect"
|
import { Effect } from "effect"
|
||||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
|
||||||
import { Global } from "@opencode-ai/core/global"
|
import { Global } from "@opencode-ai/core/global"
|
||||||
|
|
||||||
export function runTui(transport: { url: string; headers: RequestInit["headers"] }) {
|
export function runTui(transport: { url: string; headers: RequestInit["headers"] }) {
|
||||||
@@ -15,7 +14,7 @@ export function runTui(transport: { url: string; headers: RequestInit["headers"]
|
|||||||
async start() {},
|
async start() {},
|
||||||
async dispose() {},
|
async dispose() {},
|
||||||
},
|
},
|
||||||
}).pipe(Effect.provide(AppNodeBuilder.build(Global.node)))
|
}).pipe(Effect.provide(Global.defaultLayer))
|
||||||
}
|
}
|
||||||
|
|
||||||
const legacyDefaults: Record<string, unknown> = {
|
const legacyDefaults: Record<string, unknown> = {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/console-app",
|
"name": "@opencode-ai/console-app",
|
||||||
"version": "1.17.12",
|
"version": "1.17.11",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
import { action, useSubmission } from "@solidjs/router"
|
|
||||||
import { Resource } from "@opencode-ai/console-resource"
|
|
||||||
import { Show } from "solid-js"
|
|
||||||
import { useI18n } from "~/context/i18n"
|
|
||||||
|
|
||||||
const emailSignup = action(async (formData: FormData) => {
|
|
||||||
"use server"
|
|
||||||
const emailAddress = formData.get("email")!
|
|
||||||
const listId = "8b9bb82c-9d5f-11f0-975f-0df6fd1e4945"
|
|
||||||
const response = await fetch(`https://api.emailoctopus.com/lists/${listId}/contacts`, {
|
|
||||||
method: "PUT",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${Resource.EMAILOCTOPUS_API_KEY.value}`,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
email_address: emailAddress,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
console.log(response)
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
|
|
||||||
export function EmailSignup() {
|
|
||||||
const submission = useSubmission(emailSignup)
|
|
||||||
const i18n = useI18n()
|
|
||||||
return (
|
|
||||||
<section data-component="email">
|
|
||||||
<div data-slot="section-title">
|
|
||||||
<h3>{i18n.t("email.title")}</h3>
|
|
||||||
<p>{i18n.t("email.subtitle")}</p>
|
|
||||||
</div>
|
|
||||||
<form data-slot="form" action={emailSignup} method="post">
|
|
||||||
<input type="email" name="email" placeholder={i18n.t("email.placeholder")} required />
|
|
||||||
<button type="submit" disabled={submission.pending}>
|
|
||||||
{i18n.t("email.subscribe")}
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
<Show when={submission.result}>
|
|
||||||
<div style="color: #03B000; margin-top: 24px;">{i18n.t("email.success")}</div>
|
|
||||||
</Show>
|
|
||||||
<Show when={submission.error}>
|
|
||||||
<div style="color: #FF408F; margin-top: 24px;">{submission.error}</div>
|
|
||||||
</Show>
|
|
||||||
</section>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -248,6 +248,7 @@ export const dict = {
|
|||||||
"zen.privacy.exceptionsLink": "الاستثناءات التالية",
|
"zen.privacy.exceptionsLink": "الاستثناءات التالية",
|
||||||
|
|
||||||
"go.title": "OpenCode Go | نماذج برمجة منخفضة التكلفة للجميع",
|
"go.title": "OpenCode Go | نماذج برمجة منخفضة التكلفة للجميع",
|
||||||
|
"go.banner.text": "MiniMax M3: حد استخدام أكبر 3 مرات لفترة محدودة",
|
||||||
"go.meta.description":
|
"go.meta.description":
|
||||||
"يبدأ Go من $5 للشهر الأول، ثم $10/شهر، مع حدود طلب سخية لمدة 5 ساعات لـ GLM-5.2 وGLM-5.1 وKimi K2.7 Code وKimi K2.6 وMiMo-V2.5-Pro وMiMo-V2.5 وQwen3.7 Max وQwen3.7 Plus وQwen3.6 Plus وMiniMax M2.7 وMiniMax M3 وDeepSeek V4 Pro وDeepSeek V4 Flash.",
|
"يبدأ Go من $5 للشهر الأول، ثم $10/شهر، مع حدود طلب سخية لمدة 5 ساعات لـ GLM-5.2 وGLM-5.1 وKimi K2.7 Code وKimi K2.6 وMiMo-V2.5-Pro وMiMo-V2.5 وQwen3.7 Max وQwen3.7 Plus وQwen3.6 Plus وMiniMax M2.7 وMiniMax M3 وDeepSeek V4 Pro وDeepSeek V4 Flash.",
|
||||||
"go.hero.title": "نماذج برمجة منخفضة التكلفة للجميع",
|
"go.hero.title": "نماذج برمجة منخفضة التكلفة للجميع",
|
||||||
@@ -368,8 +369,6 @@ export const dict = {
|
|||||||
"zen.api.error.userMonthlyLimitReached":
|
"zen.api.error.userMonthlyLimitReached":
|
||||||
"لقد وصلت إلى حد الإنفاق الشهري البالغ ${{amount}}. إدارة حدودك هنا: {{membersUrl}}",
|
"لقد وصلت إلى حد الإنفاق الشهري البالغ ${{amount}}. إدارة حدودك هنا: {{membersUrl}}",
|
||||||
"zen.api.error.modelDisabled": "النموذج معطل",
|
"zen.api.error.modelDisabled": "النموذج معطل",
|
||||||
"zen.api.error.regionNotAllowed":
|
|
||||||
"هذا النموذج مستضاف في الصين. إذا كنت ترغب في استخدام هذا النموذج، فعّله في إعداداتك: {{consoleGoUrl}}",
|
|
||||||
"zen.api.error.trialEnded":
|
"zen.api.error.trialEnded":
|
||||||
"انتهى العرض المجاني لـ {{model}}. يمكنك مواصلة استخدام النموذج بالاشتراك في OpenCode Go - {{link}}",
|
"انتهى العرض المجاني لـ {{model}}. يمكنك مواصلة استخدام النموذج بالاشتراك في OpenCode Go - {{link}}",
|
||||||
|
|
||||||
@@ -647,9 +646,6 @@ export const dict = {
|
|||||||
"workspace.lite.subscription.useBalance": "استخدم رصيدك المتوفر بعد الوصول إلى حدود الاستخدام",
|
"workspace.lite.subscription.useBalance": "استخدم رصيدك المتوفر بعد الوصول إلى حدود الاستخدام",
|
||||||
"workspace.lite.subscription.selectProvider":
|
"workspace.lite.subscription.selectProvider":
|
||||||
'اختر "OpenCode Go" كمزود في إعدادات opencode الخاصة بك لاستخدام نماذج Go.',
|
'اختر "OpenCode Go" كمزود في إعدادات opencode الخاصة بك لاستخدام نماذج Go.',
|
||||||
"workspace.lite.providers.title": "المزودون",
|
|
||||||
"workspace.lite.providers.description": "تحكم في المزودين المستخدمين للتوجيه.",
|
|
||||||
"workspace.lite.providers.useChina": "تفعيل النماذج المستضافة في الصين",
|
|
||||||
"workspace.lite.black.message":
|
"workspace.lite.black.message":
|
||||||
"أنت مشترك حاليًا في OpenCode Black أو في قائمة الانتظار. يرجى إلغاء الاشتراك أولاً إذا كنت ترغب في التبديل إلى Go.",
|
"أنت مشترك حاليًا في OpenCode Black أو في قائمة الانتظار. يرجى إلغاء الاشتراك أولاً إذا كنت ترغب في التبديل إلى Go.",
|
||||||
"workspace.lite.other.message":
|
"workspace.lite.other.message":
|
||||||
|
|||||||
@@ -252,6 +252,7 @@ export const dict = {
|
|||||||
"zen.privacy.exceptionsLink": "seguintes exceções",
|
"zen.privacy.exceptionsLink": "seguintes exceções",
|
||||||
|
|
||||||
"go.title": "OpenCode Go | Modelos de codificação de baixo custo para todos",
|
"go.title": "OpenCode Go | Modelos de codificação de baixo custo para todos",
|
||||||
|
"go.banner.text": "MiniMax M3 tem limite de uso 3x maior por tempo limitado",
|
||||||
"go.meta.description":
|
"go.meta.description":
|
||||||
"O Go começa em $5 no primeiro mês, depois $10/mês, com limites generosos de solicitação de 5 horas para GLM-5.2, GLM-5.1, Kimi K2.7 Code, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus, MiniMax M2.7, MiniMax M3, DeepSeek V4 Pro e DeepSeek V4 Flash.",
|
"O Go começa em $5 no primeiro mês, depois $10/mês, com limites generosos de solicitação de 5 horas para GLM-5.2, GLM-5.1, Kimi K2.7 Code, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus, MiniMax M2.7, MiniMax M3, DeepSeek V4 Pro e DeepSeek V4 Flash.",
|
||||||
"go.hero.title": "Modelos de codificação de baixo custo para todos",
|
"go.hero.title": "Modelos de codificação de baixo custo para todos",
|
||||||
@@ -376,8 +377,6 @@ export const dict = {
|
|||||||
"zen.api.error.userMonthlyLimitReached":
|
"zen.api.error.userMonthlyLimitReached":
|
||||||
"Você atingiu seu limite de gastos mensais de ${{amount}}. Gerencie seus limites aqui: {{membersUrl}}",
|
"Você atingiu seu limite de gastos mensais de ${{amount}}. Gerencie seus limites aqui: {{membersUrl}}",
|
||||||
"zen.api.error.modelDisabled": "O modelo está desabilitado",
|
"zen.api.error.modelDisabled": "O modelo está desabilitado",
|
||||||
"zen.api.error.regionNotAllowed":
|
|
||||||
"Este modelo está hospedado na China. Se você quiser usar este modelo, ative-o nas suas configurações: {{consoleGoUrl}}",
|
|
||||||
"zen.api.error.trialEnded":
|
"zen.api.error.trialEnded":
|
||||||
"A promoção gratuita do {{model}} terminou. Você pode continuar usando o modelo assinando o OpenCode Go - {{link}}",
|
"A promoção gratuita do {{model}} terminou. Você pode continuar usando o modelo assinando o OpenCode Go - {{link}}",
|
||||||
|
|
||||||
@@ -657,9 +656,6 @@ export const dict = {
|
|||||||
"workspace.lite.subscription.useBalance": "Use seu saldo disponível após atingir os limites de uso",
|
"workspace.lite.subscription.useBalance": "Use seu saldo disponível após atingir os limites de uso",
|
||||||
"workspace.lite.subscription.selectProvider":
|
"workspace.lite.subscription.selectProvider":
|
||||||
'Selecione "OpenCode Go" como provedor na sua configuração do opencode para usar os modelos Go.',
|
'Selecione "OpenCode Go" como provedor na sua configuração do opencode para usar os modelos Go.',
|
||||||
"workspace.lite.providers.title": "Provedores",
|
|
||||||
"workspace.lite.providers.description": "Controle quais provedores são usados para roteamento.",
|
|
||||||
"workspace.lite.providers.useChina": "Ativar modelos hospedados na China",
|
|
||||||
"workspace.lite.black.message":
|
"workspace.lite.black.message":
|
||||||
"Você está atualmente inscrito no OpenCode Black ou na lista de espera. Por favor, cancele a assinatura primeiro se desejar mudar para o Go.",
|
"Você está atualmente inscrito no OpenCode Black ou na lista de espera. Por favor, cancele a assinatura primeiro se desejar mudar para o Go.",
|
||||||
"workspace.lite.other.message":
|
"workspace.lite.other.message":
|
||||||
|
|||||||
@@ -250,6 +250,7 @@ export const dict = {
|
|||||||
"zen.privacy.exceptionsLink": "følgende undtagelser",
|
"zen.privacy.exceptionsLink": "følgende undtagelser",
|
||||||
|
|
||||||
"go.title": "OpenCode Go | Kodningsmodeller til lav pris for alle",
|
"go.title": "OpenCode Go | Kodningsmodeller til lav pris for alle",
|
||||||
|
"go.banner.text": "MiniMax M3 får tredoblet brugsgrænse i en begrænset periode",
|
||||||
"go.meta.description":
|
"go.meta.description":
|
||||||
"Go starter ved $5 for den første måned, derefter $10/måned, med generøse 5-timers anmodningsgrænser for GLM-5.2, GLM-5.1, Kimi K2.7 Code, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus, MiniMax M2.7, MiniMax M3, DeepSeek V4 Pro og DeepSeek V4 Flash.",
|
"Go starter ved $5 for den første måned, derefter $10/måned, med generøse 5-timers anmodningsgrænser for GLM-5.2, GLM-5.1, Kimi K2.7 Code, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus, MiniMax M2.7, MiniMax M3, DeepSeek V4 Pro og DeepSeek V4 Flash.",
|
||||||
"go.hero.title": "Kodningsmodeller til lav pris for alle",
|
"go.hero.title": "Kodningsmodeller til lav pris for alle",
|
||||||
@@ -372,8 +373,6 @@ export const dict = {
|
|||||||
"zen.api.error.userMonthlyLimitReached":
|
"zen.api.error.userMonthlyLimitReached":
|
||||||
"Du har nået din månedlige forbrugsgrænse på ${{amount}}. Administrer dine grænser her: {{membersUrl}}",
|
"Du har nået din månedlige forbrugsgrænse på ${{amount}}. Administrer dine grænser her: {{membersUrl}}",
|
||||||
"zen.api.error.modelDisabled": "Modellen er deaktiveret",
|
"zen.api.error.modelDisabled": "Modellen er deaktiveret",
|
||||||
"zen.api.error.regionNotAllowed":
|
|
||||||
"Denne model hostes i Kina. Hvis du vil bruge denne model, skal du aktivere den i dine indstillinger: {{consoleGoUrl}}",
|
|
||||||
"zen.api.error.trialEnded":
|
"zen.api.error.trialEnded":
|
||||||
"Den gratis kampagne for {{model}} er afsluttet. Du kan fortsætte med at bruge modellen ved at abonnere på OpenCode Go - {{link}}",
|
"Den gratis kampagne for {{model}} er afsluttet. Du kan fortsætte med at bruge modellen ved at abonnere på OpenCode Go - {{link}}",
|
||||||
|
|
||||||
@@ -653,9 +652,6 @@ export const dict = {
|
|||||||
"workspace.lite.subscription.useBalance": "Brug din tilgængelige saldo, når du har nået forbrugsgrænserne",
|
"workspace.lite.subscription.useBalance": "Brug din tilgængelige saldo, når du har nået forbrugsgrænserne",
|
||||||
"workspace.lite.subscription.selectProvider":
|
"workspace.lite.subscription.selectProvider":
|
||||||
'Vælg "OpenCode Go" som udbyder i din opencode-konfiguration for at bruge Go-modeller.',
|
'Vælg "OpenCode Go" som udbyder i din opencode-konfiguration for at bruge Go-modeller.',
|
||||||
"workspace.lite.providers.title": "Udbydere",
|
|
||||||
"workspace.lite.providers.description": "Styr, hvilke udbydere der bruges til routing.",
|
|
||||||
"workspace.lite.providers.useChina": "Aktivér modeller hostet i Kina",
|
|
||||||
"workspace.lite.black.message":
|
"workspace.lite.black.message":
|
||||||
"Du abonnerer i øjeblikket på OpenCode Black eller er på venteliste. Afmeld venligst først, hvis du vil skifte til Go.",
|
"Du abonnerer i øjeblikket på OpenCode Black eller er på venteliste. Afmeld venligst først, hvis du vil skifte til Go.",
|
||||||
"workspace.lite.other.message":
|
"workspace.lite.other.message":
|
||||||
|
|||||||
@@ -252,6 +252,7 @@ export const dict = {
|
|||||||
"zen.privacy.exceptionsLink": "folgenden Ausnahmen",
|
"zen.privacy.exceptionsLink": "folgenden Ausnahmen",
|
||||||
|
|
||||||
"go.title": "OpenCode Go | Kostengünstige Coding-Modelle für alle",
|
"go.title": "OpenCode Go | Kostengünstige Coding-Modelle für alle",
|
||||||
|
"go.banner.text": "MiniMax M3 erhält für begrenzte Zeit 3x Nutzungslimits",
|
||||||
"go.meta.description":
|
"go.meta.description":
|
||||||
"Go beginnt bei $5 für den ersten Monat, danach $10/Monat, mit großzügigen 5-Stunden-Anfragelimits für GLM-5.2, GLM-5.1, Kimi K2.7 Code, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus, MiniMax M2.7, MiniMax M3, DeepSeek V4 Pro und DeepSeek V4 Flash.",
|
"Go beginnt bei $5 für den ersten Monat, danach $10/Monat, mit großzügigen 5-Stunden-Anfragelimits für GLM-5.2, GLM-5.1, Kimi K2.7 Code, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus, MiniMax M2.7, MiniMax M3, DeepSeek V4 Pro und DeepSeek V4 Flash.",
|
||||||
"go.hero.title": "Kostengünstige Coding-Modelle für alle",
|
"go.hero.title": "Kostengünstige Coding-Modelle für alle",
|
||||||
@@ -375,8 +376,6 @@ export const dict = {
|
|||||||
"zen.api.error.userMonthlyLimitReached":
|
"zen.api.error.userMonthlyLimitReached":
|
||||||
"Du hast dein monatliches Ausgabenlimit von ${{amount}} erreicht. Verwalte deine Limits hier: {{membersUrl}}",
|
"Du hast dein monatliches Ausgabenlimit von ${{amount}} erreicht. Verwalte deine Limits hier: {{membersUrl}}",
|
||||||
"zen.api.error.modelDisabled": "Modell ist deaktiviert",
|
"zen.api.error.modelDisabled": "Modell ist deaktiviert",
|
||||||
"zen.api.error.regionNotAllowed":
|
|
||||||
"Dieses Modell wird in China gehostet. Wenn du dieses Modell verwenden möchtest, aktiviere es in deinen Einstellungen: {{consoleGoUrl}}",
|
|
||||||
"zen.api.error.trialEnded":
|
"zen.api.error.trialEnded":
|
||||||
"Die kostenlose Aktion für {{model}} ist beendet. Du kannst das Modell weiterhin nutzen, indem du OpenCode Go abonnierst - {{link}}",
|
"Die kostenlose Aktion für {{model}} ist beendet. Du kannst das Modell weiterhin nutzen, indem du OpenCode Go abonnierst - {{link}}",
|
||||||
|
|
||||||
@@ -656,9 +655,6 @@ export const dict = {
|
|||||||
"workspace.lite.subscription.useBalance": "Nutze dein verfügbares Guthaben, nachdem die Nutzungslimits erreicht sind",
|
"workspace.lite.subscription.useBalance": "Nutze dein verfügbares Guthaben, nachdem die Nutzungslimits erreicht sind",
|
||||||
"workspace.lite.subscription.selectProvider":
|
"workspace.lite.subscription.selectProvider":
|
||||||
'Wähle "OpenCode Go" als Anbieter in deiner opencode-Konfiguration, um Go-Modelle zu verwenden.',
|
'Wähle "OpenCode Go" als Anbieter in deiner opencode-Konfiguration, um Go-Modelle zu verwenden.',
|
||||||
"workspace.lite.providers.title": "Anbieter",
|
|
||||||
"workspace.lite.providers.description": "Steuere, welche Anbieter für das Routing verwendet werden.",
|
|
||||||
"workspace.lite.providers.useChina": "In China gehostete Modelle aktivieren",
|
|
||||||
"workspace.lite.black.message":
|
"workspace.lite.black.message":
|
||||||
"Du hast derzeit OpenCode Black abonniert oder stehst auf der Warteliste. Bitte kündige zuerst, wenn du zu Go wechseln möchtest.",
|
"Du hast derzeit OpenCode Black abonniert oder stehst auf der Warteliste. Bitte kündige zuerst, wenn du zu Go wechseln möchtest.",
|
||||||
"workspace.lite.other.message":
|
"workspace.lite.other.message":
|
||||||
|
|||||||
@@ -248,6 +248,7 @@ export const dict = {
|
|||||||
"zen.privacy.exceptionsLink": "following exceptions",
|
"zen.privacy.exceptionsLink": "following exceptions",
|
||||||
|
|
||||||
"go.title": "OpenCode Go | Low cost coding models for everyone",
|
"go.title": "OpenCode Go | Low cost coding models for everyone",
|
||||||
|
"go.banner.text": "MiniMax M3 gets 3× usage limits for a limited time",
|
||||||
"go.meta.description":
|
"go.meta.description":
|
||||||
"Go starts at $5 for your first month, then $10/month, with generous 5-hour request limits for GLM-5.2, GLM-5.1, Kimi K2.7 Code, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus, MiniMax M2.7, MiniMax M3, DeepSeek V4 Pro, and DeepSeek V4 Flash.",
|
"Go starts at $5 for your first month, then $10/month, with generous 5-hour request limits for GLM-5.2, GLM-5.1, Kimi K2.7 Code, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus, MiniMax M2.7, MiniMax M3, DeepSeek V4 Pro, and DeepSeek V4 Flash.",
|
||||||
"go.hero.title": "Low cost coding models for everyone",
|
"go.hero.title": "Low cost coding models for everyone",
|
||||||
@@ -369,8 +370,6 @@ export const dict = {
|
|||||||
"zen.api.error.userMonthlyLimitReached":
|
"zen.api.error.userMonthlyLimitReached":
|
||||||
"You have reached your monthly spending limit of ${{amount}}. Manage your limits here: {{membersUrl}}",
|
"You have reached your monthly spending limit of ${{amount}}. Manage your limits here: {{membersUrl}}",
|
||||||
"zen.api.error.modelDisabled": "Model is disabled",
|
"zen.api.error.modelDisabled": "Model is disabled",
|
||||||
"zen.api.error.regionNotAllowed":
|
|
||||||
"This model is hosted in China. If you would like to use this model, enable it in your settings: {{consoleGoUrl}}",
|
|
||||||
"zen.api.error.trialEnded":
|
"zen.api.error.trialEnded":
|
||||||
"Free promotion has ended for {{model}}. You can continue using the model by subscribing to OpenCode Go - {{link}}",
|
"Free promotion has ended for {{model}}. You can continue using the model by subscribing to OpenCode Go - {{link}}",
|
||||||
|
|
||||||
@@ -650,9 +649,6 @@ export const dict = {
|
|||||||
"workspace.lite.subscription.useBalance": "Use your available balance after reaching the usage limits",
|
"workspace.lite.subscription.useBalance": "Use your available balance after reaching the usage limits",
|
||||||
"workspace.lite.subscription.selectProvider":
|
"workspace.lite.subscription.selectProvider":
|
||||||
'Select "OpenCode Go" as the provider in your opencode configuration to use Go models.',
|
'Select "OpenCode Go" as the provider in your opencode configuration to use Go models.',
|
||||||
"workspace.lite.providers.title": "Providers",
|
|
||||||
"workspace.lite.providers.description": "Control which providers are used for routing.",
|
|
||||||
"workspace.lite.providers.useChina": "Enable models hosted in China",
|
|
||||||
"workspace.lite.black.message":
|
"workspace.lite.black.message":
|
||||||
"You're currently subscribed to OpenCode Black or on the waitlist. Please unsubscribe first if you'd like to switch to Go.",
|
"You're currently subscribed to OpenCode Black or on the waitlist. Please unsubscribe first if you'd like to switch to Go.",
|
||||||
"workspace.lite.other.message":
|
"workspace.lite.other.message":
|
||||||
|
|||||||
@@ -253,6 +253,7 @@ export const dict = {
|
|||||||
"zen.privacy.exceptionsLink": "siguientes excepciones",
|
"zen.privacy.exceptionsLink": "siguientes excepciones",
|
||||||
|
|
||||||
"go.title": "OpenCode Go | Modelos de programación de bajo coste para todos",
|
"go.title": "OpenCode Go | Modelos de programación de bajo coste para todos",
|
||||||
|
"go.banner.text": "MiniMax M3 tiene límites de uso 3x mayores por tiempo limitado",
|
||||||
"go.meta.description":
|
"go.meta.description":
|
||||||
"Go comienza en $5 el primer mes, luego 10 $/mes, con generosos límites de solicitudes de 5 horas para GLM-5.2, GLM-5.1, Kimi K2.7 Code, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus, MiniMax M2.7, MiniMax M3, DeepSeek V4 Pro y DeepSeek V4 Flash.",
|
"Go comienza en $5 el primer mes, luego 10 $/mes, con generosos límites de solicitudes de 5 horas para GLM-5.2, GLM-5.1, Kimi K2.7 Code, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus, MiniMax M2.7, MiniMax M3, DeepSeek V4 Pro y DeepSeek V4 Flash.",
|
||||||
"go.hero.title": "Modelos de programación de bajo coste para todos",
|
"go.hero.title": "Modelos de programación de bajo coste para todos",
|
||||||
@@ -376,8 +377,6 @@ export const dict = {
|
|||||||
"zen.api.error.userMonthlyLimitReached":
|
"zen.api.error.userMonthlyLimitReached":
|
||||||
"Has alcanzado tu límite de gasto mensual de ${{amount}}. Gestiona tus límites aquí: {{membersUrl}}",
|
"Has alcanzado tu límite de gasto mensual de ${{amount}}. Gestiona tus límites aquí: {{membersUrl}}",
|
||||||
"zen.api.error.modelDisabled": "El modelo está deshabilitado",
|
"zen.api.error.modelDisabled": "El modelo está deshabilitado",
|
||||||
"zen.api.error.regionNotAllowed":
|
|
||||||
"Este modelo está alojado en China. Si quieres usar este modelo, actívalo en tu configuración: {{consoleGoUrl}}",
|
|
||||||
"zen.api.error.trialEnded":
|
"zen.api.error.trialEnded":
|
||||||
"La promoción gratuita de {{model}} ha finalizado. Puedes seguir usando el modelo suscribiéndote a OpenCode Go - {{link}}",
|
"La promoción gratuita de {{model}} ha finalizado. Puedes seguir usando el modelo suscribiéndote a OpenCode Go - {{link}}",
|
||||||
|
|
||||||
@@ -657,9 +656,6 @@ export const dict = {
|
|||||||
"workspace.lite.subscription.useBalance": "Usa tu saldo disponible después de alcanzar los límites de uso",
|
"workspace.lite.subscription.useBalance": "Usa tu saldo disponible después de alcanzar los límites de uso",
|
||||||
"workspace.lite.subscription.selectProvider":
|
"workspace.lite.subscription.selectProvider":
|
||||||
'Selecciona "OpenCode Go" como proveedor en tu configuración de opencode para usar los modelos Go.',
|
'Selecciona "OpenCode Go" como proveedor en tu configuración de opencode para usar los modelos Go.',
|
||||||
"workspace.lite.providers.title": "Proveedores",
|
|
||||||
"workspace.lite.providers.description": "Controla qué proveedores se usan para el enrutamiento.",
|
|
||||||
"workspace.lite.providers.useChina": "Activar modelos alojados en China",
|
|
||||||
"workspace.lite.black.message":
|
"workspace.lite.black.message":
|
||||||
"Actualmente estás suscrito a OpenCode Black o estás en la lista de espera. Por favor, cancela la suscripción primero si deseas cambiar a Go.",
|
"Actualmente estás suscrito a OpenCode Black o estás en la lista de espera. Por favor, cancela la suscripción primero si deseas cambiar a Go.",
|
||||||
"workspace.lite.other.message":
|
"workspace.lite.other.message":
|
||||||
|
|||||||
@@ -254,6 +254,7 @@ export const dict = {
|
|||||||
"zen.privacy.exceptionsLink": "exceptions suivantes",
|
"zen.privacy.exceptionsLink": "exceptions suivantes",
|
||||||
|
|
||||||
"go.title": "OpenCode Go | Modèles de code à faible coût pour tous",
|
"go.title": "OpenCode Go | Modèles de code à faible coût pour tous",
|
||||||
|
"go.banner.text": "MiniMax M3 bénéficie de limites d’utilisation 3x supérieures pour une durée limitée",
|
||||||
"go.meta.description":
|
"go.meta.description":
|
||||||
"Go commence à $5 pour le premier mois, puis 10 $/mois, avec des limites de requêtes généreuses sur 5 heures pour GLM-5.2, GLM-5.1, Kimi K2.7 Code, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus, MiniMax M2.7, MiniMax M3, DeepSeek V4 Pro et DeepSeek V4 Flash.",
|
"Go commence à $5 pour le premier mois, puis 10 $/mois, avec des limites de requêtes généreuses sur 5 heures pour GLM-5.2, GLM-5.1, Kimi K2.7 Code, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus, MiniMax M2.7, MiniMax M3, DeepSeek V4 Pro et DeepSeek V4 Flash.",
|
||||||
"go.hero.title": "Modèles de code à faible coût pour tous",
|
"go.hero.title": "Modèles de code à faible coût pour tous",
|
||||||
@@ -376,8 +377,6 @@ export const dict = {
|
|||||||
"zen.api.error.userMonthlyLimitReached":
|
"zen.api.error.userMonthlyLimitReached":
|
||||||
"Vous avez atteint votre limite de dépense mensuelle de {{amount}} $. Gérez vos limites ici : {{membersUrl}}",
|
"Vous avez atteint votre limite de dépense mensuelle de {{amount}} $. Gérez vos limites ici : {{membersUrl}}",
|
||||||
"zen.api.error.modelDisabled": "Le modèle est désactivé",
|
"zen.api.error.modelDisabled": "Le modèle est désactivé",
|
||||||
"zen.api.error.regionNotAllowed":
|
|
||||||
"Ce modèle est hébergé en Chine. Si vous souhaitez utiliser ce modèle, activez-le dans vos paramètres : {{consoleGoUrl}}",
|
|
||||||
"zen.api.error.trialEnded":
|
"zen.api.error.trialEnded":
|
||||||
"La promotion gratuite de {{model}} est terminée. Vous pouvez continuer à utiliser le modèle en vous abonnant à OpenCode Go - {{link}}",
|
"La promotion gratuite de {{model}} est terminée. Vous pouvez continuer à utiliser le modèle en vous abonnant à OpenCode Go - {{link}}",
|
||||||
|
|
||||||
@@ -663,9 +662,6 @@ export const dict = {
|
|||||||
"Utilisez votre solde disponible après avoir atteint les limites d'utilisation",
|
"Utilisez votre solde disponible après avoir atteint les limites d'utilisation",
|
||||||
"workspace.lite.subscription.selectProvider":
|
"workspace.lite.subscription.selectProvider":
|
||||||
'Sélectionnez "OpenCode Go" comme fournisseur dans votre configuration opencode pour utiliser les modèles Go.',
|
'Sélectionnez "OpenCode Go" comme fournisseur dans votre configuration opencode pour utiliser les modèles Go.',
|
||||||
"workspace.lite.providers.title": "Fournisseurs",
|
|
||||||
"workspace.lite.providers.description": "Contrôlez les fournisseurs utilisés pour le routage.",
|
|
||||||
"workspace.lite.providers.useChina": "Activer les modèles hébergés en Chine",
|
|
||||||
"workspace.lite.black.message":
|
"workspace.lite.black.message":
|
||||||
"Vous êtes actuellement abonné à OpenCode Black ou sur liste d'attente. Veuillez d'abord vous désabonner si vous souhaitez passer à Go.",
|
"Vous êtes actuellement abonné à OpenCode Black ou sur liste d'attente. Veuillez d'abord vous désabonner si vous souhaitez passer à Go.",
|
||||||
"workspace.lite.other.message":
|
"workspace.lite.other.message":
|
||||||
|
|||||||
@@ -250,6 +250,7 @@ export const dict = {
|
|||||||
"zen.privacy.exceptionsLink": "seguenti eccezioni",
|
"zen.privacy.exceptionsLink": "seguenti eccezioni",
|
||||||
|
|
||||||
"go.title": "OpenCode Go | Modelli di coding a basso costo per tutti",
|
"go.title": "OpenCode Go | Modelli di coding a basso costo per tutti",
|
||||||
|
"go.banner.text": "MiniMax M3 offre limiti di utilizzo 3x superiori per un periodo limitato",
|
||||||
"go.meta.description":
|
"go.meta.description":
|
||||||
"Go inizia a $5 per il primo mese, poi $10/mese, con generosi limiti di richiesta di 5 ore per GLM-5.2, GLM-5.1, Kimi K2.7 Code, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus, MiniMax M2.7, MiniMax M3, DeepSeek V4 Pro e DeepSeek V4 Flash.",
|
"Go inizia a $5 per il primo mese, poi $10/mese, con generosi limiti di richiesta di 5 ore per GLM-5.2, GLM-5.1, Kimi K2.7 Code, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus, MiniMax M2.7, MiniMax M3, DeepSeek V4 Pro e DeepSeek V4 Flash.",
|
||||||
"go.hero.title": "Modelli di coding a basso costo per tutti",
|
"go.hero.title": "Modelli di coding a basso costo per tutti",
|
||||||
@@ -372,8 +373,6 @@ export const dict = {
|
|||||||
"zen.api.error.userMonthlyLimitReached":
|
"zen.api.error.userMonthlyLimitReached":
|
||||||
"Hai raggiunto il tuo limite di spesa mensile di ${{amount}}. Gestisci i tuoi limiti qui: {{membersUrl}}",
|
"Hai raggiunto il tuo limite di spesa mensile di ${{amount}}. Gestisci i tuoi limiti qui: {{membersUrl}}",
|
||||||
"zen.api.error.modelDisabled": "Il modello è disabilitato",
|
"zen.api.error.modelDisabled": "Il modello è disabilitato",
|
||||||
"zen.api.error.regionNotAllowed":
|
|
||||||
"Questo modello è ospitato in Cina. Se vuoi usare questo modello, abilitalo nelle tue impostazioni: {{consoleGoUrl}}",
|
|
||||||
"zen.api.error.trialEnded":
|
"zen.api.error.trialEnded":
|
||||||
"La promozione gratuita di {{model}} è terminata. Puoi continuare a usare il modello abbonandoti a OpenCode Go - {{link}}",
|
"La promozione gratuita di {{model}} è terminata. Puoi continuare a usare il modello abbonandoti a OpenCode Go - {{link}}",
|
||||||
|
|
||||||
@@ -655,9 +654,6 @@ export const dict = {
|
|||||||
"workspace.lite.subscription.useBalance": "Usa il tuo saldo disponibile dopo aver raggiunto i limiti di utilizzo",
|
"workspace.lite.subscription.useBalance": "Usa il tuo saldo disponibile dopo aver raggiunto i limiti di utilizzo",
|
||||||
"workspace.lite.subscription.selectProvider":
|
"workspace.lite.subscription.selectProvider":
|
||||||
'Seleziona "OpenCode Go" come provider nella tua configurazione opencode per utilizzare i modelli Go.',
|
'Seleziona "OpenCode Go" come provider nella tua configurazione opencode per utilizzare i modelli Go.',
|
||||||
"workspace.lite.providers.title": "Provider",
|
|
||||||
"workspace.lite.providers.description": "Controlla quali provider vengono usati per il routing.",
|
|
||||||
"workspace.lite.providers.useChina": "Abilita modelli ospitati in Cina",
|
|
||||||
"workspace.lite.black.message":
|
"workspace.lite.black.message":
|
||||||
"Attualmente sei abbonato a OpenCode Black o sei in lista d'attesa. Annulla l'iscrizione prima se desideri passare a Go.",
|
"Attualmente sei abbonato a OpenCode Black o sei in lista d'attesa. Annulla l'iscrizione prima se desideri passare a Go.",
|
||||||
"workspace.lite.other.message":
|
"workspace.lite.other.message":
|
||||||
|
|||||||
@@ -249,6 +249,7 @@ export const dict = {
|
|||||||
"zen.privacy.exceptionsLink": "以下の例外",
|
"zen.privacy.exceptionsLink": "以下の例外",
|
||||||
|
|
||||||
"go.title": "OpenCode Go | すべての人のための低価格なコーディングモデル",
|
"go.title": "OpenCode Go | すべての人のための低価格なコーディングモデル",
|
||||||
|
"go.banner.text": "MiniMax M3の利用上限が期間限定で3倍に",
|
||||||
"go.meta.description":
|
"go.meta.description":
|
||||||
"Goは最初の月$5、その後$10/月で、GLM-5.2、GLM-5.1、Kimi K2.7 Code、Kimi K2.6、MiMo-V2.5-Pro、MiMo-V2.5、Qwen3.7 Max、Qwen3.7 Plus、Qwen3.6 Plus、MiniMax M2.7、MiniMax M3、DeepSeek V4 Pro、DeepSeek V4 Flashに対して5時間のゆとりあるリクエスト上限があります。",
|
"Goは最初の月$5、その後$10/月で、GLM-5.2、GLM-5.1、Kimi K2.7 Code、Kimi K2.6、MiMo-V2.5-Pro、MiMo-V2.5、Qwen3.7 Max、Qwen3.7 Plus、Qwen3.6 Plus、MiniMax M2.7、MiniMax M3、DeepSeek V4 Pro、DeepSeek V4 Flashに対して5時間のゆとりあるリクエスト上限があります。",
|
||||||
"go.hero.title": "すべての人のための低価格なコーディングモデル",
|
"go.hero.title": "すべての人のための低価格なコーディングモデル",
|
||||||
@@ -373,8 +374,6 @@ export const dict = {
|
|||||||
"zen.api.error.userMonthlyLimitReached":
|
"zen.api.error.userMonthlyLimitReached":
|
||||||
"月額の利用上限 ${{amount}} に達しました。こちらから上限を管理してください: {{membersUrl}}",
|
"月額の利用上限 ${{amount}} に達しました。こちらから上限を管理してください: {{membersUrl}}",
|
||||||
"zen.api.error.modelDisabled": "モデルが無効です",
|
"zen.api.error.modelDisabled": "モデルが無効です",
|
||||||
"zen.api.error.regionNotAllowed":
|
|
||||||
"このモデルは中国でホストされています。このモデルを使用したい場合は、設定で有効にしてください: {{consoleGoUrl}}",
|
|
||||||
"zen.api.error.trialEnded":
|
"zen.api.error.trialEnded":
|
||||||
"{{model}} の無料プロモーションは終了しました。OpenCode Go を購読するとモデルを引き続き使用できます - {{link}}",
|
"{{model}} の無料プロモーションは終了しました。OpenCode Go を購読するとモデルを引き続き使用できます - {{link}}",
|
||||||
|
|
||||||
@@ -655,9 +654,6 @@ export const dict = {
|
|||||||
"workspace.lite.subscription.useBalance": "利用限度額に達したら利用可能な残高を使用する",
|
"workspace.lite.subscription.useBalance": "利用限度額に達したら利用可能な残高を使用する",
|
||||||
"workspace.lite.subscription.selectProvider":
|
"workspace.lite.subscription.selectProvider":
|
||||||
"Go モデルを使用するには、opencode の設定で「OpenCode Go」をプロバイダーとして選択してください。",
|
"Go モデルを使用するには、opencode の設定で「OpenCode Go」をプロバイダーとして選択してください。",
|
||||||
"workspace.lite.providers.title": "プロバイダー",
|
|
||||||
"workspace.lite.providers.description": "ルーティングに使用するプロバイダーを管理します。",
|
|
||||||
"workspace.lite.providers.useChina": "中国でホストされているモデルを有効にする",
|
|
||||||
"workspace.lite.black.message":
|
"workspace.lite.black.message":
|
||||||
"現在 OpenCode Black を購読中、またはウェイティングリストに登録されています。Go に切り替える場合は、先に登録を解除してください。",
|
"現在 OpenCode Black を購読中、またはウェイティングリストに登録されています。Go に切り替える場合は、先に登録を解除してください。",
|
||||||
"workspace.lite.other.message":
|
"workspace.lite.other.message":
|
||||||
|
|||||||
@@ -246,6 +246,7 @@ export const dict = {
|
|||||||
"zen.privacy.exceptionsLink": "다음 예외",
|
"zen.privacy.exceptionsLink": "다음 예외",
|
||||||
|
|
||||||
"go.title": "OpenCode Go | 모두를 위한 저비용 코딩 모델",
|
"go.title": "OpenCode Go | 모두를 위한 저비용 코딩 모델",
|
||||||
|
"go.banner.text": "MiniMax M3 사용 한도가 한시적으로 3배 확대됩니다",
|
||||||
"go.meta.description":
|
"go.meta.description":
|
||||||
"Go는 첫 달 $5, 이후 $10/월로 시작하며, GLM-5.2, GLM-5.1, Kimi K2.7 Code, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus, MiniMax M2.7, MiniMax M3, DeepSeek V4 Pro, DeepSeek V4 Flash에 대해 넉넉한 5시간 요청 한도를 제공합니다.",
|
"Go는 첫 달 $5, 이후 $10/월로 시작하며, GLM-5.2, GLM-5.1, Kimi K2.7 Code, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus, MiniMax M2.7, MiniMax M3, DeepSeek V4 Pro, DeepSeek V4 Flash에 대해 넉넉한 5시간 요청 한도를 제공합니다.",
|
||||||
"go.hero.title": "모두를 위한 저비용 코딩 모델",
|
"go.hero.title": "모두를 위한 저비용 코딩 모델",
|
||||||
@@ -367,8 +368,6 @@ export const dict = {
|
|||||||
"zen.api.error.userMonthlyLimitReached":
|
"zen.api.error.userMonthlyLimitReached":
|
||||||
"월간 지출 한도인 ${{amount}}에 도달했습니다. 한도 관리를 여기서 하세요: {{membersUrl}}",
|
"월간 지출 한도인 ${{amount}}에 도달했습니다. 한도 관리를 여기서 하세요: {{membersUrl}}",
|
||||||
"zen.api.error.modelDisabled": "모델이 비활성화되었습니다",
|
"zen.api.error.modelDisabled": "모델이 비활성화되었습니다",
|
||||||
"zen.api.error.regionNotAllowed":
|
|
||||||
"이 모델은 중국에서 호스팅됩니다. 이 모델을 사용하려면 설정에서 활성화하세요: {{consoleGoUrl}}",
|
|
||||||
"zen.api.error.trialEnded":
|
"zen.api.error.trialEnded":
|
||||||
"{{model}}의 무료 프로모션이 종료되었습니다. OpenCode Go를 구독하면 모델을 계속 사용할 수 있습니다 - {{link}}",
|
"{{model}}의 무료 프로모션이 종료되었습니다. OpenCode Go를 구독하면 모델을 계속 사용할 수 있습니다 - {{link}}",
|
||||||
|
|
||||||
@@ -647,9 +646,6 @@ export const dict = {
|
|||||||
"workspace.lite.subscription.useBalance": "사용 한도 도달 후에는 보유 잔액 사용",
|
"workspace.lite.subscription.useBalance": "사용 한도 도달 후에는 보유 잔액 사용",
|
||||||
"workspace.lite.subscription.selectProvider":
|
"workspace.lite.subscription.selectProvider":
|
||||||
'Go 모델을 사용하려면 opencode 설정에서 "OpenCode Go"를 공급자로 선택하세요.',
|
'Go 모델을 사용하려면 opencode 설정에서 "OpenCode Go"를 공급자로 선택하세요.',
|
||||||
"workspace.lite.providers.title": "공급자",
|
|
||||||
"workspace.lite.providers.description": "라우팅에 사용할 공급자를 제어합니다.",
|
|
||||||
"workspace.lite.providers.useChina": "중국에서 호스팅되는 모델 활성화",
|
|
||||||
"workspace.lite.black.message":
|
"workspace.lite.black.message":
|
||||||
"현재 OpenCode Black을 구독 중이거나 대기 명단에 등록되어 있습니다. Go로 전환하려면 먼저 구독을 취소해 주세요.",
|
"현재 OpenCode Black을 구독 중이거나 대기 명단에 등록되어 있습니다. Go로 전환하려면 먼저 구독을 취소해 주세요.",
|
||||||
"workspace.lite.other.message":
|
"workspace.lite.other.message":
|
||||||
|
|||||||
@@ -250,6 +250,7 @@ export const dict = {
|
|||||||
"zen.privacy.exceptionsLink": "følgende unntak",
|
"zen.privacy.exceptionsLink": "følgende unntak",
|
||||||
|
|
||||||
"go.title": "OpenCode Go | Rimelige kodemodeller for alle",
|
"go.title": "OpenCode Go | Rimelige kodemodeller for alle",
|
||||||
|
"go.banner.text": "MiniMax M3 får 3x bruksgrense i en begrenset periode",
|
||||||
"go.meta.description":
|
"go.meta.description":
|
||||||
"Go starter på $5 for den første måneden, deretter $10/måned, med sjenerøse 5-timers forespørselsgrenser for GLM-5.2, GLM-5.1, Kimi K2.7 Code, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus, MiniMax M2.7, MiniMax M3, DeepSeek V4 Pro og DeepSeek V4 Flash.",
|
"Go starter på $5 for den første måneden, deretter $10/måned, med sjenerøse 5-timers forespørselsgrenser for GLM-5.2, GLM-5.1, Kimi K2.7 Code, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus, MiniMax M2.7, MiniMax M3, DeepSeek V4 Pro og DeepSeek V4 Flash.",
|
||||||
"go.hero.title": "Rimelige kodemodeller for alle",
|
"go.hero.title": "Rimelige kodemodeller for alle",
|
||||||
@@ -373,8 +374,6 @@ export const dict = {
|
|||||||
"zen.api.error.userMonthlyLimitReached":
|
"zen.api.error.userMonthlyLimitReached":
|
||||||
"Du har nådd din månedlige utgiftsgrense på ${{amount}}. Administrer grensene dine her: {{membersUrl}}",
|
"Du har nådd din månedlige utgiftsgrense på ${{amount}}. Administrer grensene dine her: {{membersUrl}}",
|
||||||
"zen.api.error.modelDisabled": "Modellen er deaktivert",
|
"zen.api.error.modelDisabled": "Modellen er deaktivert",
|
||||||
"zen.api.error.regionNotAllowed":
|
|
||||||
"Denne modellen hostes i Kina. Hvis du vil bruke denne modellen, aktiver den i innstillingene dine: {{consoleGoUrl}}",
|
|
||||||
"zen.api.error.trialEnded":
|
"zen.api.error.trialEnded":
|
||||||
"Den gratis kampanjen for {{model}} er avsluttet. Du kan fortsette å bruke modellen ved å abonnere på OpenCode Go - {{link}}",
|
"Den gratis kampanjen for {{model}} er avsluttet. Du kan fortsette å bruke modellen ved å abonnere på OpenCode Go - {{link}}",
|
||||||
|
|
||||||
@@ -654,9 +653,6 @@ export const dict = {
|
|||||||
"workspace.lite.subscription.useBalance": "Bruk din tilgjengelige saldo etter å ha nådd bruksgrensene",
|
"workspace.lite.subscription.useBalance": "Bruk din tilgjengelige saldo etter å ha nådd bruksgrensene",
|
||||||
"workspace.lite.subscription.selectProvider":
|
"workspace.lite.subscription.selectProvider":
|
||||||
'Velg "OpenCode Go" som leverandør i opencode-konfigurasjonen din for å bruke Go-modeller.',
|
'Velg "OpenCode Go" som leverandør i opencode-konfigurasjonen din for å bruke Go-modeller.',
|
||||||
"workspace.lite.providers.title": "Leverandører",
|
|
||||||
"workspace.lite.providers.description": "Kontroller hvilke leverandører som brukes til ruting.",
|
|
||||||
"workspace.lite.providers.useChina": "Aktiver modeller hostet i Kina",
|
|
||||||
"workspace.lite.black.message":
|
"workspace.lite.black.message":
|
||||||
"Du abonnerer for øyeblikket på OpenCode Black eller står på venteliste. Vennligst avslutt abonnementet først hvis du vil bytte til Go.",
|
"Du abonnerer for øyeblikket på OpenCode Black eller står på venteliste. Vennligst avslutt abonnementet først hvis du vil bytte til Go.",
|
||||||
"workspace.lite.other.message":
|
"workspace.lite.other.message":
|
||||||
|
|||||||
@@ -251,6 +251,7 @@ export const dict = {
|
|||||||
"zen.privacy.exceptionsLink": "następującymi wyjątkami",
|
"zen.privacy.exceptionsLink": "następującymi wyjątkami",
|
||||||
|
|
||||||
"go.title": "OpenCode Go | Niskokosztowe modele do kodowania dla każdego",
|
"go.title": "OpenCode Go | Niskokosztowe modele do kodowania dla każdego",
|
||||||
|
"go.banner.text": "MiniMax M3 oferuje 3x wyższe limity użycia przez ograniczony czas",
|
||||||
"go.meta.description":
|
"go.meta.description":
|
||||||
"Go zaczyna się od $5 za pierwszy miesiąc, potem $10/miesiąc, z hojnymi 5-godzinnymi limitami zapytań dla GLM-5.2, GLM-5.1, Kimi K2.7 Code, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus, MiniMax M2.7, MiniMax M3, DeepSeek V4 Pro i DeepSeek V4 Flash.",
|
"Go zaczyna się od $5 za pierwszy miesiąc, potem $10/miesiąc, z hojnymi 5-godzinnymi limitami zapytań dla GLM-5.2, GLM-5.1, Kimi K2.7 Code, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus, MiniMax M2.7, MiniMax M3, DeepSeek V4 Pro i DeepSeek V4 Flash.",
|
||||||
"go.hero.title": "Niskokosztowe modele do kodowania dla każdego",
|
"go.hero.title": "Niskokosztowe modele do kodowania dla każdego",
|
||||||
@@ -374,8 +375,6 @@ export const dict = {
|
|||||||
"zen.api.error.userMonthlyLimitReached":
|
"zen.api.error.userMonthlyLimitReached":
|
||||||
"Osiągnąłeś swój miesięczny limit wydatków w wysokości ${{amount}}. Zarządzaj swoimi limitami tutaj: {{membersUrl}}",
|
"Osiągnąłeś swój miesięczny limit wydatków w wysokości ${{amount}}. Zarządzaj swoimi limitami tutaj: {{membersUrl}}",
|
||||||
"zen.api.error.modelDisabled": "Model jest wyłączony",
|
"zen.api.error.modelDisabled": "Model jest wyłączony",
|
||||||
"zen.api.error.regionNotAllowed":
|
|
||||||
"Ten model jest hostowany w Chinach. Jeśli chcesz korzystać z tego modelu, włącz go w swoich ustawieniach: {{consoleGoUrl}}",
|
|
||||||
"zen.api.error.trialEnded":
|
"zen.api.error.trialEnded":
|
||||||
"Bezpłatna promocja {{model}} dobiegła końca. Możesz dalej korzystać z modelu, subskrybując OpenCode Go - {{link}}",
|
"Bezpłatna promocja {{model}} dobiegła końca. Możesz dalej korzystać z modelu, subskrybując OpenCode Go - {{link}}",
|
||||||
|
|
||||||
@@ -655,9 +654,6 @@ export const dict = {
|
|||||||
"workspace.lite.subscription.useBalance": "Użyj dostępnego salda po osiągnięciu limitów użycia",
|
"workspace.lite.subscription.useBalance": "Użyj dostępnego salda po osiągnięciu limitów użycia",
|
||||||
"workspace.lite.subscription.selectProvider":
|
"workspace.lite.subscription.selectProvider":
|
||||||
'Wybierz "OpenCode Go" jako dostawcę w konfiguracji opencode, aby używać modeli Go.',
|
'Wybierz "OpenCode Go" jako dostawcę w konfiguracji opencode, aby używać modeli Go.',
|
||||||
"workspace.lite.providers.title": "Dostawcy",
|
|
||||||
"workspace.lite.providers.description": "Kontroluj, którzy dostawcy są używani do routingu.",
|
|
||||||
"workspace.lite.providers.useChina": "Włącz modele hostowane w Chinach",
|
|
||||||
"workspace.lite.black.message":
|
"workspace.lite.black.message":
|
||||||
"Obecnie subskrybujesz OpenCode Black lub jesteś na liście oczekujących. Jeśli chcesz przejść na Go, najpierw anuluj subskrypcję.",
|
"Obecnie subskrybujesz OpenCode Black lub jesteś na liście oczekujących. Jeśli chcesz przejść na Go, najpierw anuluj subskrypcję.",
|
||||||
"workspace.lite.other.message":
|
"workspace.lite.other.message":
|
||||||
|
|||||||
@@ -254,6 +254,7 @@ export const dict = {
|
|||||||
"zen.privacy.exceptionsLink": "следующими исключениями",
|
"zen.privacy.exceptionsLink": "следующими исключениями",
|
||||||
|
|
||||||
"go.title": "OpenCode Go | Недорогие модели для кодинга для всех",
|
"go.title": "OpenCode Go | Недорогие модели для кодинга для всех",
|
||||||
|
"go.banner.text": "MiniMax M3 получает 3x лимиты использования на ограниченное время",
|
||||||
"go.meta.description":
|
"go.meta.description":
|
||||||
"Go начинается с $5 за первый месяц, затем $10/месяц, с щедрыми лимитами запросов за 5 часов для GLM-5.2, GLM-5.1, Kimi K2.7 Code, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus, MiniMax M2.7, MiniMax M3, DeepSeek V4 Pro и DeepSeek V4 Flash.",
|
"Go начинается с $5 за первый месяц, затем $10/месяц, с щедрыми лимитами запросов за 5 часов для GLM-5.2, GLM-5.1, Kimi K2.7 Code, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus, MiniMax M2.7, MiniMax M3, DeepSeek V4 Pro и DeepSeek V4 Flash.",
|
||||||
"go.hero.title": "Недорогие модели для кодинга для всех",
|
"go.hero.title": "Недорогие модели для кодинга для всех",
|
||||||
@@ -378,8 +379,6 @@ export const dict = {
|
|||||||
"zen.api.error.userMonthlyLimitReached":
|
"zen.api.error.userMonthlyLimitReached":
|
||||||
"Вы достигли ежемесячного лимита расходов в ${{amount}}. Управляйте лимитами здесь: {{membersUrl}}",
|
"Вы достигли ежемесячного лимита расходов в ${{amount}}. Управляйте лимитами здесь: {{membersUrl}}",
|
||||||
"zen.api.error.modelDisabled": "Модель отключена",
|
"zen.api.error.modelDisabled": "Модель отключена",
|
||||||
"zen.api.error.regionNotAllowed":
|
|
||||||
"Эта модель размещена в Китае. Если вы хотите использовать эту модель, включите её в настройках: {{consoleGoUrl}}",
|
|
||||||
"zen.api.error.trialEnded":
|
"zen.api.error.trialEnded":
|
||||||
"Бесплатная акция для {{model}} завершена. Вы можете продолжить использование модели, подписавшись на OpenCode Go - {{link}}",
|
"Бесплатная акция для {{model}} завершена. Вы можете продолжить использование модели, подписавшись на OpenCode Go - {{link}}",
|
||||||
|
|
||||||
@@ -661,9 +660,6 @@ export const dict = {
|
|||||||
"workspace.lite.subscription.useBalance": "Использовать доступный баланс после достижения лимитов",
|
"workspace.lite.subscription.useBalance": "Использовать доступный баланс после достижения лимитов",
|
||||||
"workspace.lite.subscription.selectProvider":
|
"workspace.lite.subscription.selectProvider":
|
||||||
'Выберите "OpenCode Go" в качестве провайдера в настройках opencode для использования моделей Go.',
|
'Выберите "OpenCode Go" в качестве провайдера в настройках opencode для использования моделей Go.',
|
||||||
"workspace.lite.providers.title": "Провайдеры",
|
|
||||||
"workspace.lite.providers.description": "Управляйте провайдерами, используемыми для маршрутизации.",
|
|
||||||
"workspace.lite.providers.useChina": "Включить модели, размещенные в Китае",
|
|
||||||
"workspace.lite.black.message":
|
"workspace.lite.black.message":
|
||||||
"Вы подписаны на OpenCode Black или находитесь в списке ожидания. Пожалуйста, сначала отмените подписку, если хотите перейти на Go.",
|
"Вы подписаны на OpenCode Black или находитесь в списке ожидания. Пожалуйста, сначала отмените подписку, если хотите перейти на Go.",
|
||||||
"workspace.lite.other.message":
|
"workspace.lite.other.message":
|
||||||
|
|||||||
@@ -249,6 +249,7 @@ export const dict = {
|
|||||||
"zen.privacy.exceptionsLink": "ข้อยกเว้นดังนี้",
|
"zen.privacy.exceptionsLink": "ข้อยกเว้นดังนี้",
|
||||||
|
|
||||||
"go.title": "OpenCode Go | โมเดลเขียนโค้ดราคาประหยัดสำหรับทุกคน",
|
"go.title": "OpenCode Go | โมเดลเขียนโค้ดราคาประหยัดสำหรับทุกคน",
|
||||||
|
"go.banner.text": "MiniMax M3 เพิ่มโควตาการใช้งานเป็น 3 เท่าในช่วงเวลาจำกัด",
|
||||||
"go.meta.description":
|
"go.meta.description":
|
||||||
"Go เริ่มต้นที่ $5 สำหรับเดือนแรก จากนั้น $10/เดือน พร้อมขีดจำกัดคำขอ 5 ชั่วโมงที่เอื้อเฟื้อสำหรับ GLM-5.2, GLM-5.1, Kimi K2.7 Code, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus, MiniMax M2.7, MiniMax M3, DeepSeek V4 Pro และ DeepSeek V4 Flash",
|
"Go เริ่มต้นที่ $5 สำหรับเดือนแรก จากนั้น $10/เดือน พร้อมขีดจำกัดคำขอ 5 ชั่วโมงที่เอื้อเฟื้อสำหรับ GLM-5.2, GLM-5.1, Kimi K2.7 Code, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus, MiniMax M2.7, MiniMax M3, DeepSeek V4 Pro และ DeepSeek V4 Flash",
|
||||||
"go.hero.title": "โมเดลเขียนโค้ดราคาประหยัดสำหรับทุกคน",
|
"go.hero.title": "โมเดลเขียนโค้ดราคาประหยัดสำหรับทุกคน",
|
||||||
@@ -369,8 +370,6 @@ export const dict = {
|
|||||||
"zen.api.error.userMonthlyLimitReached":
|
"zen.api.error.userMonthlyLimitReached":
|
||||||
"คุณถึงขีดจำกัดการใช้จ่ายรายเดือนที่ ${{amount}} แล้ว จัดการขีดจำกัดของคุณที่นี่: {{membersUrl}}",
|
"คุณถึงขีดจำกัดการใช้จ่ายรายเดือนที่ ${{amount}} แล้ว จัดการขีดจำกัดของคุณที่นี่: {{membersUrl}}",
|
||||||
"zen.api.error.modelDisabled": "โมเดลถูกปิดใช้งาน",
|
"zen.api.error.modelDisabled": "โมเดลถูกปิดใช้งาน",
|
||||||
"zen.api.error.regionNotAllowed":
|
|
||||||
"โมเดลนี้โฮสต์อยู่ในประเทศจีน หากคุณต้องการใช้โมเดลนี้ ให้เปิดใช้งานในการตั้งค่าของคุณ: {{consoleGoUrl}}",
|
|
||||||
"zen.api.error.trialEnded":
|
"zen.api.error.trialEnded":
|
||||||
"โปรโมชันฟรีสำหรับ {{model}} สิ้นสุดแล้ว คุณสามารถใช้โมเดลต่อได้โดยสมัครสมาชิก OpenCode Go - {{link}}",
|
"โปรโมชันฟรีสำหรับ {{model}} สิ้นสุดแล้ว คุณสามารถใช้โมเดลต่อได้โดยสมัครสมาชิก OpenCode Go - {{link}}",
|
||||||
|
|
||||||
@@ -650,9 +649,6 @@ export const dict = {
|
|||||||
"workspace.lite.subscription.useBalance": "ใช้ยอดคงเหลือของคุณหลังจากถึงขีดจำกัดการใช้งาน",
|
"workspace.lite.subscription.useBalance": "ใช้ยอดคงเหลือของคุณหลังจากถึงขีดจำกัดการใช้งาน",
|
||||||
"workspace.lite.subscription.selectProvider":
|
"workspace.lite.subscription.selectProvider":
|
||||||
'เลือก "OpenCode Go" เป็นผู้ให้บริการในการตั้งค่า opencode ของคุณเพื่อใช้โมเดล Go',
|
'เลือก "OpenCode Go" เป็นผู้ให้บริการในการตั้งค่า opencode ของคุณเพื่อใช้โมเดล Go',
|
||||||
"workspace.lite.providers.title": "ผู้ให้บริการ",
|
|
||||||
"workspace.lite.providers.description": "ควบคุมผู้ให้บริการที่ใช้สำหรับการกำหนดเส้นทาง",
|
|
||||||
"workspace.lite.providers.useChina": "เปิดใช้โมเดลที่โฮสต์ในจีน",
|
|
||||||
"workspace.lite.black.message":
|
"workspace.lite.black.message":
|
||||||
"ขณะนี้คุณสมัครสมาชิก OpenCode Black หรืออยู่ในรายการรอ โปรดยกเลิกการสมัครก่อนหากต้องการเปลี่ยนไปใช้ Go",
|
"ขณะนี้คุณสมัครสมาชิก OpenCode Black หรืออยู่ในรายการรอ โปรดยกเลิกการสมัครก่อนหากต้องการเปลี่ยนไปใช้ Go",
|
||||||
"workspace.lite.other.message":
|
"workspace.lite.other.message":
|
||||||
|
|||||||
@@ -252,6 +252,7 @@ export const dict = {
|
|||||||
"zen.privacy.exceptionsLink": "aşağıdaki istisnalar",
|
"zen.privacy.exceptionsLink": "aşağıdaki istisnalar",
|
||||||
|
|
||||||
"go.title": "OpenCode Go | Herkes için düşük maliyetli kodlama modelleri",
|
"go.title": "OpenCode Go | Herkes için düşük maliyetli kodlama modelleri",
|
||||||
|
"go.banner.text": "MiniMax M3 sınırlı bir süre için 3x kullanım limiti sunuyor",
|
||||||
"go.meta.description":
|
"go.meta.description":
|
||||||
"Go ilk ay $5, sonrasında ayda 10$ fiyatıyla başlar; GLM-5.2, GLM-5.1, Kimi K2.7 Code, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus, MiniMax M2.7, MiniMax M3, DeepSeek V4 Pro ve DeepSeek V4 Flash için cömert 5 saatlik istek limitleri sunar.",
|
"Go ilk ay $5, sonrasında ayda 10$ fiyatıyla başlar; GLM-5.2, GLM-5.1, Kimi K2.7 Code, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus, MiniMax M2.7, MiniMax M3, DeepSeek V4 Pro ve DeepSeek V4 Flash için cömert 5 saatlik istek limitleri sunar.",
|
||||||
"go.hero.title": "Herkes için düşük maliyetli kodlama modelleri",
|
"go.hero.title": "Herkes için düşük maliyetli kodlama modelleri",
|
||||||
@@ -376,8 +377,6 @@ export const dict = {
|
|||||||
"zen.api.error.userMonthlyLimitReached":
|
"zen.api.error.userMonthlyLimitReached":
|
||||||
"Aylık ${{amount}} harcama limitinize ulaştınız. Limitlerinizi buradan yönetin: {{membersUrl}}",
|
"Aylık ${{amount}} harcama limitinize ulaştınız. Limitlerinizi buradan yönetin: {{membersUrl}}",
|
||||||
"zen.api.error.modelDisabled": "Model devre dışı",
|
"zen.api.error.modelDisabled": "Model devre dışı",
|
||||||
"zen.api.error.regionNotAllowed":
|
|
||||||
"Bu model Çin'de barındırılıyor. Bu modeli kullanmak istiyorsanız ayarlarınızdan etkinleştirin: {{consoleGoUrl}}",
|
|
||||||
"zen.api.error.trialEnded":
|
"zen.api.error.trialEnded":
|
||||||
"{{model}} için ücretsiz promosyon sona erdi. OpenCode Go'ya abone olarak modeli kullanmaya devam edebilirsiniz - {{link}}",
|
"{{model}} için ücretsiz promosyon sona erdi. OpenCode Go'ya abone olarak modeli kullanmaya devam edebilirsiniz - {{link}}",
|
||||||
|
|
||||||
@@ -657,9 +656,6 @@ export const dict = {
|
|||||||
"workspace.lite.subscription.useBalance": "Kullanım limitlerine ulaştıktan sonra mevcut bakiyenizi kullanın",
|
"workspace.lite.subscription.useBalance": "Kullanım limitlerine ulaştıktan sonra mevcut bakiyenizi kullanın",
|
||||||
"workspace.lite.subscription.selectProvider":
|
"workspace.lite.subscription.selectProvider":
|
||||||
'Go modellerini kullanmak için opencode yapılandırmanızda "OpenCode Go"\'yu sağlayıcı olarak seçin.',
|
'Go modellerini kullanmak için opencode yapılandırmanızda "OpenCode Go"\'yu sağlayıcı olarak seçin.',
|
||||||
"workspace.lite.providers.title": "Sağlayıcılar",
|
|
||||||
"workspace.lite.providers.description": "Yönlendirme için hangi sağlayıcıların kullanılacağını kontrol edin.",
|
|
||||||
"workspace.lite.providers.useChina": "Çin'de barındırılan modelleri etkinleştir",
|
|
||||||
"workspace.lite.black.message":
|
"workspace.lite.black.message":
|
||||||
"Şu anda OpenCode Black abonesisiniz veya bekleme listesindesiniz. Go'ya geçmek istiyorsanız lütfen önce aboneliğinizi iptal edin.",
|
"Şu anda OpenCode Black abonesisiniz veya bekleme listesindesiniz. Go'ya geçmek istiyorsanız lütfen önce aboneliğinizi iptal edin.",
|
||||||
"workspace.lite.other.message":
|
"workspace.lite.other.message":
|
||||||
|
|||||||
@@ -251,6 +251,7 @@ export const dict = {
|
|||||||
"zen.privacy.exceptionsLink": "такими винятками",
|
"zen.privacy.exceptionsLink": "такими винятками",
|
||||||
|
|
||||||
"go.title": "OpenCode Go | Недорогі моделі кодування для всіх",
|
"go.title": "OpenCode Go | Недорогі моделі кодування для всіх",
|
||||||
|
"go.banner.text": "MiniMax M3 отримує 3x ліміти використання протягом обмеженого часу",
|
||||||
"go.meta.description":
|
"go.meta.description":
|
||||||
"Go починається від $5 за перший місяць, потім $10/місяць, з generous 5-годинними лімітами запитів для GLM-5.2, GLM-5.1, Kimi K2.7 Code, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus, MiniMax M2.7, MiniMax M3, DeepSeek V4 Pro та DeepSeek V4 Flash.",
|
"Go починається від $5 за перший місяць, потім $10/місяць, з generous 5-годинними лімітами запитів для GLM-5.2, GLM-5.1, Kimi K2.7 Code, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus, MiniMax M2.7, MiniMax M3, DeepSeek V4 Pro та DeepSeek V4 Flash.",
|
||||||
"go.hero.title": "Недорогі моделі кодування для всіх",
|
"go.hero.title": "Недорогі моделі кодування для всіх",
|
||||||
@@ -373,8 +374,6 @@ export const dict = {
|
|||||||
"zen.api.error.userMonthlyLimitReached":
|
"zen.api.error.userMonthlyLimitReached":
|
||||||
"Ви досягли місячного ліміту витрат ${{amount}}. Керуйте лімітами: {{membersUrl}}",
|
"Ви досягли місячного ліміту витрат ${{amount}}. Керуйте лімітами: {{membersUrl}}",
|
||||||
"zen.api.error.modelDisabled": "Модель вимкнено",
|
"zen.api.error.modelDisabled": "Модель вимкнено",
|
||||||
"zen.api.error.regionNotAllowed":
|
|
||||||
"Ця модель розміщена в Китаї. Якщо ви хочете використовувати цю модель, увімкніть її в налаштуваннях: {{consoleGoUrl}}",
|
|
||||||
"zen.api.error.trialEnded":
|
"zen.api.error.trialEnded":
|
||||||
"Безкоштовна акція для {{model}} закінчилася. Ви можете продовжити використання, підписавшись на OpenCode Go — {{link}}",
|
"Безкоштовна акція для {{model}} закінчилася. Ви можете продовжити використання, підписавшись на OpenCode Go — {{link}}",
|
||||||
|
|
||||||
@@ -653,9 +652,6 @@ export const dict = {
|
|||||||
"workspace.lite.subscription.resetsIn": "Скидається через",
|
"workspace.lite.subscription.resetsIn": "Скидається через",
|
||||||
"workspace.lite.subscription.useBalance": "Використовуйте доступний баланс після досягнення лімітів",
|
"workspace.lite.subscription.useBalance": "Використовуйте доступний баланс після досягнення лімітів",
|
||||||
"workspace.lite.subscription.selectProvider": 'Виберіть "OpenCode Go" як провайдера в конфігурації opencode.',
|
"workspace.lite.subscription.selectProvider": 'Виберіть "OpenCode Go" як провайдера в конфігурації opencode.',
|
||||||
"workspace.lite.providers.title": "Провайдери",
|
|
||||||
"workspace.lite.providers.description": "Керуйте провайдерами, які використовуються для маршрутизації.",
|
|
||||||
"workspace.lite.providers.useChina": "Увімкнути моделі, розміщені в Китаї",
|
|
||||||
"workspace.lite.black.message":
|
"workspace.lite.black.message":
|
||||||
"Ви вже підписані на OpenCode Black або в списку очікування. Спочатку скасуйте підписку, якщо хочете перейти на Go.",
|
"Ви вже підписані на OpenCode Black або в списку очікування. Спочатку скасуйте підписку, якщо хочете перейти на Go.",
|
||||||
"workspace.lite.other.message": "Інший учасник цього робочого простору вже підписаний на OpenCode Go.",
|
"workspace.lite.other.message": "Інший учасник цього робочого простору вже підписаний на OpenCode Go.",
|
||||||
|
|||||||
@@ -240,6 +240,7 @@ export const dict = {
|
|||||||
"zen.privacy.exceptionsLink": "以下例外情况除外",
|
"zen.privacy.exceptionsLink": "以下例外情况除外",
|
||||||
|
|
||||||
"go.title": "OpenCode Go | 人人可用的低成本编程模型",
|
"go.title": "OpenCode Go | 人人可用的低成本编程模型",
|
||||||
|
"go.banner.text": "MiniMax M3 限时享受 3 倍使用额度",
|
||||||
"go.meta.description":
|
"go.meta.description":
|
||||||
"Go 首月 $5,之后 $10/月,提供对 GLM-5.2、GLM-5.1、Kimi K2.7 Code、Kimi K2.6、MiMo-V2.5-Pro、MiMo-V2.5、Qwen3.7 Max、Qwen3.7 Plus、Qwen3.6 Plus、MiniMax M2.7、MiniMax M3、DeepSeek V4 Pro 和 DeepSeek V4 Flash 的 5 小时充裕请求额度。",
|
"Go 首月 $5,之后 $10/月,提供对 GLM-5.2、GLM-5.1、Kimi K2.7 Code、Kimi K2.6、MiMo-V2.5-Pro、MiMo-V2.5、Qwen3.7 Max、Qwen3.7 Plus、Qwen3.6 Plus、MiniMax M2.7、MiniMax M3、DeepSeek V4 Pro 和 DeepSeek V4 Flash 的 5 小时充裕请求额度。",
|
||||||
"go.hero.title": "人人可用的低成本编程模型",
|
"go.hero.title": "人人可用的低成本编程模型",
|
||||||
@@ -355,7 +356,6 @@ export const dict = {
|
|||||||
"您的工作区已达到每月支出限额 ${{amount}}。请在此处管理您的限额:{{billingUrl}}",
|
"您的工作区已达到每月支出限额 ${{amount}}。请在此处管理您的限额:{{billingUrl}}",
|
||||||
"zen.api.error.userMonthlyLimitReached": "您已达到每月支出限额 ${{amount}}。请在此处管理您的限额:{{membersUrl}}",
|
"zen.api.error.userMonthlyLimitReached": "您已达到每月支出限额 ${{amount}}。请在此处管理您的限额:{{membersUrl}}",
|
||||||
"zen.api.error.modelDisabled": "模型已禁用",
|
"zen.api.error.modelDisabled": "模型已禁用",
|
||||||
"zen.api.error.regionNotAllowed": "该模型部署在中国。如果你想使用该模型,请在设置中启用它:{{consoleGoUrl}}",
|
|
||||||
"zen.api.error.trialEnded": "{{model}} 的限免活动已结束。您可以订阅 OpenCode Go 继续使用该模型 - {{link}}",
|
"zen.api.error.trialEnded": "{{model}} 的限免活动已结束。您可以订阅 OpenCode Go 继续使用该模型 - {{link}}",
|
||||||
|
|
||||||
"black.meta.title": "OpenCode Black | 访问全球顶尖编程模型",
|
"black.meta.title": "OpenCode Black | 访问全球顶尖编程模型",
|
||||||
@@ -631,9 +631,6 @@ export const dict = {
|
|||||||
"workspace.lite.subscription.useBalance": "达到使用限额后使用您的可用余额",
|
"workspace.lite.subscription.useBalance": "达到使用限额后使用您的可用余额",
|
||||||
"workspace.lite.subscription.selectProvider":
|
"workspace.lite.subscription.selectProvider":
|
||||||
"在你的 opencode 配置中选择「OpenCode Go」作为提供商,即可使用 Go 模型。",
|
"在你的 opencode 配置中选择「OpenCode Go」作为提供商,即可使用 Go 模型。",
|
||||||
"workspace.lite.providers.title": "提供商",
|
|
||||||
"workspace.lite.providers.description": "控制用于路由的提供商。",
|
|
||||||
"workspace.lite.providers.useChina": "启用部署在中国的模型",
|
|
||||||
"workspace.lite.black.message": "您当前已订阅 OpenCode Black 或在候补名单中。如需切换到 Go,请先取消订阅。",
|
"workspace.lite.black.message": "您当前已订阅 OpenCode Black 或在候补名单中。如需切换到 Go,请先取消订阅。",
|
||||||
"workspace.lite.other.message": "此工作区中的另一位成员已经订阅了 OpenCode Go。每个工作区只有一名成员可以订阅。",
|
"workspace.lite.other.message": "此工作区中的另一位成员已经订阅了 OpenCode Go。每个工作区只有一名成员可以订阅。",
|
||||||
"workspace.lite.promo.description":
|
"workspace.lite.promo.description":
|
||||||
|
|||||||
@@ -240,6 +240,7 @@ export const dict = {
|
|||||||
"zen.privacy.exceptionsLink": "以下例外情況",
|
"zen.privacy.exceptionsLink": "以下例外情況",
|
||||||
|
|
||||||
"go.title": "OpenCode Go | 低成本全民編碼模型",
|
"go.title": "OpenCode Go | 低成本全民編碼模型",
|
||||||
|
"go.banner.text": "MiniMax M3 限時享有 3 倍使用額度",
|
||||||
"go.meta.description":
|
"go.meta.description":
|
||||||
"Go 首月 $5,之後 $10/月,提供對 GLM-5.2、GLM-5.1、Kimi K2.7 Code、Kimi K2.6、MiMo-V2.5-Pro、MiMo-V2.5、Qwen3.7 Max、Qwen3.7 Plus、Qwen3.6 Plus、MiniMax M2.7、MiniMax M3、DeepSeek V4 Pro 和 DeepSeek V4 Flash 的 5 小時充裕請求額度。",
|
"Go 首月 $5,之後 $10/月,提供對 GLM-5.2、GLM-5.1、Kimi K2.7 Code、Kimi K2.6、MiMo-V2.5-Pro、MiMo-V2.5、Qwen3.7 Max、Qwen3.7 Plus、Qwen3.6 Plus、MiniMax M2.7、MiniMax M3、DeepSeek V4 Pro 和 DeepSeek V4 Flash 的 5 小時充裕請求額度。",
|
||||||
"go.hero.title": "低成本全民編碼模型",
|
"go.hero.title": "低成本全民編碼模型",
|
||||||
@@ -355,7 +356,6 @@ export const dict = {
|
|||||||
"你的工作區已達到每月支出限額 ${{amount}}。請在此處管理你的限額:{{billingUrl}}",
|
"你的工作區已達到每月支出限額 ${{amount}}。請在此處管理你的限額:{{billingUrl}}",
|
||||||
"zen.api.error.userMonthlyLimitReached": "你已達到每月支出限額 ${{amount}}。請在此處管理你的限額:{{membersUrl}}",
|
"zen.api.error.userMonthlyLimitReached": "你已達到每月支出限額 ${{amount}}。請在此處管理你的限額:{{membersUrl}}",
|
||||||
"zen.api.error.modelDisabled": "模型已停用",
|
"zen.api.error.modelDisabled": "模型已停用",
|
||||||
"zen.api.error.regionNotAllowed": "此模型部署於中國。如果你想使用此模型,請在設定中啟用它:{{consoleGoUrl}}",
|
|
||||||
"zen.api.error.trialEnded": "{{model}} 的限免活动已結束。您可以訂閱 OpenCode Go 繼續使用該模型 - {{link}}",
|
"zen.api.error.trialEnded": "{{model}} 的限免活动已結束。您可以訂閱 OpenCode Go 繼續使用該模型 - {{link}}",
|
||||||
|
|
||||||
"black.meta.title": "OpenCode Black | 存取全球最佳編碼模型",
|
"black.meta.title": "OpenCode Black | 存取全球最佳編碼模型",
|
||||||
@@ -631,9 +631,6 @@ export const dict = {
|
|||||||
"workspace.lite.subscription.useBalance": "達到使用限制後使用您的可用餘額",
|
"workspace.lite.subscription.useBalance": "達到使用限制後使用您的可用餘額",
|
||||||
"workspace.lite.subscription.selectProvider":
|
"workspace.lite.subscription.selectProvider":
|
||||||
"在您的 opencode 設定中選擇「OpenCode Go」作為提供商,即可使用 Go 模型。",
|
"在您的 opencode 設定中選擇「OpenCode Go」作為提供商,即可使用 Go 模型。",
|
||||||
"workspace.lite.providers.title": "提供商",
|
|
||||||
"workspace.lite.providers.description": "控制用於路由的提供商。",
|
|
||||||
"workspace.lite.providers.useChina": "啟用部署在中國的模型",
|
|
||||||
"workspace.lite.black.message": "您目前已訂閱 OpenCode Black 或在候補名單中。若要切換至 Go,請先取消訂閱。",
|
"workspace.lite.black.message": "您目前已訂閱 OpenCode Black 或在候補名單中。若要切換至 Go,請先取消訂閱。",
|
||||||
"workspace.lite.other.message": "此工作區中的另一位成員已訂閱 OpenCode Go。每個工作區只能有一位成員訂閱。",
|
"workspace.lite.other.message": "此工作區中的另一位成員已訂閱 OpenCode Go。每個工作區只能有一位成員訂閱。",
|
||||||
"workspace.lite.promo.description":
|
"workspace.lite.promo.description":
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
export function countryFromRequest(request: Request | undefined) {
|
|
||||||
if (!request) return undefined
|
|
||||||
const cloudflareRequest = request as Request & { cf?: { country?: string } }
|
|
||||||
return cloudflareRequest.cf?.country ?? request.headers.get("cf-ipcountry") ?? undefined
|
|
||||||
}
|
|
||||||
@@ -327,6 +327,37 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-component="desktop-app-banner"] {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 32px;
|
||||||
|
|
||||||
|
[data-slot="badge"] {
|
||||||
|
background: var(--color-background-strong);
|
||||||
|
color: var(--color-text-inverted);
|
||||||
|
font-weight: 500;
|
||||||
|
padding: 4px 8px;
|
||||||
|
line-height: 1;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="content"] {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="text"] {
|
||||||
|
color: var(--color-text-strong);
|
||||||
|
line-height: 1.4;
|
||||||
|
|
||||||
|
@media (max-width: 30.625rem) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[data-slot="hero-copy"] {
|
[data-slot="hero-copy"] {
|
||||||
img {
|
img {
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
@@ -632,6 +663,10 @@ body {
|
|||||||
fill: var(--color-text-strong);
|
fill: var(--color-text-strong);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-bar][data-kind="promo"] {
|
||||||
|
fill: color-mix(in srgb, var(--bar-go) 50%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
[data-val] {
|
[data-val] {
|
||||||
fill: var(--color-text-strong);
|
fill: var(--color-text-strong);
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { For, createMemo, createSignal, onCleanup, onMount } from "solid-js"
|
|||||||
//import { HttpHeader } from "@solidjs/start"
|
//import { HttpHeader } from "@solidjs/start"
|
||||||
import goLogoLight from "../../asset/go-ornate-light.svg"
|
import goLogoLight from "../../asset/go-ornate-light.svg"
|
||||||
import goLogoDark from "../../asset/go-ornate-dark.svg"
|
import goLogoDark from "../../asset/go-ornate-dark.svg"
|
||||||
import { EmailSignup } from "~/component/email-signup"
|
|
||||||
import { Faq } from "~/component/faq"
|
import { Faq } from "~/component/faq"
|
||||||
import { Legal } from "~/component/legal"
|
import { Legal } from "~/component/legal"
|
||||||
import { Footer } from "~/component/footer"
|
import { Footer } from "~/component/footer"
|
||||||
@@ -64,10 +63,10 @@ function LimitsGraph(props: { href: string }) {
|
|||||||
{ id: "glm-5.2", name: "GLM-5.2", req: 880, d: "100ms" },
|
{ id: "glm-5.2", name: "GLM-5.2", req: 880, d: "100ms" },
|
||||||
{ id: "qwen3.7-max", name: "Qwen3.7 Max", req: 950, d: "110ms" },
|
{ id: "qwen3.7-max", name: "Qwen3.7 Max", req: 950, d: "110ms" },
|
||||||
{ id: "kimi-k2.7-code", name: "Kimi K2.7 Code", req: 1150, d: "150ms" },
|
{ id: "kimi-k2.7-code", name: "Kimi K2.7 Code", req: 1150, d: "150ms" },
|
||||||
{ id: "minimax-m3", name: "MiniMax M3", req: 3200, d: "210ms" },
|
{ id: "mimo-v2.5-pro", name: "MiMo-V2.5-Pro", req: 3250, d: "210ms" },
|
||||||
{ id: "mimo-v2.5-pro", name: "MiMo-V2.5-Pro", req: 3250, d: "240ms" },
|
{ id: "deepseek-v4-pro", name: "DeepSeek V4 Pro", req: 3450, d: "240ms" },
|
||||||
{ id: "deepseek-v4-pro", name: "DeepSeek V4 Pro", req: 3450, d: "270ms" },
|
{ id: "qwen3.7-plus", name: "Qwen3.7 Plus", req: 4300, d: "250ms" },
|
||||||
{ id: "qwen3.7-plus", name: "Qwen3.7 Plus", req: 4300, d: "300ms" },
|
{ id: "minimax-m3", name: "MiniMax M3 (3x usage)", req: 9600, baseReq: 3200, d: "280ms" },
|
||||||
{ id: "mimo-v2.5", name: "MiMo-V2.5", req: 30100, d: "340ms" },
|
{ id: "mimo-v2.5", name: "MiMo-V2.5", req: 30100, d: "340ms" },
|
||||||
{ id: "deepseek-v4-flash", name: "DeepSeek V4 Flash", req: 31650, d: "340ms" },
|
{ id: "deepseek-v4-flash", name: "DeepSeek V4 Flash", req: 31650, d: "340ms" },
|
||||||
]
|
]
|
||||||
@@ -157,12 +156,24 @@ function LimitsGraph(props: { href: string }) {
|
|||||||
<rect
|
<rect
|
||||||
x={left}
|
x={left}
|
||||||
y={gy(i()) - bh / 2}
|
y={gy(i()) - bh / 2}
|
||||||
width={Math.max(0, x(ratio(m.req)) - left)}
|
width={Math.max(0, x(ratio(m.baseReq ?? m.req)) - left)}
|
||||||
height={bh}
|
height={bh}
|
||||||
data-bar
|
data-bar
|
||||||
data-kind="go"
|
data-kind="go"
|
||||||
data-model={m.id}
|
data-model={m.id}
|
||||||
|
data-segment={m.baseReq ? "base" : undefined}
|
||||||
/>
|
/>
|
||||||
|
{m.baseReq && (
|
||||||
|
<rect
|
||||||
|
x={x(ratio(m.baseReq)) + 2}
|
||||||
|
y={gy(i()) - bh / 2}
|
||||||
|
width={Math.max(0, x(ratio(m.req)) - x(ratio(m.baseReq)) - 2)}
|
||||||
|
height={bh}
|
||||||
|
data-bar
|
||||||
|
data-kind="promo"
|
||||||
|
data-model={m.id}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</g>
|
</g>
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
@@ -252,6 +263,12 @@ export default function Home() {
|
|||||||
|
|
||||||
<div data-component="content">
|
<div data-component="content">
|
||||||
<section data-component="hero">
|
<section data-component="hero">
|
||||||
|
<div data-component="desktop-app-banner">
|
||||||
|
<span data-slot="badge">{i18n.t("home.banner.badge")}</span>
|
||||||
|
<div data-slot="content">
|
||||||
|
<span data-slot="text">{i18n.t("go.banner.text")}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div data-slot="hero-copy">
|
<div data-slot="hero-copy">
|
||||||
<img data-slot="zen logo light" src={goLogoLight} alt="" />
|
<img data-slot="zen logo light" src={goLogoLight} alt="" />
|
||||||
<img data-slot="zen logo dark" src={goLogoDark} alt="" />
|
<img data-slot="zen logo dark" src={goLogoDark} alt="" />
|
||||||
@@ -498,8 +515,6 @@ export default function Home() {
|
|||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<EmailSignup />
|
|
||||||
|
|
||||||
<Footer />
|
<Footer />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import video from "../asset/lander/opencode-min.mp4"
|
|||||||
import videoPoster from "../asset/lander/opencode-poster.png"
|
import videoPoster from "../asset/lander/opencode-poster.png"
|
||||||
import { IconCopy, IconCheck } from "../component/icon"
|
import { IconCopy, IconCheck } from "../component/icon"
|
||||||
import { A, createAsync } from "@solidjs/router"
|
import { A, createAsync } from "@solidjs/router"
|
||||||
import { EmailSignup } from "~/component/email-signup"
|
|
||||||
import { Tabs } from "@kobalte/core/tabs"
|
import { Tabs } from "@kobalte/core/tabs"
|
||||||
import { Faq } from "~/component/faq"
|
import { Faq } from "~/component/faq"
|
||||||
import { Header } from "~/component/header"
|
import { Header } from "~/component/header"
|
||||||
@@ -826,8 +825,6 @@ export default function Home() {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<EmailSignup />
|
|
||||||
|
|
||||||
<Footer />
|
<Footer />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -75,40 +75,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="providers-section"] {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--space-3);
|
|
||||||
margin-top: var(--space-6);
|
|
||||||
padding-top: var(--space-6);
|
|
||||||
border-top: 1px solid var(--color-border-muted);
|
|
||||||
|
|
||||||
[data-slot="providers-header"] {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--space-1);
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
color: var(--color-text);
|
|
||||||
font-size: var(--font-size-lg);
|
|
||||||
font-weight: 600;
|
|
||||||
line-height: 1.2;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
color: var(--color-text-muted);
|
|
||||||
font-size: var(--font-size-sm);
|
|
||||||
line-height: 1.5;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="setting-row"] {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="toggle-label"] {
|
[data-slot="toggle-label"] {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|||||||
@@ -5,9 +5,7 @@ import { Modal } from "~/component/modal"
|
|||||||
import { Billing } from "@opencode-ai/console-core/billing.js"
|
import { Billing } from "@opencode-ai/console-core/billing.js"
|
||||||
import { Database, eq, and, isNull } from "@opencode-ai/console-core/drizzle/index.js"
|
import { Database, eq, and, isNull } from "@opencode-ai/console-core/drizzle/index.js"
|
||||||
import { BillingTable, LiteTable } from "@opencode-ai/console-core/schema/billing.sql.js"
|
import { BillingTable, LiteTable } from "@opencode-ai/console-core/schema/billing.sql.js"
|
||||||
import { WorkspaceTable } from "@opencode-ai/console-core/schema/workspace.sql.js"
|
|
||||||
import { Actor } from "@opencode-ai/console-core/actor.js"
|
import { Actor } from "@opencode-ai/console-core/actor.js"
|
||||||
import { Workspace } from "@opencode-ai/console-core/workspace.js"
|
|
||||||
import { Subscription } from "@opencode-ai/console-core/subscription.js"
|
import { Subscription } from "@opencode-ai/console-core/subscription.js"
|
||||||
import { LiteData } from "@opencode-ai/console-core/lite.js"
|
import { LiteData } from "@opencode-ai/console-core/lite.js"
|
||||||
import { withActor } from "~/context/auth.withActor"
|
import { withActor } from "~/context/auth.withActor"
|
||||||
@@ -18,8 +16,6 @@ import { useLanguage } from "~/context/language"
|
|||||||
import { formError } from "~/lib/form-error"
|
import { formError } from "~/lib/form-error"
|
||||||
import { formatResetTime, liteResetTimeKeys } from "~/lib/format-reset-time"
|
import { formatResetTime, liteResetTimeKeys } from "~/lib/format-reset-time"
|
||||||
import { createReferralFromCookie } from "~/lib/referral-invite"
|
import { createReferralFromCookie } from "~/lib/referral-invite"
|
||||||
import { getRequestEvent } from "solid-js/web"
|
|
||||||
import { countryFromRequest } from "~/lib/request-country"
|
|
||||||
|
|
||||||
import { IconAlipay, IconUpi } from "~/component/icon"
|
import { IconAlipay, IconUpi } from "~/component/icon"
|
||||||
|
|
||||||
@@ -38,11 +34,9 @@ export const queryLiteSubscription = query(async (workspaceID: string) => {
|
|||||||
timeMonthlyUpdated: LiteTable.timeMonthlyUpdated,
|
timeMonthlyUpdated: LiteTable.timeMonthlyUpdated,
|
||||||
timeCreated: LiteTable.timeCreated,
|
timeCreated: LiteTable.timeCreated,
|
||||||
lite: BillingTable.lite,
|
lite: BillingTable.lite,
|
||||||
region: WorkspaceTable.region,
|
|
||||||
})
|
})
|
||||||
.from(BillingTable)
|
.from(BillingTable)
|
||||||
.innerJoin(LiteTable, eq(LiteTable.workspaceID, BillingTable.workspaceID))
|
.innerJoin(LiteTable, eq(LiteTable.workspaceID, BillingTable.workspaceID))
|
||||||
.innerJoin(WorkspaceTable, eq(WorkspaceTable.id, BillingTable.workspaceID))
|
|
||||||
.where(and(eq(LiteTable.workspaceID, Actor.workspace()), isNull(LiteTable.timeDeleted)))
|
.where(and(eq(LiteTable.workspaceID, Actor.workspace()), isNull(LiteTable.timeDeleted)))
|
||||||
.then((r) => r[0]),
|
.then((r) => r[0]),
|
||||||
)
|
)
|
||||||
@@ -54,8 +48,6 @@ export const queryLiteSubscription = query(async (workspaceID: string) => {
|
|||||||
return {
|
return {
|
||||||
mine,
|
mine,
|
||||||
useBalance: row.lite?.useBalance ?? false,
|
useBalance: row.lite?.useBalance ?? false,
|
||||||
region:
|
|
||||||
row.region ?? (await Workspace.setDefaultRegion({ country: countryFromRequest(getRequestEvent()?.request) })),
|
|
||||||
rollingUsage: Subscription.analyzeRollingUsage({
|
rollingUsage: Subscription.analyzeRollingUsage({
|
||||||
limit: limits.rollingLimit,
|
limit: limits.rollingLimit,
|
||||||
window: limits.rollingWindow,
|
window: limits.rollingWindow,
|
||||||
@@ -136,24 +128,6 @@ const setLiteUseBalance = action(async (form: FormData) => {
|
|||||||
)
|
)
|
||||||
}, "setLiteUseBalance")
|
}, "setLiteUseBalance")
|
||||||
|
|
||||||
const setGoProviderRouting = action(async (form: FormData) => {
|
|
||||||
"use server"
|
|
||||||
const workspaceID = form.get("workspaceID") as string | null
|
|
||||||
if (!workspaceID) return { error: formError.workspaceRequired }
|
|
||||||
const useChinaProviders = (form.get("useChinaProviders") as string | null) === "true"
|
|
||||||
|
|
||||||
return json(
|
|
||||||
await withActor(
|
|
||||||
() =>
|
|
||||||
Workspace.update({ region: useChinaProviders ? ["us", "eu", "sg"] : ["us", "eu", "sg", "cn"] })
|
|
||||||
.then(() => ({ error: undefined }))
|
|
||||||
.catch((e) => ({ error: e.message as string })),
|
|
||||||
workspaceID,
|
|
||||||
),
|
|
||||||
{ revalidate: queryLiteSubscription.key },
|
|
||||||
)
|
|
||||||
}, "go.providerRouting.set")
|
|
||||||
|
|
||||||
function LiteUsageItem(props: { label: string; usage: { usagePercent: number; resetInSec: number } }) {
|
function LiteUsageItem(props: { label: string; usage: { usagePercent: number; resetInSec: number } }) {
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
|
|
||||||
@@ -185,7 +159,6 @@ export function LiteSection(props: { lite: LiteSubscription | undefined }) {
|
|||||||
const checkoutAction = useAction(createLiteCheckoutUrl)
|
const checkoutAction = useAction(createLiteCheckoutUrl)
|
||||||
const checkoutSubmission = useSubmission(createLiteCheckoutUrl)
|
const checkoutSubmission = useSubmission(createLiteCheckoutUrl)
|
||||||
const useBalanceSubmission = useSubmission(setLiteUseBalance)
|
const useBalanceSubmission = useSubmission(setLiteUseBalance)
|
||||||
const providerRoutingSubmission = useSubmission(setGoProviderRouting)
|
|
||||||
const [store, setStore] = createStore({
|
const [store, setStore] = createStore({
|
||||||
loading: undefined as undefined | "session" | "checkout" | "alipay" | "upi",
|
loading: undefined as undefined | "session" | "checkout" | "alipay" | "upi",
|
||||||
showModal: false,
|
showModal: false,
|
||||||
@@ -259,28 +232,6 @@ export function LiteSection(props: { lite: LiteSubscription | undefined }) {
|
|||||||
<span></span>
|
<span></span>
|
||||||
</label>
|
</label>
|
||||||
</form>
|
</form>
|
||||||
{/*
|
|
||||||
<div data-slot="providers-section">
|
|
||||||
<div data-slot="providers-header">
|
|
||||||
<h3>{i18n.t("workspace.lite.providers.title")}</h3>
|
|
||||||
<p>{i18n.t("workspace.lite.providers.description")}</p>
|
|
||||||
</div>
|
|
||||||
<form action={setGoProviderRouting} method="post" data-slot="setting-row">
|
|
||||||
<p>{i18n.t("workspace.lite.providers.useChina")}</p>
|
|
||||||
<input type="hidden" name="workspaceID" value={params.id} />
|
|
||||||
<input type="hidden" name="useChinaProviders" value={sub().region.includes("cn") ? "true" : "false"} />
|
|
||||||
<label data-slot="toggle-label">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={sub().region.includes("cn")}
|
|
||||||
disabled={providerRoutingSubmission.pending}
|
|
||||||
onChange={(e) => e.currentTarget.form?.requestSubmit()}
|
|
||||||
/>
|
|
||||||
<span></span>
|
|
||||||
</label>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
*/}
|
|
||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
</Show>
|
</Show>
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ const updateWorkspace = action(async (form: FormData) => {
|
|||||||
.catch((e) => ({ error: e.message as string })),
|
.catch((e) => ({ error: e.message as string })),
|
||||||
workspaceID,
|
workspaceID,
|
||||||
),
|
),
|
||||||
{ revalidate: getWorkspaceInfo.key },
|
|
||||||
)
|
)
|
||||||
}, "workspace.update")
|
}, "workspace.update")
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import avatarJay from "../../asset/lander/avatar-jay.png"
|
|||||||
import avatarFrank from "../../asset/lander/avatar-frank.png"
|
import avatarFrank from "../../asset/lander/avatar-frank.png"
|
||||||
import avatarAdam from "../../asset/lander/avatar-adam.png"
|
import avatarAdam from "../../asset/lander/avatar-adam.png"
|
||||||
import avatarDavid from "../../asset/lander/avatar-david.png"
|
import avatarDavid from "../../asset/lander/avatar-david.png"
|
||||||
import { EmailSignup } from "~/component/email-signup"
|
|
||||||
import { Faq } from "~/component/faq"
|
import { Faq } from "~/component/faq"
|
||||||
import { Legal } from "~/component/legal"
|
import { Legal } from "~/component/legal"
|
||||||
import { Footer } from "~/component/footer"
|
import { Footer } from "~/component/footer"
|
||||||
@@ -324,8 +323,6 @@ export default function Home() {
|
|||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<EmailSignup />
|
|
||||||
|
|
||||||
<Footer />
|
<Footer />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ export class CreditsError extends Error {}
|
|||||||
export class MonthlyLimitError extends Error {}
|
export class MonthlyLimitError extends Error {}
|
||||||
export class UserLimitError extends Error {}
|
export class UserLimitError extends Error {}
|
||||||
export class ModelError extends Error {}
|
export class ModelError extends Error {}
|
||||||
export class RegionError extends Error {}
|
|
||||||
|
|
||||||
class LimitError extends Error {
|
class LimitError extends Error {
|
||||||
retryAfter?: number
|
retryAfter?: number
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import {
|
|||||||
MonthlyLimitError,
|
MonthlyLimitError,
|
||||||
UserLimitError,
|
UserLimitError,
|
||||||
ModelError,
|
ModelError,
|
||||||
RegionError,
|
|
||||||
RateLimitError,
|
RateLimitError,
|
||||||
FreeUsageLimitError,
|
FreeUsageLimitError,
|
||||||
GoUsageLimitError,
|
GoUsageLimitError,
|
||||||
@@ -50,8 +49,6 @@ import { createModelTpmLimiter } from "./modelTpmLimiter"
|
|||||||
import { createModelTpsLimiter } from "./modelTpsLimiter"
|
import { createModelTpsLimiter } from "./modelTpsLimiter"
|
||||||
import { createProviderBudgetTracker } from "./providerBudgetTracker"
|
import { createProviderBudgetTracker } from "./providerBudgetTracker"
|
||||||
import { accumulateUsage, HOT_WORKSPACES } from "./usageBatcher"
|
import { accumulateUsage, HOT_WORKSPACES } from "./usageBatcher"
|
||||||
import { Workspace } from "@opencode-ai/console-core/workspace.js"
|
|
||||||
import { countryFromRequest } from "~/lib/request-country"
|
|
||||||
|
|
||||||
type ZenData = Awaited<ReturnType<typeof ZenData.list>>
|
type ZenData = Awaited<ReturnType<typeof ZenData.list>>
|
||||||
type RetryOptions = {
|
type RetryOptions = {
|
||||||
@@ -128,24 +125,6 @@ export async function handler(
|
|||||||
: createKeyRateLimiter(modelInfo.id, modelInfo.rateLimit, zenApiKey, input.request)
|
: createKeyRateLimiter(modelInfo.id, modelInfo.rateLimit, zenApiKey, input.request)
|
||||||
await rateLimiter?.check()
|
await rateLimiter?.check()
|
||||||
const authInfo = await authenticate(modelInfo, zenApiKey)
|
const authInfo = await authenticate(modelInfo, zenApiKey)
|
||||||
const allowedRegions = authInfo?.region
|
|
||||||
? authInfo.region
|
|
||||||
: await (async () => {
|
|
||||||
if (!authInfo) return
|
|
||||||
return Actor.provide("system", { workspaceID: authInfo.workspaceID }, () =>
|
|
||||||
Workspace.setDefaultRegion({ country: countryFromRequest(input.request) }),
|
|
||||||
)
|
|
||||||
})()
|
|
||||||
/*
|
|
||||||
if (true) {
|
|
||||||
if (!allowedRegions?.includes("unavailable"))
|
|
||||||
throw new RegionError(
|
|
||||||
t("zen.api.error.regionNotAllowed", {
|
|
||||||
consoleGoUrl: `https://opencode.ai/workspace/${authInfo.workspaceID}/go`,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
const stickyId = sessionId ? sessionId : (authInfo?.workspaceID ?? ip)
|
const stickyId = sessionId ? sessionId : (authInfo?.workspaceID ?? ip)
|
||||||
const stickyTracker = createStickyTracker(modelInfo.id, modelInfo.stickyProvider, stickyId)
|
const stickyTracker = createStickyTracker(modelInfo.id, modelInfo.stickyProvider, stickyId)
|
||||||
const stickyProvider = await stickyTracker?.get()
|
const stickyProvider = await stickyTracker?.get()
|
||||||
@@ -237,9 +216,6 @@ export async function handler(
|
|||||||
return headers
|
return headers
|
||||||
})(),
|
})(),
|
||||||
body: reqBody,
|
body: reqBody,
|
||||||
// Propagate caller disconnects to the upstream provider request so
|
|
||||||
// abandoned Console requests do not leave orphaned inference work open.
|
|
||||||
signal: input.request.signal,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if (providerInfo.id.startsWith("console.")) {
|
if (providerInfo.id.startsWith("console.")) {
|
||||||
@@ -335,10 +311,9 @@ export async function handler(
|
|||||||
const streamConverter = createStreamPartConverter(providerInfo.format, opts.format)
|
const streamConverter = createStreamPartConverter(providerInfo.format, opts.format)
|
||||||
const usageParser = providerInfo.createUsageParser()
|
const usageParser = providerInfo.createUsageParser()
|
||||||
const binaryDecoder = providerInfo.createBinaryStreamDecoder()
|
const binaryDecoder = providerInfo.createBinaryStreamDecoder()
|
||||||
let reader: ReadableStreamDefaultReader<Uint8Array> | undefined
|
|
||||||
const stream = new ReadableStream({
|
const stream = new ReadableStream({
|
||||||
start(c) {
|
start(c) {
|
||||||
reader = res.body?.getReader()
|
const reader = res.body?.getReader()
|
||||||
const decoder = new TextDecoder()
|
const decoder = new TextDecoder()
|
||||||
const encoder = new TextEncoder()
|
const encoder = new TextEncoder()
|
||||||
|
|
||||||
@@ -424,11 +399,6 @@ export async function handler(
|
|||||||
|
|
||||||
return pump()
|
return pump()
|
||||||
},
|
},
|
||||||
cancel() {
|
|
||||||
// When the downstream caller stops reading, release the upstream
|
|
||||||
// response body instead of keeping the provider/inference stream alive.
|
|
||||||
return reader?.cancel()
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
return new Response(stream, {
|
return new Response(stream, {
|
||||||
status: resStatus,
|
status: resStatus,
|
||||||
@@ -436,15 +406,6 @@ export async function handler(
|
|||||||
headers: resHeaders,
|
headers: resHeaders,
|
||||||
})
|
})
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
// The caller disconnected before we finished. Because the outbound provider
|
|
||||||
// request shares input.request.signal, an aborted caller surfaces here as an
|
|
||||||
// AbortError. There is no client left to receive a body, so skip the error
|
|
||||||
// metric and 500 and return a quiet client-closed response.
|
|
||||||
if (input.request.signal.aborted || error?.name === "AbortError") {
|
|
||||||
logger.debug("REQUEST ABORTED BY CALLER")
|
|
||||||
return new Response(null, { status: 499 })
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.metric({
|
logger.metric({
|
||||||
"error.type": error.constructor.name,
|
"error.type": error.constructor.name,
|
||||||
"error.message": error.message,
|
"error.message": error.message,
|
||||||
@@ -458,15 +419,6 @@ export async function handler(
|
|||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error instanceof RegionError)
|
|
||||||
return new Response(
|
|
||||||
JSON.stringify({
|
|
||||||
type: "error",
|
|
||||||
error: { type: error.constructor.name, message: error.message },
|
|
||||||
}),
|
|
||||||
{ status: 403 },
|
|
||||||
)
|
|
||||||
|
|
||||||
// Note: both top level "type" and "error.type" fields are used by the @ai-sdk/anthropic client to render the error message.
|
// Note: both top level "type" and "error.type" fields are used by the @ai-sdk/anthropic client to render the error message.
|
||||||
if (
|
if (
|
||||||
error instanceof AuthError ||
|
error instanceof AuthError ||
|
||||||
@@ -587,69 +539,70 @@ export async function handler(
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use fallback provider if max retries reached
|
if (retry.retryCount !== MAX_FAILOVER_RETRIES) {
|
||||||
const fallbackProvider = allProviders.find((provider) => provider.id === modelInfo.fallbackProvider)
|
let topPriority = Infinity
|
||||||
if (retry.retryCount === MAX_FAILOVER_RETRIES) return fallbackProvider
|
const providers = allProviders
|
||||||
|
.filter((provider) => provider.weight !== 0)
|
||||||
|
.filter((provider) => !retry.excludeProviders.includes(provider.id))
|
||||||
|
.filter((provider) => {
|
||||||
|
if (provider.budgetPriority === undefined) return true
|
||||||
|
if (!providerBudget) return true
|
||||||
|
return providerBudget.qualify(provider.id, provider.budgetPriority)
|
||||||
|
})
|
||||||
|
.filter((provider) => {
|
||||||
|
if (!provider.tpmLimit) return true
|
||||||
|
const usage = modelTpmLimits?.[`${provider.id}/${provider.model}`] ?? 0
|
||||||
|
return usage < provider.tpmLimit * 1_000_000
|
||||||
|
})
|
||||||
|
.filter((provider) => {
|
||||||
|
if (!provider.tpsGoal) return true
|
||||||
|
const tps = modelTpsLimits?.[`${provider.id}/${provider.model}/${provider.tpsGoal}`] ?? {
|
||||||
|
qualify: 0,
|
||||||
|
unqualify: 0,
|
||||||
|
}
|
||||||
|
const isLowTps = tps.qualify + tps.unqualify > 10 && tps.qualify < tps.unqualify
|
||||||
|
return !isLowTps
|
||||||
|
})
|
||||||
|
.map((provider) => {
|
||||||
|
topPriority = Math.min(topPriority, provider.priority)
|
||||||
|
return provider
|
||||||
|
})
|
||||||
|
.filter((p) => p.priority <= topPriority)
|
||||||
|
.flatMap((provider) => Array<typeof provider>(provider.weight).fill(provider))
|
||||||
|
|
||||||
let topPriority = Infinity
|
// Use the last 4 characters of session ID to select a provider
|
||||||
const providers = allProviders
|
let h = 0
|
||||||
.filter((provider) => provider.weight !== 0)
|
const l = stickyId.length
|
||||||
.filter((provider) => !retry.excludeProviders.includes(provider.id))
|
for (let i = l - 4; i < l; i++) {
|
||||||
.filter((provider) => {
|
h = (h * 31 + stickyId.charCodeAt(i)) | 0 // 32-bit int
|
||||||
if (provider.budgetPriority === undefined) return true
|
}
|
||||||
if (!providerBudget) return true
|
const index = (h >>> 0) % providers.length // make unsigned + range 0..length-1
|
||||||
return providerBudget.qualify(provider.id, provider.budgetPriority)
|
const provider = providers[index || 0]
|
||||||
})
|
|
||||||
.filter((provider) => {
|
// sticky provider does not exist => use selected provider
|
||||||
if (!provider.tpmLimit) return true
|
if (!stickyProviderId) return provider
|
||||||
const usage = modelTpmLimits?.[`${provider.id}/${provider.model}`] ?? 0
|
const stickProvider = allProviders.find((provider) => provider.id === stickyProviderId)
|
||||||
return usage < provider.tpmLimit * 1_000_000
|
if (!stickProvider) return provider
|
||||||
})
|
|
||||||
.filter((provider) => {
|
const preferBudgetProvider =
|
||||||
if (!provider.tpsGoal) return true
|
provider.budgetPriority !== undefined && providerBudget?.prefer(provider.id, provider.budgetPriority)
|
||||||
|
|
||||||
|
const preferTpsProvider = (() => {
|
||||||
|
if (!provider.tpsGoal) return false
|
||||||
const tps = modelTpsLimits?.[`${provider.id}/${provider.model}/${provider.tpsGoal}`] ?? {
|
const tps = modelTpsLimits?.[`${provider.id}/${provider.model}/${provider.tpsGoal}`] ?? {
|
||||||
qualify: 0,
|
qualify: 0,
|
||||||
unqualify: 0,
|
unqualify: 0,
|
||||||
}
|
}
|
||||||
const isLowTps = tps.qualify + tps.unqualify > 10 && tps.qualify < tps.unqualify
|
return tps.qualify > tps.unqualify * 3
|
||||||
return !isLowTps
|
})()
|
||||||
})
|
|
||||||
.map((provider) => {
|
|
||||||
topPriority = Math.min(topPriority, provider.priority)
|
|
||||||
return provider
|
|
||||||
})
|
|
||||||
.filter((p) => p.priority <= topPriority)
|
|
||||||
.flatMap((provider) => Array<typeof provider>(provider.weight).fill(provider))
|
|
||||||
|
|
||||||
// Use the last 4 characters of session ID to select a provider
|
if (!preferBudgetProvider && !preferTpsProvider) return stickProvider
|
||||||
let h = 0
|
|
||||||
const l = stickyId.length
|
return provider
|
||||||
for (let i = l - 4; i < l; i++) {
|
|
||||||
h = (h * 31 + stickyId.charCodeAt(i)) | 0 // 32-bit int
|
|
||||||
}
|
}
|
||||||
const index = (h >>> 0) % providers.length // make unsigned + range 0..length-1
|
|
||||||
const provider = providers[index || 0] ?? fallbackProvider
|
|
||||||
|
|
||||||
// sticky provider does not exist => use selected provider
|
// fallback provider
|
||||||
if (!stickyProviderId) return provider
|
return allProviders.find((provider) => provider.id === modelInfo.fallbackProvider)
|
||||||
const stickProvider = allProviders.find((provider) => provider.id === stickyProviderId)
|
|
||||||
if (!stickProvider) return provider
|
|
||||||
|
|
||||||
const preferBudgetProvider =
|
|
||||||
provider.budgetPriority !== undefined && providerBudget?.prefer(provider.id, provider.budgetPriority)
|
|
||||||
|
|
||||||
const preferTpsProvider = (() => {
|
|
||||||
if (!provider.tpsGoal) return false
|
|
||||||
const tps = modelTpsLimits?.[`${provider.id}/${provider.model}/${provider.tpsGoal}`] ?? {
|
|
||||||
qualify: 0,
|
|
||||||
unqualify: 0,
|
|
||||||
}
|
|
||||||
return tps.qualify > tps.unqualify * 3
|
|
||||||
})()
|
|
||||||
|
|
||||||
if (!preferBudgetProvider && !preferTpsProvider) return stickProvider
|
|
||||||
|
|
||||||
return provider
|
|
||||||
})()
|
})()
|
||||||
|
|
||||||
if (!modelProvider) throw new ModelError(t("zen.api.error.noProviderAvailable"))
|
if (!modelProvider) throw new ModelError(t("zen.api.error.noProviderAvailable"))
|
||||||
@@ -686,10 +639,7 @@ export async function handler(
|
|||||||
tx
|
tx
|
||||||
.select({
|
.select({
|
||||||
apiKey: KeyTable.id,
|
apiKey: KeyTable.id,
|
||||||
workspace: {
|
workspaceID: KeyTable.workspaceID,
|
||||||
id: WorkspaceTable.id,
|
|
||||||
region: WorkspaceTable.region,
|
|
||||||
},
|
|
||||||
billing: {
|
billing: {
|
||||||
balance: BillingTable.balance,
|
balance: BillingTable.balance,
|
||||||
paymentMethodID: BillingTable.paymentMethodID,
|
paymentMethodID: BillingTable.paymentMethodID,
|
||||||
@@ -767,13 +717,13 @@ export async function handler(
|
|||||||
if (
|
if (
|
||||||
modelInfo.id.startsWith("alpha-") &&
|
modelInfo.id.startsWith("alpha-") &&
|
||||||
Resource.App.stage === "production" &&
|
Resource.App.stage === "production" &&
|
||||||
!ADMIN_WORKSPACES.includes(data.workspace.id)
|
!ADMIN_WORKSPACES.includes(data.workspaceID)
|
||||||
)
|
)
|
||||||
throw new AuthError(t("zen.api.error.modelNotSupported", { model: modelInfo.id }))
|
throw new AuthError(t("zen.api.error.modelNotSupported", { model: modelInfo.id }))
|
||||||
|
|
||||||
logger.metric({
|
logger.metric({
|
||||||
api_key: data.apiKey,
|
api_key: data.apiKey,
|
||||||
workspace: data.workspace.id,
|
workspace: data.workspaceID,
|
||||||
user_id: data.user.id,
|
user_id: data.user.id,
|
||||||
...(() => {
|
...(() => {
|
||||||
if (data.billing.subscription)
|
if (data.billing.subscription)
|
||||||
@@ -790,14 +740,13 @@ export async function handler(
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
apiKeyId: data.apiKey,
|
apiKeyId: data.apiKey,
|
||||||
workspaceID: data.workspace.id,
|
workspaceID: data.workspaceID,
|
||||||
region: data.workspace.region,
|
|
||||||
billing: data.billing,
|
billing: data.billing,
|
||||||
user: data.user,
|
user: data.user,
|
||||||
black: data.black,
|
black: data.black,
|
||||||
lite: data.lite,
|
lite: data.lite,
|
||||||
provider: data.provider,
|
provider: data.provider,
|
||||||
isFree: ADMIN_WORKSPACES.includes(data.workspace.id),
|
isFree: ADMIN_WORKSPACES.includes(data.workspaceID),
|
||||||
isDisabled: !!data.timeDisabled,
|
isDisabled: !!data.timeDisabled,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
ALTER TABLE `workspace` ADD `region` json;
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"name": "@opencode-ai/console-core",
|
"name": "@opencode-ai/console-core",
|
||||||
"version": "1.17.12",
|
"version": "1.17.11",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { json, primaryKey, mysqlTable, uniqueIndex, varchar } from "drizzle-orm/mysql-core"
|
import { primaryKey, mysqlTable, uniqueIndex, varchar } from "drizzle-orm/mysql-core"
|
||||||
import { timestamps, ulid } from "../drizzle/types"
|
import { timestamps, ulid } from "../drizzle/types"
|
||||||
|
|
||||||
export const WorkspaceTable = mysqlTable(
|
export const WorkspaceTable = mysqlTable(
|
||||||
@@ -7,7 +7,6 @@ export const WorkspaceTable = mysqlTable(
|
|||||||
id: ulid("id").notNull().primaryKey(),
|
id: ulid("id").notNull().primaryKey(),
|
||||||
slug: varchar("slug", { length: 255 }),
|
slug: varchar("slug", { length: 255 }),
|
||||||
name: varchar("name", { length: 255 }).notNull(),
|
name: varchar("name", { length: 255 }).notNull(),
|
||||||
region: json("region").$type<("us" | "eu" | "sg" | "cn")[]>(),
|
|
||||||
...timestamps,
|
...timestamps,
|
||||||
},
|
},
|
||||||
(table) => [uniqueIndex("slug").on(table.slug)],
|
(table) => [uniqueIndex("slug").on(table.slug)],
|
||||||
|
|||||||
@@ -11,9 +11,6 @@ import { Key } from "./key"
|
|||||||
import { and, eq, isNull, sql } from "drizzle-orm"
|
import { and, eq, isNull, sql } from "drizzle-orm"
|
||||||
|
|
||||||
export namespace Workspace {
|
export namespace Workspace {
|
||||||
export const Region = z.enum(["us", "eu", "sg", "cn"])
|
|
||||||
export type Region = z.infer<typeof Region>
|
|
||||||
|
|
||||||
export const create = fn(
|
export const create = fn(
|
||||||
z.object({
|
z.object({
|
||||||
name: z.string().min(1),
|
name: z.string().min(1),
|
||||||
@@ -60,41 +57,22 @@ export namespace Workspace {
|
|||||||
|
|
||||||
export const update = fn(
|
export const update = fn(
|
||||||
z.object({
|
z.object({
|
||||||
name: z.string().min(1).max(255).optional(),
|
name: z.string().min(1).max(255),
|
||||||
region: z.array(Region).min(1).optional(),
|
|
||||||
}),
|
}),
|
||||||
async (input) => {
|
async ({ name }) => {
|
||||||
Actor.assertAdmin()
|
Actor.assertAdmin()
|
||||||
const workspaceID = Actor.workspace()
|
const workspaceID = Actor.workspace()
|
||||||
return await Database.use((tx) =>
|
return await Database.use((tx) =>
|
||||||
tx
|
tx
|
||||||
.update(WorkspaceTable)
|
.update(WorkspaceTable)
|
||||||
.set({
|
.set({
|
||||||
...("name" in input ? { name: input.name } : {}),
|
name,
|
||||||
...("region" in input ? { region: input.region } : {}),
|
|
||||||
})
|
})
|
||||||
.where(eq(WorkspaceTable.id, workspaceID)),
|
.where(eq(WorkspaceTable.id, workspaceID)),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
export const setDefaultRegion = fn(
|
|
||||||
z.object({
|
|
||||||
country: z.string().optional(),
|
|
||||||
}),
|
|
||||||
async (input) => {
|
|
||||||
const region: Workspace.Region[] =
|
|
||||||
input.country?.toUpperCase() === "CN" ? ["us", "eu", "sg", "cn"] : ["us", "eu", "sg"]
|
|
||||||
await Database.use((tx) =>
|
|
||||||
tx
|
|
||||||
.update(WorkspaceTable)
|
|
||||||
.set({ region })
|
|
||||||
.where(and(eq(WorkspaceTable.id, Actor.workspace()), isNull(WorkspaceTable.region))),
|
|
||||||
)
|
|
||||||
return region
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
export const remove = fn(z.void(), async () => {
|
export const remove = fn(z.void(), async () => {
|
||||||
await Database.use((tx) =>
|
await Database.use((tx) =>
|
||||||
tx
|
tx
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/console-function",
|
"name": "@opencode-ai/console-function",
|
||||||
"version": "1.17.12",
|
"version": "1.17.11",
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/console-mail",
|
"name": "@opencode-ai/console-mail",
|
||||||
"version": "1.17.12",
|
"version": "1.17.11",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jsx-email/all": "2.2.3",
|
"@jsx-email/all": "2.2.3",
|
||||||
"@jsx-email/cli": "1.4.3",
|
"@jsx-email/cli": "1.4.3",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/console-support",
|
"name": "@opencode-ai/console-support",
|
||||||
"version": "1.17.12",
|
"version": "1.17.11",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"version": "1.17.12",
|
"version": "1.17.11",
|
||||||
"name": "@opencode-ai/core",
|
"name": "@opencode-ai/core",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -121,7 +121,7 @@
|
|||||||
"npm-package-arg": "13.0.2",
|
"npm-package-arg": "13.0.2",
|
||||||
"semver": "^7.6.3",
|
"semver": "^7.6.3",
|
||||||
"turndown": "7.2.0",
|
"turndown": "7.2.0",
|
||||||
"venice-ai-sdk-provider": "2.1.1",
|
"venice-ai-sdk-provider": "2.0.2",
|
||||||
"which": "6.0.1",
|
"which": "6.0.1",
|
||||||
"xdg-basedir": "5.1.0",
|
"xdg-basedir": "5.1.0",
|
||||||
"zod": "catalog:"
|
"zod": "catalog:"
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export interface Interface extends State.Transformable<Draft> {
|
|||||||
|
|
||||||
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/Agent") {}
|
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/Agent") {}
|
||||||
|
|
||||||
const layer = Layer.effect(
|
export const layer = Layer.effect(
|
||||||
Service,
|
Service,
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const state = State.create<Data, Draft>({
|
const state = State.create<Data, Draft>({
|
||||||
|
|||||||
@@ -233,3 +233,5 @@ export const locationLayer = Layer.effect(
|
|||||||
)
|
)
|
||||||
|
|
||||||
export const node = makeLocationNode({ service: Service, layer: locationLayer, deps: [] })
|
export const node = makeLocationNode({ service: Service, layer: locationLayer, deps: [] })
|
||||||
|
|
||||||
|
export const defaultLayer = locationLayer
|
||||||
|
|||||||
@@ -360,6 +360,8 @@ export const make = Effect.gen(function* () {
|
|||||||
return Service.of({ list, get, start, extend, wait, waitForPromotion, promote, cancel })
|
return Service.of({ list, get, start, extend, wait, waitForPromotion, promote, cancel })
|
||||||
})
|
})
|
||||||
|
|
||||||
const layer = Layer.effect(Service, make)
|
export const layer = Layer.effect(Service, make)
|
||||||
|
|
||||||
|
export const defaultLayer = layer
|
||||||
|
|
||||||
export const node = makeGlobalNode({ service: Service, layer, deps: [] })
|
export const node = makeGlobalNode({ service: Service, layer, deps: [] })
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export interface Interface extends State.Transformable<Draft> {
|
|||||||
|
|
||||||
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/Catalog") {}
|
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/Catalog") {}
|
||||||
|
|
||||||
const layer = Layer.effect(
|
export const layer = Layer.effect(
|
||||||
Service,
|
Service,
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const events = yield* EventV2.Service
|
const events = yield* EventV2.Service
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export interface Interface extends State.Transformable<Draft> {
|
|||||||
|
|
||||||
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/Command") {}
|
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/Command") {}
|
||||||
|
|
||||||
const layer = Layer.effect(
|
export const layer = Layer.effect(
|
||||||
Service,
|
Service,
|
||||||
Effect.sync(() => {
|
Effect.sync(() => {
|
||||||
const state = State.create<Data, Draft>({
|
const state = State.create<Data, Draft>({
|
||||||
|
|||||||
@@ -132,14 +132,14 @@ export interface Interface {
|
|||||||
|
|
||||||
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/Config") {}
|
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/Config") {}
|
||||||
|
|
||||||
const layer = Layer.effect(
|
export const layer = Layer.effect(
|
||||||
Service,
|
Service,
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const fs = yield* FSUtil.Service
|
const fs = yield* FSUtil.Service
|
||||||
const global = yield* Global.Service
|
const global = yield* Global.Service
|
||||||
const location = yield* Location.Service
|
const location = yield* Location.Service
|
||||||
const policy = yield* Policy.Service
|
const policy = yield* Policy.Service
|
||||||
const names = ["opencode.json", "opencode.jsonc"]
|
const names = ["config.json", "opencode.json", "opencode.jsonc"]
|
||||||
const decodeOptions = { errors: "all", onExcessProperty: "ignore", propertyOrder: "original" } as const
|
const decodeOptions = { errors: "all", onExcessProperty: "ignore", propertyOrder: "original" } as const
|
||||||
const decodeInfo = Schema.decodeUnknownOption(Info, decodeOptions)
|
const decodeInfo = Schema.decodeUnknownOption(Info, decodeOptions)
|
||||||
const decodeV1Info = Schema.decodeUnknownOption(ConfigV1.Info, decodeOptions)
|
const decodeV1Info = Schema.decodeUnknownOption(ConfigV1.Info, decodeOptions)
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
export * as MoveSession from "./move-session"
|
export * as MoveSession from "./move-session"
|
||||||
|
|
||||||
import { Context, DateTime, Effect, Layer, Schema } from "effect"
|
import { Context, DateTime, Effect, Layer, Schema } from "effect"
|
||||||
import { makeGlobalNode } from "../effect/app-node"
|
|
||||||
import { EventV2 } from "../event"
|
import { EventV2 } from "../event"
|
||||||
import { Git } from "../git"
|
import { Git } from "../git"
|
||||||
import { Location } from "../location"
|
import { Location } from "../location"
|
||||||
@@ -66,7 +65,7 @@ export interface Interface {
|
|||||||
|
|
||||||
export class Service extends Context.Service<Service, Interface>()("@opencode/ControlPlaneMoveSession") {}
|
export class Service extends Context.Service<Service, Interface>()("@opencode/ControlPlaneMoveSession") {}
|
||||||
|
|
||||||
const layer = Layer.effect(
|
export const layer = Layer.effect(
|
||||||
Service,
|
Service,
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const git = yield* Git.Service
|
const git = yield* Git.Service
|
||||||
@@ -141,8 +140,9 @@ const layer = Layer.effect(
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
export const node = makeGlobalNode({
|
export const defaultLayer = layer.pipe(
|
||||||
service: Service,
|
Layer.provide(Git.defaultLayer),
|
||||||
layer,
|
Layer.provide(EventV2.defaultLayer),
|
||||||
deps: [Git.node, EventV2.node, ProjectV2.node, SessionStore.node],
|
Layer.provide(ProjectV2.defaultLayer),
|
||||||
})
|
Layer.provide(SessionStore.defaultLayer),
|
||||||
|
)
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export interface Interface {
|
|||||||
|
|
||||||
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/Credential") {}
|
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/Credential") {}
|
||||||
|
|
||||||
const layer = Layer.effect(
|
export const layer = Layer.effect(
|
||||||
Service,
|
Service,
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const { db } = yield* Database.Service
|
const { db } = yield* Database.Service
|
||||||
@@ -135,4 +135,6 @@ const layer = Layer.effect(
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
export const defaultLayer = layer.pipe(Layer.provide(Database.defaultLayer))
|
||||||
|
|
||||||
export const node = makeGlobalNode({ service: Service, layer, deps: [Database.node] })
|
export const node = makeGlobalNode({ service: Service, layer, deps: [Database.node] })
|
||||||
|
|||||||
@@ -497,11 +497,12 @@ export const make = Effect.gen(function* () {
|
|||||||
return makeSpawner(spawnCommand)
|
return makeSpawner(spawnCommand)
|
||||||
})
|
})
|
||||||
|
|
||||||
const layer: Layer.Layer<ChildProcessSpawner, never, FileSystem.FileSystem | Path.Path> = Layer.effect(
|
export const layer: Layer.Layer<ChildProcessSpawner, never, FileSystem.FileSystem | Path.Path> = Layer.effect(
|
||||||
ChildProcessSpawner,
|
ChildProcessSpawner,
|
||||||
make,
|
make,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
export const defaultLayer = layer.pipe(Layer.provide(NodeFileSystem.layer), Layer.provide(NodePath.layer))
|
||||||
export const node = makeGlobalNode({ service: ChildProcessSpawner, layer, deps: [filesystem, path] })
|
export const node = makeGlobalNode({ service: ChildProcessSpawner, layer, deps: [filesystem, path] })
|
||||||
|
|
||||||
export * as CrossSpawnSpawner from "./cross-spawn-spawner"
|
export * as CrossSpawnSpawner from "./cross-spawn-spawner"
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export interface Interface {
|
|||||||
|
|
||||||
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/storage/Database") {}
|
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/storage/Database") {}
|
||||||
|
|
||||||
const layer = Layer.effect(
|
export const layer = Layer.effect(
|
||||||
Service,
|
Service,
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const db = yield* makeDatabase
|
const db = yield* makeDatabase
|
||||||
@@ -54,4 +54,10 @@ export function path() {
|
|||||||
return join(Global.Path.data, `opencode-${InstallationChannel.replace(/[^a-zA-Z0-9._-]/g, "-")}.db`)
|
return join(Global.Path.data, `opencode-${InstallationChannel.replace(/[^a-zA-Z0-9._-]/g, "-")}.db`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const defaultLayer = Layer.unwrap(
|
||||||
|
Effect.gen(function* () {
|
||||||
|
return layerFromPath(path())
|
||||||
|
}),
|
||||||
|
).pipe(Layer.provide(Global.defaultLayer))
|
||||||
|
|
||||||
export const node = makeGlobalNode({ service: Service, layer: layerFromPath(path()), deps: [] })
|
export const node = makeGlobalNode({ service: Service, layer: layerFromPath(path()), deps: [] })
|
||||||
|
|||||||
@@ -272,50 +272,7 @@ export function compile<A, E, const Items extends Replacements = readonly []>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function replacementMapFrom(replacements?: Replacements) {
|
function replacementMapFrom(replacements?: Replacements) {
|
||||||
return (
|
return new Map(replacements?.map(([source, replacement]) => [source.name, replacementNode(source, replacement)]))
|
||||||
replacements?.reduce((map, [source, replacement]) => {
|
|
||||||
const normalized = rewriteReplacementDependencies(replacementNode(source, replacement), map)
|
|
||||||
const current = new Map([[source.name, normalized]])
|
|
||||||
for (const [name, node] of map) map.set(name, rewriteReplacementDependencies(node, current))
|
|
||||||
map.set(source.name, normalized)
|
|
||||||
return map
|
|
||||||
}, new Map<string, AnyNode>()) ?? new Map<string, AnyNode>()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function rewriteReplacementDependencies(root: AnyNode, replacements: ReadonlyMap<string, AnyNode>) {
|
|
||||||
if (replacements.size === 0) return root
|
|
||||||
const cache = new Map<AnyNode, AnyNode>()
|
|
||||||
const visiting = new Set<AnyNode>()
|
|
||||||
const stack: AnyNode[] = []
|
|
||||||
|
|
||||||
const recur = (node: AnyNode, isRoot = false): AnyNode => {
|
|
||||||
const target = isRoot ? node : (replacements.get(node.name) ?? node)
|
|
||||||
const cached = cache.get(target)
|
|
||||||
if (cached !== undefined || cache.has(target)) return cached!
|
|
||||||
if (visiting.has(target)) {
|
|
||||||
const start = stack.indexOf(target)
|
|
||||||
throw new Error(
|
|
||||||
`Cycle detected in layer tree: ${[...stack.slice(start), target].map((item) => item.name).join(" -> ")}`,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
visiting.add(target)
|
|
||||||
stack.push(target)
|
|
||||||
try {
|
|
||||||
const dependencies = target.dependencies.map((dependency) => recur(dependency))
|
|
||||||
const result = dependencies.every((dependency, index) => dependency === target.dependencies[index])
|
|
||||||
? target
|
|
||||||
: { ...target, dependencies }
|
|
||||||
cache.set(target, result)
|
|
||||||
return result
|
|
||||||
} finally {
|
|
||||||
stack.pop()
|
|
||||||
visiting.delete(target)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return recur(root, true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hasUnbound(root: Node<unknown, unknown, any>, source: AnyNode): boolean {
|
export function hasUnbound(root: Node<unknown, unknown, any>, source: AnyNode): boolean {
|
||||||
|
|||||||
@@ -634,5 +634,7 @@ export const layerWith = (options?: LayerOptions) =>
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
const layer = layerWith()
|
export const layer = layerWith()
|
||||||
export const node = makeGlobalNode({ service: Service, layer: layer, deps: [Database.node] })
|
export const node = makeGlobalNode({ service: Service, layer: layer, deps: [Database.node] })
|
||||||
|
|
||||||
|
export const defaultLayer = layer.pipe(Layer.provide(Database.defaultLayer))
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ export class Service extends Context.Service<Service, Interface>()("@opencode/v2
|
|||||||
* write under the same process-local lock so cooperating OpenCode mutations do
|
* write under the same process-local lock so cooperating OpenCode mutations do
|
||||||
* not overwrite changes made from the same stale content.
|
* not overwrite changes made from the same stale content.
|
||||||
*/
|
*/
|
||||||
const layer = Layer.effect(
|
export const layer = Layer.effect(
|
||||||
Service,
|
Service,
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const fs = yield* FSUtil.Service
|
const fs = yield* FSUtil.Service
|
||||||
|
|||||||
@@ -111,6 +111,10 @@ const baseLayer = Layer.effect(
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
export const layer = baseLayer.pipe(Layer.provide(FileSystemSearch.locationLayer), Layer.provide(FSUtil.defaultLayer))
|
||||||
|
|
||||||
|
export const locationLayer = layer
|
||||||
|
|
||||||
export const node = makeLocationNode({
|
export const node = makeLocationNode({
|
||||||
service: Service,
|
service: Service,
|
||||||
layer: baseLayer,
|
layer: baseLayer,
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export interface Interface {}
|
|||||||
|
|
||||||
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/FileWatcher") {}
|
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/FileWatcher") {}
|
||||||
|
|
||||||
const layer = Layer.effect(
|
export const layer = Layer.effect(
|
||||||
Service,
|
Service,
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
if (yield* Flag.OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER) return Service.of({})
|
if (yield* Flag.OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER) return Service.of({})
|
||||||
@@ -133,6 +133,8 @@ const layer = Layer.effect(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
export const locationLayer = layer.pipe(Layer.provide(Config.locationLayer), Layer.provide(Git.defaultLayer))
|
||||||
|
|
||||||
export const node = makeLocationNode({
|
export const node = makeLocationNode({
|
||||||
service: Service,
|
service: Service,
|
||||||
layer,
|
layer,
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ export namespace FSUtil {
|
|||||||
|
|
||||||
export const use = serviceUse(Service)
|
export const use = serviceUse(Service)
|
||||||
|
|
||||||
const layer = Layer.effect(
|
export const layer = Layer.effect(
|
||||||
Service,
|
Service,
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const fs = yield* FileSystem.FileSystem
|
const fs = yield* FileSystem.FileSystem
|
||||||
@@ -200,6 +200,7 @@ export namespace FSUtil {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
export const defaultLayer = layer.pipe(Layer.provide(NodeFileSystem.layer))
|
||||||
export const node = makeGlobalNode({ service: Service, layer: layer, deps: [filesystem] })
|
export const node = makeGlobalNode({ service: Service, layer: layer, deps: [filesystem] })
|
||||||
|
|
||||||
// Pure helpers that don't need Effect (path manipulation, sync operations)
|
// Pure helpers that don't need Effect (path manipulation, sync operations)
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ export interface Interface {
|
|||||||
|
|
||||||
export class Service extends Context.Service<Service, Interface>()("@opencode/GitV2") {}
|
export class Service extends Context.Service<Service, Interface>()("@opencode/GitV2") {}
|
||||||
|
|
||||||
const layer = Layer.effect(
|
export const layer = Layer.effect(
|
||||||
Service,
|
Service,
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const fs = yield* FSUtil.Service
|
const fs = yield* FSUtil.Service
|
||||||
@@ -943,6 +943,7 @@ const layer = Layer.effect(
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
export const defaultLayer = layer.pipe(Layer.provide(FSUtil.defaultLayer), Layer.provide(AppProcess.defaultLayer))
|
||||||
export const node = makeGlobalNode({ service: Service, layer: layer, deps: [FSUtil.node, AppProcess.node] })
|
export const node = makeGlobalNode({ service: Service, layer: layer, deps: [FSUtil.node, AppProcess.node] })
|
||||||
|
|
||||||
interface Result {
|
interface Result {
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ export async function convertToOpenAIResponsesInput({
|
|||||||
: {
|
: {
|
||||||
image_url: `data:${mediaType};base64,${convertToBase64(part.data)}`,
|
image_url: `data:${mediaType};base64,${convertToBase64(part.data)}`,
|
||||||
}),
|
}),
|
||||||
detail: part.providerOptions?.copilot?.imageDetail,
|
detail: part.providerOptions?.openai?.imageDetail,
|
||||||
}
|
}
|
||||||
} else if (part.mediaType === "application/pdf") {
|
} else if (part.mediaType === "application/pdf") {
|
||||||
if (part.data instanceof URL) {
|
if (part.data instanceof URL) {
|
||||||
@@ -127,7 +127,7 @@ export async function convertToOpenAIResponsesInput({
|
|||||||
input.push({
|
input.push({
|
||||||
role: "assistant",
|
role: "assistant",
|
||||||
content: [{ type: "output_text", text: part.text }],
|
content: [{ type: "output_text", text: part.text }],
|
||||||
id: (part.providerOptions?.copilot?.itemId as string) ?? undefined,
|
id: (part.providerOptions?.openai?.itemId as string) ?? undefined,
|
||||||
})
|
})
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -143,7 +143,7 @@ export async function convertToOpenAIResponsesInput({
|
|||||||
input.push({
|
input.push({
|
||||||
type: "local_shell_call",
|
type: "local_shell_call",
|
||||||
call_id: part.toolCallId,
|
call_id: part.toolCallId,
|
||||||
id: (part.providerOptions?.copilot?.itemId as string) ?? undefined,
|
id: (part.providerOptions?.openai?.itemId as string) ?? undefined,
|
||||||
action: {
|
action: {
|
||||||
type: "exec",
|
type: "exec",
|
||||||
command: parsedInput.action.command,
|
command: parsedInput.action.command,
|
||||||
@@ -162,7 +162,7 @@ export async function convertToOpenAIResponsesInput({
|
|||||||
call_id: part.toolCallId,
|
call_id: part.toolCallId,
|
||||||
name: part.toolName,
|
name: part.toolName,
|
||||||
arguments: JSON.stringify(part.input),
|
arguments: JSON.stringify(part.input),
|
||||||
id: (part.providerOptions?.copilot?.itemId as string) ?? undefined,
|
id: (part.providerOptions?.openai?.itemId as string) ?? undefined,
|
||||||
})
|
})
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -275,7 +275,7 @@ export async function convertToOpenAIResponsesInput({
|
|||||||
const output = part.output
|
const output = part.output
|
||||||
|
|
||||||
if (output.type === "execution-denied") {
|
if (output.type === "execution-denied") {
|
||||||
const approvalId = (output.providerOptions?.copilot as { approvalId?: string } | undefined)?.approvalId
|
const approvalId = (output.providerOptions?.openai as { approvalId?: string } | undefined)?.approvalId
|
||||||
|
|
||||||
if (approvalId) {
|
if (approvalId) {
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -525,7 +525,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV3 {
|
|||||||
type: "reasoning" as const,
|
type: "reasoning" as const,
|
||||||
text: summary.text,
|
text: summary.text,
|
||||||
providerMetadata: {
|
providerMetadata: {
|
||||||
copilot: {
|
openai: {
|
||||||
itemId: part.id,
|
itemId: part.id,
|
||||||
reasoningEncryptedContent: part.encrypted_content ?? null,
|
reasoningEncryptedContent: part.encrypted_content ?? null,
|
||||||
},
|
},
|
||||||
@@ -563,7 +563,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV3 {
|
|||||||
toolName: "local_shell",
|
toolName: "local_shell",
|
||||||
input: JSON.stringify({ action: part.action } satisfies z.infer<typeof localShellInputSchema>),
|
input: JSON.stringify({ action: part.action } satisfies z.infer<typeof localShellInputSchema>),
|
||||||
providerMetadata: {
|
providerMetadata: {
|
||||||
copilot: {
|
openai: {
|
||||||
itemId: part.id,
|
itemId: part.id,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -574,7 +574,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV3 {
|
|||||||
|
|
||||||
case "message": {
|
case "message": {
|
||||||
for (const contentPart of part.content) {
|
for (const contentPart of part.content) {
|
||||||
if (options.providerOptions?.copilot?.logprobs && contentPart.logprobs) {
|
if (options.providerOptions?.openai?.logprobs && contentPart.logprobs) {
|
||||||
logprobs.push(contentPart.logprobs)
|
logprobs.push(contentPart.logprobs)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -582,7 +582,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV3 {
|
|||||||
type: "text",
|
type: "text",
|
||||||
text: contentPart.text,
|
text: contentPart.text,
|
||||||
providerMetadata: {
|
providerMetadata: {
|
||||||
copilot: {
|
openai: {
|
||||||
itemId: part.id,
|
itemId: part.id,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -622,7 +622,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV3 {
|
|||||||
toolName: part.name,
|
toolName: part.name,
|
||||||
input: part.arguments,
|
input: part.arguments,
|
||||||
providerMetadata: {
|
providerMetadata: {
|
||||||
copilot: {
|
openai: {
|
||||||
itemId: part.id,
|
itemId: part.id,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -724,15 +724,15 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV3 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const providerMetadata: SharedV3ProviderMetadata = {
|
const providerMetadata: SharedV3ProviderMetadata = {
|
||||||
copilot: { responseId: response.id },
|
openai: { responseId: response.id },
|
||||||
}
|
}
|
||||||
|
|
||||||
if (logprobs.length > 0) {
|
if (logprobs.length > 0) {
|
||||||
providerMetadata.copilot.logprobs = logprobs
|
providerMetadata.openai.logprobs = logprobs
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof response.service_tier === "string") {
|
if (typeof response.service_tier === "string") {
|
||||||
providerMetadata.copilot.serviceTier = response.service_tier
|
providerMetadata.openai.serviceTier = response.service_tier
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -954,7 +954,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV3 {
|
|||||||
type: "text-start",
|
type: "text-start",
|
||||||
id: value.item.id,
|
id: value.item.id,
|
||||||
providerMetadata: {
|
providerMetadata: {
|
||||||
copilot: {
|
openai: {
|
||||||
itemId: value.item.id,
|
itemId: value.item.id,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -971,7 +971,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV3 {
|
|||||||
type: "reasoning-start",
|
type: "reasoning-start",
|
||||||
id: `${value.item.id}:0`,
|
id: `${value.item.id}:0`,
|
||||||
providerMetadata: {
|
providerMetadata: {
|
||||||
copilot: {
|
openai: {
|
||||||
itemId: value.item.id,
|
itemId: value.item.id,
|
||||||
reasoningEncryptedContent: value.item.encrypted_content ?? null,
|
reasoningEncryptedContent: value.item.encrypted_content ?? null,
|
||||||
},
|
},
|
||||||
@@ -994,7 +994,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV3 {
|
|||||||
toolName: value.item.name,
|
toolName: value.item.name,
|
||||||
input: value.item.arguments,
|
input: value.item.arguments,
|
||||||
providerMetadata: {
|
providerMetadata: {
|
||||||
copilot: {
|
openai: {
|
||||||
itemId: value.item.id,
|
itemId: value.item.id,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -1103,7 +1103,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV3 {
|
|||||||
},
|
},
|
||||||
} satisfies z.infer<typeof localShellInputSchema>),
|
} satisfies z.infer<typeof localShellInputSchema>),
|
||||||
providerMetadata: {
|
providerMetadata: {
|
||||||
copilot: { itemId: value.item.id },
|
openai: { itemId: value.item.id },
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
} else if (value.item.type === "message") {
|
} else if (value.item.type === "message") {
|
||||||
@@ -1122,7 +1122,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV3 {
|
|||||||
type: "reasoning-end",
|
type: "reasoning-end",
|
||||||
id: `${activeReasoningPart.canonicalId}:${summaryIndex}`,
|
id: `${activeReasoningPart.canonicalId}:${summaryIndex}`,
|
||||||
providerMetadata: {
|
providerMetadata: {
|
||||||
copilot: {
|
openai: {
|
||||||
itemId: activeReasoningPart.canonicalId,
|
itemId: activeReasoningPart.canonicalId,
|
||||||
reasoningEncryptedContent: value.item.encrypted_content ?? null,
|
reasoningEncryptedContent: value.item.encrypted_content ?? null,
|
||||||
},
|
},
|
||||||
@@ -1209,7 +1209,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV3 {
|
|||||||
type: "text-start",
|
type: "text-start",
|
||||||
id: currentTextId,
|
id: currentTextId,
|
||||||
providerMetadata: {
|
providerMetadata: {
|
||||||
copilot: { itemId: value.item_id },
|
openai: { itemId: value.item_id },
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -1220,7 +1220,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV3 {
|
|||||||
delta: value.delta,
|
delta: value.delta,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (options.providerOptions?.copilot?.logprobs && value.logprobs) {
|
if (options.providerOptions?.openai?.logprobs && value.logprobs) {
|
||||||
logprobs.push(value.logprobs)
|
logprobs.push(value.logprobs)
|
||||||
}
|
}
|
||||||
} else if (isResponseReasoningSummaryPartAddedChunk(value)) {
|
} else if (isResponseReasoningSummaryPartAddedChunk(value)) {
|
||||||
@@ -1235,7 +1235,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV3 {
|
|||||||
type: "reasoning-start",
|
type: "reasoning-start",
|
||||||
id: `${activeItem.canonicalId}:${value.summary_index}`,
|
id: `${activeItem.canonicalId}:${value.summary_index}`,
|
||||||
providerMetadata: {
|
providerMetadata: {
|
||||||
copilot: {
|
openai: {
|
||||||
itemId: activeItem.canonicalId,
|
itemId: activeItem.canonicalId,
|
||||||
reasoningEncryptedContent: activeItem.encryptedContent ?? null,
|
reasoningEncryptedContent: activeItem.encryptedContent ?? null,
|
||||||
},
|
},
|
||||||
@@ -1252,7 +1252,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV3 {
|
|||||||
id: `${activeItem.canonicalId}:${value.summary_index}`,
|
id: `${activeItem.canonicalId}:${value.summary_index}`,
|
||||||
delta: value.delta,
|
delta: value.delta,
|
||||||
providerMetadata: {
|
providerMetadata: {
|
||||||
copilot: {
|
openai: {
|
||||||
itemId: activeItem.canonicalId,
|
itemId: activeItem.canonicalId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -1306,17 +1306,17 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV3 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const providerMetadata: SharedV3ProviderMetadata = {
|
const providerMetadata: SharedV3ProviderMetadata = {
|
||||||
copilot: {
|
openai: {
|
||||||
responseId,
|
responseId,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if (logprobs.length > 0) {
|
if (logprobs.length > 0) {
|
||||||
providerMetadata.copilot.logprobs = logprobs
|
providerMetadata.openai.logprobs = logprobs
|
||||||
}
|
}
|
||||||
|
|
||||||
if (serviceTier !== undefined) {
|
if (serviceTier !== undefined) {
|
||||||
providerMetadata.copilot.serviceTier = serviceTier
|
providerMetadata.openai.serviceTier = serviceTier
|
||||||
}
|
}
|
||||||
|
|
||||||
controller.enqueue({
|
controller.enqueue({
|
||||||
|
|||||||
@@ -71,11 +71,12 @@ export function make(input: Partial<Interface> = {}): Interface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const layer = Layer.effect(
|
export const layer = Layer.effect(
|
||||||
Service,
|
Service,
|
||||||
Effect.sync(() => Service.of(make())),
|
Effect.sync(() => Service.of(make())),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
export const defaultLayer = layer
|
||||||
export const node = makeGlobalNode({ service: Service, layer: layer, deps: [] })
|
export const node = makeGlobalNode({ service: Service, layer: layer, deps: [] })
|
||||||
|
|
||||||
export const layerWith = (input: Partial<Interface>) =>
|
export const layerWith = (input: Partial<Interface>) =>
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export interface Interface {
|
|||||||
|
|
||||||
export class Service extends Context.Service<Service, Interface>()("@opencode/Image") {}
|
export class Service extends Context.Service<Service, Interface>()("@opencode/Image") {}
|
||||||
|
|
||||||
const layer = Layer.effect(
|
export const layer = Layer.effect(
|
||||||
Service,
|
Service,
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const config = yield* Config.Service
|
const config = yield* Config.Service
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user