mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-10 11:39:45 -04:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 27db54c859 |
@@ -367,8 +367,8 @@
|
||||
"@opentelemetry/exporter-trace-otlp-http": "0.214.0",
|
||||
"@opentelemetry/sdk-trace-base": "2.6.1",
|
||||
"@opentelemetry/sdk-trace-node": "2.6.1",
|
||||
"@opentui/core": "0.1.99",
|
||||
"@opentui/solid": "0.1.99",
|
||||
"@opentui/core": "0.1.101",
|
||||
"@opentui/solid": "0.1.101",
|
||||
"@parcel/watcher": "2.5.1",
|
||||
"@pierre/diffs": "catalog:",
|
||||
"@solid-primitives/event-bus": "1.1.2",
|
||||
@@ -466,16 +466,16 @@
|
||||
"zod": "catalog:",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@opentui/core": "0.1.99",
|
||||
"@opentui/solid": "0.1.99",
|
||||
"@opentui/core": "0.1.101",
|
||||
"@opentui/solid": "0.1.101",
|
||||
"@tsconfig/node22": "catalog:",
|
||||
"@types/node": "catalog:",
|
||||
"@typescript/native-preview": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@opentui/core": ">=0.1.99",
|
||||
"@opentui/solid": ">=0.1.99",
|
||||
"@opentui/core": ">=0.1.101",
|
||||
"@opentui/solid": ">=0.1.101",
|
||||
},
|
||||
"optionalPeers": [
|
||||
"@opentui/core",
|
||||
@@ -1604,21 +1604,21 @@
|
||||
|
||||
"@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="],
|
||||
|
||||
"@opentui/core": ["@opentui/core@0.1.99", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "marked": "17.0.1", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.99", "@opentui/core-darwin-x64": "0.1.99", "@opentui/core-linux-arm64": "0.1.99", "@opentui/core-linux-x64": "0.1.99", "@opentui/core-win32-arm64": "0.1.99", "@opentui/core-win32-x64": "0.1.99", "bun-webgpu": "0.1.5", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-I3+AEgGzqNWIpWX9g2WOscSPwtQDNOm4KlBjxBWCZjLxkF07u77heWXF7OiAdhKLtNUW6TFiyt6yznqAZPdG3A=="],
|
||||
"@opentui/core": ["@opentui/core@0.1.101", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "marked": "17.0.1", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.101", "@opentui/core-darwin-x64": "0.1.101", "@opentui/core-linux-arm64": "0.1.101", "@opentui/core-linux-x64": "0.1.101", "@opentui/core-win32-arm64": "0.1.101", "@opentui/core-win32-x64": "0.1.101", "bun-webgpu": "0.1.5", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-8jUhNKnwCDO3Y2iiEmagoQLjgX5l1WbddQiwky8B5JU4FW0/WRHairBmU1kRAQBmhdeg57dVinSG4iu2PAtKEA=="],
|
||||
|
||||
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.99", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bzVrqeX2vb5iWrc/ftOUOqeUY8XO+qSgoTwj5TXHuwagavgwD3Hpeyjx8+icnTTeM4pao0som1WR9xfye6/X5Q=="],
|
||||
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.101", "", { "os": "darwin", "cpu": "arm64" }, "sha512-HtqZh8TIKCH1Nge5J0etBCpzYfPY4fVcq110uJm2As6D/dTTPv8r4J+KkrqoSphkpj/Y2b4t7KpqNHthXA0EVw=="],
|
||||
|
||||
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.99", "", { "os": "darwin", "cpu": "x64" }, "sha512-VE4FrXBYpkxnvkqcCV1a8aN9jyyMJMihVW+V2NLCtp+4yQsj0AapG5TiUSN76XnmSZRptxDy5rBmEempeoIZbg=="],
|
||||
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.101", "", { "os": "darwin", "cpu": "x64" }, "sha512-o5ClQWnGG1inRE2YZAatPw1jPEAJni00amcoIfKBj8e1WS+fQA+iQTq1xFunNcyNPObLDCVuW1X+NrbK9xmPvQ=="],
|
||||
|
||||
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.99", "", { "os": "linux", "cpu": "arm64" }, "sha512-viXQsbpS7yHjYkl7+am32JdvG96QU9lvHh1UiZtpOxcNUUqiYmA2ZwZFPD2Bi54jNyj5l2hjH6YkD3DzE2FEWA=="],
|
||||
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.101", "", { "os": "linux", "cpu": "arm64" }, "sha512-E/weY7DQpaPWGYDPD0CROHowUotqnVlk7Kb6l9+iZCrxm9s7HPRHkcMDVmcWDqHEqa/J879EJcqaUDzDArqC+w=="],
|
||||
|
||||
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.99", "", { "os": "linux", "cpu": "x64" }, "sha512-WLoEFINOSp0tZSR9y4LUuGc7n4Y7H1wcpjUPzQ9vChkYDXrfZltEanzoDWbDcQ4kZQW5tHVC7LrZHpAsRLwFZg=="],
|
||||
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.101", "", { "os": "linux", "cpu": "x64" }, "sha512-+Bfr8jLbbR1WREUMCCvSZ44G1+WU2lPqJx7x1StTa9iFNEdicxCdd0QQsO6cnKn5yW+2Pr/FdrqHbxSQw3ejbA=="],
|
||||
|
||||
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.99", "", { "os": "win32", "cpu": "arm64" }, "sha512-yWMOLWCEO8HdrctU1dMkgZC8qGkiO4Dwr4/e11tTvVpRmYhDsP/IR89ZjEEtOwnKwFOFuB/MxvflqaEWVQ2g5Q=="],
|
||||
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.101", "", { "os": "win32", "cpu": "arm64" }, "sha512-LTMIHJzJrVqS8mgpp+tuyVHuqYlicQTvFi/sTsJ6Xswf1asatsvZYsbQByhBLpFT80j10G7uvDa361S5gjCUDA=="],
|
||||
|
||||
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.99", "", { "os": "win32", "cpu": "x64" }, "sha512-aYRlsL2w8YRL6vPd7/hrqlNVkXU3QowWb01TOvAcHS8UAsXaGFUr47kSDyjxDi1wg1MzmVduCfsC7T3NoThV1w=="],
|
||||
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.101", "", { "os": "win32", "cpu": "x64" }, "sha512-VaMs5bg6y0tYKptaEK8Hy5wTp4m//wJRKUdW8uvrS9cFgxyovZGuw0+TfK3NgbdeX+8jWm8LEAiak4jle5BABg=="],
|
||||
|
||||
"@opentui/solid": ["@opentui/solid@0.1.99", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.99", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.10", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.11" } }, "sha512-DrqqO4h2V88FmeIP2cErYkMU0ZK5MrUsZw3w6IzZpoXyyiL4/9qpWzUq+CXx+r16VP2iGxDJwGKUmtFAzUch2Q=="],
|
||||
"@opentui/solid": ["@opentui/solid@0.1.101", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.101", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.10", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.11" } }, "sha512-STY2FQYtVS2rhUgpslG6mM0EAkgobBDF91+B+SNmvXIkJwP+ydP6UVgcuIo5McIbb9GIbAODx5X2Q48PSR7hgw=="],
|
||||
|
||||
"@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
|
||||
|
||||
|
||||
+4
-4
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"nodeModules": {
|
||||
"x86_64-linux": "sha256-NczRp8MPppkqP8PQfWMUWJ/Wofvf2YVy5m4i22Pi3jg=",
|
||||
"aarch64-linux": "sha256-QIxGOu8Fj+sWgc9hKvm1BLiIErxEtd17SPlwZGac9sQ=",
|
||||
"aarch64-darwin": "sha256-Rb9qbMM+ARn0iBCaZurwcoUBCplbMXEZwrXVKextp3I=",
|
||||
"x86_64-darwin": "sha256-KVxOKkaVV7W+K4reEk14MTLgmtoqwCYDqDNXNeS6ync="
|
||||
"x86_64-linux": "sha256-DOGOZdPdkcuyDhVAyWHGsL4rrV28S+YFZj/VORuoQ8Q=",
|
||||
"aarch64-linux": "sha256-WRnAaEoKvgFFZ+UkbYtD9gBw0HtV1jdUqv7yUE2uTAQ=",
|
||||
"aarch64-darwin": "sha256-LxIj/dsL88M99T3WLaD9FL6Qdu2TV+kr1RMZaZ3i4WM=",
|
||||
"x86_64-darwin": "sha256-PgIvplw6yz9KN5nBWox3BXZIXDbkJ3ZuDPKKSVF82MU="
|
||||
}
|
||||
}
|
||||
|
||||
@@ -504,7 +504,7 @@ export function DialogSelectServer() {
|
||||
|
||||
return (
|
||||
<Dialog title={formTitle()}>
|
||||
<div class="flex flex-1 min-h-0 flex-col gap-2">
|
||||
<div class="flex flex-col gap-2">
|
||||
<Show
|
||||
when={!isFormMode()}
|
||||
fallback={
|
||||
@@ -539,7 +539,7 @@ export function DialogSelectServer() {
|
||||
if (x) void select(x)
|
||||
}}
|
||||
divider={true}
|
||||
class="flex-1 min-h-0 px-5 [&_[data-slot=list-search-wrapper]]:w-full [&_[data-slot=list-scroll]]:flex-1 [&_[data-slot=list-scroll]]:overflow-y-auto [&_[data-slot=list-items]]:bg-surface-base [&_[data-slot=list-items]]:rounded-md [&_[data-slot=list-item]]:min-h-14 [&_[data-slot=list-item]]:p-3 [&_[data-slot=list-item]]:!bg-transparent"
|
||||
class="px-5 [&_[data-slot=list-search-wrapper]]:w-full [&_[data-slot=list-scroll]]h-[300px] [&_[data-slot=list-scroll]]:overflow-y-auto [&_[data-slot=list-items]]:bg-surface-base [&_[data-slot=list-items]]:rounded-md [&_[data-slot=list-item]]:min-h-14 [&_[data-slot=list-item]]:p-3 [&_[data-slot=list-item]]:!bg-transparent"
|
||||
>
|
||||
{(i) => {
|
||||
const key = ServerConnection.key(i)
|
||||
@@ -619,7 +619,7 @@ export function DialogSelectServer() {
|
||||
</List>
|
||||
</Show>
|
||||
|
||||
<div class="shrink-0 px-5 pb-5">
|
||||
<div class="px-5 pb-5">
|
||||
<Show
|
||||
when={isFormMode()}
|
||||
fallback={
|
||||
|
||||
@@ -54,7 +54,7 @@ import { PromptImageAttachments } from "./prompt-input/image-attachments"
|
||||
import { PromptDragOverlay } from "./prompt-input/drag-overlay"
|
||||
import { promptPlaceholder } from "./prompt-input/placeholder"
|
||||
import { ImagePreview } from "@opencode-ai/ui/image-preview"
|
||||
import { useQueries } from "@tanstack/solid-query"
|
||||
import { useQueries, useQuery } from "@tanstack/solid-query"
|
||||
import { loadAgentsQuery, loadProvidersQuery } from "@/context/global-sync/bootstrap"
|
||||
|
||||
interface PromptInputProps {
|
||||
@@ -1257,9 +1257,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
}))
|
||||
|
||||
const agentsLoading = () => agentsQuery.isLoading
|
||||
const agentsShouldFadeIn = createMemo((prev) => prev ?? agentsLoading())
|
||||
const providersLoading = () => agentsLoading() || providersQuery.isLoading || globalProvidersQuery.isLoading
|
||||
const providersShouldFadeIn = createMemo((prev) => prev ?? providersLoading())
|
||||
|
||||
const [promptReady] = createResource(
|
||||
() => prompt.ready().promise,
|
||||
@@ -1462,10 +1460,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
</div>
|
||||
<div class="flex items-center gap-1.5 min-w-0 flex-1 h-7">
|
||||
<Show when={!agentsLoading()}>
|
||||
<div
|
||||
data-component="prompt-agent-control"
|
||||
style={agentsShouldFadeIn() ? { animation: "fade-in 0.3s" } : undefined}
|
||||
>
|
||||
<div data-component="prompt-agent-control" style={{ animation: "fade-in 0.3s" }}>
|
||||
<TooltipKeybind
|
||||
placement="top"
|
||||
gutter={4}
|
||||
@@ -1491,10 +1486,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
</Show>
|
||||
<Show when={!providersLoading()}>
|
||||
<Show when={store.mode !== "shell"}>
|
||||
<div
|
||||
data-component="prompt-model-control"
|
||||
style={providersShouldFadeIn() ? { animation: "fade-in 0.3s" } : undefined}
|
||||
>
|
||||
<div data-component="prompt-model-control" style={{ animation: "fade-in 0.3s" }}>
|
||||
<Show
|
||||
when={providers.paid().length > 0}
|
||||
fallback={
|
||||
@@ -1565,10 +1557,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
</TooltipKeybind>
|
||||
</Show>
|
||||
</div>
|
||||
<div
|
||||
data-component="prompt-variant-control"
|
||||
style={providersShouldFadeIn() ? { animation: "fade-in 0.3s" } : undefined}
|
||||
>
|
||||
<div data-component="prompt-variant-control" style={{ animation: "fade-in 0.3s" }}>
|
||||
<TooltipKeybind
|
||||
placement="top"
|
||||
gutter={4}
|
||||
|
||||
@@ -280,18 +280,6 @@ export const SettingsGeneral: Component = () => {
|
||||
/>
|
||||
</div>
|
||||
</SettingsRow>
|
||||
|
||||
<SettingsRow
|
||||
title={language.t("settings.general.row.showSessionProgressBar.title")}
|
||||
description={language.t("settings.general.row.showSessionProgressBar.description")}
|
||||
>
|
||||
<div data-action="settings-show-session-progress-bar">
|
||||
<Switch
|
||||
checked={settings.general.showSessionProgressBar()}
|
||||
onChange={(checked) => settings.general.setShowSessionProgressBar(checked)}
|
||||
/>
|
||||
</div>
|
||||
</SettingsRow>
|
||||
</SettingsList>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -10,7 +10,7 @@ import type {
|
||||
import { showToast } from "@opencode-ai/ui/toast"
|
||||
import { getFilename } from "@opencode-ai/shared/util/path"
|
||||
import { batch, createContext, getOwner, onCleanup, onMount, type ParentProps, untrack, useContext } from "solid-js"
|
||||
import { createStore, produce, reconcile, unwrap } from "solid-js/store"
|
||||
import { createStore, produce, reconcile } from "solid-js/store"
|
||||
import { useLanguage } from "@/context/language"
|
||||
import { Persist, persisted } from "@/utils/persist"
|
||||
import type { InitError } from "../pages/error"
|
||||
@@ -95,8 +95,13 @@ function createGlobalSync() {
|
||||
)
|
||||
}
|
||||
|
||||
const setProjects = (next: Project[] | ((draft: Project[]) => Project[])) => {
|
||||
const setProjects = (next: Project[] | ((draft: Project[]) => void)) => {
|
||||
projectWritten = true
|
||||
if (typeof next === "function") {
|
||||
setGlobalStore("project", produce(next))
|
||||
cacheProjects()
|
||||
return
|
||||
}
|
||||
setGlobalStore("project", next)
|
||||
cacheProjects()
|
||||
}
|
||||
@@ -111,7 +116,7 @@ function createGlobalSync() {
|
||||
|
||||
const set = ((...input: unknown[]) => {
|
||||
if (input[0] === "project" && (Array.isArray(input[1]) || typeof input[1] === "function")) {
|
||||
setProjects(input[1] as Project[] | ((draft: Project[]) => Project[]))
|
||||
setProjects(input[1] as Project[] | ((draft: Project[]) => void))
|
||||
return input[1]
|
||||
}
|
||||
return (setGlobalStore as (...args: unknown[]) => unknown)(...input)
|
||||
|
||||
@@ -21,7 +21,7 @@ const SKIP_PARTS = new Set(["patch", "step-start", "step-finish"])
|
||||
export function applyGlobalEvent(input: {
|
||||
event: { type: string; properties?: unknown }
|
||||
project: Project[]
|
||||
setGlobalProject: (next: Project[] | ((draft: Project[]) => Project[])) => void
|
||||
setGlobalProject: (next: Project[] | ((draft: Project[]) => void)) => void
|
||||
refresh: () => void
|
||||
}) {
|
||||
if (input.event.type === "global.disposed" || input.event.type === "server.connected") {
|
||||
@@ -33,18 +33,14 @@ export function applyGlobalEvent(input: {
|
||||
const properties = input.event.properties as Project
|
||||
const result = Binary.search(input.project, properties.id, (s) => s.id)
|
||||
if (result.found) {
|
||||
input.setGlobalProject(
|
||||
produce((draft) => {
|
||||
draft[result.index] = { ...draft[result.index], ...properties }
|
||||
}),
|
||||
)
|
||||
input.setGlobalProject((draft) => {
|
||||
draft[result.index] = { ...draft[result.index], ...properties }
|
||||
})
|
||||
return
|
||||
}
|
||||
input.setGlobalProject(
|
||||
produce((draft) => {
|
||||
draft.splice(result.index, 0, properties)
|
||||
}),
|
||||
)
|
||||
input.setGlobalProject((draft) => {
|
||||
draft.splice(result.index, 0, properties)
|
||||
})
|
||||
}
|
||||
|
||||
function cleanupSessionCaches(
|
||||
|
||||
@@ -31,7 +31,6 @@ export interface Settings {
|
||||
showReasoningSummaries: boolean
|
||||
shellToolPartsExpanded: boolean
|
||||
editToolPartsExpanded: boolean
|
||||
showSessionProgressBar: boolean
|
||||
}
|
||||
updates: {
|
||||
startup: boolean
|
||||
@@ -116,7 +115,6 @@ const defaultSettings: Settings = {
|
||||
showReasoningSummaries: false,
|
||||
shellToolPartsExpanded: false,
|
||||
editToolPartsExpanded: false,
|
||||
showSessionProgressBar: true,
|
||||
},
|
||||
updates: {
|
||||
startup: true,
|
||||
@@ -229,13 +227,6 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont
|
||||
setEditToolPartsExpanded(value: boolean) {
|
||||
setStore("general", "editToolPartsExpanded", value)
|
||||
},
|
||||
showSessionProgressBar: withFallback(
|
||||
() => store.general?.showSessionProgressBar,
|
||||
defaultSettings.general.showSessionProgressBar,
|
||||
),
|
||||
setShowSessionProgressBar(value: boolean) {
|
||||
setStore("general", "showSessionProgressBar", value)
|
||||
},
|
||||
},
|
||||
updates: {
|
||||
startup: withFallback(() => store.updates?.startup, defaultSettings.updates.startup),
|
||||
|
||||
@@ -582,8 +582,6 @@ export const dict = {
|
||||
"settings.general.row.editToolPartsExpanded.title": "توسيع أجزاء أداة edit",
|
||||
"settings.general.row.editToolPartsExpanded.description":
|
||||
"إظهار أجزاء أدوات edit و write و patch موسعة بشكل افتراضي في الشريط الزمني",
|
||||
"settings.general.row.showSessionProgressBar.title": "إظهار شريط تقدم الجلسة",
|
||||
"settings.general.row.showSessionProgressBar.description": "عرض شريط التقدم المتحرك أعلى الجلسة أثناء عمل الوكيل",
|
||||
"settings.general.row.wayland.title": "استخدام Wayland الأصلي",
|
||||
"settings.general.row.wayland.description": "تعطيل التراجع إلى X11 على Wayland. يتطلب إعادة التشغيل.",
|
||||
"settings.general.row.wayland.tooltip":
|
||||
|
||||
@@ -590,9 +590,6 @@ export const dict = {
|
||||
"settings.general.row.editToolPartsExpanded.title": "Expandir partes da ferramenta de edição",
|
||||
"settings.general.row.editToolPartsExpanded.description":
|
||||
"Mostrar partes das ferramentas de edição, escrita e patch expandidas por padrão na linha do tempo",
|
||||
"settings.general.row.showSessionProgressBar.title": "Mostrar barra de progresso da sessão",
|
||||
"settings.general.row.showSessionProgressBar.description":
|
||||
"Exibir a barra de progresso animada no topo da sessão quando o agente estiver trabalhando",
|
||||
"settings.general.row.wayland.title": "Usar Wayland nativo",
|
||||
"settings.general.row.wayland.description": "Desabilitar fallback X11 no Wayland. Requer reinicialização.",
|
||||
"settings.general.row.wayland.tooltip":
|
||||
|
||||
@@ -655,9 +655,6 @@ export const dict = {
|
||||
"settings.general.row.editToolPartsExpanded.title": "Proširi dijelove alata za uređivanje",
|
||||
"settings.general.row.editToolPartsExpanded.description":
|
||||
"Prikaži dijelove alata za uređivanje, pisanje i patch podrazumijevano proširene na vremenskoj traci",
|
||||
"settings.general.row.showSessionProgressBar.title": "Prikaži traku napretka sesije",
|
||||
"settings.general.row.showSessionProgressBar.description":
|
||||
"Prikaži animiranu traku napretka na vrhu sesije kada agent radi",
|
||||
"settings.general.row.wayland.title": "Koristi nativni Wayland",
|
||||
"settings.general.row.wayland.description": "Onemogući X11 fallback na Waylandu. Zahtijeva restart.",
|
||||
"settings.general.row.wayland.tooltip":
|
||||
|
||||
@@ -649,9 +649,6 @@ export const dict = {
|
||||
"settings.general.row.editToolPartsExpanded.title": "Udvid edit-værktøjsdele",
|
||||
"settings.general.row.editToolPartsExpanded.description":
|
||||
"Vis edit-, write- og patch-værktøjsdele udvidet som standard i tidslinjen",
|
||||
"settings.general.row.showSessionProgressBar.title": "Vis sessionens fremdriftslinje",
|
||||
"settings.general.row.showSessionProgressBar.description":
|
||||
"Vis den animerede fremdriftslinje øverst i sessionen, når agenten arbejder",
|
||||
"settings.general.row.wayland.title": "Brug native Wayland",
|
||||
"settings.general.row.wayland.description": "Deaktiver X11-fallback på Wayland. Kræver genstart.",
|
||||
"settings.general.row.wayland.tooltip":
|
||||
|
||||
@@ -601,9 +601,6 @@ export const dict = {
|
||||
"settings.general.row.editToolPartsExpanded.title": "Edit-Tool-Abschnitte ausklappen",
|
||||
"settings.general.row.editToolPartsExpanded.description":
|
||||
"Edit-, Write- und Patch-Tool-Abschnitte standardmäßig in der Timeline ausgeklappt anzeigen",
|
||||
"settings.general.row.showSessionProgressBar.title": "Sitzungsfortschrittsleiste anzeigen",
|
||||
"settings.general.row.showSessionProgressBar.description":
|
||||
"Die animierte Fortschrittsleiste oben in der Sitzung anzeigen, wenn der Agent arbeitet",
|
||||
"settings.general.row.wayland.title": "Natives Wayland verwenden",
|
||||
"settings.general.row.wayland.description": "X11-Fallback unter Wayland deaktivieren. Erfordert Neustart.",
|
||||
"settings.general.row.wayland.tooltip":
|
||||
|
||||
@@ -762,9 +762,6 @@ export const dict = {
|
||||
"settings.general.row.editToolPartsExpanded.title": "Expand edit tool parts",
|
||||
"settings.general.row.editToolPartsExpanded.description":
|
||||
"Show edit, write, and patch tool parts expanded by default in the timeline",
|
||||
"settings.general.row.showSessionProgressBar.title": "Show session progress bar",
|
||||
"settings.general.row.showSessionProgressBar.description":
|
||||
"Display the animated progress bar at the top of the session when the agent is working",
|
||||
|
||||
"settings.general.row.wayland.title": "Use native Wayland",
|
||||
"settings.general.row.wayland.description": "Disable X11 fallback on Wayland. Requires restart.",
|
||||
|
||||
@@ -659,9 +659,6 @@ export const dict = {
|
||||
"settings.general.row.editToolPartsExpanded.title": "Expandir partes de la herramienta de edición",
|
||||
"settings.general.row.editToolPartsExpanded.description":
|
||||
"Mostrar las partes de las herramientas de edición, escritura y parcheado expandidas por defecto en la línea de tiempo",
|
||||
"settings.general.row.showSessionProgressBar.title": "Mostrar barra de progreso de la sesión",
|
||||
"settings.general.row.showSessionProgressBar.description":
|
||||
"Mostrar la barra de progreso animada en la parte superior de la sesión cuando el agente esté trabajando",
|
||||
"settings.general.row.wayland.title": "Usar Wayland nativo",
|
||||
"settings.general.row.wayland.description": "Deshabilitar fallback a X11 en Wayland. Requiere reinicio.",
|
||||
"settings.general.row.wayland.tooltip":
|
||||
|
||||
@@ -598,9 +598,6 @@ export const dict = {
|
||||
"settings.general.row.editToolPartsExpanded.title": "Développer les parties de l'outil edit",
|
||||
"settings.general.row.editToolPartsExpanded.description":
|
||||
"Afficher les parties des outils edit, write et patch développées par défaut dans la chronologie",
|
||||
"settings.general.row.showSessionProgressBar.title": "Afficher la barre de progression de la session",
|
||||
"settings.general.row.showSessionProgressBar.description":
|
||||
"Afficher la barre de progression animée en haut de la session lorsque l'agent travaille",
|
||||
"settings.general.row.wayland.title": "Utiliser Wayland natif",
|
||||
"settings.general.row.wayland.description": "Désactiver le repli X11 sur Wayland. Nécessite un redémarrage.",
|
||||
"settings.general.row.wayland.tooltip":
|
||||
|
||||
@@ -587,9 +587,6 @@ export const dict = {
|
||||
"settings.general.row.editToolPartsExpanded.title": "edit ツールパーツを展開",
|
||||
"settings.general.row.editToolPartsExpanded.description":
|
||||
"タイムラインで edit、write、patch ツールパーツをデフォルトで展開して表示します",
|
||||
"settings.general.row.showSessionProgressBar.title": "セッション進行状況バーを表示",
|
||||
"settings.general.row.showSessionProgressBar.description":
|
||||
"エージェントの作業中に、セッション上部にアニメーション付きの進行状況バーを表示します",
|
||||
"settings.general.row.wayland.title": "ネイティブWaylandを使用",
|
||||
"settings.general.row.wayland.description": "WaylandでのX11フォールバックを無効にします。再起動が必要です。",
|
||||
"settings.general.row.wayland.tooltip":
|
||||
|
||||
@@ -583,9 +583,6 @@ export const dict = {
|
||||
"settings.general.row.editToolPartsExpanded.title": "edit 도구 파트 펼치기",
|
||||
"settings.general.row.editToolPartsExpanded.description":
|
||||
"타임라인에서 기본적으로 edit, write, patch 도구 파트를 펼친 상태로 표시합니다",
|
||||
"settings.general.row.showSessionProgressBar.title": "세션 진행 표시줄 표시",
|
||||
"settings.general.row.showSessionProgressBar.description":
|
||||
"에이전트가 작업 중일 때 세션 상단에 애니메이션 진행 표시줄을 표시합니다",
|
||||
"settings.general.row.wayland.title": "네이티브 Wayland 사용",
|
||||
"settings.general.row.wayland.description": "Wayland에서 X11 폴백을 비활성화합니다. 다시 시작해야 합니다.",
|
||||
"settings.general.row.wayland.tooltip":
|
||||
|
||||
@@ -656,9 +656,6 @@ export const dict = {
|
||||
"settings.general.row.editToolPartsExpanded.title": "Utvid edit-verktøydeler",
|
||||
"settings.general.row.editToolPartsExpanded.description":
|
||||
"Vis edit-, write- og patch-verktøydeler utvidet som standard i tidslinjen",
|
||||
"settings.general.row.showSessionProgressBar.title": "Vis fremdriftslinje for sesjonen",
|
||||
"settings.general.row.showSessionProgressBar.description":
|
||||
"Vis den animerte fremdriftslinjen øverst i sesjonen når agenten jobber",
|
||||
"settings.general.row.wayland.title": "Bruk innebygd Wayland",
|
||||
"settings.general.row.wayland.description": "Deaktiver X11-fallback på Wayland. Krever omstart.",
|
||||
"settings.general.row.wayland.tooltip":
|
||||
|
||||
@@ -588,9 +588,6 @@ export const dict = {
|
||||
"settings.general.row.editToolPartsExpanded.title": "Rozwijaj elementy narzędzia edit",
|
||||
"settings.general.row.editToolPartsExpanded.description":
|
||||
"Domyślnie pokazuj rozwinięte elementy narzędzi edit, write i patch na osi czasu",
|
||||
"settings.general.row.showSessionProgressBar.title": "Pokazuj pasek postępu sesji",
|
||||
"settings.general.row.showSessionProgressBar.description":
|
||||
"Wyświetlaj animowany pasek postępu u góry sesji, gdy agent pracuje",
|
||||
"settings.general.row.wayland.title": "Użyj natywnego Wayland",
|
||||
"settings.general.row.wayland.description": "Wyłącz fallback X11 na Wayland. Wymaga restartu.",
|
||||
"settings.general.row.wayland.tooltip":
|
||||
|
||||
@@ -656,9 +656,6 @@ export const dict = {
|
||||
"settings.general.row.editToolPartsExpanded.title": "Разворачивать элементы инструмента edit",
|
||||
"settings.general.row.editToolPartsExpanded.description":
|
||||
"Показывать элементы инструментов edit, write и patch в ленте развернутыми по умолчанию",
|
||||
"settings.general.row.showSessionProgressBar.title": "Показывать индикатор прогресса сессии",
|
||||
"settings.general.row.showSessionProgressBar.description":
|
||||
"Показывать анимированный индикатор прогресса вверху сессии, когда агент работает",
|
||||
"settings.general.row.wayland.title": "Использовать нативный Wayland",
|
||||
"settings.general.row.wayland.description": "Отключить X11 fallback на Wayland. Требуется перезапуск.",
|
||||
"settings.general.row.wayland.tooltip":
|
||||
|
||||
@@ -647,9 +647,6 @@ export const dict = {
|
||||
"settings.general.row.editToolPartsExpanded.title": "ขยายส่วนเครื่องมือ edit",
|
||||
"settings.general.row.editToolPartsExpanded.description":
|
||||
"แสดงส่วนเครื่องมือ edit, write และ patch แบบขยายตามค่าเริ่มต้นในไทม์ไลน์",
|
||||
"settings.general.row.showSessionProgressBar.title": "แสดงแถบความคืบหน้าของเซสชัน",
|
||||
"settings.general.row.showSessionProgressBar.description":
|
||||
"แสดงแถบความคืบหน้าแบบเคลื่อนไหวที่ด้านบนของเซสชันเมื่อเอเจนต์กำลังทำงาน",
|
||||
"settings.general.row.wayland.title": "ใช้ Wayland แบบเนทีฟ",
|
||||
"settings.general.row.wayland.description": "ปิดใช้งาน X11 fallback บน Wayland ต้องรีสตาร์ท",
|
||||
"settings.general.row.wayland.tooltip": "บน Linux ที่มีจอภาพรีเฟรชเรตแบบผสม Wayland แบบเนทีฟอาจเสถียรกว่า",
|
||||
|
||||
@@ -663,10 +663,6 @@ export const dict = {
|
||||
"settings.general.row.editToolPartsExpanded.description":
|
||||
"Zaman çizelgesinde düzenleme, yazma ve yama araç bileşenlerini varsayılan olarak genişletilmiş göster",
|
||||
|
||||
"settings.general.row.showSessionProgressBar.title": "Oturum ilerleme çubuğunu göster",
|
||||
"settings.general.row.showSessionProgressBar.description":
|
||||
"Ajan çalışırken oturumun üst kısmında animasyonlu ilerleme çubuğunu göster",
|
||||
|
||||
"settings.general.row.wayland.title": "Yerel Wayland kullan",
|
||||
"settings.general.row.wayland.description":
|
||||
"Wayland'da X11 geri dönüşünü devre dışı bırak. Yeniden başlatma gerektirir.",
|
||||
|
||||
@@ -646,8 +646,6 @@ export const dict = {
|
||||
"settings.general.row.shellToolPartsExpanded.description": "默认在时间线中展开 shell 工具部分",
|
||||
"settings.general.row.editToolPartsExpanded.title": "展开编辑工具部分",
|
||||
"settings.general.row.editToolPartsExpanded.description": "默认在时间线中展开 edit、write 和 patch 工具部分",
|
||||
"settings.general.row.showSessionProgressBar.title": "显示会话进度条",
|
||||
"settings.general.row.showSessionProgressBar.description": "当智能体正在工作时,在会话顶部显示动画进度条",
|
||||
"settings.general.row.wayland.title": "使用原生 Wayland",
|
||||
"settings.general.row.wayland.description": "在 Wayland 上禁用 X11 回退。需要重启。",
|
||||
"settings.general.row.wayland.tooltip": "在混合刷新率显示器的 Linux 系统上,原生 Wayland 可能更稳定。",
|
||||
|
||||
@@ -642,8 +642,6 @@ export const dict = {
|
||||
"settings.general.row.shellToolPartsExpanded.description": "在時間軸中預設展開 shell 工具區塊",
|
||||
"settings.general.row.editToolPartsExpanded.title": "展開 edit 工具區塊",
|
||||
"settings.general.row.editToolPartsExpanded.description": "在時間軸中預設展開 edit、write 和 patch 工具區塊",
|
||||
"settings.general.row.showSessionProgressBar.title": "顯示工作階段進度列",
|
||||
"settings.general.row.showSessionProgressBar.description": "當代理程式正在運作時,在工作階段頂部顯示動畫進度列",
|
||||
"settings.general.row.wayland.title": "使用原生 Wayland",
|
||||
"settings.general.row.wayland.description": "在 Wayland 上停用 X11 後備模式。需要重新啟動。",
|
||||
"settings.general.row.wayland.tooltip": "在混合更新率螢幕的 Linux 系統上,原生 Wayland 可能更穩定。",
|
||||
|
||||
@@ -43,9 +43,7 @@ export const ProjectIcon = (props: { project: LocalProject; class?: string; noti
|
||||
<Avatar
|
||||
fallback={name()}
|
||||
src={
|
||||
props.project.id === OPENCODE_PROJECT_ID
|
||||
? "https://opencode.ai/favicon.svg"
|
||||
: props.project.icon?.override || props.project.icon?.url
|
||||
props.project.id === OPENCODE_PROJECT_ID ? "https://opencode.ai/favicon.svg" : props.project.icon?.override
|
||||
}
|
||||
{...getAvatarColors(props.project.icon?.color)}
|
||||
class="size-full rounded"
|
||||
|
||||
@@ -721,7 +721,7 @@ export function MessageTimeline(props: {
|
||||
"md:max-w-200 md:mx-auto 2xl:max-w-[1000px]": props.centered,
|
||||
}}
|
||||
>
|
||||
<Show when={workingStatus() !== "hidden" && settings.general.showSessionProgressBar()}>
|
||||
<Show when={workingStatus() !== "hidden"}>
|
||||
<div
|
||||
data-component="session-progress"
|
||||
data-state={workingStatus()}
|
||||
|
||||
@@ -249,7 +249,7 @@ export const dict = {
|
||||
|
||||
"go.title": "OpenCode Go | نماذج برمجة منخفضة التكلفة للجميع",
|
||||
"go.meta.description":
|
||||
"يبدأ Go من $5 للشهر الأول، ثم $10/شهر، مع حدود طلب سخية لمدة 5 ساعات لـ GLM-5.1 وGLM-5 وKimi K2.5 وKimi K2.6 وMiMo-V2-Pro وMiMo-V2-Omni وQwen3.5 Plus وQwen3.6 Plus وMiniMax M2.5 وMiniMax M2.7.",
|
||||
"يبدأ Go من $5 للشهر الأول، ثم $10/شهر، مع حدود طلب سخية لمدة 5 ساعات لـ GLM-5.1 وGLM-5 و Kimi K2.5 وMiMo-V2-Pro وMiMo-V2-Omni و Qwen3.5 Plus و Qwen3.6 Plus و MiniMax M2.5 وMiniMax M2.7.",
|
||||
"go.hero.title": "نماذج برمجة منخفضة التكلفة للجميع",
|
||||
"go.hero.body":
|
||||
"يجلب Go البرمجة الوكيلة للمبرمجين حول العالم. يوفر حدودًا سخية ووصولًا موثوقًا إلى أقوى النماذج مفتوحة المصدر، حتى تتمكن من البناء باستخدام وكلاء أقوياء دون القلق بشأن التكلفة أو التوفر.",
|
||||
@@ -261,8 +261,6 @@ export const dict = {
|
||||
"go.cta.promo": "$5 للشهر الأول",
|
||||
"go.pricing.body":
|
||||
"استخدمه مع أي وكيل. $5 للشهر الأول، ثم $10/شهر. قم بزيادة الرصيد إذا لزم الأمر. الإلغاء في أي وقت.",
|
||||
"go.banner.badge": "3x",
|
||||
"go.banner.text": "Kimi K2.6: حد الاستخدام 3 أضعاف حتى 27 أبريل",
|
||||
"go.graph.free": "مجاني",
|
||||
"go.graph.freePill": "Big Pickle ونماذج مجانية",
|
||||
"go.graph.go": "Go",
|
||||
@@ -300,7 +298,7 @@ export const dict = {
|
||||
"go.problem.item2": "حدود سخية ووصول موثوق",
|
||||
"go.problem.item3": "مصمم لأكبر عدد ممكن من المبرمجين",
|
||||
"go.problem.item4":
|
||||
"يتضمن GLM-5.1 وGLM-5 وKimi K2.5 وKimi K2.6 وMiMo-V2-Pro وMiMo-V2-Omni وQwen3.5 Plus وQwen3.6 Plus وMiniMax M2.5 وMiniMax M2.7",
|
||||
"يتضمن GLM-5.1 وGLM-5 وKimi K2.5 وMiMo-V2-Pro وMiMo-V2-Omni وQwen3.5 Plus وQwen3.6 Plus وMiniMax M2.5 وMiniMax M2.7",
|
||||
"go.how.title": "كيف يعمل Go",
|
||||
"go.how.body": "يبدأ Go من $5 للشهر الأول، ثم $10/شهر. يمكنك استخدامه مع OpenCode أو أي وكيل.",
|
||||
"go.how.step1.title": "أنشئ حسابًا",
|
||||
@@ -324,7 +322,7 @@ export const dict = {
|
||||
"go.faq.a2": "يتضمن Go النماذج المدرجة أدناه، مع حدود سخية وإتاحة موثوقة.",
|
||||
"go.faq.q3": "هل Go هو نفسه Zen؟",
|
||||
"go.faq.a3":
|
||||
"لا. Zen هو الدفع حسب الاستخدام، بينما يبدأ Go من $5 للشهر الأول، ثم $10/شهر، مع حدود سخية ووصول موثوق إلى نماذج المصدر المفتوح GLM-5.1 وGLM-5 وKimi K2.5 وKimi K2.6 وMiMo-V2-Pro وMiMo-V2-Omni وQwen3.5 Plus وQwen3.6 Plus وMiniMax M2.5 وMiniMax M2.7.",
|
||||
"لا. Zen هو الدفع حسب الاستخدام، بينما يبدأ Go من $5 للشهر الأول، ثم $10/شهر، مع حدود سخية ووصول موثوق إلى نماذج المصدر المفتوح GLM-5.1 وGLM-5 و Kimi K2.5 وMiMo-V2-Pro وMiMo-V2-Omni و Qwen3.5 Plus و Qwen3.6 Plus و MiniMax M2.5 وMiniMax M2.7.",
|
||||
"go.faq.q4": "كم تكلفة Go؟",
|
||||
"go.faq.a4.p1.beforePricing": "تكلفة Go",
|
||||
"go.faq.a4.p1.pricingLink": "$5 للشهر الأول",
|
||||
@@ -347,7 +345,7 @@ export const dict = {
|
||||
|
||||
"go.faq.q9": "ما الفرق بين النماذج المجانية وGo؟",
|
||||
"go.faq.a9":
|
||||
"تشمل النماذج المجانية Big Pickle بالإضافة إلى النماذج الترويجية المتاحة في ذلك الوقت، مع حصة 200 طلب/يوم. يتضمن Go نماذج GLM-5.1 وGLM-5 وKimi K2.5 وKimi K2.6 وMiMo-V2-Pro وMiMo-V2-Omni وQwen3.5 Plus وQwen3.6 Plus وMiniMax M2.5 وMiniMax M2.7 مع حصص طلبات أعلى مطبقة عبر نوافذ متجددة (5 ساعات، أسبوعيًا، وشهريًا)، تعادل تقريبًا 12 دولارًا كل 5 ساعات، و30 دولارًا في الأسبوع، و60 دولارًا في الشهر (تختلف أعداد الطلبات الفعلية حسب النموذج والاستخدام).",
|
||||
"تشمل النماذج المجانية Big Pickle بالإضافة إلى النماذج الترويجية المتاحة في ذلك الوقت، مع حصة 200 طلب/يوم. يتضمن Go نماذج GLM-5.1 وGLM-5 وKimi K2.5 وMiMo-V2-Pro وMiMo-V2-Omni وQwen3.5 Plus وQwen3.6 Plus وMiniMax M2.5 وMiniMax M2.7 مع حصص طلبات أعلى مطبقة عبر نوافذ متجددة (5 ساعات، أسبوعيًا، وشهريًا)، تعادل تقريبًا 12 دولارًا كل 5 ساعات، و30 دولارًا في الأسبوع، و60 دولارًا في الشهر (تختلف أعداد الطلبات الفعلية حسب النموذج والاستخدام).",
|
||||
|
||||
"zen.api.error.rateLimitExceeded": "تم تجاوز حد الطلبات. يرجى المحاولة مرة أخرى لاحقًا.",
|
||||
"zen.api.error.modelNotSupported": "النموذج {{model}} غير مدعوم",
|
||||
|
||||
@@ -253,7 +253,7 @@ export const dict = {
|
||||
|
||||
"go.title": "OpenCode Go | Modelos de codificação de baixo custo para todos",
|
||||
"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.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 e MiniMax M2.7.",
|
||||
"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.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 e MiniMax M2.7.",
|
||||
"go.hero.title": "Modelos de codificação de baixo custo para todos",
|
||||
"go.hero.body":
|
||||
"O Go traz a codificação com agentes para programadores em todo o mundo. Oferecendo limites generosos e acesso confiável aos modelos de código aberto mais capazes, para que você possa construir com agentes poderosos sem se preocupar com custos ou disponibilidade.",
|
||||
@@ -265,8 +265,6 @@ export const dict = {
|
||||
"go.cta.promo": "$5 no primeiro mês",
|
||||
"go.pricing.body":
|
||||
"Use com qualquer agente. $5 no primeiro mês, depois $10/mês. Recarregue o crédito se necessário. Cancele a qualquer momento.",
|
||||
"go.banner.badge": "3x",
|
||||
"go.banner.text": "Kimi K2.6: limite de uso 3x maior até 27 de abril",
|
||||
"go.graph.free": "Grátis",
|
||||
"go.graph.freePill": "Big Pickle e modelos gratuitos",
|
||||
"go.graph.go": "Go",
|
||||
@@ -305,7 +303,7 @@ export const dict = {
|
||||
"go.problem.item2": "Limites generosos e acesso confiável",
|
||||
"go.problem.item3": "Feito para o maior número possível de programadores",
|
||||
"go.problem.item4":
|
||||
"Inclui GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 e MiniMax M2.7",
|
||||
"Inclui GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 e MiniMax M2.7",
|
||||
"go.how.title": "Como o Go funciona",
|
||||
"go.how.body":
|
||||
"O Go começa em $5 no primeiro mês, depois $10/mês. Você pode usá-lo com o OpenCode ou qualquer agente.",
|
||||
@@ -331,7 +329,7 @@ export const dict = {
|
||||
"go.faq.a2": "O Go inclui os modelos listados abaixo, com limites generosos e acesso confiável.",
|
||||
"go.faq.q3": "O Go é o mesmo que o Zen?",
|
||||
"go.faq.a3":
|
||||
"Não. Zen é pay-as-you-go, enquanto o Go começa em $5 no primeiro mês, depois $10/mês, com limites generosos e acesso confiável aos modelos open source GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 e MiniMax M2.7.",
|
||||
"Não. Zen é pay-as-you-go, enquanto o Go começa em $5 no primeiro mês, depois $10/mês, com limites generosos e acesso confiável aos modelos open source GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 e MiniMax M2.7.",
|
||||
"go.faq.q4": "Quanto custa o Go?",
|
||||
"go.faq.a4.p1.beforePricing": "O Go custa",
|
||||
"go.faq.a4.p1.pricingLink": "$5 no primeiro mês",
|
||||
@@ -355,7 +353,7 @@ export const dict = {
|
||||
|
||||
"go.faq.q9": "Qual a diferença entre os modelos gratuitos e o Go?",
|
||||
"go.faq.a9":
|
||||
"Os modelos gratuitos incluem Big Pickle e modelos promocionais disponíveis no momento, com uma cota de 200 requisições/dia. O Go inclui GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 e MiniMax M2.7 com cotas de requisição mais altas aplicadas em janelas móveis (5 horas, semanal e mensal), aproximadamente equivalentes a $12 por 5 horas, $30 por semana e $60 por mês (as contagens reais de requisições variam de acordo com o modelo e o uso).",
|
||||
"Os modelos gratuitos incluem Big Pickle e modelos promocionais disponíveis no momento, com uma cota de 200 requisições/dia. O Go inclui GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 e MiniMax M2.7 com cotas de requisição mais altas aplicadas em janelas móveis (5 horas, semanal e mensal), aproximadamente equivalentes a $12 por 5 horas, $30 por semana e $60 por mês (as contagens reais de requisições variam de acordo com o modelo e o uso).",
|
||||
|
||||
"zen.api.error.rateLimitExceeded": "Limite de taxa excedido. Por favor, tente novamente mais tarde.",
|
||||
"zen.api.error.modelNotSupported": "Modelo {{model}} não suportado",
|
||||
|
||||
@@ -251,7 +251,7 @@ export const dict = {
|
||||
|
||||
"go.title": "OpenCode Go | Kodningsmodeller til lav pris for alle",
|
||||
"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.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 og MiniMax M2.7.",
|
||||
"Go starter ved $5 for den første måned, derefter $10/måned, med generøse 5-timers anmodningsgrænser for GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 og MiniMax M2.7.",
|
||||
"go.hero.title": "Kodningsmodeller til lav pris for alle",
|
||||
"go.hero.body":
|
||||
"Go bringer agentisk kodning til programmører over hele verden. Med generøse grænser og pålidelig adgang til de mest kapable open source-modeller, så du kan bygge med kraftfulde agenter uden at bekymre dig om omkostninger eller tilgængelighed.",
|
||||
@@ -263,8 +263,6 @@ export const dict = {
|
||||
"go.cta.promo": "$5 første måned",
|
||||
"go.pricing.body":
|
||||
"Brug med enhver agent. $5 første måned, derefter $10/måned. Tank op med kredit efter behov. Afmeld når som helst.",
|
||||
"go.banner.badge": "3x",
|
||||
"go.banner.text": "Kimi K2.6: brugsgrænsen tredoblet til 27. april",
|
||||
"go.graph.free": "Gratis",
|
||||
"go.graph.freePill": "Big Pickle og gratis modeller",
|
||||
"go.graph.go": "Go",
|
||||
@@ -302,7 +300,7 @@ export const dict = {
|
||||
"go.problem.item2": "Generøse grænser og pålidelig adgang",
|
||||
"go.problem.item3": "Bygget til så mange programmører som muligt",
|
||||
"go.problem.item4":
|
||||
"Inkluderer GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 og MiniMax M2.7",
|
||||
"Inkluderer GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 og MiniMax M2.7",
|
||||
"go.how.title": "Hvordan Go virker",
|
||||
"go.how.body":
|
||||
"Go starter ved $5 for den første måned, derefter $10/måned. Du kan bruge det med OpenCode eller enhver agent.",
|
||||
@@ -328,7 +326,7 @@ export const dict = {
|
||||
"go.faq.a2": "Go inkluderer modellerne nedenfor med generøse grænser og pålidelig adgang.",
|
||||
"go.faq.q3": "Er Go det samme som Zen?",
|
||||
"go.faq.a3":
|
||||
"Nej. Zen er pay-as-you-go, mens Go starter ved $5 for den første måned, derefter $10/måned, med generøse grænser og pålidelig adgang til open source-modellerne GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 og MiniMax M2.7.",
|
||||
"Nej. Zen er pay-as-you-go, mens Go starter ved $5 for den første måned, derefter $10/måned, med generøse grænser og pålidelig adgang til open source-modellerne GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 og MiniMax M2.7.",
|
||||
"go.faq.q4": "Hvad koster Go?",
|
||||
"go.faq.a4.p1.beforePricing": "Go koster",
|
||||
"go.faq.a4.p1.pricingLink": "$5 første måned",
|
||||
@@ -351,7 +349,7 @@ export const dict = {
|
||||
|
||||
"go.faq.q9": "Hvad er forskellen på gratis modeller og Go?",
|
||||
"go.faq.a9":
|
||||
"Gratis modeller inkluderer Big Pickle plus salgsfremmende modeller tilgængelige på det tidspunkt, med en kvote på 200 forespørgsler/dag. Go inkluderer GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 og MiniMax M2.7 med højere anmodningskvoter håndhævet over rullende vinduer (5-timers, ugentlig og månedlig), nogenlunde svarende til $12 pr. 5 timer, $30 pr. uge og $60 pr. måned (faktiske anmodningstal varierer efter model og brug).",
|
||||
"Gratis modeller inkluderer Big Pickle plus salgsfremmende modeller tilgængelige på det tidspunkt, med en kvote på 200 forespørgsler/dag. Go inkluderer GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 og MiniMax M2.7 med højere anmodningskvoter håndhævet over rullende vinduer (5-timers, ugentlig og månedlig), nogenlunde svarende til $12 pr. 5 timer, $30 pr. uge og $60 pr. måned (faktiske anmodningstal varierer efter model og brug).",
|
||||
|
||||
"zen.api.error.rateLimitExceeded": "Hastighedsgrænse overskredet. Prøv venligst igen senere.",
|
||||
"zen.api.error.modelNotSupported": "Model {{model}} understøttes ikke",
|
||||
|
||||
@@ -253,7 +253,7 @@ export const dict = {
|
||||
|
||||
"go.title": "OpenCode Go | Kostengünstige Coding-Modelle für alle",
|
||||
"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.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 und MiniMax M2.7.",
|
||||
"Go beginnt bei $5 für den ersten Monat, danach $10/Monat, mit großzügigen 5-Stunden-Anfragelimits für GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 und MiniMax M2.7.",
|
||||
"go.hero.title": "Kostengünstige Coding-Modelle für alle",
|
||||
"go.hero.body":
|
||||
"Go bringt Agentic Coding zu Programmierern auf der ganzen Welt. Mit großzügigen Limits und zuverlässigem Zugang zu den leistungsfähigsten Open-Source-Modellen, damit du mit leistungsstarken Agenten entwickeln kannst, ohne dir Gedanken über Kosten oder Verfügbarkeit zu machen.",
|
||||
@@ -265,8 +265,6 @@ export const dict = {
|
||||
"go.cta.promo": "$5 im ersten Monat",
|
||||
"go.pricing.body":
|
||||
"Mit jedem Agenten nutzbar. $5 im ersten Monat, danach $10/Monat. Guthaben bei Bedarf aufladen. Jederzeit kündbar.",
|
||||
"go.banner.badge": "3x",
|
||||
"go.banner.text": "Kimi K2.6: Nutzungslimit bis zum 27. April verdreifacht",
|
||||
"go.graph.free": "Kostenlos",
|
||||
"go.graph.freePill": "Big Pickle und kostenlose Modelle",
|
||||
"go.graph.go": "Go",
|
||||
@@ -304,7 +302,7 @@ export const dict = {
|
||||
"go.problem.item2": "Großzügige Limits und zuverlässiger Zugang",
|
||||
"go.problem.item3": "Für so viele Programmierer wie möglich gebaut",
|
||||
"go.problem.item4":
|
||||
"Beinhaltet GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 und MiniMax M2.7",
|
||||
"Beinhaltet GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 und MiniMax M2.7",
|
||||
"go.how.title": "Wie Go funktioniert",
|
||||
"go.how.body":
|
||||
"Go beginnt bei $5 für den ersten Monat, danach $10/Monat. Du kannst es mit OpenCode oder jedem Agenten nutzen.",
|
||||
@@ -330,7 +328,7 @@ export const dict = {
|
||||
"go.faq.a2": "Go umfasst die unten aufgeführten Modelle mit großzügigen Limits und zuverlässigem Zugriff.",
|
||||
"go.faq.q3": "Ist Go dasselbe wie Zen?",
|
||||
"go.faq.a3":
|
||||
"Nein. Zen ist Pay-as-you-go, während Go bei $5 für den ersten Monat beginnt, danach $10/Monat, mit großzügigen Limits und zuverlässigem Zugang zu den Open-Source-Modellen GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 und MiniMax M2.7.",
|
||||
"Nein. Zen ist Pay-as-you-go, während Go bei $5 für den ersten Monat beginnt, danach $10/Monat, mit großzügigen Limits und zuverlässigem Zugang zu den Open-Source-Modellen GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 und MiniMax M2.7.",
|
||||
"go.faq.q4": "Wie viel kostet Go?",
|
||||
"go.faq.a4.p1.beforePricing": "Go kostet",
|
||||
"go.faq.a4.p1.pricingLink": "$5 im ersten Monat",
|
||||
@@ -354,7 +352,7 @@ export const dict = {
|
||||
|
||||
"go.faq.q9": "Was ist der Unterschied zwischen kostenlosen Modellen und Go?",
|
||||
"go.faq.a9":
|
||||
"Kostenlose Modelle beinhalten Big Pickle sowie Werbemodelle, die zum jeweiligen Zeitpunkt verfügbar sind, mit einem Kontingent von 200 Anfragen/Tag. Go beinhaltet GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 und MiniMax M2.7 mit höheren Anfragekontingenten, die über rollierende Zeitfenster (5 Stunden, wöchentlich und monatlich) durchgesetzt werden, grob äquivalent zu $12 pro 5 Stunden, $30 pro Woche und $60 pro Monat (tatsächliche Anfragezahlen variieren je nach Modell und Nutzung).",
|
||||
"Kostenlose Modelle beinhalten Big Pickle sowie Werbemodelle, die zum jeweiligen Zeitpunkt verfügbar sind, mit einem Kontingent von 200 Anfragen/Tag. Go beinhaltet GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 und MiniMax M2.7 mit höheren Anfragekontingenten, die über rollierende Zeitfenster (5 Stunden, wöchentlich und monatlich) durchgesetzt werden, grob äquivalent zu $12 pro 5 Stunden, $30 pro Woche und $60 pro Monat (tatsächliche Anfragezahlen variieren je nach Modell und Nutzung).",
|
||||
|
||||
"zen.api.error.rateLimitExceeded": "Ratenlimit überschritten. Bitte versuche es später erneut.",
|
||||
"zen.api.error.modelNotSupported": "Modell {{model}} wird nicht unterstützt",
|
||||
|
||||
@@ -248,9 +248,7 @@ export const dict = {
|
||||
|
||||
"go.title": "OpenCode Go | Low cost coding models for everyone",
|
||||
"go.meta.description":
|
||||
"Go starts at $5 for your first month, then $10/month, with generous 5-hour request limits for GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, and MiniMax M2.7.",
|
||||
"go.banner.badge": "3x",
|
||||
"go.banner.text": "Kimi K2.6 gets 3× usage limits through April 27",
|
||||
"Go starts at $5 for your first month, then $10/month, with generous 5-hour request limits for GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, and MiniMax M2.7.",
|
||||
"go.hero.title": "Low cost coding models for everyone",
|
||||
"go.hero.body":
|
||||
"Go brings agentic coding to programmers around the world. Offering generous limits and reliable access to the most capable open-source models, so you can build with powerful agents without worrying about cost or availability.",
|
||||
@@ -298,7 +296,7 @@ export const dict = {
|
||||
"go.problem.item2": "Generous limits and reliable access",
|
||||
"go.problem.item3": "Built for as many programmers as possible",
|
||||
"go.problem.item4":
|
||||
"Includes GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, and MiniMax M2.7",
|
||||
"Includes GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, and MiniMax M2.7",
|
||||
"go.how.title": "How Go works",
|
||||
"go.how.body": "Go starts at $5 for your first month, then $10/month. You can use it with OpenCode or any agent.",
|
||||
"go.how.step1.title": "Create an account",
|
||||
@@ -323,7 +321,7 @@ export const dict = {
|
||||
"go.faq.a2": "Go includes the models listed below, with generous limits and reliable access.",
|
||||
"go.faq.q3": "Is Go the same as Zen?",
|
||||
"go.faq.a3":
|
||||
"No. Zen is pay-as-you-go, while Go starts at $5 for your first month, then $10/month, with generous limits and reliable access to open-source models GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, and MiniMax M2.7.",
|
||||
"No. Zen is pay-as-you-go, while Go starts at $5 for your first month, then $10/month, with generous limits and reliable access to open-source models GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, and MiniMax M2.7.",
|
||||
"go.faq.q4": "How much does Go cost?",
|
||||
"go.faq.a4.p1.beforePricing": "Go costs",
|
||||
"go.faq.a4.p1.pricingLink": "$5 first month",
|
||||
@@ -347,7 +345,7 @@ export const dict = {
|
||||
|
||||
"go.faq.q9": "What is the difference between free models and Go?",
|
||||
"go.faq.a9":
|
||||
"Free models include Big Pickle plus promotional models available at the time, with a quota of 200 requests/day. Go includes GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, and MiniMax M2.7 with higher request quotas enforced across rolling windows (5-hour, weekly, and monthly), roughly equivalent to $12 per 5 hours, $30 per week, and $60 per month (actual request counts vary by model and usage).",
|
||||
"Free models include Big Pickle plus promotional models available at the time, with a quota of 200 requests/day. Go includes GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, and MiniMax M2.7 with higher request quotas enforced across rolling windows (5-hour, weekly, and monthly), roughly equivalent to $12 per 5 hours, $30 per week, and $60 per month (actual request counts vary by model and usage).",
|
||||
|
||||
"zen.api.error.rateLimitExceeded": "Rate limit exceeded. Please try again later.",
|
||||
"zen.api.error.modelNotSupported": "Model {{model}} not supported",
|
||||
|
||||
@@ -254,7 +254,7 @@ export const dict = {
|
||||
|
||||
"go.title": "OpenCode Go | Modelos de programación de bajo coste para todos",
|
||||
"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.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 y MiniMax M2.7.",
|
||||
"Go comienza en $5 el primer mes, luego 10 $/mes, con generosos límites de solicitudes de 5 horas para GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 y MiniMax M2.7.",
|
||||
"go.hero.title": "Modelos de programación de bajo coste para todos",
|
||||
"go.hero.body":
|
||||
"Go lleva la programación agéntica a programadores de todo el mundo. Ofrece límites generosos y acceso fiable a los modelos de código abierto más capaces, para que puedas crear con agentes potentes sin preocuparte por el coste o la disponibilidad.",
|
||||
@@ -266,8 +266,6 @@ export const dict = {
|
||||
"go.cta.promo": "$5 el primer mes",
|
||||
"go.pricing.body":
|
||||
"Úsalo con cualquier agente. $5 el primer mes, luego 10 $/mes. Recarga crédito si es necesario. Cancela en cualquier momento.",
|
||||
"go.banner.badge": "3x",
|
||||
"go.banner.text": "Kimi K2.6: límite de uso triplicado hasta el 27 de abril",
|
||||
"go.graph.free": "Gratis",
|
||||
"go.graph.freePill": "Big Pickle y modelos gratuitos",
|
||||
"go.graph.go": "Go",
|
||||
@@ -306,7 +304,7 @@ export const dict = {
|
||||
"go.problem.item2": "Límites generosos y acceso fiable",
|
||||
"go.problem.item3": "Creado para tantos programadores como sea posible",
|
||||
"go.problem.item4":
|
||||
"Incluye GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 y MiniMax M2.7",
|
||||
"Incluye GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 y MiniMax M2.7",
|
||||
"go.how.title": "Cómo funciona Go",
|
||||
"go.how.body": "Go comienza en $5 el primer mes, luego 10 $/mes. Puedes usarlo con OpenCode o cualquier agente.",
|
||||
"go.how.step1.title": "Crear una cuenta",
|
||||
@@ -331,7 +329,7 @@ export const dict = {
|
||||
"go.faq.a2": "Go incluye los modelos que se indican abajo, con límites generosos y acceso confiable.",
|
||||
"go.faq.q3": "¿Es Go lo mismo que Zen?",
|
||||
"go.faq.a3":
|
||||
"No. Zen es pago por uso, mientras que Go comienza en $5 el primer mes, luego 10 $/mes, con límites generosos y acceso fiable a los modelos de código abierto GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 y MiniMax M2.7.",
|
||||
"No. Zen es pago por uso, mientras que Go comienza en $5 el primer mes, luego 10 $/mes, con límites generosos y acceso fiable a los modelos de código abierto GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 y MiniMax M2.7.",
|
||||
"go.faq.q4": "¿Cuánto cuesta Go?",
|
||||
"go.faq.a4.p1.beforePricing": "Go cuesta",
|
||||
"go.faq.a4.p1.pricingLink": "$5 el primer mes",
|
||||
@@ -355,7 +353,7 @@ export const dict = {
|
||||
|
||||
"go.faq.q9": "¿Cuál es la diferencia entre los modelos gratuitos y Go?",
|
||||
"go.faq.a9":
|
||||
"Los modelos gratuitos incluyen Big Pickle más modelos promocionales disponibles en el momento, con una cuota de 200 solicitudes/día. Go incluye GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 y MiniMax M2.7 con cuotas de solicitud más altas aplicadas a través de ventanas móviles (5 horas, semanal y mensual), aproximadamente equivalente a 12 $ por 5 horas, 30 $ por semana y 60 $ por mes (los recuentos reales de solicitudes varían según el modelo y el uso).",
|
||||
"Los modelos gratuitos incluyen Big Pickle más modelos promocionales disponibles en el momento, con una cuota de 200 solicitudes/día. Go incluye GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 y MiniMax M2.7 con cuotas de solicitud más altas aplicadas a través de ventanas móviles (5 horas, semanal y mensual), aproximadamente equivalente a 12 $ por 5 horas, 30 $ por semana y 60 $ por mes (los recuentos reales de solicitudes varían según el modelo y el uso).",
|
||||
|
||||
"zen.api.error.rateLimitExceeded": "Límite de tasa excedido. Por favor, inténtalo de nuevo más tarde.",
|
||||
"zen.api.error.modelNotSupported": "Modelo {{model}} no soportado",
|
||||
|
||||
@@ -255,7 +255,7 @@ export const dict = {
|
||||
|
||||
"go.title": "OpenCode Go | Modèles de code à faible coût pour tous",
|
||||
"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.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 et MiniMax M2.7.",
|
||||
"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.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 et MiniMax M2.7.",
|
||||
"go.hero.title": "Modèles de code à faible coût pour tous",
|
||||
"go.hero.body":
|
||||
"Go apporte le codage agentique aux programmeurs du monde entier. Offrant des limites généreuses et un accès fiable aux modèles open source les plus capables, pour que vous puissiez construire avec des agents puissants sans vous soucier du coût ou de la disponibilité.",
|
||||
@@ -267,8 +267,6 @@ export const dict = {
|
||||
"go.cta.promo": "$5 le premier mois",
|
||||
"go.pricing.body":
|
||||
"Utilisez-le avec n'importe quel agent. $5 le premier mois, puis 10 $/mois. Rechargez du crédit si nécessaire. Annulez à tout moment.",
|
||||
"go.banner.badge": "3x",
|
||||
"go.banner.text": "Kimi K2.6 : limites d’utilisation triplées jusqu’au 27 avril",
|
||||
"go.graph.free": "Gratuit",
|
||||
"go.graph.freePill": "Big Pickle et modèles gratuits",
|
||||
"go.graph.go": "Go",
|
||||
@@ -306,7 +304,7 @@ export const dict = {
|
||||
"go.problem.item2": "Limites généreuses et accès fiable",
|
||||
"go.problem.item3": "Conçu pour autant de programmeurs que possible",
|
||||
"go.problem.item4":
|
||||
"Inclut GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 et MiniMax M2.7",
|
||||
"Inclut GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 et MiniMax M2.7",
|
||||
"go.how.title": "Comment fonctionne Go",
|
||||
"go.how.body":
|
||||
"Go commence à $5 pour le premier mois, puis 10 $/mois. Vous pouvez l'utiliser avec OpenCode ou n'importe quel agent.",
|
||||
@@ -332,7 +330,7 @@ export const dict = {
|
||||
"go.faq.a2": "Go inclut les modèles ci-dessous, avec des limites généreuses et un accès fiable.",
|
||||
"go.faq.q3": "Est-ce que Go est la même chose que Zen ?",
|
||||
"go.faq.a3":
|
||||
"Non. Zen est un paiement à l'utilisation, tandis que Go commence à $5 pour le premier mois, puis 10 $/mois, avec des limites généreuses et un accès fiable aux modèles open source GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 et MiniMax M2.7.",
|
||||
"Non. Zen est un paiement à l'utilisation, tandis que Go commence à $5 pour le premier mois, puis 10 $/mois, avec des limites généreuses et un accès fiable aux modèles open source GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 et MiniMax M2.7.",
|
||||
"go.faq.q4": "Combien coûte Go ?",
|
||||
"go.faq.a4.p1.beforePricing": "Go coûte",
|
||||
"go.faq.a4.p1.pricingLink": "$5 le premier mois",
|
||||
@@ -355,7 +353,7 @@ export const dict = {
|
||||
"Oui, vous pouvez utiliser Go avec n'importe quel agent. Suivez les instructions de configuration dans votre agent de code préféré.",
|
||||
"go.faq.q9": "Quelle est la différence entre les modèles gratuits et Go ?",
|
||||
"go.faq.a9":
|
||||
"Les modèles gratuits incluent Big Pickle ainsi que des modèles promotionnels disponibles à ce moment-là, avec un quota de 200 requêtes/jour. Go inclut GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 et MiniMax M2.7 avec des quotas de requêtes plus élevés appliqués sur des fenêtres glissantes (5 heures, hebdomadaire et mensuelle), à peu près équivalent à 12 $ par 5 heures, 30 $ par semaine et 60 $ par mois (le nombre réel de requêtes varie selon le modèle et l'utilisation).",
|
||||
"Les modèles gratuits incluent Big Pickle ainsi que des modèles promotionnels disponibles à ce moment-là, avec un quota de 200 requêtes/jour. Go inclut GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 et MiniMax M2.7 avec des quotas de requêtes plus élevés appliqués sur des fenêtres glissantes (5 heures, hebdomadaire et mensuelle), à peu près équivalent à 12 $ par 5 heures, 30 $ par semaine et 60 $ par mois (le nombre réel de requêtes varie selon le modèle et l'utilisation).",
|
||||
|
||||
"zen.api.error.rateLimitExceeded": "Limite de débit dépassée. Veuillez réessayer plus tard.",
|
||||
"zen.api.error.modelNotSupported": "Modèle {{model}} non pris en charge",
|
||||
|
||||
@@ -251,7 +251,7 @@ export const dict = {
|
||||
|
||||
"go.title": "OpenCode Go | Modelli di coding a basso costo per tutti",
|
||||
"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.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 e MiniMax M2.7.",
|
||||
"Go inizia a $5 per il primo mese, poi $10/mese, con generosi limiti di richiesta di 5 ore per GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 e MiniMax M2.7.",
|
||||
"go.hero.title": "Modelli di coding a basso costo per tutti",
|
||||
"go.hero.body":
|
||||
"Go porta il coding agentico ai programmatori di tutto il mondo. Offrendo limiti generosi e un accesso affidabile ai modelli open source più capaci, in modo da poter costruire con agenti potenti senza preoccuparsi dei costi o della disponibilità.",
|
||||
@@ -263,8 +263,6 @@ export const dict = {
|
||||
"go.cta.promo": "$5 il primo mese",
|
||||
"go.pricing.body":
|
||||
"Usalo con qualsiasi agente. $5 il primo mese, poi $10/mese. Ricarica il credito se necessario. Annulla in qualsiasi momento.",
|
||||
"go.banner.badge": "3x",
|
||||
"go.banner.text": "Kimi K2.6: limite d'uso triplicato fino al 27 aprile",
|
||||
"go.graph.free": "Gratis",
|
||||
"go.graph.freePill": "Big Pickle e modelli gratuiti",
|
||||
"go.graph.go": "Go",
|
||||
@@ -302,7 +300,7 @@ export const dict = {
|
||||
"go.problem.item2": "Limiti generosi e accesso affidabile",
|
||||
"go.problem.item3": "Costruito per il maggior numero possibile di programmatori",
|
||||
"go.problem.item4":
|
||||
"Include GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 e MiniMax M2.7",
|
||||
"Include GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 e MiniMax M2.7",
|
||||
"go.how.title": "Come funziona Go",
|
||||
"go.how.body": "Go inizia a $5 per il primo mese, poi $10/mese. Puoi usarlo con OpenCode o qualsiasi agente.",
|
||||
"go.how.step1.title": "Crea un account",
|
||||
@@ -327,7 +325,7 @@ export const dict = {
|
||||
"go.faq.a2": "Go include i modelli elencati di seguito, con limiti generosi e accesso affidabile.",
|
||||
"go.faq.q3": "Go è lo stesso di Zen?",
|
||||
"go.faq.a3":
|
||||
"No. Zen è a consumo, mentre Go inizia a $5 per il primo mese, poi $10/mese, con limiti generosi e accesso affidabile ai modelli open source GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 e MiniMax M2.7.",
|
||||
"No. Zen è a consumo, mentre Go inizia a $5 per il primo mese, poi $10/mese, con limiti generosi e accesso affidabile ai modelli open source GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 e MiniMax M2.7.",
|
||||
"go.faq.q4": "Quanto costa Go?",
|
||||
"go.faq.a4.p1.beforePricing": "Go costa",
|
||||
"go.faq.a4.p1.pricingLink": "$5 il primo mese",
|
||||
@@ -351,7 +349,7 @@ export const dict = {
|
||||
|
||||
"go.faq.q9": "Qual è la differenza tra i modelli gratuiti e Go?",
|
||||
"go.faq.a9":
|
||||
"I modelli gratuiti includono Big Pickle più modelli promozionali disponibili al momento, con una quota di 200 richieste/giorno. Go include GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 e MiniMax M2.7 con quote di richiesta più elevate applicate su finestre mobili (5 ore, settimanale e mensile), approssimativamente equivalenti a $12 ogni 5 ore, $30 a settimana e $60 al mese (il conteggio effettivo delle richieste varia in base al modello e all'utilizzo).",
|
||||
"I modelli gratuiti includono Big Pickle più modelli promozionali disponibili al momento, con una quota di 200 richieste/giorno. Go include GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 e MiniMax M2.7 con quote di richiesta più elevate applicate su finestre mobili (5 ore, settimanale e mensile), approssimativamente equivalenti a $12 ogni 5 ore, $30 a settimana e $60 al mese (il conteggio effettivo delle richieste varia in base al modello e all'utilizzo).",
|
||||
|
||||
"zen.api.error.rateLimitExceeded": "Limite di richieste superato. Riprova più tardi.",
|
||||
"zen.api.error.modelNotSupported": "Modello {{model}} non supportato",
|
||||
|
||||
@@ -250,7 +250,7 @@ export const dict = {
|
||||
|
||||
"go.title": "OpenCode Go | すべての人のための低価格なコーディングモデル",
|
||||
"go.meta.description":
|
||||
"Goは最初の月$5、その後$10/月で、GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2-Pro、MiMo-V2-Omni、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5、MiniMax M2.7に対して5時間のゆとりあるリクエスト上限があります。",
|
||||
"Goは最初の月$5、その後$10/月で、GLM-5.1、GLM-5、Kimi K2.5、MiMo-V2-Pro、MiMo-V2-Omni、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5、MiniMax M2.7に対して5時間のゆとりあるリクエスト上限があります。",
|
||||
"go.hero.title": "すべての人のための低価格なコーディングモデル",
|
||||
"go.hero.body":
|
||||
"Goは、世界中のプログラマーにエージェント型コーディングをもたらします。最も高性能なオープンソースモデルへの十分な制限と安定したアクセスを提供し、コストや可用性を気にすることなく強力なエージェントで構築できます。",
|
||||
@@ -262,8 +262,6 @@ export const dict = {
|
||||
"go.cta.promo": "初月 $5",
|
||||
"go.pricing.body":
|
||||
"どのエージェントでも使えます。最初の月$5、その後$10/月。必要に応じてクレジットを追加。いつでもキャンセルできます。",
|
||||
"go.banner.badge": "3x",
|
||||
"go.banner.text": "Kimi K2.6、4月27日まで利用上限が3倍に",
|
||||
"go.graph.free": "無料",
|
||||
"go.graph.freePill": "Big Pickleと無料モデル",
|
||||
"go.graph.go": "Go",
|
||||
@@ -302,7 +300,7 @@ export const dict = {
|
||||
"go.problem.item2": "十分な制限と安定したアクセス",
|
||||
"go.problem.item3": "できるだけ多くのプログラマーのために構築",
|
||||
"go.problem.item4":
|
||||
"GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2-Pro、MiMo-V2-Omni、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5、MiniMax M2.7を含む",
|
||||
"GLM-5.1、GLM-5、Kimi K2.5、MiMo-V2-Pro、MiMo-V2-Omni、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5、MiniMax M2.7を含む",
|
||||
"go.how.title": "Goの仕組み",
|
||||
"go.how.body": "Goは最初の月$5、その後$10/月で始まります。OpenCodeまたは任意のエージェントで使えます。",
|
||||
"go.how.step1.title": "アカウントを作成",
|
||||
@@ -327,7 +325,7 @@ export const dict = {
|
||||
"go.faq.a2": "Go には、十分な利用上限と安定したアクセスを備えた、以下のモデルが含まれます。",
|
||||
"go.faq.q3": "GoはZenと同じですか?",
|
||||
"go.faq.a3":
|
||||
"いいえ。Zenは従量課金制ですが、Goは最初の月$5、その後$10/月で始まり、GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2-Pro、MiMo-V2-Omni、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5、MiniMax M2.7のオープンソースモデルに対して、ゆとりある上限と信頼できるアクセスを提供します。",
|
||||
"いいえ。Zenは従量課金制ですが、Goは最初の月$5、その後$10/月で始まり、GLM-5.1、GLM-5、Kimi K2.5、MiMo-V2-Pro、MiMo-V2-Omni、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5、MiniMax M2.7のオープンソースモデルに対して、ゆとりある上限と信頼できるアクセスを提供します。",
|
||||
"go.faq.q4": "Goの料金は?",
|
||||
"go.faq.a4.p1.beforePricing": "Goは",
|
||||
"go.faq.a4.p1.pricingLink": "最初の月$5",
|
||||
@@ -351,7 +349,7 @@ export const dict = {
|
||||
|
||||
"go.faq.q9": "無料モデルとGoの違いは何ですか?",
|
||||
"go.faq.a9":
|
||||
"無料モデルにはBig Pickleと、その時点で利用可能なプロモーションモデルが含まれ、1日200リクエストの制限があります。GoにはGLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2-Pro、MiMo-V2-Omni、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5、MiniMax M2.7が含まれ、ローリングウィンドウ(5時間、週間、月間)全体でより高いリクエスト制限が適用されます。これは概算で5時間あたり$12、週間$30、月間$60相当です(実際のリクエスト数はモデルと使用状況により異なります)。",
|
||||
"無料モデルにはBig Pickleと、その時点で利用可能なプロモーションモデルが含まれ、1日200リクエストの制限があります。GoにはGLM-5.1、GLM-5、Kimi K2.5、MiMo-V2-Pro、MiMo-V2-Omni、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5、MiniMax M2.7が含まれ、ローリングウィンドウ(5時間、週間、月間)全体でより高いリクエスト制限が適用されます。これは概算で5時間あたり$12、週間$30、月間$60相当です(実際のリクエスト数はモデルと使用状況により異なります)。",
|
||||
|
||||
"zen.api.error.rateLimitExceeded": "レート制限を超えました。後でもう一度お試しください。",
|
||||
"zen.api.error.modelNotSupported": "モデル {{model}} はサポートされていません",
|
||||
|
||||
@@ -247,7 +247,7 @@ export const dict = {
|
||||
|
||||
"go.title": "OpenCode Go | 모두를 위한 저비용 코딩 모델",
|
||||
"go.meta.description":
|
||||
"Go는 첫 달 $5, 이후 $10/월로 시작하며, GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7에 대해 넉넉한 5시간 요청 한도를 제공합니다.",
|
||||
"Go는 첫 달 $5, 이후 $10/월로 시작하며, GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7에 대해 넉넉한 5시간 요청 한도를 제공합니다.",
|
||||
"go.hero.title": "모두를 위한 저비용 코딩 모델",
|
||||
"go.hero.body":
|
||||
"Go는 전 세계 프로그래머들에게 에이전트 코딩을 제공합니다. 가장 유능한 오픈 소스 모델에 대한 넉넉한 한도와 안정적인 액세스를 제공하므로, 비용이나 가용성 걱정 없이 강력한 에이전트로 빌드할 수 있습니다.",
|
||||
@@ -259,8 +259,6 @@ export const dict = {
|
||||
"go.cta.promo": "첫 달 $5",
|
||||
"go.pricing.body":
|
||||
"어떤 에이전트와도 사용할 수 있습니다. 첫 달 $5, 이후 $10/월. 필요하면 크레딧을 충전하세요. 언제든지 취소할 수 있습니다.",
|
||||
"go.banner.badge": "3x",
|
||||
"go.banner.text": "Kimi K2.6, 4월 27일까지 사용 한도 3배 확대",
|
||||
"go.graph.free": "무료",
|
||||
"go.graph.freePill": "Big Pickle 및 무료 모델",
|
||||
"go.graph.go": "Go",
|
||||
@@ -299,7 +297,7 @@ export const dict = {
|
||||
"go.problem.item2": "넉넉한 한도와 안정적인 액세스",
|
||||
"go.problem.item3": "가능한 한 많은 프로그래머를 위해 제작됨",
|
||||
"go.problem.item4":
|
||||
"GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7 포함",
|
||||
"GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7 포함",
|
||||
"go.how.title": "Go 작동 방식",
|
||||
"go.how.body": "Go는 첫 달 $5, 이후 $10/월로 시작합니다. OpenCode 또는 어떤 에이전트와도 함께 사용할 수 있습니다.",
|
||||
"go.how.step1.title": "계정 생성",
|
||||
@@ -323,7 +321,7 @@ export const dict = {
|
||||
"go.faq.a2": "Go에는 넉넉한 한도와 안정적인 액세스를 제공하는 아래 모델이 포함됩니다.",
|
||||
"go.faq.q3": "Go는 Zen과 같은가요?",
|
||||
"go.faq.a3":
|
||||
"아니요. Zen은 종량제인 반면, Go는 첫 달 $5, 이후 $10/월로 시작하며, GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7 오픈 소스 모델에 대한 넉넉한 한도와 안정적인 액세스를 제공합니다.",
|
||||
"아니요. Zen은 종량제인 반면, Go는 첫 달 $5, 이후 $10/월로 시작하며, GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7 오픈 소스 모델에 대한 넉넉한 한도와 안정적인 액세스를 제공합니다.",
|
||||
"go.faq.q4": "Go 비용은 얼마인가요?",
|
||||
"go.faq.a4.p1.beforePricing": "Go 비용은",
|
||||
"go.faq.a4.p1.pricingLink": "첫 달 $5",
|
||||
@@ -346,7 +344,7 @@ export const dict = {
|
||||
|
||||
"go.faq.q9": "무료 모델과 Go의 차이점은 무엇인가요?",
|
||||
"go.faq.a9":
|
||||
"무료 모델에는 Big Pickle과 당시 사용 가능한 프로모션 모델이 포함되며, 하루 200회 요청 할당량이 적용됩니다. Go는 GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7를 포함하며, 롤링 윈도우(5시간, 주간, 월간)에 걸쳐 더 높은 요청 할당량을 적용합니다. 이는 대략 5시간당 $12, 주당 $30, 월 $60에 해당합니다(실제 요청 수는 모델 및 사용량에 따라 다름).",
|
||||
"무료 모델에는 Big Pickle과 당시 사용 가능한 프로모션 모델이 포함되며, 하루 200회 요청 할당량이 적용됩니다. Go는 GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7를 포함하며, 롤링 윈도우(5시간, 주간, 월간)에 걸쳐 더 높은 요청 할당량을 적용합니다. 이는 대략 5시간당 $12, 주당 $30, 월 $60에 해당합니다(실제 요청 수는 모델 및 사용량에 따라 다름).",
|
||||
|
||||
"zen.api.error.rateLimitExceeded": "속도 제한을 초과했습니다. 나중에 다시 시도해 주세요.",
|
||||
"zen.api.error.modelNotSupported": "{{model}} 모델은 지원되지 않습니다",
|
||||
|
||||
@@ -251,7 +251,7 @@ export const dict = {
|
||||
|
||||
"go.title": "OpenCode Go | Rimelige kodemodeller for alle",
|
||||
"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.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 og MiniMax M2.7.",
|
||||
"Go starter på $5 for den første måneden, deretter $10/måned, med sjenerøse 5-timers forespørselsgrenser for GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 og MiniMax M2.7.",
|
||||
"go.hero.title": "Rimelige kodemodeller for alle",
|
||||
"go.hero.body":
|
||||
"Go bringer agent-koding til programmerere over hele verden. Med rause grenser og pålitelig tilgang til de mest kapable åpen kildekode-modellene, kan du bygge med kraftige agenter uten å bekymre deg for kostnader eller tilgjengelighet.",
|
||||
@@ -263,8 +263,6 @@ export const dict = {
|
||||
"go.cta.promo": "$5 første måned",
|
||||
"go.pricing.body":
|
||||
"Bruk med hvilken som helst agent. $5 første måned, deretter $10/måned. Fyll på kreditt ved behov. Avslutt når som helst.",
|
||||
"go.banner.badge": "3x",
|
||||
"go.banner.text": "Kimi K2.6: bruksgrensen er tredoblet til 27. april",
|
||||
"go.graph.free": "Gratis",
|
||||
"go.graph.freePill": "Big Pickle og gratis modeller",
|
||||
"go.graph.go": "Go",
|
||||
@@ -302,7 +300,7 @@ export const dict = {
|
||||
"go.problem.item2": "Rause grenser og pålitelig tilgang",
|
||||
"go.problem.item3": "Bygget for så mange programmerere som mulig",
|
||||
"go.problem.item4":
|
||||
"Inkluderer GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 og MiniMax M2.7",
|
||||
"Inkluderer GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 og MiniMax M2.7",
|
||||
"go.how.title": "Hvordan Go fungerer",
|
||||
"go.how.body":
|
||||
"Go starter på $5 for den første måneden, deretter $10/måned. Du kan bruke det med OpenCode eller hvilken som helst agent.",
|
||||
@@ -328,7 +326,7 @@ export const dict = {
|
||||
"go.faq.a2": "Go inkluderer modellene nedenfor, med høye grenser og pålitelig tilgang.",
|
||||
"go.faq.q3": "Er Go det samme som Zen?",
|
||||
"go.faq.a3":
|
||||
"Nei. Zen er betaling etter bruk, mens Go starter på $5 for den første måneden, deretter $10/måned, med sjenerøse grenser og pålitelig tilgang til åpen kildekode-modellene GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 og MiniMax M2.7.",
|
||||
"Nei. Zen er betaling etter bruk, mens Go starter på $5 for den første måneden, deretter $10/måned, med sjenerøse grenser og pålitelig tilgang til åpen kildekode-modellene GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 og MiniMax M2.7.",
|
||||
"go.faq.q4": "Hva koster Go?",
|
||||
"go.faq.a4.p1.beforePricing": "Go koster",
|
||||
"go.faq.a4.p1.pricingLink": "$5 første måned",
|
||||
@@ -352,7 +350,7 @@ export const dict = {
|
||||
|
||||
"go.faq.q9": "Hva er forskjellen mellom gratis modeller og Go?",
|
||||
"go.faq.a9":
|
||||
"Gratis modeller inkluderer Big Pickle pluss kampanjemodeller tilgjengelig på det tidspunktet, med en kvote på 200 forespørsler/dag. Go inkluderer GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 og MiniMax M2.7 med høyere kvoter håndhevet over rullerende vinduer (5 timer, ukentlig og månedlig), omtrent tilsvarende $12 per 5 timer, $30 per uke og $60 per måned (faktiske forespørselsantall varierer etter modell og bruk).",
|
||||
"Gratis modeller inkluderer Big Pickle pluss kampanjemodeller tilgjengelig på det tidspunktet, med en kvote på 200 forespørsler/dag. Go inkluderer GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 og MiniMax M2.7 med høyere kvoter håndhevet over rullerende vinduer (5 timer, ukentlig og månedlig), omtrent tilsvarende $12 per 5 timer, $30 per uke og $60 per måned (faktiske forespørselsantall varierer etter modell og bruk).",
|
||||
|
||||
"zen.api.error.rateLimitExceeded": "Rate limit overskredet. Vennligst prøv igjen senere.",
|
||||
"zen.api.error.modelNotSupported": "Modell {{model}} støttes ikke",
|
||||
|
||||
@@ -252,7 +252,7 @@ export const dict = {
|
||||
|
||||
"go.title": "OpenCode Go | Niskokosztowe modele do kodowania dla każdego",
|
||||
"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.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 i MiniMax M2.7.",
|
||||
"Go zaczyna się od $5 za pierwszy miesiąc, potem $10/miesiąc, z hojnymi 5-godzinnymi limitami zapytań dla GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 i MiniMax M2.7.",
|
||||
"go.hero.title": "Niskokosztowe modele do kodowania dla każdego",
|
||||
"go.hero.body":
|
||||
"Go udostępnia programowanie z agentami programistom na całym świecie. Oferuje hojne limity i niezawodny dostęp do najzdolniejszych modeli open source, dzięki czemu możesz budować za pomocą potężnych agentów, nie martwiąc się o koszty czy dostępność.",
|
||||
@@ -264,8 +264,6 @@ export const dict = {
|
||||
"go.cta.promo": "$5 pierwszy miesiąc",
|
||||
"go.pricing.body":
|
||||
"Używaj z dowolnym agentem. $5 za pierwszy miesiąc, potem $10/miesiąc. Doładuj konto w razie potrzeby. Anuluj w dowolnym momencie.",
|
||||
"go.banner.badge": "3x",
|
||||
"go.banner.text": "Kimi K2.6: limit użycia zwiększony 3× do 27 kwietnia",
|
||||
"go.graph.free": "Darmowe",
|
||||
"go.graph.freePill": "Big Pickle i darmowe modele",
|
||||
"go.graph.go": "Go",
|
||||
@@ -303,7 +301,7 @@ export const dict = {
|
||||
"go.problem.item2": "Hojne limity i niezawodny dostęp",
|
||||
"go.problem.item3": "Stworzony dla jak największej liczby programistów",
|
||||
"go.problem.item4":
|
||||
"Zawiera GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 i MiniMax M2.7",
|
||||
"Zawiera GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 i MiniMax M2.7",
|
||||
"go.how.title": "Jak działa Go",
|
||||
"go.how.body":
|
||||
"Go zaczyna się od $5 za pierwszy miesiąc, potem $10/miesiąc. Możesz go używać z OpenCode lub dowolnym agentem.",
|
||||
@@ -329,7 +327,7 @@ export const dict = {
|
||||
"go.faq.a2": "Go obejmuje poniższe modele z wysokimi limitami i niezawodnym dostępem.",
|
||||
"go.faq.q3": "Czy Go to to samo co Zen?",
|
||||
"go.faq.a3":
|
||||
"Nie. Zen to model płatności za użycie, podczas gdy Go zaczyna się od $5 za pierwszy miesiąc, potem $10/miesiąc, z hojnymi limitami i niezawodnym dostępem do modeli open source GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 i MiniMax M2.7.",
|
||||
"Nie. Zen to model płatności za użycie, podczas gdy Go zaczyna się od $5 za pierwszy miesiąc, potem $10/miesiąc, z hojnymi limitami i niezawodnym dostępem do modeli open source GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 i MiniMax M2.7.",
|
||||
"go.faq.q4": "Ile kosztuje Go?",
|
||||
"go.faq.a4.p1.beforePricing": "Go kosztuje",
|
||||
"go.faq.a4.p1.pricingLink": "$5 za pierwszy miesiąc",
|
||||
@@ -353,7 +351,7 @@ export const dict = {
|
||||
|
||||
"go.faq.q9": "Jaka jest różnica między darmowymi modelami a Go?",
|
||||
"go.faq.a9":
|
||||
"Darmowe modele obejmują Big Pickle oraz modele promocyjne dostępne w danym momencie, z limitem 200 zapytań/dzień. Go zawiera GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 i MiniMax M2.7 z wyższymi limitami zapytań egzekwowanymi w oknach kroczących (5-godzinnych, tygodniowych i miesięcznych), w przybliżeniu równoważnymi $12 na 5 godzin, $30 tygodniowo i $60 miesięcznie (rzeczywista liczba zapytań zależy od modelu i użycia).",
|
||||
"Darmowe modele obejmują Big Pickle oraz modele promocyjne dostępne w danym momencie, z limitem 200 zapytań/dzień. Go zawiera GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 i MiniMax M2.7 z wyższymi limitami zapytań egzekwowanymi w oknach kroczących (5-godzinnych, tygodniowych i miesięcznych), w przybliżeniu równoważnymi $12 na 5 godzin, $30 tygodniowo i $60 miesięcznie (rzeczywista liczba zapytań zależy od modelu i użycia).",
|
||||
|
||||
"zen.api.error.rateLimitExceeded": "Przekroczono limit zapytań. Spróbuj ponownie później.",
|
||||
"zen.api.error.modelNotSupported": "Model {{model}} nie jest obsługiwany",
|
||||
|
||||
@@ -255,7 +255,7 @@ export const dict = {
|
||||
|
||||
"go.title": "OpenCode Go | Недорогие модели для кодинга для всех",
|
||||
"go.meta.description":
|
||||
"Go начинается с $5 за первый месяц, затем $10/месяц, с щедрыми лимитами запросов за 5 часов для GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 и MiniMax M2.7.",
|
||||
"Go начинается с $5 за первый месяц, затем $10/месяц, с щедрыми лимитами запросов за 5 часов для GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 и MiniMax M2.7.",
|
||||
"go.hero.title": "Недорогие модели для кодинга для всех",
|
||||
"go.hero.body":
|
||||
"Go открывает доступ к агентам-программистам разработчикам по всему миру. Предлагая щедрые лимиты и надежный доступ к наиболее способным моделям с открытым исходным кодом, вы можете создавать проекты с мощными агентами, не беспокоясь о затратах или доступности.",
|
||||
@@ -267,8 +267,6 @@ export const dict = {
|
||||
"go.cta.promo": "$5 первый месяц",
|
||||
"go.pricing.body":
|
||||
"Используйте с любым агентом. $5 за первый месяц, затем $10/месяц. Пополняйте баланс при необходимости. Отменить можно в любое время.",
|
||||
"go.banner.badge": "3x",
|
||||
"go.banner.text": "Kimi K2.6: лимит использования увеличен в 3 раза до 27 апреля",
|
||||
"go.graph.free": "Бесплатно",
|
||||
"go.graph.freePill": "Big Pickle и бесплатные модели",
|
||||
"go.graph.go": "Go",
|
||||
@@ -307,7 +305,7 @@ export const dict = {
|
||||
"go.problem.item2": "Щедрые лимиты и надежный доступ",
|
||||
"go.problem.item3": "Создан для максимального числа программистов",
|
||||
"go.problem.item4":
|
||||
"Включает GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 и MiniMax M2.7",
|
||||
"Включает GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 и MiniMax M2.7",
|
||||
"go.how.title": "Как работает Go",
|
||||
"go.how.body":
|
||||
"Go начинается с $5 за первый месяц, затем $10/месяц. Вы можете использовать его с OpenCode или любым агентом.",
|
||||
@@ -333,7 +331,7 @@ export const dict = {
|
||||
"go.faq.a2": "Go включает перечисленные ниже модели с щедрыми лимитами и надежным доступом.",
|
||||
"go.faq.q3": "Go — это то же самое, что и Zen?",
|
||||
"go.faq.a3":
|
||||
"Нет. Zen - это оплата по мере использования, в то время как Go начинается с $5 за первый месяц, затем $10/месяц, с щедрыми лимитами и надежным доступом к моделям с открытым исходным кодом GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 и MiniMax M2.7.",
|
||||
"Нет. Zen - это оплата по мере использования, в то время как Go начинается с $5 за первый месяц, затем $10/месяц, с щедрыми лимитами и надежным доступом к моделям с открытым исходным кодом GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 и MiniMax M2.7.",
|
||||
"go.faq.q4": "Сколько стоит Go?",
|
||||
"go.faq.a4.p1.beforePricing": "Go стоит",
|
||||
"go.faq.a4.p1.pricingLink": "$5 за первый месяц",
|
||||
@@ -357,7 +355,7 @@ export const dict = {
|
||||
|
||||
"go.faq.q9": "В чем разница между бесплатными моделями и Go?",
|
||||
"go.faq.a9":
|
||||
"Бесплатные модели включают Big Pickle плюс промо-модели, доступные на данный момент, с квотой 200 запросов/день. Go включает GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 и MiniMax M2.7 с более высокими квотами запросов, применяемыми в скользящих окнах (5 часов, неделя и месяц), что примерно эквивалентно $12 за 5 часов, $30 в неделю и $60 в месяц (фактическое количество запросов зависит от модели и использования).",
|
||||
"Бесплатные модели включают Big Pickle плюс промо-модели, доступные на данный момент, с квотой 200 запросов/день. Go включает GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 и MiniMax M2.7 с более высокими квотами запросов, применяемыми в скользящих окнах (5 часов, неделя и месяц), что примерно эквивалентно $12 за 5 часов, $30 в неделю и $60 в месяц (фактическое количество запросов зависит от модели и использования).",
|
||||
|
||||
"zen.api.error.rateLimitExceeded": "Превышен лимит запросов. Пожалуйста, попробуйте позже.",
|
||||
"zen.api.error.modelNotSupported": "Модель {{model}} не поддерживается",
|
||||
|
||||
@@ -250,7 +250,7 @@ export const dict = {
|
||||
|
||||
"go.title": "OpenCode Go | โมเดลเขียนโค้ดราคาประหยัดสำหรับทุกคน",
|
||||
"go.meta.description":
|
||||
"Go เริ่มต้นที่ $5 สำหรับเดือนแรก จากนั้น $10/เดือน พร้อมขีดจำกัดคำขอ 5 ชั่วโมงที่เอื้อเฟื้อสำหรับ GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 และ MiniMax M2.7",
|
||||
"Go เริ่มต้นที่ $5 สำหรับเดือนแรก จากนั้น $10/เดือน พร้อมขีดจำกัดคำขอ 5 ชั่วโมงที่เอื้อเฟื้อสำหรับ GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 และ MiniMax M2.7",
|
||||
"go.hero.title": "โมเดลเขียนโค้ดราคาประหยัดสำหรับทุกคน",
|
||||
"go.hero.body":
|
||||
"Go นำการเขียนโค้ดแบบเอเจนต์มาสู่นักเขียนโปรแกรมทั่วโลก เสนอขีดจำกัดที่กว้างขวางและการเข้าถึงโมเดลโอเพนซอร์สที่มีความสามารถสูงสุดได้อย่างน่าเชื่อถือ เพื่อให้คุณสามารถสร้างสรรค์ด้วยเอเจนต์ที่ทรงพลังโดยไม่ต้องกังวลเรื่องค่าใช้จ่ายหรือความพร้อมใช้งาน",
|
||||
@@ -261,8 +261,6 @@ export const dict = {
|
||||
"go.cta.price": "$10/เดือน",
|
||||
"go.cta.promo": "$5 เดือนแรก",
|
||||
"go.pricing.body": "ใช้กับเอเจนต์ใดก็ได้ $5 ในเดือนแรก จากนั้น $10/เดือน เติมเครดิตหากจำเป็น ยกเลิกได้ตลอดเวลา",
|
||||
"go.banner.badge": "3x",
|
||||
"go.banner.text": "Kimi K2.6 โควตาการใช้งานเพิ่มเป็น 3 เท่า ถึง 27 เม.ย.",
|
||||
"go.graph.free": "ฟรี",
|
||||
"go.graph.freePill": "Big Pickle และโมเดลฟรี",
|
||||
"go.graph.go": "Go",
|
||||
@@ -300,7 +298,7 @@ export const dict = {
|
||||
"go.problem.item2": "ขีดจำกัดที่กว้างขวางและการเข้าถึงที่เชื่อถือได้",
|
||||
"go.problem.item3": "สร้างขึ้นเพื่อโปรแกรมเมอร์จำนวนมากที่สุดเท่าที่จะเป็นไปได้",
|
||||
"go.problem.item4":
|
||||
"รวมถึง GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 และ MiniMax M2.7",
|
||||
"รวมถึง GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 และ MiniMax M2.7",
|
||||
"go.how.title": "Go ทำงานอย่างไร",
|
||||
"go.how.body": "Go เริ่มต้นที่ $5 สำหรับเดือนแรก จากนั้น $10/เดือน คุณสามารถใช้กับ OpenCode หรือเอเจนต์ใดก็ได้",
|
||||
"go.how.step1.title": "สร้างบัญชี",
|
||||
@@ -325,7 +323,7 @@ export const dict = {
|
||||
"go.faq.a2": "Go รวมโมเดลด้านล่างนี้ พร้อมขีดจำกัดที่มากและการเข้าถึงที่เชื่อถือได้",
|
||||
"go.faq.q3": "Go เหมือนกับ Zen หรือไม่?",
|
||||
"go.faq.a3":
|
||||
"ไม่ Zen เป็นแบบจ่ายตามการใช้งาน ในขณะที่ Go เริ่มต้นที่ $5 สำหรับเดือนแรก จากนั้น $10/เดือน พร้อมขีดจำกัดที่เอื้อเฟื้อและการเข้าถึงโมเดลโอเพนซอร์ส GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 และ MiniMax M2.7 อย่างเชื่อถือได้",
|
||||
"ไม่ Zen เป็นแบบจ่ายตามการใช้งาน ในขณะที่ Go เริ่มต้นที่ $5 สำหรับเดือนแรก จากนั้น $10/เดือน พร้อมขีดจำกัดที่เอื้อเฟื้อและการเข้าถึงโมเดลโอเพนซอร์ส GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 และ MiniMax M2.7 อย่างเชื่อถือได้",
|
||||
"go.faq.q4": "Go ราคาเท่าไหร่?",
|
||||
"go.faq.a4.p1.beforePricing": "Go ราคา",
|
||||
"go.faq.a4.p1.pricingLink": "$5 เดือนแรก",
|
||||
@@ -348,7 +346,7 @@ export const dict = {
|
||||
|
||||
"go.faq.q9": "ความแตกต่างระหว่างโมเดลฟรีและ Go คืออะไร?",
|
||||
"go.faq.a9":
|
||||
"โมเดลฟรีรวมถึง Big Pickle บวกกับโมเดลโปรโมชั่นที่มีให้ในขณะนั้น ด้วยโควต้า 200 คำขอ/วัน Go รวมถึง GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 และ MiniMax M2.7 ที่มีโควต้าคำขอสูงกว่า ซึ่งบังคับใช้ผ่านช่วงเวลาหมุนเวียน (5 ชั่วโมง, รายสัปดาห์ และรายเดือน) เทียบเท่าประมาณ $12 ต่อ 5 ชั่วโมง, $30 ต่อสัปดาห์ และ $60 ต่อเดือน (จำนวนคำขอจริงจะแตกต่างกันไปตามโมเดลและการใช้งาน)",
|
||||
"โมเดลฟรีรวมถึง Big Pickle บวกกับโมเดลโปรโมชั่นที่มีให้ในขณะนั้น ด้วยโควต้า 200 คำขอ/วัน Go รวมถึง GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 และ MiniMax M2.7 ที่มีโควต้าคำขอสูงกว่า ซึ่งบังคับใช้ผ่านช่วงเวลาหมุนเวียน (5 ชั่วโมง, รายสัปดาห์ และรายเดือน) เทียบเท่าประมาณ $12 ต่อ 5 ชั่วโมง, $30 ต่อสัปดาห์ และ $60 ต่อเดือน (จำนวนคำขอจริงจะแตกต่างกันไปตามโมเดลและการใช้งาน)",
|
||||
|
||||
"zen.api.error.rateLimitExceeded": "เกินขีดจำกัดอัตราการใช้งาน กรุณาลองใหม่ในภายหลัง",
|
||||
"zen.api.error.modelNotSupported": "ไม่รองรับโมเดล {{model}}",
|
||||
|
||||
@@ -253,7 +253,7 @@ export const dict = {
|
||||
|
||||
"go.title": "OpenCode Go | Herkes için düşük maliyetli kodlama modelleri",
|
||||
"go.meta.description":
|
||||
"Go ilk ay $5, sonrasında ayda 10$ fiyatıyla başlar; GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 ve MiniMax M2.7 için cömert 5 saatlik istek limitleri sunar.",
|
||||
"Go ilk ay $5, sonrasında ayda 10$ fiyatıyla başlar; GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 ve MiniMax M2.7 için cömert 5 saatlik istek limitleri sunar.",
|
||||
"go.hero.title": "Herkes için düşük maliyetli kodlama modelleri",
|
||||
"go.hero.body":
|
||||
"Go, dünya çapındaki programcılara ajan tabanlı kodlama getiriyor. En yetenekli açık kaynaklı modellere cömert limitler ve güvenilir erişim sunarak, maliyet veya erişilebilirlik konusunda endişelenmeden güçlü ajanlarla geliştirme yapmanızı sağlar.",
|
||||
@@ -265,8 +265,6 @@ export const dict = {
|
||||
"go.cta.promo": "İlk ay $5",
|
||||
"go.pricing.body":
|
||||
"Herhangi bir ajanla kullanın. İlk ay $5, sonrasında ayda 10$. Gerekirse kredi yükleyin. İstediğiniz zaman iptal edin.",
|
||||
"go.banner.badge": "3x",
|
||||
"go.banner.text": "Kimi K2.6: kullanım limiti 27 Nisan'a kadar 3 katına çıktı",
|
||||
"go.graph.free": "Ücretsiz",
|
||||
"go.graph.freePill": "Big Pickle ve ücretsiz modeller",
|
||||
"go.graph.go": "Go",
|
||||
@@ -305,7 +303,7 @@ export const dict = {
|
||||
"go.problem.item2": "Cömert limitler ve güvenilir erişim",
|
||||
"go.problem.item3": "Mümkün olduğunca çok programcı için geliştirildi",
|
||||
"go.problem.item4":
|
||||
"GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 ve MiniMax M2.7 içerir",
|
||||
"GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 ve MiniMax M2.7 içerir",
|
||||
"go.how.title": "Go nasıl çalışır?",
|
||||
"go.how.body":
|
||||
"Go ilk ay $5, sonrasında ayda 10$ fiyatıyla başlar. OpenCode veya herhangi bir ajanla kullanabilirsiniz.",
|
||||
@@ -331,7 +329,7 @@ export const dict = {
|
||||
"go.faq.a2": "Go, aşağıda listelenen modelleri cömert limitler ve güvenilir erişimle sunar.",
|
||||
"go.faq.q3": "Go, Zen ile aynı mı?",
|
||||
"go.faq.a3":
|
||||
"Hayır. Zen kullandıkça öde modelidir, Go ise ilk ay $5, sonrasında ayda 10$ fiyatıyla başlar; GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 ve MiniMax M2.7 açık kaynak modellerine cömert limitler ve güvenilir erişim sunar.",
|
||||
"Hayır. Zen kullandıkça öde modelidir, Go ise ilk ay $5, sonrasında ayda 10$ fiyatıyla başlar; GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 ve MiniMax M2.7 açık kaynak modellerine cömert limitler ve güvenilir erişim sunar.",
|
||||
"go.faq.q4": "Go ne kadar?",
|
||||
"go.faq.a4.p1.beforePricing": "Go'nun maliyeti",
|
||||
"go.faq.a4.p1.pricingLink": "İlk ay $5",
|
||||
@@ -355,7 +353,7 @@ export const dict = {
|
||||
|
||||
"go.faq.q9": "Ücretsiz modeller ve Go arasındaki fark nedir?",
|
||||
"go.faq.a9":
|
||||
"Ücretsiz modeller, günlük 200 istek kotası ile Big Pickle ve o sırada mevcut olan promosyonel modelleri içerir. Go ise GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 ve MiniMax M2.7 modellerini; yuvarlanan pencereler (5 saatlik, haftalık ve aylık) üzerinden uygulanan daha yüksek istek kotalarıyla içerir. Bu kotalar kabaca her 5 saatte 12$, haftada 30$ ve ayda 60$ değerine eşdeğerdir (gerçek istek sayıları modele ve kullanıma göre değişir).",
|
||||
"Ücretsiz modeller, günlük 200 istek kotası ile Big Pickle ve o sırada mevcut olan promosyonel modelleri içerir. Go ise GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 ve MiniMax M2.7 modellerini; yuvarlanan pencereler (5 saatlik, haftalık ve aylık) üzerinden uygulanan daha yüksek istek kotalarıyla içerir. Bu kotalar kabaca her 5 saatte 12$, haftada 30$ ve ayda 60$ değerine eşdeğerdir (gerçek istek sayıları modele ve kullanıma göre değişir).",
|
||||
|
||||
"zen.api.error.rateLimitExceeded": "İstek limiti aşıldı. Lütfen daha sonra tekrar deneyin.",
|
||||
"zen.api.error.modelNotSupported": "{{model}} modeli desteklenmiyor",
|
||||
|
||||
@@ -241,7 +241,7 @@ export const dict = {
|
||||
|
||||
"go.title": "OpenCode Go | 人人可用的低成本编程模型",
|
||||
"go.meta.description":
|
||||
"Go 首月 $5,之后 $10/月,提供对 GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2-Pro、MiMo-V2-Omni、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5 和 MiniMax M2.7 的 5 小时充裕请求额度。",
|
||||
"Go 首月 $5,之后 $10/月,提供对 GLM-5.1、GLM-5、Kimi K2.5、MiMo-V2-Pro、MiMo-V2-Omni、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5 和 MiniMax M2.7 的 5 小时充裕请求额度。",
|
||||
"go.hero.title": "人人可用的低成本编程模型",
|
||||
"go.hero.body":
|
||||
"Go 将代理编程带给全世界的程序员。提供充裕的限额和对最强大的开源模型的可靠访问,让您可以利用强大的代理进行构建,而无需担心成本或可用性。",
|
||||
@@ -252,8 +252,6 @@ export const dict = {
|
||||
"go.cta.price": "$10/月",
|
||||
"go.cta.promo": "首月 $5",
|
||||
"go.pricing.body": "可配合任何代理使用。首月 $5,之后 $10/月。如有需要可充值。随时取消。",
|
||||
"go.banner.badge": "3x",
|
||||
"go.banner.text": "Kimi K2.6 使用额度提升至 3 倍,限时至 4 月 27 日",
|
||||
"go.graph.free": "免费",
|
||||
"go.graph.freePill": "Big Pickle 和免费模型",
|
||||
"go.graph.go": "Go",
|
||||
@@ -291,7 +289,7 @@ export const dict = {
|
||||
"go.problem.item2": "充裕的限额和可靠的访问",
|
||||
"go.problem.item3": "为尽可能多的程序员打造",
|
||||
"go.problem.item4":
|
||||
"包含 GLM-5.1, GLM-5, Kimi K2.5、Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 和 MiniMax M2.7",
|
||||
"包含 GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 和 MiniMax M2.7",
|
||||
"go.how.title": "Go 如何工作",
|
||||
"go.how.body": "Go 起价为首月 $5,之后 $10/月。您可以将其与 OpenCode 或任何代理搭配使用。",
|
||||
"go.how.step1.title": "创建账户",
|
||||
@@ -313,7 +311,7 @@ export const dict = {
|
||||
"go.faq.a2": "Go 包含下方列出的模型,提供充足的限额和可靠的访问。",
|
||||
"go.faq.q3": "Go 和 Zen 一样吗?",
|
||||
"go.faq.a3":
|
||||
"不。Zen 是按量付费,而 Go 首月 $5,之后 $10/月,提供充裕的额度,并可可靠地访问 GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2-Pro、MiMo-V2-Omni、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5 和 MiniMax M2.7 等开源模型。",
|
||||
"不。Zen 是按量付费,而 Go 首月 $5,之后 $10/月,提供充裕的额度,并可可靠地访问 GLM-5.1、GLM-5、Kimi K2.5、MiMo-V2-Pro、MiMo-V2-Omni、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5 和 MiniMax M2.7 等开源模型。",
|
||||
"go.faq.q4": "Go 多少钱?",
|
||||
"go.faq.a4.p1.beforePricing": "Go 费用为",
|
||||
"go.faq.a4.p1.pricingLink": "首月 $5",
|
||||
@@ -335,7 +333,7 @@ export const dict = {
|
||||
|
||||
"go.faq.q9": "免费模型和 Go 之间的区别是什么?",
|
||||
"go.faq.a9":
|
||||
"免费模型包含 Big Pickle 加上当时可用的促销模型,每天有 200 次请求的配额。Go 包含 GLM-5.1, GLM-5, Kimi K2.5、Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 和 MiniMax M2.7,并在滚动窗口(5 小时、每周和每月)内执行更高的请求配额,大致相当于每 5 小时 $12、每周 $30 和每月 $60(实际请求计数因模型和使用情况而异)。",
|
||||
"免费模型包含 Big Pickle 加上当时可用的促销模型,每天有 200 次请求的配额。Go 包含 GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 和 MiniMax M2.7,并在滚动窗口(5 小时、每周和每月)内执行更高的请求配额,大致相当于每 5 小时 $12、每周 $30 和每月 $60(实际请求计数因模型和使用情况而异)。",
|
||||
|
||||
"zen.api.error.rateLimitExceeded": "超出速率限制。请稍后重试。",
|
||||
"zen.api.error.modelNotSupported": "不支持模型 {{model}}",
|
||||
|
||||
@@ -241,7 +241,7 @@ export const dict = {
|
||||
|
||||
"go.title": "OpenCode Go | 低成本全民編碼模型",
|
||||
"go.meta.description":
|
||||
"Go 首月 $5,之後 $10/月,提供對 GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2-Pro、MiMo-V2-Omni、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5 和 MiniMax M2.7 的 5 小時充裕請求額度。",
|
||||
"Go 首月 $5,之後 $10/月,提供對 GLM-5.1、GLM-5、Kimi K2.5、MiMo-V2-Pro、MiMo-V2-Omni、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5 和 MiniMax M2.7 的 5 小時充裕請求額度。",
|
||||
"go.hero.title": "低成本全民編碼模型",
|
||||
"go.hero.body":
|
||||
"Go 將代理編碼帶給全世界的程式設計師。提供寬裕的限額以及對最強大開源模型的穩定存取,讓你可以使用強大的代理進行構建,而無需擔心成本或可用性。",
|
||||
@@ -252,8 +252,6 @@ export const dict = {
|
||||
"go.cta.price": "$10/月",
|
||||
"go.cta.promo": "首月 $5",
|
||||
"go.pricing.body": "可搭配任何代理使用。首月 $5,之後 $10/月。如有需要可儲值。隨時取消。",
|
||||
"go.banner.badge": "3x",
|
||||
"go.banner.text": "Kimi K2.6 使用額度提升至 3 倍,限時至 4 月 27 日",
|
||||
"go.graph.free": "免費",
|
||||
"go.graph.freePill": "Big Pickle 與免費模型",
|
||||
"go.graph.go": "Go",
|
||||
@@ -291,7 +289,7 @@ export const dict = {
|
||||
"go.problem.item2": "寬裕的限額與穩定存取",
|
||||
"go.problem.item3": "專為盡可能多的程式設計師打造",
|
||||
"go.problem.item4":
|
||||
"包含 GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2-Pro、MiMo-V2-Omni、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5 與 MiniMax M2.7",
|
||||
"包含 GLM-5.1、GLM-5、Kimi K2.5、MiMo-V2-Pro、MiMo-V2-Omni、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5 與 MiniMax M2.7",
|
||||
"go.how.title": "Go 如何運作",
|
||||
"go.how.body": "Go 起價為首月 $5,之後 $10/月。您可以將其與 OpenCode 或任何代理搭配使用。",
|
||||
"go.how.step1.title": "建立帳號",
|
||||
@@ -313,7 +311,7 @@ export const dict = {
|
||||
"go.faq.a2": "Go 包含下方列出的模型,提供充足的額度與穩定的存取。",
|
||||
"go.faq.q3": "Go 與 Zen 一樣嗎?",
|
||||
"go.faq.a3":
|
||||
"不。Zen 是按量付費,而 Go 首月 $5,之後 $10/月,提供充裕的額度,並可可靠地存取 GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2-Pro、MiMo-V2-Omni、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5 和 MiniMax M2.7 等開源模型。",
|
||||
"不。Zen 是按量付費,而 Go 首月 $5,之後 $10/月,提供充裕的額度,並可可靠地存取 GLM-5.1、GLM-5、Kimi K2.5、MiMo-V2-Pro、MiMo-V2-Omni、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5 和 MiniMax M2.7 等開源模型。",
|
||||
"go.faq.q4": "Go 費用是多少?",
|
||||
"go.faq.a4.p1.beforePricing": "Go 費用為",
|
||||
"go.faq.a4.p1.pricingLink": "首月 $5",
|
||||
@@ -335,7 +333,7 @@ export const dict = {
|
||||
|
||||
"go.faq.q9": "免費模型與 Go 有什麼區別?",
|
||||
"go.faq.a9":
|
||||
"免費模型包括 Big Pickle 以及當時可用的促銷模型,配額為 200 次請求/天。Go 包括 GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2-Pro、MiMo-V2-Omni、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5 與 MiniMax M2.7,並在滾動視窗(5 小時、每週和每月)內執行更高的請求配額,大約相當於每 5 小時 $12、每週 $30 和每月 $60(實際請求數因模型和使用情況而異)。",
|
||||
"免費模型包括 Big Pickle 以及當時可用的促銷模型,配額為 200 次請求/天。Go 包括 GLM-5.1、GLM-5、Kimi K2.5、MiMo-V2-Pro、MiMo-V2-Omni、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5 與 MiniMax M2.7,並在滾動視窗(5 小時、每週和每月)內執行更高的請求配額,大約相當於每 5 小時 $12、每週 $30 和每月 $60(實際請求數因模型和使用情況而異)。",
|
||||
|
||||
"zen.api.error.rateLimitExceeded": "超出頻率限制。請稍後再試。",
|
||||
"zen.api.error.modelNotSupported": "不支援模型 {{model}}",
|
||||
|
||||
@@ -27,7 +27,22 @@ function splitFullName(fullName: string) {
|
||||
return { firstName: parts[0], lastName: parts.slice(1).join(" ") }
|
||||
}
|
||||
|
||||
function getEmailOctopusApiKey() {
|
||||
if (process.env.EMAILOCTOPUS_API_KEY) return process.env.EMAILOCTOPUS_API_KEY
|
||||
try {
|
||||
return Resource.EMAILOCTOPUS_API_KEY.value
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
function subscribe(email: string, fullName: string) {
|
||||
const apiKey = getEmailOctopusApiKey()
|
||||
if (!apiKey) {
|
||||
console.warn("Skipping EmailOctopus subscribe: missing API key")
|
||||
return Promise.resolve(false)
|
||||
}
|
||||
|
||||
const name = splitFullName(fullName)
|
||||
const fields: Record<string, string> = {}
|
||||
if (name.firstName) fields.FirstName = name.firstName
|
||||
@@ -39,14 +54,14 @@ function subscribe(email: string, fullName: string) {
|
||||
return fetch(`https://api.emailoctopus.com/lists/${EMAIL_OCTOPUS_LIST_ID}/contacts`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
Authorization: `Bearer ${Resource.EMAILOCTOPUS_API_KEY.value}`,
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
}).then(
|
||||
async (res) => {
|
||||
(res) => {
|
||||
if (!res.ok) {
|
||||
console.error("EmailOctopus subscribe failed:", res.status, res.statusText, await res.text())
|
||||
console.error("EmailOctopus subscribe failed:", res.status, res.statusText)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
||||
@@ -306,7 +306,7 @@ body {
|
||||
[data-component="hero"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: calc(var(--vertical-padding) * 1.5) var(--padding);
|
||||
padding: calc(var(--vertical-padding) * 2) var(--padding);
|
||||
|
||||
[data-slot="zen logo dark"] {
|
||||
display: none;
|
||||
@@ -326,37 +326,6 @@ 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"] {
|
||||
img {
|
||||
margin-bottom: 24px;
|
||||
@@ -575,14 +544,6 @@ body {
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
[data-bonus] {
|
||||
color: var(--color-text-weak);
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="plot-labels"] {
|
||||
@@ -662,10 +623,6 @@ body {
|
||||
fill: var(--color-text-strong);
|
||||
}
|
||||
|
||||
[data-bar][data-kind="promo"] {
|
||||
fill: color-mix(in srgb, var(--bar-go) 50%, transparent);
|
||||
}
|
||||
|
||||
[data-val] {
|
||||
fill: var(--color-text-strong);
|
||||
font-size: 13px;
|
||||
|
||||
@@ -26,7 +26,6 @@ const models = [
|
||||
{ name: "GLM-5.1", provider: "DeepInfra, Z.ai" },
|
||||
{ name: "GLM-5", provider: "DeepInfra, Z.ai" },
|
||||
{ name: "Kimi K2.5", provider: "Moonshot AI" },
|
||||
{ name: "Kimi K2.6", provider: "Moonshot AI" },
|
||||
{ name: "MiMo-V2-Pro", provider: "Xiaomi MiMo" },
|
||||
{ name: "MiMo-V2-Omni", provider: "Xiaomi MiMo" },
|
||||
{ name: "Qwen3.5 Plus", provider: "Alibaba Cloud Model Studio" },
|
||||
@@ -59,8 +58,8 @@ function LimitsGraph(props: { href: string }) {
|
||||
const free = 200
|
||||
const graph = [
|
||||
{ id: "glm-5.1", name: "GLM-5.1", req: 880, d: "100ms" },
|
||||
{ id: "kimi-k2.6", name: "Kimi K2.6 (3x usage)", req: 3450, baseReq: 1150, d: "150ms" },
|
||||
{ id: "mimo-v2-pro", name: "MiMo-V2-Pro", req: 1290, d: "150ms" },
|
||||
{ id: "kimi", name: "Kimi K2.5", req: 1850, d: "240ms" },
|
||||
{ id: "qwen3.6-plus", name: "Qwen3.6 Plus", req: 3300, d: "280ms" },
|
||||
{ id: "minimax-m2.7", name: "MiniMax M2.7", req: 3400, d: "300ms" },
|
||||
{ id: "qwen3.5-plus", name: "Qwen3.5 Plus", req: 10200, d: "360ms" },
|
||||
@@ -78,7 +77,7 @@ function LimitsGraph(props: { href: string }) {
|
||||
const rmax = Math.max(1, ...graph.map((m) => ratio(m.req)))
|
||||
const log = (n: number) => Math.log10(Math.max(n, 1))
|
||||
const base = 24
|
||||
const p = 2.2
|
||||
const p = 1.8
|
||||
const x = (r: number) => left + base + Math.pow(log(r) / log(rmax), p) * (plot - base)
|
||||
const start = (x(1) / w) * 100
|
||||
|
||||
@@ -151,24 +150,12 @@ function LimitsGraph(props: { href: string }) {
|
||||
<rect
|
||||
x={left}
|
||||
y={gy(i()) - bh / 2}
|
||||
width={Math.max(0, x(ratio(m.baseReq ?? m.req)) - left)}
|
||||
width={Math.max(0, x(ratio(m.req)) - left)}
|
||||
height={bh}
|
||||
data-bar
|
||||
data-kind="go"
|
||||
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>
|
||||
)}
|
||||
</For>
|
||||
@@ -258,12 +245,6 @@ export default function Home() {
|
||||
|
||||
<div data-component="content">
|
||||
<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">
|
||||
<img data-slot="zen logo light" src={goLogoLight} alt="" />
|
||||
<img data-slot="zen logo dark" src={goLogoDark} alt="" />
|
||||
@@ -456,26 +437,28 @@ export default function Home() {
|
||||
<li>
|
||||
<Faq question={i18n.t("go.faq.q2")}>
|
||||
{i18n.t("go.faq.a2")}
|
||||
<div data-slot="faq-models">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{i18n.t("workspace.models.table.model")}</th>
|
||||
<th>{i18n.t("workspace.providers.table.provider")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<For each={models}>
|
||||
{(m) => (
|
||||
<tr>
|
||||
<td>{m.name}</td>
|
||||
<td>{m.provider}</td>
|
||||
</tr>
|
||||
)}
|
||||
</For>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{language.locale() === "en" && (
|
||||
<div data-slot="faq-models">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{i18n.t("workspace.models.table.model")}</th>
|
||||
<th>{i18n.t("workspace.providers.table.provider")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<For each={models}>
|
||||
{(m) => (
|
||||
<tr>
|
||||
<td>{m.name}</td>
|
||||
<td>{m.provider}</td>
|
||||
</tr>
|
||||
)}
|
||||
</For>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</Faq>
|
||||
</li>
|
||||
<li>
|
||||
|
||||
@@ -286,7 +286,6 @@ export function LiteSection() {
|
||||
<h3 data-slot="promo-models-title">{i18n.t("workspace.lite.promo.modelsTitle")}</h3>
|
||||
<ul data-slot="promo-models">
|
||||
<li>Kimi K2.5</li>
|
||||
<li>Kimi K2.6</li>
|
||||
<li>GLM-5</li>
|
||||
<li>GLM-5.1</li>
|
||||
<li>Mimo-V2-Pro</li>
|
||||
|
||||
@@ -461,8 +461,7 @@ export async function handler(
|
||||
}
|
||||
|
||||
if (retry.retryCount !== MAX_FAILOVER_RETRIES) {
|
||||
let topPriority = Infinity
|
||||
const providers = modelInfo.providers
|
||||
const allProviders = modelInfo.providers
|
||||
.filter((provider) => !provider.disabled)
|
||||
.filter((provider) => provider.weight !== 0)
|
||||
.filter((provider) => !retry.excludeProviders.includes(provider.id))
|
||||
@@ -471,10 +470,9 @@ export async function handler(
|
||||
const usage = modelTpmLimits?.[`${provider.id}/${provider.model}`] ?? 0
|
||||
return usage < provider.tpmLimit * 1_000_000
|
||||
})
|
||||
.map((provider) => {
|
||||
topPriority = Math.min(topPriority, provider.priority)
|
||||
return provider
|
||||
})
|
||||
|
||||
const topPriority = Math.min(...allProviders.map((p) => p.priority))
|
||||
const providers = allProviders
|
||||
.filter((p) => p.priority <= topPriority)
|
||||
.flatMap((provider) => Array<typeof provider>(provider.weight).fill(provider))
|
||||
|
||||
|
||||
@@ -1,46 +1,50 @@
|
||||
import { and, Database, eq, inArray, sql } from "@opencode-ai/console-core/drizzle/index.js"
|
||||
import { ModelTpmRateLimitTable } from "@opencode-ai/console-core/schema/ip.sql.js"
|
||||
import { ModelRateLimitTable } from "@opencode-ai/console-core/schema/ip.sql.js"
|
||||
import { UsageInfo } from "./provider/provider"
|
||||
|
||||
export function createModelTpmLimiter(providers: { id: string; model: string; tpmLimit?: number }[]) {
|
||||
const ids = providers.filter((p) => p.tpmLimit).map((p) => `${p.id}/${p.model}`)
|
||||
if (ids.length === 0) return
|
||||
const keys = providers.filter((p) => p.tpmLimit).map((p) => `${p.id}/${p.model}`)
|
||||
if (keys.length === 0) return
|
||||
|
||||
const yyyyMMddHHmm = parseInt(
|
||||
new Date(Date.now())
|
||||
.toISOString()
|
||||
.replace(/[^0-9]/g, "")
|
||||
.substring(0, 12),
|
||||
)
|
||||
const yyyyMMddHHmm = new Date(Date.now())
|
||||
.toISOString()
|
||||
.replace(/[^0-9]/g, "")
|
||||
.substring(0, 12)
|
||||
|
||||
return {
|
||||
check: async () => {
|
||||
const data = await Database.use((tx) =>
|
||||
tx
|
||||
.select()
|
||||
.from(ModelTpmRateLimitTable)
|
||||
.where(and(inArray(ModelTpmRateLimitTable.id, ids), eq(ModelTpmRateLimitTable.interval, yyyyMMddHHmm))),
|
||||
.from(ModelRateLimitTable)
|
||||
.where(and(inArray(ModelRateLimitTable.key, keys), eq(ModelRateLimitTable.interval, yyyyMMddHHmm))),
|
||||
)
|
||||
|
||||
// convert to map of model to count
|
||||
return data.reduce(
|
||||
(acc, curr) => {
|
||||
acc[curr.id] = curr.count
|
||||
acc[curr.key] = curr.count
|
||||
return acc
|
||||
},
|
||||
{} as Record<string, number>,
|
||||
)
|
||||
},
|
||||
track: async (provider: string, model: string, usageInfo: UsageInfo) => {
|
||||
const id = `${provider}/${model}`
|
||||
if (!ids.includes(id)) return
|
||||
const usage = usageInfo.inputTokens
|
||||
track: async (id: string, model: string, usageInfo: UsageInfo) => {
|
||||
const key = `${id}/${model}`
|
||||
if (!keys.includes(key)) return
|
||||
const usage =
|
||||
usageInfo.inputTokens +
|
||||
usageInfo.outputTokens +
|
||||
(usageInfo.reasoningTokens ?? 0) +
|
||||
(usageInfo.cacheReadTokens ?? 0) +
|
||||
(usageInfo.cacheWrite5mTokens ?? 0) +
|
||||
(usageInfo.cacheWrite1hTokens ?? 0)
|
||||
if (usage <= 0) return
|
||||
await Database.use((tx) =>
|
||||
tx
|
||||
.insert(ModelTpmRateLimitTable)
|
||||
.values({ id, interval: yyyyMMddHHmm, count: usage })
|
||||
.onDuplicateKeyUpdate({ set: { count: sql`${ModelTpmRateLimitTable.count} + ${usage}` } }),
|
||||
.insert(ModelRateLimitTable)
|
||||
.values({ key, interval: yyyyMMddHHmm, count: usage })
|
||||
.onDuplicateKeyUpdate({ set: { count: sql`${ModelRateLimitTable.count} + ${usage}` } }),
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
CREATE TABLE `model_tpm_limit` (
|
||||
`id` varchar(255) NOT NULL,
|
||||
`interval` int NOT NULL,
|
||||
`count` int NOT NULL,
|
||||
CONSTRAINT PRIMARY KEY(`id`,`interval`)
|
||||
);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,3 +0,0 @@
|
||||
ALTER TABLE `model_tpm_limit` DROP PRIMARY KEY;--> statement-breakpoint
|
||||
ALTER TABLE `model_tpm_limit` ADD PRIMARY KEY (`id`);--> statement-breakpoint
|
||||
ALTER TABLE `model_tpm_limit` DROP COLUMN `interval`;
|
||||
-2657
File diff suppressed because it is too large
Load Diff
@@ -1 +0,0 @@
|
||||
DROP TABLE `model_rate_limit`;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +0,0 @@
|
||||
CREATE TABLE `model_tpm_rate_limit` (
|
||||
`id` varchar(255) PRIMARY KEY,
|
||||
`interval` bigint NOT NULL,
|
||||
`count` int NOT NULL
|
||||
);
|
||||
-2657
File diff suppressed because it is too large
Load Diff
@@ -1,3 +0,0 @@
|
||||
DROP TABLE `model_tpm_limit`;--> statement-breakpoint
|
||||
ALTER TABLE `model_tpm_rate_limit` DROP PRIMARY KEY;--> statement-breakpoint
|
||||
ALTER TABLE `model_tpm_rate_limit` ADD PRIMARY KEY (`id`,`interval`);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
import { mysqlTable, int, primaryKey, varchar, bigint } from "drizzle-orm/mysql-core"
|
||||
import { mysqlTable, int, primaryKey, varchar } from "drizzle-orm/mysql-core"
|
||||
import { timestamps } from "../drizzle/types"
|
||||
|
||||
export const IpTable = mysqlTable(
|
||||
@@ -31,12 +31,12 @@ export const KeyRateLimitTable = mysqlTable(
|
||||
(table) => [primaryKey({ columns: [table.key, table.interval] })],
|
||||
)
|
||||
|
||||
export const ModelTpmRateLimitTable = mysqlTable(
|
||||
"model_tpm_rate_limit",
|
||||
export const ModelRateLimitTable = mysqlTable(
|
||||
"model_rate_limit",
|
||||
{
|
||||
id: varchar("id", { length: 255 }).notNull(),
|
||||
interval: bigint("interval", { mode: "number" }).notNull(),
|
||||
key: varchar("key", { length: 255 }).notNull(),
|
||||
interval: varchar("interval", { length: 40 }).notNull(),
|
||||
count: int("count").notNull(),
|
||||
},
|
||||
(table) => [primaryKey({ columns: [table.id, table.interval] })],
|
||||
(table) => [primaryKey({ columns: [table.key, table.interval] })],
|
||||
)
|
||||
|
||||
@@ -53,10 +53,6 @@ export default defineConfig({
|
||||
build: {
|
||||
rollupOptions: {
|
||||
input: { index: "src/preload/index.ts" },
|
||||
output: {
|
||||
format: "cjs",
|
||||
entryFileNames: "[name].js",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -42,13 +42,7 @@ import { initLogging } from "./logging"
|
||||
import { parseMarkdown } from "./markdown"
|
||||
import { createMenu } from "./menu"
|
||||
import { getDefaultServerUrl, getWslConfig, setDefaultServerUrl, setWslConfig, spawnLocalServer } from "./server"
|
||||
import {
|
||||
createLoadingWindow,
|
||||
createMainWindow,
|
||||
registerRendererProtocol,
|
||||
setBackgroundColor,
|
||||
setDockIcon,
|
||||
} from "./windows"
|
||||
import { createLoadingWindow, createMainWindow, setBackgroundColor, setDockIcon } from "./windows"
|
||||
import { drizzle } from "drizzle-orm/node-sqlite/driver"
|
||||
import type { Server } from "virtual:opencode-server"
|
||||
|
||||
@@ -112,7 +106,6 @@ function setupApp() {
|
||||
|
||||
void app.whenReady().then(async () => {
|
||||
app.setAsDefaultProtocolClient("opencode")
|
||||
registerRendererProtocol()
|
||||
setDockIcon()
|
||||
setupAutoUpdater()
|
||||
await initialize()
|
||||
@@ -195,10 +188,15 @@ async function initialize() {
|
||||
logger.log("loading task finished")
|
||||
})()
|
||||
|
||||
const globals = {
|
||||
updaterEnabled: UPDATER_ENABLED,
|
||||
deepLinks: pendingDeepLinks,
|
||||
}
|
||||
|
||||
if (needsMigration) {
|
||||
const show = await Promise.race([loadingTask.then(() => false), delay(1_000).then(() => true)])
|
||||
if (show) {
|
||||
overlay = createLoadingWindow()
|
||||
overlay = createLoadingWindow(globals)
|
||||
await delay(1_000)
|
||||
}
|
||||
}
|
||||
@@ -210,7 +208,7 @@ async function initialize() {
|
||||
await loadingComplete.promise
|
||||
}
|
||||
|
||||
mainWindow = createMainWindow()
|
||||
mainWindow = createMainWindow(globals)
|
||||
wireMenu()
|
||||
|
||||
overlay?.close()
|
||||
@@ -247,8 +245,6 @@ registerIpcHandlers({
|
||||
initEmitter.off("step", listener)
|
||||
}
|
||||
},
|
||||
getWindowConfig: () => ({ updaterEnabled: UPDATER_ENABLED }),
|
||||
consumeInitialDeepLinks: () => pendingDeepLinks.splice(0),
|
||||
getDefaultServerUrl: () => getDefaultServerUrl(),
|
||||
setDefaultServerUrl: (url) => setDefaultServerUrl(url),
|
||||
getWslConfig: () => Promise.resolve(getWslConfig()),
|
||||
|
||||
@@ -2,14 +2,7 @@ import { execFile } from "node:child_process"
|
||||
import { BrowserWindow, Notification, app, clipboard, dialog, ipcMain, shell } from "electron"
|
||||
import type { IpcMainEvent, IpcMainInvokeEvent } from "electron"
|
||||
|
||||
import type {
|
||||
InitStep,
|
||||
ServerReadyData,
|
||||
SqliteMigrationProgress,
|
||||
TitlebarTheme,
|
||||
WindowConfig,
|
||||
WslConfig,
|
||||
} from "../preload/types"
|
||||
import type { InitStep, ServerReadyData, SqliteMigrationProgress, TitlebarTheme, WslConfig } from "../preload/types"
|
||||
import { getStore } from "./store"
|
||||
import { setTitlebar } from "./windows"
|
||||
|
||||
@@ -21,8 +14,6 @@ const pickerFilters = (ext?: string[]) => {
|
||||
type Deps = {
|
||||
killSidecar: () => void
|
||||
awaitInitialization: (sendStep: (step: InitStep) => void) => Promise<ServerReadyData>
|
||||
getWindowConfig: () => Promise<WindowConfig> | WindowConfig
|
||||
consumeInitialDeepLinks: () => Promise<string[]> | string[]
|
||||
getDefaultServerUrl: () => Promise<string | null> | string | null
|
||||
setDefaultServerUrl: (url: string | null) => Promise<void> | void
|
||||
getWslConfig: () => Promise<WslConfig>
|
||||
@@ -46,8 +37,6 @@ export function registerIpcHandlers(deps: Deps) {
|
||||
const send = (step: InitStep) => event.sender.send("init-step", step)
|
||||
return deps.awaitInitialization(send)
|
||||
})
|
||||
ipcMain.handle("get-window-config", () => deps.getWindowConfig())
|
||||
ipcMain.handle("consume-initial-deep-links", () => deps.consumeInitialDeepLinks())
|
||||
ipcMain.handle("get-default-server-url", () => deps.getDefaultServerUrl())
|
||||
ipcMain.handle("set-default-server-url", (_event: IpcMainInvokeEvent, url: string | null) =>
|
||||
deps.setDefaultServerUrl(url),
|
||||
|
||||
@@ -47,7 +47,7 @@ export function createMenu(deps: Deps) {
|
||||
{
|
||||
label: "New Window",
|
||||
accelerator: "Cmd+Shift+N",
|
||||
click: () => createMainWindow(),
|
||||
click: () => createMainWindow({ updaterEnabled: UPDATER_ENABLED }),
|
||||
},
|
||||
{ type: "separator" },
|
||||
{ role: "close" },
|
||||
|
||||
@@ -39,7 +39,6 @@ export async function spawnLocalServer(hostname: string, port: number, password:
|
||||
hostname,
|
||||
username: "opencode",
|
||||
password,
|
||||
cors: ["oc://renderer"],
|
||||
})
|
||||
|
||||
const wait = (async () => {
|
||||
|
||||
@@ -1,24 +1,15 @@
|
||||
import windowState from "electron-window-state"
|
||||
import { app, BrowserWindow, net, nativeImage, nativeTheme, protocol } from "electron"
|
||||
import { dirname, isAbsolute, join, relative, resolve } from "node:path"
|
||||
import { fileURLToPath, pathToFileURL } from "node:url"
|
||||
import { app, BrowserWindow, nativeImage, nativeTheme } from "electron"
|
||||
import { dirname, join } from "node:path"
|
||||
import { fileURLToPath } from "node:url"
|
||||
import type { TitlebarTheme } from "../preload/types"
|
||||
|
||||
const root = dirname(fileURLToPath(import.meta.url))
|
||||
const rendererRoot = join(root, "../renderer")
|
||||
const rendererProtocol = "oc"
|
||||
const rendererHost = "renderer"
|
||||
type Globals = {
|
||||
updaterEnabled: boolean
|
||||
deepLinks?: string[]
|
||||
}
|
||||
|
||||
protocol.registerSchemesAsPrivileged([
|
||||
{
|
||||
scheme: rendererProtocol,
|
||||
privileges: {
|
||||
secure: true,
|
||||
standard: true,
|
||||
supportFetchAPI: true,
|
||||
},
|
||||
},
|
||||
])
|
||||
const root = dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
let backgroundColor: string | undefined
|
||||
|
||||
@@ -63,7 +54,7 @@ export function setDockIcon() {
|
||||
if (!icon.isEmpty()) app.dock?.setIcon(icon)
|
||||
}
|
||||
|
||||
export function createMainWindow() {
|
||||
export function createMainWindow(globals: Globals) {
|
||||
const state = windowState({
|
||||
defaultWidth: 1280,
|
||||
defaultHeight: 800,
|
||||
@@ -93,29 +84,15 @@ export function createMainWindow() {
|
||||
}
|
||||
: {}),
|
||||
webPreferences: {
|
||||
preload: join(root, "../preload/index.js"),
|
||||
contextIsolation: true,
|
||||
nodeIntegration: false,
|
||||
sandbox: true,
|
||||
preload: join(root, "../preload/index.mjs"),
|
||||
sandbox: false,
|
||||
},
|
||||
})
|
||||
|
||||
win.webContents.session.webRequest.onBeforeSendHeaders((details, callback) => {
|
||||
const { requestHeaders } = details
|
||||
upsertKeyValue(requestHeaders, "Access-Control-Allow-Origin", ["*"])
|
||||
callback({ requestHeaders })
|
||||
})
|
||||
|
||||
win.webContents.session.webRequest.onHeadersReceived((details, callback) => {
|
||||
const { responseHeaders = {} } = details
|
||||
upsertKeyValue(responseHeaders, "Access-Control-Allow-Origin", ["*"])
|
||||
upsertKeyValue(responseHeaders, "Access-Control-Allow-Headers", ["*"])
|
||||
callback({ responseHeaders })
|
||||
})
|
||||
|
||||
state.manage(win)
|
||||
loadWindow(win, "index.html")
|
||||
wireZoom(win)
|
||||
injectGlobals(win, globals)
|
||||
|
||||
win.once("ready-to-show", () => {
|
||||
win.show()
|
||||
@@ -124,7 +101,7 @@ export function createMainWindow() {
|
||||
return win
|
||||
}
|
||||
|
||||
export function createLoadingWindow() {
|
||||
export function createLoadingWindow(globals: Globals) {
|
||||
const mode = tone()
|
||||
const win = new BrowserWindow({
|
||||
width: 640,
|
||||
@@ -143,37 +120,17 @@ export function createLoadingWindow() {
|
||||
}
|
||||
: {}),
|
||||
webPreferences: {
|
||||
preload: join(root, "../preload/index.js"),
|
||||
contextIsolation: true,
|
||||
nodeIntegration: false,
|
||||
sandbox: true,
|
||||
preload: join(root, "../preload/index.mjs"),
|
||||
sandbox: false,
|
||||
},
|
||||
})
|
||||
|
||||
loadWindow(win, "loading.html")
|
||||
injectGlobals(win, globals)
|
||||
|
||||
return win
|
||||
}
|
||||
|
||||
export function registerRendererProtocol() {
|
||||
if (protocol.isProtocolHandled(rendererProtocol)) return
|
||||
|
||||
protocol.handle(rendererProtocol, (request) => {
|
||||
const url = new URL(request.url)
|
||||
if (url.host !== rendererHost) {
|
||||
return new Response("Not found", { status: 404 })
|
||||
}
|
||||
|
||||
const file = resolve(rendererRoot, `.${decodeURIComponent(url.pathname)}`)
|
||||
const rel = relative(rendererRoot, file)
|
||||
if (rel.startsWith("..") || isAbsolute(rel)) {
|
||||
return new Response("Not found", { status: 404 })
|
||||
}
|
||||
|
||||
return net.fetch(pathToFileURL(file).toString())
|
||||
})
|
||||
}
|
||||
|
||||
function loadWindow(win: BrowserWindow, html: string) {
|
||||
const devUrl = process.env.ELECTRON_RENDERER_URL
|
||||
if (devUrl) {
|
||||
@@ -182,25 +139,25 @@ function loadWindow(win: BrowserWindow, html: string) {
|
||||
return
|
||||
}
|
||||
|
||||
void win.loadURL(`${rendererProtocol}://${rendererHost}/${html}`)
|
||||
void win.loadFile(join(root, `../renderer/${html}`))
|
||||
}
|
||||
|
||||
function injectGlobals(win: BrowserWindow, globals: Globals) {
|
||||
win.webContents.on("dom-ready", () => {
|
||||
const deepLinks = globals.deepLinks ?? []
|
||||
const data = {
|
||||
updaterEnabled: globals.updaterEnabled,
|
||||
deepLinks: Array.isArray(deepLinks) ? deepLinks.splice(0) : deepLinks,
|
||||
}
|
||||
void win.webContents.executeJavaScript(
|
||||
`window.__OPENCODE__ = Object.assign(window.__OPENCODE__ ?? {}, ${JSON.stringify(data)})`,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
function wireZoom(win: BrowserWindow) {
|
||||
win.webContents.setZoomFactor(1)
|
||||
win.webContents.on("zoom-changed", () => {
|
||||
win.webContents.setZoomFactor(1)
|
||||
})
|
||||
}
|
||||
|
||||
function upsertKeyValue(obj: Record<string, any>, keyToChange: string, value: any) {
|
||||
const keyToChangeLower = keyToChange.toLowerCase()
|
||||
for (const key of Object.keys(obj)) {
|
||||
if (key.toLowerCase() === keyToChangeLower) {
|
||||
// Reassign old key
|
||||
obj[key] = value
|
||||
// Done
|
||||
return
|
||||
}
|
||||
}
|
||||
// Insert at end instead
|
||||
obj[keyToChange] = value
|
||||
}
|
||||
|
||||
@@ -11,8 +11,6 @@ const api: ElectronAPI = {
|
||||
ipcRenderer.removeListener("init-step", handler)
|
||||
})
|
||||
},
|
||||
getWindowConfig: () => ipcRenderer.invoke("get-window-config"),
|
||||
consumeInitialDeepLinks: () => ipcRenderer.invoke("consume-initial-deep-links"),
|
||||
getDefaultServerUrl: () => ipcRenderer.invoke("get-default-server-url"),
|
||||
setDefaultServerUrl: (url) => ipcRenderer.invoke("set-default-server-url", url),
|
||||
getWslConfig: () => ipcRenderer.invoke("get-wsl-config"),
|
||||
|
||||
@@ -15,16 +15,10 @@ export type TitlebarTheme = {
|
||||
mode: "light" | "dark"
|
||||
}
|
||||
|
||||
export type WindowConfig = {
|
||||
updaterEnabled: boolean
|
||||
}
|
||||
|
||||
export type ElectronAPI = {
|
||||
killSidecar: () => Promise<void>
|
||||
installCli: () => Promise<string>
|
||||
awaitInitialization: (onStep: (step: InitStep) => void) => Promise<ServerReadyData>
|
||||
getWindowConfig: () => Promise<WindowConfig>
|
||||
consumeInitialDeepLinks: () => Promise<string[]>
|
||||
getDefaultServerUrl: () => Promise<string | null>
|
||||
setDefaultServerUrl: (url: string | null) => Promise<void>
|
||||
getWslConfig: () => Promise<WslConfig>
|
||||
|
||||
@@ -4,6 +4,8 @@ declare global {
|
||||
interface Window {
|
||||
api: ElectronAPI
|
||||
__OPENCODE__?: {
|
||||
updaterEnabled?: boolean
|
||||
wsl?: boolean
|
||||
deepLinks?: string[]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,9 @@ const root = resolve(dir, "../..")
|
||||
const html = async (name: string) => Bun.file(join(dir, name)).text()
|
||||
|
||||
/**
|
||||
* Packaged Electron windows load renderer HTML via the privileged `oc://`
|
||||
* protocol. Root-relative asset paths like `src="/foo.js"` would resolve from
|
||||
* the protocol origin root instead of relative to the current HTML entrypoint.
|
||||
* Electron loads renderer HTML via `win.loadFile()` which uses the `file://`
|
||||
* protocol. Absolute paths like `src="/foo.js"` resolve to the filesystem root
|
||||
* (e.g. `file:///C:/foo.js` on Windows) instead of relative to the app bundle.
|
||||
*
|
||||
* All local resource references must use relative paths (`./`).
|
||||
*/
|
||||
|
||||
@@ -20,6 +20,7 @@ import { createEffect, createResource, onCleanup, onMount, Show } from "solid-js
|
||||
import { render } from "solid-js/web"
|
||||
import pkg from "../../package.json"
|
||||
import { initI18n, t } from "./i18n"
|
||||
import { UPDATER_ENABLED } from "./updater"
|
||||
import { webviewZoom } from "./webview-zoom"
|
||||
import "./styles.css"
|
||||
import { useTheme } from "@opencode-ai/ui/theme"
|
||||
@@ -42,7 +43,8 @@ const emitDeepLinks = (urls: string[]) => {
|
||||
}
|
||||
|
||||
const listenForDeepLinks = () => {
|
||||
void window.api.consumeInitialDeepLinks().then((urls) => emitDeepLinks(urls))
|
||||
const startUrls = window.__OPENCODE__?.deepLinks ?? []
|
||||
if (startUrls.length) emitDeepLinks(startUrls)
|
||||
return window.api.onDeepLink((urls) => emitDeepLinks(urls))
|
||||
}
|
||||
|
||||
@@ -55,21 +57,13 @@ const createPlatform = (): Platform => {
|
||||
return undefined
|
||||
})()
|
||||
|
||||
const isWslEnabled = async () => {
|
||||
if (os !== "windows") return false
|
||||
return window.api
|
||||
.getWslConfig()
|
||||
.then((config) => config.enabled)
|
||||
.catch(() => false)
|
||||
}
|
||||
|
||||
const wslHome = async () => {
|
||||
if (!(await isWslEnabled())) return undefined
|
||||
if (os !== "windows" || !window.__OPENCODE__?.wsl) return undefined
|
||||
return window.api.wslPath("~", "windows").catch(() => undefined)
|
||||
}
|
||||
|
||||
const handleWslPicker = async <T extends string | string[]>(result: T | null): Promise<T | null> => {
|
||||
if (!result || !(await isWslEnabled())) return result
|
||||
if (!result || !window.__OPENCODE__?.wsl) return result
|
||||
if (Array.isArray(result)) {
|
||||
return Promise.all(result.map((path) => window.api.wslPath(path, "linux").catch(() => path))) as any
|
||||
}
|
||||
@@ -143,7 +137,7 @@ const createPlatform = (): Platform => {
|
||||
if (os === "windows") {
|
||||
const resolvedApp = app ? await window.api.resolveAppPath(app).catch(() => null) : null
|
||||
const resolvedPath = await (async () => {
|
||||
if (await isWslEnabled()) {
|
||||
if (window.__OPENCODE__?.wsl) {
|
||||
const converted = await window.api.wslPath(path, "windows").catch(() => null)
|
||||
if (converted) return converted
|
||||
}
|
||||
@@ -165,14 +159,12 @@ const createPlatform = (): Platform => {
|
||||
storage,
|
||||
|
||||
checkUpdate: async () => {
|
||||
const config = await window.api.getWindowConfig().catch(() => ({ updaterEnabled: false }))
|
||||
if (!config.updaterEnabled) return { updateAvailable: false }
|
||||
if (!UPDATER_ENABLED()) return { updateAvailable: false }
|
||||
return window.api.checkUpdate()
|
||||
},
|
||||
|
||||
update: async () => {
|
||||
const config = await window.api.getWindowConfig().catch(() => ({ updaterEnabled: false }))
|
||||
if (!config.updaterEnabled) return
|
||||
if (!UPDATER_ENABLED()) return
|
||||
await window.api.installUpdate()
|
||||
},
|
||||
|
||||
@@ -202,7 +194,11 @@ const createPlatform = (): Platform => {
|
||||
return fetch(input, init)
|
||||
},
|
||||
|
||||
getWslEnabled: () => isWslEnabled(),
|
||||
getWslEnabled: async () => {
|
||||
const next = await window.api.getWslConfig().catch(() => null)
|
||||
if (next) return next.enabled
|
||||
return window.__OPENCODE__!.wsl ?? false
|
||||
},
|
||||
|
||||
setWslEnabled: async (enabled) => {
|
||||
await window.api.setWslConfig({ enabled })
|
||||
@@ -253,7 +249,6 @@ listenForDeepLinks()
|
||||
|
||||
render(() => {
|
||||
const platform = createPlatform()
|
||||
const [windowConfig] = createResource(() => window.api.getWindowConfig().catch(() => ({ updaterEnabled: false })))
|
||||
const loadLocale = async () => {
|
||||
const current = await platform.storage?.("opencode.global.dat").getItem("language")
|
||||
const legacy = current ? undefined : await platform.storage?.().getItem("language.v1")
|
||||
@@ -330,15 +325,7 @@ render(() => {
|
||||
return (
|
||||
<PlatformProvider value={platform}>
|
||||
<AppBaseProviders locale={locale.latest}>
|
||||
<Show
|
||||
when={
|
||||
!defaultServer.loading &&
|
||||
!sidecar.loading &&
|
||||
!windowConfig.loading &&
|
||||
!windowCount.loading &&
|
||||
!locale.loading
|
||||
}
|
||||
>
|
||||
<Show when={!defaultServer.loading && !sidecar.loading && !windowCount.loading && !locale.loading}>
|
||||
{(_) => {
|
||||
return (
|
||||
<AppInterface
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { initI18n, t } from "./i18n"
|
||||
|
||||
export const UPDATER_ENABLED = () => window.__OPENCODE__?.updaterEnabled ?? false
|
||||
|
||||
export async function runUpdater({ alertOnFail }: { alertOnFail: boolean }) {
|
||||
await initI18n()
|
||||
try {
|
||||
|
||||
@@ -123,8 +123,8 @@
|
||||
"@opentelemetry/exporter-trace-otlp-http": "0.214.0",
|
||||
"@opentelemetry/sdk-trace-base": "2.6.1",
|
||||
"@opentelemetry/sdk-trace-node": "2.6.1",
|
||||
"@opentui/core": "0.1.99",
|
||||
"@opentui/solid": "0.1.99",
|
||||
"@opentui/core": "0.1.101",
|
||||
"@opentui/solid": "0.1.101",
|
||||
"@parcel/watcher": "2.5.1",
|
||||
"@pierre/diffs": "catalog:",
|
||||
"@solid-primitives/event-bus": "1.1.2",
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
// This script runs a separate OpenCode server to be used as a remote
|
||||
// workspace, simulating a remote environment but all local to make
|
||||
// debugger easier
|
||||
//
|
||||
// *Important*: make sure you add the debug workspace plugin first.
|
||||
// In `.opencode/opencode.jsonc` in the root of this project add:
|
||||
//
|
||||
// "plugin": ["../packages/opencode/src/control-plane/dev/debug-workspace-plugin.ts"]
|
||||
//
|
||||
// Afterwards, run `./packages/opencode/script/run-workspace-server`
|
||||
|
||||
import { stat } from "node:fs/promises"
|
||||
import { setTimeout as sleep } from "node:timers/promises"
|
||||
|
||||
const DEV_DATA_FILE = "/tmp/opencode-workspace-dev-data.json"
|
||||
const RESTART_POLL_INTERVAL = 250
|
||||
|
||||
async function readData() {
|
||||
return await Bun.file(DEV_DATA_FILE).json()
|
||||
}
|
||||
|
||||
async function readDataMtime() {
|
||||
return await stat(DEV_DATA_FILE)
|
||||
.then((info) => info.mtimeMs)
|
||||
.catch((error) => {
|
||||
if (typeof error === "object" && error && "code" in error && error.code === "ENOENT") {
|
||||
return undefined
|
||||
}
|
||||
|
||||
throw error
|
||||
})
|
||||
}
|
||||
|
||||
async function readSnapshot() {
|
||||
while (true) {
|
||||
try {
|
||||
const before = await readDataMtime()
|
||||
if (before === undefined) {
|
||||
await sleep(RESTART_POLL_INTERVAL)
|
||||
continue
|
||||
}
|
||||
|
||||
const data = await readData()
|
||||
const after = await readDataMtime()
|
||||
|
||||
if (before === after) {
|
||||
return { data, mtime: after }
|
||||
}
|
||||
} catch (error) {
|
||||
if (typeof error === "object" && error && "code" in error && error.code === "ENOENT") {
|
||||
await sleep(RESTART_POLL_INTERVAL)
|
||||
continue
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function startDevServer(data: any) {
|
||||
const env = Object.fromEntries(Object.entries(data.env ?? {}).filter(([, value]) => value !== undefined))
|
||||
|
||||
return Bun.spawn(["bun", "run", "dev", "serve", "--port", String(data.port), "--print-logs"], {
|
||||
env: {
|
||||
...process.env,
|
||||
...env,
|
||||
XDG_DATA_HOME: "/tmp/data",
|
||||
},
|
||||
stdin: "inherit",
|
||||
stdout: "inherit",
|
||||
stderr: "inherit",
|
||||
})
|
||||
}
|
||||
|
||||
async function waitForRestartSignal(mtime: number, signal: AbortSignal) {
|
||||
while (!signal.aborted) {
|
||||
await sleep(RESTART_POLL_INTERVAL)
|
||||
if (signal.aborted) return false
|
||||
if ((await readDataMtime()) !== mtime) return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
while (true) {
|
||||
const { data, mtime } = await readSnapshot()
|
||||
const proc = startDevServer(data)
|
||||
const restartAbort = new AbortController()
|
||||
|
||||
const result = await Promise.race([
|
||||
proc.exited.then((code) => ({ type: "exit" as const, code })),
|
||||
waitForRestartSignal(mtime, restartAbort.signal).then((restart) => ({ type: "restart" as const, restart })),
|
||||
])
|
||||
|
||||
restartAbort.abort()
|
||||
|
||||
if (result.type === "restart" && result.restart) {
|
||||
proc.kill()
|
||||
await proc.exited
|
||||
continue
|
||||
}
|
||||
|
||||
process.exit(result.code)
|
||||
}
|
||||
@@ -55,7 +55,7 @@ const configFile = process.argv[2]
|
||||
const tuiFile = process.argv[3]
|
||||
|
||||
console.log(configFile)
|
||||
await Bun.write(configFile, JSON.stringify(generate(Config.Info.zod), null, 2))
|
||||
await Bun.write(configFile, JSON.stringify(generate(Config.Info), null, 2))
|
||||
|
||||
if (tuiFile) {
|
||||
console.log(tuiFile)
|
||||
|
||||
@@ -224,7 +224,7 @@ When to use each:
|
||||
|
||||
Promoting a previously-anonymous schema to Schema.Class is acceptable when it is top-level or endpoint-facing, but call it out in the PR — it is an additive SDK change (`export type Foo = ...` newly appears) even if it preserves the JSON shape.
|
||||
|
||||
Schemas that are **not** pure objects (enums, unions, records, tuples) cannot use Schema.Class. For those — and for pure-object schemas where handlers populate plain objects rather than class instances — add `.annotate({ identifier: "FooName" })` to get the same named-ref behavior without the `instanceof` requirement:
|
||||
Schemas that are **not** pure objects (enums, unions, records, tuples) cannot use Schema.Class. For those, add `.annotate({ identifier: "FooName" })` to get the same named-ref behavior:
|
||||
|
||||
```ts
|
||||
export const Action = Schema.Literals(["ask", "allow", "deny"]).annotate({ identifier: "PermissionActionConfig" })
|
||||
@@ -373,9 +373,9 @@ The first slice is successful if:
|
||||
|
||||
- `Schema.Class` works well for route DTOs such as `Question.Request`, `Question.Info`, and `Question.Reply`.
|
||||
- scalar or collection schemas such as `Question.Answer` should stay as schemas and use helpers like `withStatics(...)` instead of being forced into classes.
|
||||
- if an `HttpApi` success schema uses `Schema.Class`, the handler or underlying service needs to return real schema instances rather than plain objects. `Schema.Class`'s Declaration AST enforces `input instanceof self || input.[ClassTypeId]` during encode (see effect-smol `Schema.ts:10479-10484`). Plain objects from zod parse fail with `Expected Foo, got {...}`. This surfaced on `GET /config` where the service returns zod-parsed plain objects and `Config.InfoSchema` referenced `ConfigProvider.Info` (class). The fix was to convert pure-object classes to `Schema.Struct(...).annotate({ identifier: "..." })` — same named SDK `$ref`, no instance requirement. Verified byte-identical `types.gen.ts` vs `dev`.
|
||||
- if an `HttpApi` success schema uses `Schema.Class`, the handler or underlying service needs to return real schema instances rather than plain objects.
|
||||
- internal event payloads can stay anonymous when we want to avoid adding extra named OpenAPI component churn for non-route shapes.
|
||||
- `Schema.Class` emits named `$ref` in OpenAPI — only use it for types that already had `.meta({ ref })` in the old Zod schema **and** when the handler/service returns real instances. For schemas that need a named `$ref` but are populated from plain objects, use `Schema.Struct(...).annotate({ identifier: "..." })` instead. Inner/nested types should stay as `Schema.Struct` to avoid SDK shape changes.
|
||||
- `Schema.Class` emits named `$ref` in OpenAPI — only use it for types that already had `.meta({ ref })` in the old Zod schema. Inner/nested types should stay as `Schema.Struct` to avoid SDK shape changes.
|
||||
|
||||
### Integration
|
||||
|
||||
@@ -404,7 +404,8 @@ Current instance route inventory:
|
||||
- `provider` - `bridged`
|
||||
endpoints: `GET /provider`, `GET /provider/auth`, `POST /provider/:providerID/oauth/authorize`, `POST /provider/:providerID/oauth/callback`
|
||||
- `config` - `bridged` (partial)
|
||||
bridged endpoints: `GET /config`, `GET /config/providers`
|
||||
bridged endpoint: `GET /config/providers`
|
||||
later endpoint: `GET /config`
|
||||
defer `PATCH /config` for now
|
||||
- `project` - `bridged` (partial)
|
||||
bridged endpoints: `GET /project`, `GET /project/current`
|
||||
@@ -430,8 +431,9 @@ Current instance route inventory:
|
||||
Recommended near-term sequence:
|
||||
|
||||
1. `workspace` read endpoints (`GET /experimental/workspace/adaptor`, `GET /experimental/workspace`, `GET /experimental/workspace/status`)
|
||||
2. `file` JSON read endpoints
|
||||
3. `mcp` JSON read endpoints
|
||||
2. `config` full read endpoint (`GET /config`)
|
||||
3. `file` JSON read endpoints
|
||||
4. `mcp` JSON read endpoints
|
||||
|
||||
## Checklist
|
||||
|
||||
@@ -447,8 +449,8 @@ Recommended near-term sequence:
|
||||
- [x] port remaining provider endpoints (`GET /provider`, OAuth mutations)
|
||||
- [x] port `config` providers read endpoint
|
||||
- [x] port `project` read endpoints (`GET /project`, `GET /project/current`)
|
||||
- [x] port `GET /config` full read endpoint
|
||||
- [ ] port `workspace` read endpoints
|
||||
- [ ] port `GET /config` full read endpoint
|
||||
- [ ] port `file` JSON read endpoints
|
||||
- [ ] decide when to remove the flag and make Effect routes the default
|
||||
|
||||
|
||||
@@ -224,6 +224,7 @@ These tools mostly use direct getters for path resolution and repo-relative disp
|
||||
- `src/tool/bash.ts`
|
||||
- `src/tool/edit.ts`
|
||||
- `src/tool/lsp.ts`
|
||||
- `src/tool/multiedit.ts`
|
||||
- `src/tool/plan.ts`
|
||||
- `src/tool/read.ts`
|
||||
- `src/tool/write.ts`
|
||||
|
||||
@@ -97,7 +97,7 @@ creating a parallel schema source of truth.
|
||||
|
||||
## Escape hatches
|
||||
|
||||
The walker in `@/util/effect-zod` exposes two explicit escape hatches for
|
||||
The walker in `@/util/effect-zod` exposes three explicit escape hatches for
|
||||
cases the pure-Schema path cannot express. Each one stays in the codebase
|
||||
only as long as its upstream or local dependency requires it — inline
|
||||
comments document when each can be deleted.
|
||||
@@ -109,7 +109,19 @@ Replaces the entire derivation with a hand-crafted zod schema. Used when:
|
||||
- the target carries external `$ref` metadata (e.g.
|
||||
`config/model-id.ts` points at `https://models.dev/...`)
|
||||
- the target is a zod-only schema that cannot yet be expressed as Schema
|
||||
(e.g. `ConfigAgent.Info`, `Log.Level`)
|
||||
(e.g. `ConfigAgent.Info`, `ConfigPermission.Info`, `Log.Level`)
|
||||
|
||||
### `ZodPreprocess` annotation
|
||||
|
||||
Wraps the derived zod schema with `z.preprocess(fn, inner)`. Used by
|
||||
`config/permission.ts` to inject `__originalKeys` before parsing, because
|
||||
`Schema.StructWithRest` canonicalises output (known fields first, catchall
|
||||
after) and destroys the user's original property order — which permission
|
||||
rule precedence depends on.
|
||||
|
||||
Tracked upstream as `effect:core/wlh553`: "Schema: add preserveInputOrder
|
||||
(or pre-parse hook) for open structs." Once that lands, `ZodPreprocess` and
|
||||
the `__originalKeys` hack can both be deleted.
|
||||
|
||||
### Local `DeepMutable<T>` in `config/config.ts`
|
||||
|
||||
@@ -204,6 +216,7 @@ emitted JSON Schema must stay byte-identical.
|
||||
- [ ] `src/tool/grep.ts`
|
||||
- [ ] `src/tool/invalid.ts`
|
||||
- [ ] `src/tool/lsp.ts`
|
||||
- [ ] `src/tool/multiedit.ts`
|
||||
- [ ] `src/tool/plan.ts`
|
||||
- [ ] `src/tool/question.ts`
|
||||
- [ ] `src/tool/read.ts`
|
||||
|
||||
@@ -46,6 +46,7 @@ These exported tool definitions currently use `Tool.define(...)` in `src/tool`:
|
||||
- [x] `grep.ts`
|
||||
- [x] `invalid.ts`
|
||||
- [x] `lsp.ts`
|
||||
- [x] `multiedit.ts`
|
||||
- [x] `plan.ts`
|
||||
- [x] `question.ts`
|
||||
- [x] `read.ts`
|
||||
@@ -81,6 +82,7 @@ Notable items that are already effectively on the target path and do not need se
|
||||
- `write.ts`
|
||||
- `codesearch.ts`
|
||||
- `websearch.ts`
|
||||
- `multiedit.ts`
|
||||
- `edit.ts`
|
||||
|
||||
## Filesystem notes
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { render, TimeToFirstDraw, useKeyboard, useRenderer, useTerminalDimensions } from "@opentui/solid"
|
||||
import * as Clipboard from "@tui/util/clipboard"
|
||||
import * as Selection from "@tui/util/selection"
|
||||
import * as Terminal from "@tui/util/terminal"
|
||||
import { createCliRenderer, MouseButton, type CliRendererConfig } from "@opentui/core"
|
||||
import { RouteProvider, useRoute } from "@tui/context/route"
|
||||
import {
|
||||
@@ -120,12 +119,6 @@ export function tui(input: {
|
||||
const unguard = win32InstallCtrlCGuard()
|
||||
win32DisableProcessedInput()
|
||||
|
||||
const mode = await Terminal.getTerminalBackgroundColor()
|
||||
|
||||
// Re-clear after getTerminalBackgroundColor() because setRawMode(false)
|
||||
// restores the original console mode, including processed input on Windows.
|
||||
win32DisableProcessedInput()
|
||||
|
||||
const onExit = async () => {
|
||||
unguard?.()
|
||||
resolve()
|
||||
@@ -136,6 +129,7 @@ export function tui(input: {
|
||||
}
|
||||
|
||||
const renderer = await createCliRenderer(rendererConfig(input.config))
|
||||
const mode = (await renderer.waitForThemeMode(1000)) ?? "dark"
|
||||
|
||||
await render(() => {
|
||||
return (
|
||||
|
||||
@@ -9,7 +9,6 @@ import { useSDK } from "../../context/sdk"
|
||||
import { SplitBorder } from "../../component/border"
|
||||
import { useSync } from "../../context/sync"
|
||||
import { useTextareaKeybindings } from "../../component/textarea-keybindings"
|
||||
import { useProject } from "../../context/project"
|
||||
import path from "path"
|
||||
import { LANGUAGE_EXTENSIONS } from "@/lsp/language"
|
||||
import { Keybind } from "@/util"
|
||||
@@ -132,7 +131,6 @@ function TextBody(props: { title: string; description?: string; icon?: string })
|
||||
|
||||
export function PermissionPrompt(props: { request: PermissionRequest }) {
|
||||
const sdk = useSDK()
|
||||
const project = useProject()
|
||||
const sync = useSync()
|
||||
const [store, setStore] = createStore({
|
||||
stage: "permission" as PermissionStage,
|
||||
@@ -189,7 +187,6 @@ export function PermissionPrompt(props: { request: PermissionRequest }) {
|
||||
void sdk.client.permission.reply({
|
||||
reply: "always",
|
||||
requestID: props.request.id,
|
||||
workspace: project.workspace.current(),
|
||||
})
|
||||
}}
|
||||
/>
|
||||
@@ -201,7 +198,6 @@ export function PermissionPrompt(props: { request: PermissionRequest }) {
|
||||
reply: "reject",
|
||||
requestID: props.request.id,
|
||||
message: message || undefined,
|
||||
workspace: project.workspace.current(),
|
||||
})
|
||||
}}
|
||||
onCancel={() => {
|
||||
@@ -454,14 +450,12 @@ export function PermissionPrompt(props: { request: PermissionRequest }) {
|
||||
void sdk.client.permission.reply({
|
||||
reply: "reject",
|
||||
requestID: props.request.id,
|
||||
workspace: project.workspace.current(),
|
||||
})
|
||||
return
|
||||
}
|
||||
void sdk.client.permission.reply({
|
||||
reply: "once",
|
||||
requestID: props.request.id,
|
||||
workspace: project.workspace.current(),
|
||||
})
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -17,12 +17,6 @@ function parse(color: string): RGBA | null {
|
||||
return null
|
||||
}
|
||||
|
||||
function mode(background: RGBA | null): "dark" | "light" {
|
||||
if (!background) return "dark"
|
||||
const luminance = (0.299 * background.r + 0.587 * background.g + 0.114 * background.b) / 255
|
||||
return luminance > 0.5 ? "light" : "dark"
|
||||
}
|
||||
|
||||
/**
|
||||
* Query terminal colors including background, foreground, and palette (0-15).
|
||||
* Uses OSC escape sequences to retrieve actual terminal color values.
|
||||
@@ -100,36 +94,3 @@ export async function colors(): Promise<{
|
||||
}, 1000)
|
||||
})
|
||||
}
|
||||
|
||||
// Keep startup mode detection separate from `colors()`: the TUI boot path only
|
||||
// needs OSC 11 and should resolve on the first background response instead of
|
||||
// waiting on the full palette query used by system theme generation.
|
||||
export async function getTerminalBackgroundColor(): Promise<"dark" | "light"> {
|
||||
if (!process.stdin.isTTY) return "dark"
|
||||
|
||||
return new Promise((resolve) => {
|
||||
let timeout: NodeJS.Timeout
|
||||
|
||||
const cleanup = () => {
|
||||
process.stdin.setRawMode(false)
|
||||
process.stdin.removeListener("data", handler)
|
||||
clearTimeout(timeout)
|
||||
}
|
||||
|
||||
const handler = (data: Buffer) => {
|
||||
const match = data.toString().match(/\x1b]11;([^\x07\x1b]+)/)
|
||||
if (!match) return
|
||||
cleanup()
|
||||
resolve(mode(parse(match[1])))
|
||||
}
|
||||
|
||||
process.stdin.setRawMode(true)
|
||||
process.stdin.on("data", handler)
|
||||
process.stdout.write("\x1b]11;?\x07")
|
||||
|
||||
timeout = setTimeout(() => {
|
||||
cleanup()
|
||||
resolve("dark")
|
||||
}, 1000)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ export * as ConfigAgent from "./agent"
|
||||
import { Schema } from "effect"
|
||||
import z from "zod"
|
||||
import { Bus } from "@/bus"
|
||||
import { zod } from "@/util/effect-zod"
|
||||
import { zod, ZodOverride } from "@/util/effect-zod"
|
||||
import { Log } from "../util"
|
||||
import { NamedError } from "@opencode-ai/shared/util/error"
|
||||
import { Glob } from "@opencode-ai/shared/util/glob"
|
||||
@@ -22,6 +22,12 @@ const Color = Schema.Union([
|
||||
Schema.Literals(["primary", "secondary", "accent", "success", "warning", "error", "info"]),
|
||||
])
|
||||
|
||||
// ConfigPermission.Info is a zod schema (its `.preprocess(...).transform(...)`
|
||||
// shape lives outside the Effect Schema type system), so the walker reaches it
|
||||
// via ZodOverride rather than a pure Schema reference. This preserves the
|
||||
// `$ref: PermissionConfig` emitted in openapi.json.
|
||||
const PermissionRef = Schema.Any.annotate({ [ZodOverride]: ConfigPermission.Info })
|
||||
|
||||
const AgentSchema = Schema.StructWithRest(
|
||||
Schema.Struct({
|
||||
model: Schema.optional(ConfigModelID),
|
||||
@@ -48,7 +54,7 @@ const AgentSchema = Schema.StructWithRest(
|
||||
description: "Maximum number of agentic iterations before forcing text-only response",
|
||||
}),
|
||||
maxSteps: Schema.optional(PositiveInt).annotate({ description: "@deprecated Use 'steps' field instead." }),
|
||||
permission: Schema.optional(ConfigPermission.Info),
|
||||
permission: Schema.optional(PermissionRef),
|
||||
}),
|
||||
[Schema.Record(Schema.String, Schema.Any)],
|
||||
)
|
||||
@@ -87,7 +93,7 @@ const normalize = (agent: z.infer<typeof Info>) => {
|
||||
const permission: ConfigPermission.Info = {}
|
||||
for (const [tool, enabled] of Object.entries(agent.tools ?? {})) {
|
||||
const action = enabled ? "allow" : "deny"
|
||||
if (tool === "write" || tool === "edit" || tool === "patch") {
|
||||
if (tool === "write" || tool === "edit" || tool === "patch" || tool === "multiedit") {
|
||||
permission.edit = action
|
||||
continue
|
||||
}
|
||||
@@ -95,8 +101,7 @@ const normalize = (agent: z.infer<typeof Info>) => {
|
||||
}
|
||||
globalThis.Object.assign(permission, agent.permission)
|
||||
|
||||
const steps = agent.steps ?? agent.maxSteps
|
||||
return { ...agent, options, permission, ...(steps !== undefined ? { steps } : {}) }
|
||||
return { ...agent, options, permission, steps: agent.steps ?? agent.maxSteps }
|
||||
}
|
||||
|
||||
export const Info = zod(AgentSchema).transform(normalize).meta({ ref: "AgentConfig" }) as unknown as z.ZodType<
|
||||
|
||||
@@ -25,7 +25,6 @@ import { Context, Duration, Effect, Exit, Fiber, Layer, Option, Schema } from "e
|
||||
import { EffectFlock } from "@opencode-ai/shared/util/effect-flock"
|
||||
import { InstanceRef } from "@/effect/instance-ref"
|
||||
import { zod, ZodOverride } from "@/util/effect-zod"
|
||||
import { withStatics } from "@/util/schema"
|
||||
import { ConfigAgent } from "./agent"
|
||||
import { ConfigCommand } from "./command"
|
||||
import { ConfigFormatter } from "./formatter"
|
||||
@@ -86,20 +85,13 @@ export type Layout = ConfigLayout.Layout
|
||||
// ZodOverride-annotated Schema.Any. Walker sees the annotation and emits the
|
||||
// exact zod directly, preserving component $refs.
|
||||
const AgentRef = Schema.Any.annotate({ [ZodOverride]: ConfigAgent.Info })
|
||||
const PermissionRef = Schema.Any.annotate({ [ZodOverride]: ConfigPermission.Info })
|
||||
const LogLevelRef = Schema.Any.annotate({ [ZodOverride]: Log.Level })
|
||||
|
||||
const PositiveInt = Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThan(0))
|
||||
const NonNegativeInt = Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0))
|
||||
|
||||
// The Effect Schema is the canonical source of truth. The `.zod` compatibility
|
||||
// surface is derived so existing Hono validators keep working without a parallel
|
||||
// Zod definition.
|
||||
//
|
||||
// The walker emits `z.object({...})` which is non-strict by default. Config
|
||||
// historically uses `.strict()` (additionalProperties: false in openapi.json),
|
||||
// so layer that on after derivation. Re-apply the Config ref afterward
|
||||
// since `.strict()` strips the walker's meta annotation.
|
||||
export const Info = Schema.Struct({
|
||||
const InfoSchema = Schema.Struct({
|
||||
$schema: Schema.optional(Schema.String).annotate({
|
||||
description: "JSON schema reference for configuration validation",
|
||||
}),
|
||||
@@ -197,7 +189,7 @@ export const Info = Schema.Struct({
|
||||
description: "Additional instruction files or patterns to include",
|
||||
}),
|
||||
layout: Schema.optional(ConfigLayout.Layout).annotate({ description: "@deprecated Always uses stretch layout." }),
|
||||
permission: Schema.optional(ConfigPermission.Info),
|
||||
permission: Schema.optional(PermissionRef),
|
||||
tools: Schema.optional(Schema.Record(Schema.String, Schema.Boolean)),
|
||||
enterprise: Schema.optional(
|
||||
Schema.Struct({
|
||||
@@ -243,14 +235,6 @@ export const Info = Schema.Struct({
|
||||
}),
|
||||
),
|
||||
})
|
||||
.annotate({ identifier: "Config" })
|
||||
.pipe(
|
||||
withStatics((s) => ({
|
||||
zod: (zod(s) as unknown as z.ZodObject<any>).strict().meta({ ref: "Config" }) as unknown as z.ZodType<
|
||||
DeepMutable<Schema.Schema.Type<typeof s>>
|
||||
>,
|
||||
})),
|
||||
)
|
||||
|
||||
// Schema.Struct produces readonly types by default, but the service code
|
||||
// below mutates Info objects directly (e.g. `config.mode = ...`). Strip the
|
||||
@@ -272,7 +256,15 @@ type DeepMutable<T> = T extends readonly [unknown, ...unknown[]]
|
||||
? { -readonly [K in keyof T]: DeepMutable<T[K]> }
|
||||
: T
|
||||
|
||||
export type Info = DeepMutable<Schema.Schema.Type<typeof Info>> & {
|
||||
// The walker emits `z.object({...})` which is non-strict by default. Config
|
||||
// historically uses `.strict()` (additionalProperties: false in openapi.json),
|
||||
// so layer that on after derivation. Re-apply the Config ref afterward
|
||||
// since `.strict()` strips the walker's meta annotation.
|
||||
export const Info = (zod(InfoSchema) as unknown as z.ZodObject<any>)
|
||||
.strict()
|
||||
.meta({ ref: "Config" }) as unknown as z.ZodType<DeepMutable<Schema.Schema.Type<typeof InfoSchema>>>
|
||||
|
||||
export type Info = z.output<typeof Info> & {
|
||||
// plugin_origins is derived state, not a persisted config field. It keeps each winning plugin spec together
|
||||
// with the file and scope it came from so later runtime code can make location-sensitive decisions.
|
||||
plugin_origins?: ConfigPlugin.Origin[]
|
||||
@@ -369,7 +361,7 @@ export const layer = Layer.effect(
|
||||
),
|
||||
)
|
||||
const parsed = ConfigParse.jsonc(expanded, source)
|
||||
const data = ConfigParse.schema(Info.zod, normalizeLoadedConfig(parsed, source), source)
|
||||
const data = ConfigParse.schema(Info, normalizeLoadedConfig(parsed, source), source)
|
||||
if (!("path" in options)) return data
|
||||
|
||||
yield* Effect.promise(() => resolveLoadedPlugins(data, options.path))
|
||||
@@ -668,7 +660,7 @@ export const layer = Layer.effect(
|
||||
const perms: Record<string, ConfigPermission.Action> = {}
|
||||
for (const [tool, enabled] of Object.entries(result.tools)) {
|
||||
const action: ConfigPermission.Action = enabled ? "allow" : "deny"
|
||||
if (tool === "write" || tool === "edit" || tool === "patch") {
|
||||
if (tool === "write" || tool === "edit" || tool === "patch" || tool === "multiedit") {
|
||||
perms.edit = action
|
||||
continue
|
||||
}
|
||||
@@ -761,13 +753,13 @@ export const layer = Layer.effect(
|
||||
|
||||
let next: Info
|
||||
if (!file.endsWith(".jsonc")) {
|
||||
const existing = ConfigParse.schema(Info.zod, ConfigParse.jsonc(before, file), file)
|
||||
const existing = ConfigParse.schema(Info, ConfigParse.jsonc(before, file), file)
|
||||
const merged = mergeDeep(writable(existing), writable(config))
|
||||
yield* fs.writeFileString(file, JSON.stringify(merged, null, 2)).pipe(Effect.orDie)
|
||||
next = merged
|
||||
} else {
|
||||
const updated = patchJsonc(before, writable(config))
|
||||
next = ConfigParse.schema(Info.zod, ConfigParse.jsonc(updated, file), file)
|
||||
next = ConfigParse.schema(Info, ConfigParse.jsonc(updated, file), file)
|
||||
yield* fs.writeFileString(file, updated).pipe(Effect.orDie)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Schema } from "effect"
|
||||
import { zod } from "@/util/effect-zod"
|
||||
import { withStatics } from "@/util/schema"
|
||||
|
||||
export const Local = Schema.Struct({
|
||||
export class Local extends Schema.Class<Local>("McpLocalConfig")({
|
||||
type: Schema.Literal("local").annotate({ description: "Type of MCP server connection" }),
|
||||
command: Schema.mutable(Schema.Array(Schema.String)).annotate({
|
||||
description: "Command and arguments to run the MCP server",
|
||||
@@ -16,12 +16,11 @@ export const Local = Schema.Struct({
|
||||
timeout: Schema.optional(Schema.Number).annotate({
|
||||
description: "Timeout in ms for MCP server requests. Defaults to 5000 (5 seconds) if not specified.",
|
||||
}),
|
||||
})
|
||||
.annotate({ identifier: "McpLocalConfig" })
|
||||
.pipe(withStatics((s) => ({ zod: zod(s) })))
|
||||
export type Local = Schema.Schema.Type<typeof Local>
|
||||
}) {
|
||||
static readonly zod = zod(this)
|
||||
}
|
||||
|
||||
export const OAuth = Schema.Struct({
|
||||
export class OAuth extends Schema.Class<OAuth>("McpOAuthConfig")({
|
||||
clientId: Schema.optional(Schema.String).annotate({
|
||||
description: "OAuth client ID. If not provided, dynamic client registration (RFC 7591) will be attempted.",
|
||||
}),
|
||||
@@ -32,12 +31,11 @@ export const OAuth = Schema.Struct({
|
||||
redirectUri: Schema.optional(Schema.String).annotate({
|
||||
description: "OAuth redirect URI (default: http://127.0.0.1:19876/mcp/oauth/callback).",
|
||||
}),
|
||||
})
|
||||
.annotate({ identifier: "McpOAuthConfig" })
|
||||
.pipe(withStatics((s) => ({ zod: zod(s) })))
|
||||
export type OAuth = Schema.Schema.Type<typeof OAuth>
|
||||
}) {
|
||||
static readonly zod = zod(this)
|
||||
}
|
||||
|
||||
export const Remote = Schema.Struct({
|
||||
export class Remote extends Schema.Class<Remote>("McpRemoteConfig")({
|
||||
type: Schema.Literal("remote").annotate({ description: "Type of MCP server connection" }),
|
||||
url: Schema.String.annotate({ description: "URL of the remote MCP server" }),
|
||||
enabled: Schema.optional(Schema.Boolean).annotate({
|
||||
@@ -52,10 +50,9 @@ export const Remote = Schema.Struct({
|
||||
timeout: Schema.optional(Schema.Number).annotate({
|
||||
description: "Timeout in ms for MCP server requests. Defaults to 5000 (5 seconds) if not specified.",
|
||||
}),
|
||||
})
|
||||
.annotate({ identifier: "McpRemoteConfig" })
|
||||
.pipe(withStatics((s) => ({ zod: zod(s) })))
|
||||
export type Remote = Schema.Schema.Type<typeof Remote>
|
||||
}) {
|
||||
static readonly zod = zod(this)
|
||||
}
|
||||
|
||||
export const Info = Schema.Union([Local, Remote])
|
||||
.annotate({ discriminator: "type" })
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export * as ConfigPermission from "./permission"
|
||||
import { Schema, SchemaGetter } from "effect"
|
||||
import { zod } from "@/util/effect-zod"
|
||||
import { Schema } from "effect"
|
||||
import { zod, ZodPreprocess } from "@/util/effect-zod"
|
||||
import { withStatics } from "@/util/schema"
|
||||
|
||||
export const Action = Schema.Literals(["ask", "allow", "deny"])
|
||||
@@ -18,19 +18,21 @@ export const Rule = Schema.Union([Action, Object])
|
||||
.pipe(withStatics((s) => ({ zod: zod(s) })))
|
||||
export type Rule = Schema.Schema.Type<typeof Rule>
|
||||
|
||||
// Known permission keys get explicit types — most are full Rule (either a
|
||||
// single Action or a per-pattern object), but a handful of tools take no
|
||||
// sub-target patterns and are Action-only. Unknown keys fall through the
|
||||
// Record rest signature as Rule.
|
||||
//
|
||||
// StructWithRest canonicalises key order on decode (known first, then rest),
|
||||
// which used to require the `__originalKeys` preprocess hack because
|
||||
// `Permission.fromConfig` depended on the user's insertion order. That
|
||||
// dependency is gone — `fromConfig` now sorts top-level keys so wildcard
|
||||
// permissions come before specifics, making the final precedence
|
||||
// order-independent.
|
||||
const InputObject = Schema.StructWithRest(
|
||||
// Captures the user's original property insertion order before Schema.Struct
|
||||
// canonicalises the object. See the `ZodPreprocess` comment in
|
||||
// `util/effect-zod.ts` for the full rationale — in short: rule precedence is
|
||||
// encoded in JSON key order (`evaluate.ts` uses `findLast`, so later keys win)
|
||||
// and `Schema.StructWithRest` would otherwise drop that order.
|
||||
const permissionPreprocess = (val: unknown) => {
|
||||
if (typeof val === "object" && val !== null && !Array.isArray(val)) {
|
||||
return { __originalKeys: globalThis.Object.keys(val), ...val }
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
const ObjectShape = Schema.StructWithRest(
|
||||
Schema.Struct({
|
||||
__originalKeys: Schema.optional(Schema.mutable(Schema.Array(Schema.String))),
|
||||
read: Schema.optional(Rule),
|
||||
edit: Schema.optional(Rule),
|
||||
glob: Schema.optional(Rule),
|
||||
@@ -51,29 +53,24 @@ const InputObject = Schema.StructWithRest(
|
||||
[Schema.Record(Schema.String, Rule)],
|
||||
)
|
||||
|
||||
// Input the user writes in config: either a single Action (shorthand for "*")
|
||||
// or an object of per-target rules.
|
||||
const InputSchema = Schema.Union([Action, InputObject])
|
||||
const InnerSchema = Schema.Union([ObjectShape, Action]).annotate({
|
||||
[ZodPreprocess]: permissionPreprocess,
|
||||
})
|
||||
|
||||
// Normalise the Action shorthand into `{ "*": action }`. Object inputs pass
|
||||
// through untouched.
|
||||
const normalizeInput = (input: Schema.Schema.Type<typeof InputSchema>): Schema.Schema.Type<typeof InputObject> =>
|
||||
typeof input === "string" ? { "*": input } : input
|
||||
// Post-parse: drop the __originalKeys metadata and rebuild the rule map in the
|
||||
// user's original insertion order. A plain string input (the Action branch of
|
||||
// the union) becomes `{ "*": action }`.
|
||||
const transform = (x: unknown): Record<string, Rule> => {
|
||||
if (typeof x === "string") return { "*": x as Action }
|
||||
const obj = x as { __originalKeys?: string[] } & Record<string, unknown>
|
||||
const { __originalKeys, ...rest } = obj
|
||||
if (!__originalKeys) return rest as Record<string, Rule>
|
||||
const result: Record<string, Rule> = {}
|
||||
for (const key of __originalKeys) {
|
||||
if (key in rest) result[key] = rest[key] as Rule
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export const Info = InputSchema.pipe(
|
||||
Schema.decodeTo(InputObject, {
|
||||
decode: SchemaGetter.transform(normalizeInput),
|
||||
// Not perfectly invertible (we lose whether the user originally typed an
|
||||
// Action shorthand), but the object form is always a valid representation
|
||||
// of the same rules.
|
||||
encode: SchemaGetter.passthrough({ strict: false }),
|
||||
}),
|
||||
)
|
||||
.annotate({ identifier: "PermissionConfig" })
|
||||
.pipe(
|
||||
// Walker already emits the decodeTo transform into the derived zod (see
|
||||
// `encoded()` in effect-zod.ts), so just expose that directly.
|
||||
withStatics((s) => ({ zod: zod(s) })),
|
||||
)
|
||||
type _Info = Schema.Schema.Type<typeof InputObject>
|
||||
export type Info = { -readonly [K in keyof _Info]: _Info[K] }
|
||||
export const Info = zod(InnerSchema).transform(transform).meta({ ref: "PermissionConfig" })
|
||||
export type Info = Record<string, Rule>
|
||||
|
||||
@@ -70,7 +70,7 @@ export const Model = Schema.Struct({
|
||||
),
|
||||
}).pipe(withStatics((s) => ({ zod: zod(s) })))
|
||||
|
||||
export const Info = Schema.Struct({
|
||||
export class Info extends Schema.Class<Info>("ProviderConfig")({
|
||||
api: Schema.optional(Schema.String),
|
||||
name: Schema.optional(Schema.String),
|
||||
env: Schema.optional(Schema.mutable(Schema.Array(Schema.String))),
|
||||
@@ -107,9 +107,8 @@ export const Info = Schema.Struct({
|
||||
),
|
||||
),
|
||||
models: Schema.optional(Schema.Record(Schema.String, Model)),
|
||||
})
|
||||
.annotate({ identifier: "ProviderConfig" })
|
||||
.pipe(withStatics((s) => ({ zod: zod(s) })))
|
||||
export type Info = Schema.Schema.Type<typeof Info>
|
||||
}) {
|
||||
static readonly zod = zod(this)
|
||||
}
|
||||
|
||||
export * as ConfigProvider from "./provider"
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { Schema } from "effect"
|
||||
import { zod } from "@/util/effect-zod"
|
||||
import { withStatics } from "@/util/schema"
|
||||
|
||||
export const Server = Schema.Struct({
|
||||
export class Server extends Schema.Class<Server>("ServerConfig")({
|
||||
port: Schema.optional(Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThan(0))).annotate({
|
||||
description: "Port to listen on",
|
||||
}),
|
||||
@@ -14,9 +13,8 @@ export const Server = Schema.Struct({
|
||||
cors: Schema.optional(Schema.mutable(Schema.Array(Schema.String))).annotate({
|
||||
description: "Additional domains to allow for CORS",
|
||||
}),
|
||||
})
|
||||
.annotate({ identifier: "ServerConfig" })
|
||||
.pipe(withStatics((s) => ({ zod: zod(s) })))
|
||||
export type Server = Schema.Schema.Type<typeof Server>
|
||||
}) {
|
||||
static readonly zod = zod(this)
|
||||
}
|
||||
|
||||
export * as ConfigServer from "./server"
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
import type { Plugin } from "@opencode-ai/plugin"
|
||||
import { rename, writeFile } from "node:fs/promises"
|
||||
import { randomInt } from "node:crypto"
|
||||
import { setTimeout as sleep } from "node:timers/promises"
|
||||
|
||||
const DEV_DATA_FILE = "/tmp/opencode-workspace-dev-data.json"
|
||||
const DEV_DATA_TEMP_FILE = `${DEV_DATA_FILE}.tmp`
|
||||
|
||||
async function waitForHealth(port: number) {
|
||||
const url = `http://127.0.0.1:${port}/global/health`
|
||||
const started = Date.now()
|
||||
|
||||
while (Date.now() - started < 30_000) {
|
||||
try {
|
||||
const response = await fetch(url)
|
||||
if (response.ok) {
|
||||
return
|
||||
}
|
||||
} catch {}
|
||||
|
||||
await sleep(250)
|
||||
}
|
||||
|
||||
throw new Error(`Timed out waiting for debug server health check at ${url}`)
|
||||
}
|
||||
|
||||
let PORT: number | undefined
|
||||
|
||||
async function writeDebugData(port: number, id: string, env: Record<string, string | undefined>) {
|
||||
await writeFile(
|
||||
DEV_DATA_TEMP_FILE,
|
||||
JSON.stringify(
|
||||
{
|
||||
port,
|
||||
id,
|
||||
env,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
)
|
||||
|
||||
await rename(DEV_DATA_TEMP_FILE, DEV_DATA_FILE)
|
||||
}
|
||||
|
||||
export const DebugWorkspacePlugin: Plugin = async ({ experimental_workspace }) => {
|
||||
experimental_workspace.register("debug", {
|
||||
name: "Debug",
|
||||
description: "Create a debugging server",
|
||||
configure(config) {
|
||||
return config
|
||||
},
|
||||
async create(config, env) {
|
||||
const port = randomInt(5000, 9001)
|
||||
PORT = port
|
||||
|
||||
await writeDebugData(port, config.id, env)
|
||||
|
||||
await waitForHealth(port)
|
||||
},
|
||||
async remove(_config) {},
|
||||
target(_config) {
|
||||
return {
|
||||
type: "remote",
|
||||
url: `http://localhost:${PORT!}/`,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
return {}
|
||||
}
|
||||
|
||||
export default DebugWorkspacePlugin
|
||||
@@ -290,18 +290,8 @@ function expand(pattern: string): string {
|
||||
}
|
||||
|
||||
export function fromConfig(permission: ConfigPermission.Info) {
|
||||
// Sort top-level keys so wildcard permissions (`*`, `mcp_*`) come before
|
||||
// specific ones. Combined with `findLast` in evaluate(), this gives the
|
||||
// intuitive semantic "specific tool rules override the `*` fallback"
|
||||
// regardless of the user's JSON key order. Sub-pattern order inside a
|
||||
// single permission key is preserved — only top-level keys are sorted.
|
||||
const entries = Object.entries(permission).sort(([a], [b]) => {
|
||||
const aWild = a.includes("*")
|
||||
const bWild = b.includes("*")
|
||||
return aWild === bWild ? 0 : aWild ? -1 : 1
|
||||
})
|
||||
const ruleset: Ruleset = []
|
||||
for (const [key, value] of entries) {
|
||||
for (const [key, value] of Object.entries(permission)) {
|
||||
if (typeof value === "string") {
|
||||
ruleset.push({ permission: key, action: value, pattern: "*" })
|
||||
continue
|
||||
@@ -317,7 +307,7 @@ export function merge(...rulesets: Ruleset[]): Ruleset {
|
||||
return rulesets.flat()
|
||||
}
|
||||
|
||||
const EDIT_TOOLS = ["edit", "write", "apply_patch"]
|
||||
const EDIT_TOOLS = ["edit", "write", "apply_patch", "multiedit"]
|
||||
|
||||
export function disabled(tools: string[], ruleset: Ruleset): Set<string> {
|
||||
const result = new Set<string>()
|
||||
|
||||
@@ -19,7 +19,6 @@ import { zod } from "@/util/effect-zod"
|
||||
import { iife } from "@/util/iife"
|
||||
import { Global } from "../global"
|
||||
import path from "path"
|
||||
import { pathToFileURL } from "url"
|
||||
import { Effect, Layer, Context, Schema, Types } from "effect"
|
||||
import { EffectBridge } from "@/effect"
|
||||
import { InstanceState } from "@/effect"
|
||||
@@ -1507,10 +1506,7 @@ const layer: Layer.Layer<
|
||||
installedPath = model.api.npm
|
||||
}
|
||||
|
||||
// `installedPath` is a local entry path or an existing `file://` URL. Normalize
|
||||
// only path inputs so Node on Windows accepts the dynamic import.
|
||||
const importSpec = installedPath.startsWith("file://") ? installedPath : pathToFileURL(installedPath).href
|
||||
const mod = await import(importSpec)
|
||||
const mod = await import(installedPath)
|
||||
|
||||
const fn = mod[Object.keys(mod).find((key) => key.startsWith("create"))!]
|
||||
const loaded = fn({
|
||||
|
||||
@@ -147,7 +147,7 @@ export const GlobalRoutes = lazy(() =>
|
||||
description: "Get global config info",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(Config.Info.zod),
|
||||
schema: resolver(Config.Info),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -168,14 +168,14 @@ export const GlobalRoutes = lazy(() =>
|
||||
description: "Successfully updated global config",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(Config.Info.zod),
|
||||
schema: resolver(Config.Info),
|
||||
},
|
||||
},
|
||||
},
|
||||
...errors(400),
|
||||
},
|
||||
}),
|
||||
validator("json", Config.Info.zod),
|
||||
validator("json", Config.Info),
|
||||
async (c) => {
|
||||
const config = c.req.valid("json")
|
||||
const next = await AppRuntime.runPromise(Config.Service.use((cfg) => cfg.updateGlobal(config)))
|
||||
|
||||
@@ -20,7 +20,7 @@ export const ConfigRoutes = lazy(() =>
|
||||
description: "Get config info",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(Config.Info.zod),
|
||||
schema: resolver(Config.Info),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -43,14 +43,14 @@ export const ConfigRoutes = lazy(() =>
|
||||
description: "Successfully updated config",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(Config.Info.zod),
|
||||
schema: resolver(Config.Info),
|
||||
},
|
||||
},
|
||||
},
|
||||
...errors(400),
|
||||
},
|
||||
}),
|
||||
validator("json", Config.Info.zod),
|
||||
validator("json", Config.Info),
|
||||
async (c) =>
|
||||
jsonRequest("ConfigRoutes.update", c, function* () {
|
||||
const config = c.req.valid("json")
|
||||
|
||||
@@ -9,15 +9,6 @@ export const ConfigApi = HttpApi.make("config")
|
||||
.add(
|
||||
HttpApiGroup.make("config")
|
||||
.add(
|
||||
HttpApiEndpoint.get("get", root, {
|
||||
success: Config.Info,
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "config.get",
|
||||
summary: "Get configuration",
|
||||
description: "Retrieve the current OpenCode configuration settings and preferences.",
|
||||
}),
|
||||
),
|
||||
HttpApiEndpoint.get("providers", `${root}/providers`, {
|
||||
success: Provider.ConfigProvidersResult,
|
||||
}).annotateMerge(
|
||||
@@ -45,23 +36,16 @@ export const ConfigApi = HttpApi.make("config")
|
||||
|
||||
export const configHandlers = Layer.unwrap(
|
||||
Effect.gen(function* () {
|
||||
const providerSvc = yield* Provider.Service
|
||||
const configSvc = yield* Config.Service
|
||||
|
||||
const get = Effect.fn("ConfigHttpApi.get")(function* () {
|
||||
return yield* configSvc.get()
|
||||
})
|
||||
const svc = yield* Provider.Service
|
||||
|
||||
const providers = Effect.fn("ConfigHttpApi.providers")(function* () {
|
||||
const providers = yield* providerSvc.list()
|
||||
const providers = yield* svc.list()
|
||||
return {
|
||||
providers: Object.values(providers),
|
||||
default: Provider.defaultModelIDs(providers),
|
||||
}
|
||||
})
|
||||
|
||||
return HttpApiBuilder.group(ConfigApi, "config", (handlers) =>
|
||||
handlers.handle("get", get).handle("providers", providers),
|
||||
)
|
||||
return HttpApiBuilder.group(ConfigApi, "config", (handlers) => handlers.handle("providers", providers))
|
||||
}),
|
||||
).pipe(Layer.provide(Provider.defaultLayer), Layer.provide(Config.defaultLayer))
|
||||
|
||||
@@ -40,7 +40,6 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => {
|
||||
app.post("/question/:requestID/reject", (c) => handler(c.req.raw, context))
|
||||
app.get("/permission", (c) => handler(c.req.raw, context))
|
||||
app.post("/permission/:requestID/reply", (c) => handler(c.req.raw, context))
|
||||
app.get("/config", (c) => handler(c.req.raw, context))
|
||||
app.get("/config/providers", (c) => handler(c.req.raw, context))
|
||||
app.get("/provider", (c) => handler(c.req.raw, context))
|
||||
app.get("/provider/auth", (c) => handler(c.req.raw, context))
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import z from "zod"
|
||||
import { Effect } from "effect"
|
||||
import * as Tool from "./tool"
|
||||
import { EditTool } from "./edit"
|
||||
import DESCRIPTION from "./multiedit.txt"
|
||||
import path from "path"
|
||||
import { Instance } from "../project/instance"
|
||||
|
||||
export const MultiEditTool = Tool.define(
|
||||
"multiedit",
|
||||
Effect.gen(function* () {
|
||||
const editInfo = yield* EditTool
|
||||
const edit = yield* editInfo.init()
|
||||
|
||||
return {
|
||||
description: DESCRIPTION,
|
||||
parameters: z.object({
|
||||
filePath: z.string().describe("The absolute path to the file to modify"),
|
||||
edits: z
|
||||
.array(
|
||||
z.object({
|
||||
filePath: z.string().describe("The absolute path to the file to modify"),
|
||||
oldString: z.string().describe("The text to replace"),
|
||||
newString: z.string().describe("The text to replace it with (must be different from oldString)"),
|
||||
replaceAll: z.boolean().optional().describe("Replace all occurrences of oldString (default false)"),
|
||||
}),
|
||||
)
|
||||
.describe("Array of edit operations to perform sequentially on the file"),
|
||||
}),
|
||||
execute: (
|
||||
params: {
|
||||
filePath: string
|
||||
edits: Array<{ filePath: string; oldString: string; newString: string; replaceAll?: boolean }>
|
||||
},
|
||||
ctx: Tool.Context,
|
||||
) =>
|
||||
Effect.gen(function* () {
|
||||
const results = []
|
||||
for (const [, entry] of params.edits.entries()) {
|
||||
const result = yield* edit.execute(
|
||||
{
|
||||
filePath: params.filePath,
|
||||
oldString: entry.oldString,
|
||||
newString: entry.newString,
|
||||
replaceAll: entry.replaceAll,
|
||||
},
|
||||
ctx,
|
||||
)
|
||||
results.push(result)
|
||||
}
|
||||
return {
|
||||
title: path.relative(Instance.worktree, params.filePath),
|
||||
metadata: {
|
||||
results: results.map((r) => r.metadata),
|
||||
},
|
||||
output: results.at(-1)!.output,
|
||||
}
|
||||
}),
|
||||
}
|
||||
}),
|
||||
)
|
||||
@@ -0,0 +1,41 @@
|
||||
This is a tool for making multiple edits to a single file in one operation. It is built on top of the Edit tool and allows you to perform multiple find-and-replace operations efficiently. Prefer this tool over the Edit tool when you need to make multiple edits to the same file.
|
||||
|
||||
Before using this tool:
|
||||
|
||||
1. Use the Read tool to understand the file's contents and context
|
||||
2. Verify the directory path is correct
|
||||
|
||||
To make multiple file edits, provide the following:
|
||||
1. file_path: The absolute path to the file to modify (must be absolute, not relative)
|
||||
2. edits: An array of edit operations to perform, where each edit contains:
|
||||
- oldString: The text to replace (must match the file contents exactly, including all whitespace and indentation)
|
||||
- newString: The edited text to replace the oldString
|
||||
- replaceAll: Replace all occurrences of oldString. This parameter is optional and defaults to false.
|
||||
|
||||
IMPORTANT:
|
||||
- All edits are applied in sequence, in the order they are provided
|
||||
- Each edit operates on the result of the previous edit
|
||||
- All edits must be valid for the operation to succeed - if any edit fails, none will be applied
|
||||
- This tool is ideal when you need to make several changes to different parts of the same file
|
||||
|
||||
CRITICAL REQUIREMENTS:
|
||||
1. All edits follow the same requirements as the single Edit tool
|
||||
2. The edits are atomic - either all succeed or none are applied
|
||||
3. Plan your edits carefully to avoid conflicts between sequential operations
|
||||
|
||||
WARNING:
|
||||
- The tool will fail if edits.oldString doesn't match the file contents exactly (including whitespace)
|
||||
- The tool will fail if edits.oldString and edits.newString are the same
|
||||
- Since edits are applied in sequence, ensure that earlier edits don't affect the text that later edits are trying to find
|
||||
|
||||
When making edits:
|
||||
- Ensure all edits result in idiomatic, correct code
|
||||
- Do not leave the code in a broken state
|
||||
- Always use absolute file paths (starting with /)
|
||||
- Only use emojis if the user explicitly requests it. Avoid adding emojis to files unless asked.
|
||||
- Use replaceAll for replacing and renaming strings across the file. This parameter is useful if you want to rename a variable for instance.
|
||||
|
||||
If you want to create a new file, use:
|
||||
- A new file path, including dir name if needed
|
||||
- First edit: empty oldString and the new file's contents as newString
|
||||
- Subsequent edits: normal edit operations on the created content
|
||||
@@ -157,9 +157,9 @@ export const layer: Layer.Layer<
|
||||
if (matches.length) yield* config.waitForDependencies()
|
||||
for (const match of matches) {
|
||||
const namespace = path.basename(match, path.extname(match))
|
||||
// `match` is an absolute filesystem path from `Glob.scanSync(..., { absolute: true })`.
|
||||
// Import it as `file://` so Node on Windows accepts the dynamic import.
|
||||
const mod = yield* Effect.promise(() => import(pathToFileURL(match).href))
|
||||
const mod = yield* Effect.promise(
|
||||
() => import(process.platform === "win32" ? match : pathToFileURL(match).href),
|
||||
)
|
||||
for (const [id, def] of Object.entries<ToolDefinition>(mod)) {
|
||||
custom.push(fromPlugin(id === "default" ? namespace : `${namespace}_${id}`, def))
|
||||
}
|
||||
|
||||
@@ -8,6 +8,43 @@ import z from "zod"
|
||||
*/
|
||||
export const ZodOverride: unique symbol = Symbol.for("effect-zod/override")
|
||||
|
||||
/**
|
||||
* Annotation key for a pre-parse transform that runs on the raw input before
|
||||
* the derived Zod schema validates it. The walker emits
|
||||
* `z.preprocess(fn, inner)` when this annotation is present.
|
||||
*
|
||||
* Models zod's `z.preprocess(fn, schema)` pattern — useful when the schema
|
||||
* needs to inspect the user's raw input (e.g. to capture insertion order)
|
||||
* before `Schema.Struct` canonicalises the object.
|
||||
*
|
||||
* TODO: This exists to paper over a missing Effect Schema feature. The
|
||||
* parser canonicalises open struct output (known fields first in
|
||||
* declaration order, then catchall fields) before any user-defined
|
||||
* transform sees the value, and there is no pre-parse hook — so the
|
||||
* user's original property insertion order is gone by the time
|
||||
* `Schema.decodeTo` or `middlewareDecoding` runs.
|
||||
*
|
||||
* That canonicalisation is a reasonable default, but `config/permission.ts`
|
||||
* encodes rule precedence in the user's JSON key order (`evaluate.ts`
|
||||
* uses `findLast`, so later entries win), which the canonicalisation
|
||||
* silently destroys.
|
||||
*
|
||||
* The cleanest upstream fix would be either:
|
||||
*
|
||||
* 1. A `preserveInputOrder` option on `Schema.Struct` /
|
||||
* `Schema.StructWithRest` that keeps the input's insertion order in
|
||||
* the parsed object (opt-in; canonical order stays default).
|
||||
* 2. A generic pre-parse hook (`Schema.preprocess(schema, fn)` or a
|
||||
* transformation whose decode receives the raw `unknown`).
|
||||
*
|
||||
* Either of those would let us delete `ZodPreprocess` and the
|
||||
* `__originalKeys` hack. Alternatively, the permission model could move
|
||||
* to specificity-based precedence (exact keys beat wildcards) or an
|
||||
* explicit ordered array of rules, which removes the ordering
|
||||
* dependency at the data-model level.
|
||||
*/
|
||||
export const ZodPreprocess: unique symbol = Symbol.for("effect-zod/preprocess")
|
||||
|
||||
// AST nodes are immutable and frequently shared across schemas (e.g. a single
|
||||
// Schema.Class embedded in multiple parents). Memoizing by node identity
|
||||
// avoids rebuilding equivalent Zod subtrees and keeps derived children stable
|
||||
@@ -48,9 +85,11 @@ function walkUncached(ast: SchemaAST.AST): z.ZodTypeAny {
|
||||
const hasTransform = hasEncoding && !(SchemaAST.isOptional(ast) && extractDefault(ast) !== undefined)
|
||||
const base = hasTransform ? encoded(ast) : body(ast)
|
||||
const checked = ast.checks?.length ? applyChecks(base, ast.checks, ast) : base
|
||||
const preprocess = (ast.annotations as { [ZodPreprocess]?: (val: unknown) => unknown } | undefined)?.[ZodPreprocess]
|
||||
const out = preprocess ? z.preprocess(preprocess, checked) : checked
|
||||
const desc = SchemaAST.resolveDescription(ast)
|
||||
const ref = SchemaAST.resolveIdentifier(ast)
|
||||
const described = desc ? checked.describe(desc) : checked
|
||||
const described = desc ? out.describe(desc) : out
|
||||
return ref ? described.meta({ ref }) : described
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user