Merge branch 'dev' into opentui

This commit is contained in:
Dax Raad 2025-10-29 19:43:46 -04:00
commit 133daad8f6
30 changed files with 1058 additions and 63 deletions

View file

@ -17,6 +17,7 @@ If you are unsure if a PR would be accepted, feel free to ask a maintainer or lo
- [`help wanted`](https://github.com/sst/opencode/issues?q=is%3Aissue%20state%3Aopen%20label%3Ahelp-wanted)
- [`good first issue`](https://github.com/sst/opencode/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22)
- [`bug`](https://github.com/sst/opencode/issues?q=is%3Aissue%20state%3Aopen%20label%3Abug)
- [`perf`](https://github.com/sst/opencode/issues?q=is%3Aopen%20is%3Aissue%20label%3A%22perf%22)
> [!NOTE]
> PRs that ignore these guardrails will likely be closed.

View file

@ -39,7 +39,7 @@
},
"packages/console/core": {
"name": "@opencode-ai/console-core",
"version": "0.15.24",
"version": "0.15.28",
"dependencies": {
"@aws-sdk/client-sts": "3.782.0",
"@jsx-email/render": "1.1.1",
@ -66,7 +66,7 @@
},
"packages/console/function": {
"name": "@opencode-ai/console-function",
"version": "0.15.24",
"version": "0.15.28",
"dependencies": {
"@ai-sdk/anthropic": "2.0.0",
"@ai-sdk/openai": "2.0.2",
@ -90,7 +90,7 @@
},
"packages/console/mail": {
"name": "@opencode-ai/console-mail",
"version": "0.15.24",
"version": "0.15.28",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",
@ -111,7 +111,7 @@
},
"packages/desktop": {
"name": "@opencode-ai/desktop",
"version": "0.15.24",
"version": "0.15.28",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*",
@ -152,7 +152,7 @@
},
"packages/function": {
"name": "@opencode-ai/function",
"version": "0.15.24",
"version": "0.15.28",
"dependencies": {
"@octokit/auth-app": "8.0.1",
"@octokit/rest": "22.0.0",
@ -168,7 +168,7 @@
},
"packages/opencode": {
"name": "opencode",
"version": "0.15.24",
"version": "0.15.28",
"bin": {
"opencode": "./bin/opencode",
},
@ -228,6 +228,10 @@
"@babel/core": "7.28.4",
"@octokit/webhooks-types": "7.6.1",
"@opencode-ai/script": "workspace:*",
"@parcel/watcher-darwin-arm64": "2.5.1",
"@parcel/watcher-darwin-x64": "2.5.1",
"@parcel/watcher-linux-arm64-glibc": "2.5.1",
"@parcel/watcher-linux-x64-glibc": "2.5.1",
"@parcel/watcher-win32-x64": "2.5.1",
"@standard-schema/spec": "1.0.0",
"@tsconfig/bun": "catalog:",
@ -244,7 +248,7 @@
},
"packages/plugin": {
"name": "@opencode-ai/plugin",
"version": "0.15.24",
"version": "0.15.28",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"zod": "catalog:",
@ -264,7 +268,7 @@
},
"packages/sdk/js": {
"name": "@opencode-ai/sdk",
"version": "0.15.24",
"version": "0.15.28",
"devDependencies": {
"@hey-api/openapi-ts": "0.81.0",
"@tsconfig/node22": "catalog:",
@ -275,7 +279,7 @@
},
"packages/slack": {
"name": "@opencode-ai/slack",
"version": "0.15.24",
"version": "0.15.28",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"@slack/bolt": "^3.17.1",
@ -288,7 +292,7 @@
},
"packages/ui": {
"name": "@opencode-ai/ui",
"version": "0.15.24",
"version": "0.15.28",
"dependencies": {
"@kobalte/core": "catalog:",
"@pierre/precision-diffs": "catalog:",
@ -311,7 +315,7 @@
},
"packages/web": {
"name": "@opencode-ai/web",
"version": "0.15.24",
"version": "0.15.28",
"dependencies": {
"@astrojs/cloudflare": "12.6.3",
"@astrojs/markdown-remark": "6.3.1",

View file

@ -7,7 +7,7 @@
"dev:remote": "VITE_AUTH_URL=https://auth.dev.opencode.ai bun sst shell --stage=dev bun dev",
"build": "vinxi build && ../../opencode/script/schema.ts ./.output/public/config.json",
"start": "vinxi start",
"version": "0.15.24"
"version": "0.15.28"
},
"dependencies": {
"@ibm/plex": "6.4.1",

View file

@ -36,6 +36,9 @@ export function Header(props: { zen?: boolean }) {
<li>
<a href="/docs">Docs</a>
</li>
<li>
<A href="/enterprise">Enterprise</A>
</li>
<li>
<Switch>
<Match when={props.zen}>
@ -107,6 +110,9 @@ export function Header(props: { zen?: boolean }) {
<li>
<a href="/docs">Docs</a>
</li>
<li>
<A href="/enterprise">Enterprise</A>
</li>
<li>
<Switch>
<Match when={props.zen}>

View file

@ -0,0 +1,50 @@
import type { APIEvent } from "@solidjs/start/server"
import { AWS } from "@opencode-ai/console-core/aws.js"
interface EnterpriseFormData {
name: string
role: string
email: string
message: string
}
export async function POST(event: APIEvent) {
try {
const body = (await event.request.json()) as EnterpriseFormData
// Validate required fields
if (!body.name || !body.role || !body.email || !body.message) {
return Response.json({ error: "All fields are required" }, { status: 400 })
}
// Validate email format
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
if (!emailRegex.test(body.email)) {
return Response.json({ error: "Invalid email format" }, { status: 400 })
}
// Create email content
const emailContent = `
New Enterprise Inquiry
Name: ${body.name}
Role: ${body.role}
Email: ${body.email}
Message:
${body.message}
`.trim()
// Send email using AWS SES
await AWS.sendEmail({
to: "enterprise@opencode.ai",
subject: `Enterprise Inquiry from ${body.name}`,
body: emailContent,
})
return Response.json({ success: true, message: "Form submitted successfully" }, { status: 200 })
} catch (error) {
console.error("Error processing enterprise form:", error)
return Response.json({ error: "Internal server error" }, { status: 500 })
}
}

View file

@ -0,0 +1,544 @@
::selection {
background: var(--color-background-interactive);
color: var(--color-text-strong);
@media (prefers-color-scheme: dark) {
background: var(--color-background-interactive);
color: var(--color-text-inverted);
}
}
[data-page="enterprise"] {
--color-background: hsl(0, 20%, 99%);
--color-background-weak: hsl(0, 8%, 97%);
--color-background-weak-hover: hsl(0, 8%, 94%);
--color-background-strong: hsl(0, 5%, 12%);
--color-background-strong-hover: hsl(0, 5%, 18%);
--color-background-interactive: hsl(62, 84%, 88%);
--color-background-interactive-weaker: hsl(64, 74%, 95%);
--color-text: hsl(0, 1%, 39%);
--color-text-weak: hsl(0, 1%, 60%);
--color-text-weaker: hsl(30, 2%, 81%);
--color-text-strong: hsl(0, 5%, 12%);
--color-text-inverted: hsl(0, 20%, 99%);
--color-text-success: hsl(119, 100%, 35%);
--color-border: hsl(30, 2%, 81%);
--color-border-weak: hsl(0, 1%, 85%);
--color-icon: hsl(0, 1%, 55%);
--color-success: hsl(142, 76%, 36%);
background: var(--color-background);
font-family: var(--font-mono);
color: var(--color-text);
padding-bottom: 5rem;
@media (prefers-color-scheme: dark) {
--color-background: hsl(0, 9%, 7%);
--color-background-weak: hsl(0, 6%, 10%);
--color-background-weak-hover: hsl(0, 6%, 15%);
--color-background-strong: hsl(0, 15%, 94%);
--color-background-strong-hover: hsl(0, 15%, 97%);
--color-background-interactive: hsl(62, 100%, 90%);
--color-background-interactive-weaker: hsl(60, 20%, 8%);
--color-text: hsl(0, 4%, 71%);
--color-text-weak: hsl(0, 2%, 49%);
--color-text-weaker: hsl(0, 3%, 28%);
--color-text-strong: hsl(0, 15%, 94%);
--color-text-inverted: hsl(0, 9%, 7%);
--color-text-success: hsl(119, 60%, 72%);
--color-border: hsl(0, 3%, 28%);
--color-border-weak: hsl(0, 4%, 23%);
--color-icon: hsl(10, 3%, 43%);
--color-success: hsl(142, 76%, 46%);
}
/* Header and Footer styles - copied from index.css */
[data-component="top"] {
padding: 24px 5rem;
height: 80px;
position: sticky;
top: 0;
display: flex;
justify-content: space-between;
align-items: center;
background: var(--color-background);
border-bottom: 1px solid var(--color-border-weak);
z-index: 10;
@media (max-width: 60rem) {
padding: 24px 1.5rem;
}
img {
height: 34px;
width: auto;
}
[data-component="nav-desktop"] {
ul {
display: flex;
justify-content: space-between;
gap: 48px;
li {
display: inline-block;
a {
text-decoration: none;
span {
color: var(--color-text-weak);
}
}
a:hover {
text-decoration: underline;
text-underline-offset: 2px;
text-decoration-thickness: 1px;
}
}
}
@media (max-width: 40rem) {
display: none;
}
}
[data-component="nav-mobile"] {
button > svg {
color: var(--color-icon);
}
}
[data-component="nav-mobile-toggle"] {
border: none;
background: none;
outline: none;
height: 40px;
width: 40px;
cursor: pointer;
margin-right: -8px;
}
[data-component="nav-mobile-toggle"]:hover {
background: var(--color-background-weak);
}
[data-component="nav-mobile"] {
display: none;
@media (max-width: 40rem) {
display: block;
[data-component="nav-mobile-icon"] {
cursor: pointer;
height: 40px;
width: 40px;
display: flex;
align-items: center;
justify-content: center;
}
[data-component="nav-mobile-menu-list"] {
position: fixed;
background: var(--color-background);
top: 80px;
left: 0;
right: 0;
height: 100vh;
ul {
list-style: none;
padding: 20px 0;
li {
a {
text-decoration: none;
padding: 20px;
display: block;
span {
color: var(--color-text-weak);
}
}
a:hover {
background: var(--color-background-weak);
}
}
}
}
}
}
[data-slot="logo dark"] {
display: none;
}
@media (prefers-color-scheme: dark) {
[data-slot="logo light"] {
display: none;
}
[data-slot="logo dark"] {
display: block;
}
}
}
[data-component="footer"] {
border-top: 1px solid var(--color-border-weak);
display: flex;
flex-direction: row;
@media (max-width: 65rem) {
border-bottom: 1px solid var(--color-border-weak);
}
[data-slot="cell"] {
flex: 1;
text-align: center;
a {
text-decoration: none;
padding: 2rem 0;
width: 100%;
display: block;
span {
color: var(--color-text-weak);
@media (max-width: 40rem) {
display: none;
}
}
}
a:hover {
background: var(--color-background-weak);
text-decoration: underline;
text-underline-offset: 2px;
text-decoration-thickness: 1px;
}
}
[data-slot="cell"] + [data-slot="cell"] {
border-left: 1px solid var(--color-border-weak);
@media (max-width: 40rem) {
border-left: none;
}
}
/* Mobile: third column on its own row */
@media (max-width: 25rem) {
flex-wrap: wrap;
[data-slot="cell"] {
flex: 1 0 100%;
border-left: none;
border-top: 1px solid var(--color-border-weak);
}
[data-slot="cell"]:nth-child(1) {
border-top: none;
}
}
}
[data-component="container"] {
max-width: 67.5rem;
margin: 0 auto;
border: 1px solid var(--color-border-weak);
border-top: none;
@media (max-width: 65rem) {
border: none;
}
}
[data-component="content"] {
}
[data-component="enterprise-content"] {
padding: 4rem 0;
@media (max-width: 60rem) {
padding: 2rem 0;
}
}
[data-component="enterprise-columns"] {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 4rem;
padding: 4rem 5rem;
@media (max-width: 80rem) {
gap: 3rem;
}
@media (max-width: 60rem) {
grid-template-columns: 1fr;
gap: 3rem;
padding: 2rem 1.5rem;
}
}
[data-component="enterprise-column-1"] {
h2 {
font-size: 1.5rem;
font-weight: 500;
color: var(--color-text-strong);
margin-bottom: 1rem;
}
h3 {
font-size: 1.25rem;
font-weight: 500;
color: var(--color-text-strong);
margin: 2rem 0 1rem 0;
}
p {
line-height: 1.6;
margin-bottom: 1.5rem;
color: var(--color-text);
}
[data-component="testimonial"] {
margin-top: 4rem;
font-weight: 500;
color: var(--color-text-strong);
[data-component="quotation"] {
svg {
margin-bottom: 1rem;
opacity: 20%;
}
}
[data-component="testimonial-logo"] {
svg {
margin-top: 1.5rem;
}
}
}
}
[data-component="enterprise-column-2"] {
[data-component="enterprise-form"] {
padding: 0;
h2 {
font-size: 1.5rem;
font-weight: 500;
color: var(--color-text-strong);
margin-bottom: 1.5rem;
}
[data-component="form-group"] {
margin-bottom: 1.5rem;
label {
display: block;
font-weight: 500;
color: var(--color-text-weak);
margin-bottom: 0.5rem;
font-size: 0.875rem;
}
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus,
input:-webkit-autofill:active {
transition: background-color 5000000s ease-in-out 0s;
}
input:-webkit-autofill {
-webkit-text-fill-color: var(--color-text-strong) !important;
}
input:-moz-autofill {
-moz-text-fill-color: var(--color-text-strong) !important;
}
input,
textarea {
width: 100%;
padding: 0.75rem;
border: 1px solid var(--color-border-weak);
border-radius: 4px;
background: var(--color-background-weak);
color: var(--color-text-strong);
font-family: inherit;
&::placeholder {
color: var(--color-text-weak);
}
&:focus {
background: var(--color-background-interactive-weaker);
outline: none;
border: none;
color: var(--color-text-strong);
border: 1px solid var(--color-background-strong);
box-shadow: 0 0 0 3px var(--color-background-interactive);
@media (prefers-color-scheme: dark) {
box-shadow: none;
border: 1px solid var(--color-background-interactive)
}
}
}
textarea {
resize: vertical;
min-height: 120px;
}
}
[data-component="submit-button"] {
padding: 0.5rem 1.5rem;
background: var(--color-background-strong);
color: var(--color-text-inverted);
border: none;
border-radius: 4px;
font-weight: 500;
cursor: pointer;
transition: background-color 0.2s ease;
&:hover:not(:disabled) {
background: var(--color-background-strong-hover);
}
&:disabled {
opacity: 0.6;
cursor: not-allowed;
}
}
[data-component="success-message"] {
margin-top: 1rem;
padding: 1rem 0;
color: var(--color-text-success);
text-align: left;
}
}
}
[data-component="faq"] {
border-top: 1px solid var(--color-border-weak);
padding: 4rem 5rem;
@media (max-width: 60rem) {
padding: 2rem 1.5rem;
}
[data-slot="section-title"] {
margin-bottom: 24px;
h3 {
font-size: 16px;
font-weight: 500;
color: var(--color-text-strong);
margin-bottom: 12px;
}
p {
margin-bottom: 12px;
color: var(--color-text);
}
}
ul {
padding: 0;
li {
list-style: none;
margin-bottom: 24px;
line-height: 200%;
@media (max-width: 60rem) {
line-height: 180%;
}
}
}
[data-slot="faq-question"] {
display: flex;
gap: 16px;
margin-bottom: 8px;
color: var(--color-text-strong);
font-weight: 500;
cursor: pointer;
background: none;
border: none;
padding: 0;
[data-slot="faq-icon-plus"] {
flex-shrink: 0;
color: var(--color-text-weak);
margin-top: 2px;
[data-closed] & {
display: block;
}
[data-expanded] & {
display: none;
}
}
[data-slot="faq-icon-minus"] {
flex-shrink: 0;
color: var(--color-text-weak);
margin-top: 2px;
[data-closed] & {
display: none;
}
[data-expanded] & {
display: block;
}
}
[data-slot="faq-question-text"] {
flex-grow: 1;
text-align: left;
}
}
[data-slot="faq-answer"] {
margin-left: 40px;
margin-bottom: 32px;
color: var(--color-text);
}
}
[data-component="legal"] {
color: var(--color-text-weak);
text-align: center;
padding: 2rem 5rem;
@media (max-width: 60rem) {
padding: 2rem 1.5rem;
}
a {
color: var(--color-text-weak);
text-decoration: none;
}
}
a {
color: var(--color-text-strong);
text-decoration: underline;
text-underline-offset: 2px;
text-decoration-thickness: 1px;
&:hover {
text-decoration-thickness: 2px;
}
}
}

View file

@ -0,0 +1,254 @@
import "./index.css"
import { Title, Meta } from "@solidjs/meta"
import { createSignal } from "solid-js"
import { Header } from "~/component/header"
import { Footer } from "~/component/footer"
import { Legal } from "~/component/legal"
import { Faq } from "~/component/faq"
export default function Enterprise() {
const [formData, setFormData] = createSignal({
name: "",
role: "",
email: "",
message: "",
})
const [isSubmitting, setIsSubmitting] = createSignal(false)
const [showSuccess, setShowSuccess] = createSignal(false)
const handleInputChange = (field: string) => (e: Event) => {
const target = e.target as HTMLInputElement | HTMLTextAreaElement
setFormData((prev) => ({ ...prev, [field]: target.value }))
}
const handleSubmit = async (e: Event) => {
e.preventDefault()
setIsSubmitting(true)
try {
const response = await fetch("/api/enterprise", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(formData()),
})
if (response.ok) {
setShowSuccess(true)
setFormData({
name: "",
role: "",
email: "",
message: "",
})
setTimeout(() => setShowSuccess(false), 5000)
}
} catch (error) {
console.error("Failed to submit form:", error)
} finally {
setIsSubmitting(false)
}
}
return (
<main data-page="enterprise">
<Title>OpenCode | Enterprise solutions for your organisation</Title>
<Meta name="description" content="Contact OpenCode for enterprise solutions" />
<div data-component="container">
<Header />
<div data-component="content">
<section data-component="enterprise-content">
<div data-component="enterprise-columns">
<div data-component="enterprise-column-1">
<h2>Your code is yours</h2>
<p>
OpenCode operates securely inside your organization with no data or context stored and no licensing restrictions or ownership claims. Start a trial with your team today, then scale confidently with enterprise-grade features including SSO, private registries, and self-hosting.
</p>
<p>
Let us know and how we can help.
</p>
<div data-component="testimonial">
<div data-component="quotation">
<svg width="20" height="17" viewBox="0 0 20 17" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M19.4118 0L16.5882 9.20833H20V17H12.2353V10.0938L16 0H19.4118ZM7.17647 0L4.35294 9.20833H7.76471V17H0V10.0938L3.76471 0H7.17647Z"
fill="currentColor" />
</svg>
</div>
Thanks to OpenCode, we found a way to create software to track all our assets even the imaginary ones.
<div data-component="testimonial-logo">
<svg width="80" height="79" viewBox="0 0 80 79" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M0 39.3087L10.0579 29.251L15.6862 34.7868L13.7488 36.7248L10.3345 33.2186L8.48897 35.0639L11.8111 38.4781L9.96557 40.4156L6.55181 37.0018L4.06028 39.4928L7.56674 42.9991L5.62884 44.845L0 39.3087Z"
fill="#0083C6" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M17.7182 36.8164L20.2094 39.4003L16.6108 46.9666L22.2393 41.3374L24.3615 43.46L14.2118 53.5179L11.9047 51.1187L15.4112 43.3677L9.78254 49.0888L7.66016 46.9666L17.7182 36.8164Z"
fill="#0083C6" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M42.8139 61.915L45.3055 64.4064L41.6145 71.9731L47.243 66.3441L49.3652 68.4663L39.3077 78.5244L36.9088 76.1252L40.5072 68.374L34.7866 74.0953L32.6641 71.9731L42.8139 61.915Z"
fill="#0083C6" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M16.4258 55.7324L26.4833 45.582L28.6061 47.7042C31.0049 50.1034 32.3892 51.9497 30.1746 54.1642C28.7902 55.548 27.6831 56.0094 26.1145 54.9016L26.0222 54.994C27.2218 56.1941 26.9448 57.1162 25.4688 58.5931L23.9 60.1615C23.4383 60.6232 22.8847 61.2693 22.7927 62.0067L20.6705 59.8845C20.7625 59.146 21.3161 58.5008 21.778 58.1316L23.5307 56.3788C24.269 55.6403 23.715 54.2555 23.254 53.8872L22.8847 53.4256L18.548 57.7623L16.4258 55.7324ZM24.3611 51.9495C25.4689 53.0563 26.4833 53.3332 27.4984 52.3178C28.5134 51.3957 28.2367 50.3802 27.1295 49.1812L24.3611 51.9495Z"
fill="#0083C6" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M33.4952 66.9899C31.096 69.3891 28.8815 68.4659 27.4047 66.9899C26.021 65.6062 25.0978 63.3907 27.4972 60.9003L31.8336 56.6548C34.2333 54.2556 36.4478 55.0864 37.9241 56.5635C39.308 58.0396 40.2311 60.2541 37.8315 62.6531L33.4952 66.9899ZM29.0659 63.5752C28.6048 64.0369 28.6048 64.7753 29.1583 65.3292C29.6196 65.8821 30.4502 65.7897 30.8194 65.4215L36.2633 59.9769C36.7246 59.6076 36.7246 58.7779 36.171 58.3164C35.7097 57.7626 34.8791 57.7626 34.5101 58.2241L29.0659 63.5752Z"
fill="#0083C6" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M78.5267 39.308L68.2845 29.0654L47.5231 49.735L49.6453 51.8572L68.2845 33.2179L74.3746 39.308L47.2461 66.3435L49.3683 68.4657L78.5267 39.308Z"
fill="#0083C6" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M49.6443 51.8577L43.3695 45.4902L64.0386 24.8215L53.7969 14.4873L33.0352 35.2482L35.1574 37.3705L53.7969 18.7315L59.7947 24.8215L39.1251 45.4902L47.5221 53.9799L49.6443 51.8577Z"
fill="#2D9C5C" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M35.1564 37.3706L28.7896 31.0038L49.5515 10.3347L39.3088 0L10.0586 29.2507L12.1804 31.2804L39.3088 4.24476L45.3066 10.3347L24.6377 31.0038L33.0342 39.4008L35.1564 37.3706Z"
fill="#E92A35" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M77.2332 52.4105C76.0336 52.4105 75.111 51.4884 75.111 50.196C75.111 48.9046 76.0336 47.9814 77.2332 47.9814C78.3405 47.9814 79.263 48.9046 79.263 50.196C79.263 51.4884 78.3405 52.4105 77.2332 52.4105ZM77.2332 52.9643C78.7098 52.9643 80.0015 51.6729 80.0015 50.196C80.0015 48.6276 78.7096 47.4287 77.2332 47.4287C75.6644 47.4287 74.4648 48.6278 74.4648 50.196C74.4647 51.6731 75.6643 52.9643 77.2332 52.9643ZM76.1259 51.7653H76.6797V50.3804H77.0485L77.8788 51.7653H78.4332L77.6023 50.3804C78.1558 50.2881 78.4332 50.0122 78.4332 49.5507C78.4332 48.9046 78.0633 48.6276 77.3253 48.6276H76.1257V51.7653H76.1259ZM76.6797 49.0892H77.2332C77.5102 49.0892 77.8788 49.0892 77.8788 49.4586C77.8788 49.9202 77.6023 49.9202 77.2332 49.9202H76.6797V49.0892Z"
fill="#0083C6" />
</svg>
</div>
</div>
</div>
<div data-component="enterprise-column-2">
<div data-component="enterprise-form">
<form onSubmit={handleSubmit}>
<div data-component="form-group">
<label for="name">Full name</label>
<input
id="name"
type="text"
required
value={formData().name}
onInput={handleInputChange('name')}
placeholder="Jeff Bezos"
/>
</div>
<div data-component="form-group">
<label for="role">Role</label>
<input
id="role"
type="text"
required
value={formData().role}
onInput={handleInputChange('role')}
placeholder="Executive Chairman"
/>
</div>
<div data-component="form-group">
<label for="email">Company email</label>
<input
id="email"
type="email"
required
value={formData().email}
onInput={handleInputChange('email')}
placeholder="jeff@amazon.com"
/>
</div>
<div data-component="form-group">
<label for="message">What problem are you trying to
solve?</label>
<textarea
id="message"
required
rows={5}
value={formData().message}
onInput={handleInputChange('message')}
placeholder="We need help with..."
/>
</div>
<button type="submit" disabled={isSubmitting()}
data-component="submit-button">
{isSubmitting() ? 'Sending...' : 'Send'}
</button>
</form>
{showSuccess() && (
<div data-component="success-message">
Message sent, we'll be in touch soon.
</div>
)}
</div>
</div>
</div>
</section>
<section data-component="faq">
<div data-slot="section-title">
<h3>FAQ</h3>
</div>
<ul>
<li>
<Faq question="Does Opencode store our code or context data?">
No. OpenCode never stores your code or context data. All
processing happens locally or directly with your AI provider.
</Faq>
</li>
<li>
<Faq question="Who owns the code generated with OpenCode?">
You do. All code produced is yours, with no licensing
restrictions or ownership claims.
</Faq>
</li>
<li>
<Faq
question="How can we trial OpenCode inside our organization?">
Simply install and run an internal trial with your team. Since
OpenCode doesnt store any data, your developers can get
started right away.
</Faq>
</li>
<li>
<Faq
question="What happens if someone uses the `/share` feature?">
By default, sharing is disabled. If enabled, conversations are
sent to our share service and cached through our CDN. For
enterprise use, we recommend disabling or self-hosting this
feature.
</Faq>
</li>
<li>
<Faq question="Can OpenCode integrate with our companys SSO?">
Yes. Enterprise deployments can include SSO integration so all
sessions and shared conversations are protected by your
authentication system.
</Faq>
</li>
<li>
<Faq question="Can OpenCode be self-hosted?">
Absolutely. You can fully self-host OpenCode, including the
share feature, ensuring that data and pages are accessible
only after authentication.
</Faq>
</li>
<li>
<Faq
question="How do we get started with enterprise deployment?">
Contact us to discuss pricing, implementation, and enterprise
options like SSO, private registries, and self-hosting.
</Faq>
</li>
</ul>
</section>
</div>
<Footer />
</div>
<Legal />
</main>
)
}

View file

@ -827,12 +827,8 @@ body {
outline: none;
border: none;
color: var(--color-text-strong);
border: 1px solid var(--color-background-strong); /* Tailwind blue-600 as example */
/* Tailwind-style ring */
border: 1px solid var(--color-background-strong);
box-shadow: 0 0 0 3px var(--color-background-interactive);
/* mimics "ring-2 ring-blue-600/50" */
@media (prefers-color-scheme: dark) {
box-shadow: none;

View file

@ -188,6 +188,7 @@ export function fromAnthropicRequest(body: any): CommonRequest {
})()
return {
model: body.model,
max_tokens: body.max_tokens,
temperature: body.temperature,
top_p: body.top_p,

View file

@ -114,6 +114,7 @@ export function fromOaCompatibleRequest(body: any): CommonRequest {
}
return {
model: body.model,
max_tokens: body.max_tokens,
temperature: body.temperature,
top_p: body.top_p,

View file

@ -177,6 +177,7 @@ export function fromOpenaiRequest(body: any): CommonRequest {
})()
return {
model: body.model,
max_tokens: body.max_output_tokens ?? body.max_tokens,
temperature: body.temperature,
top_p: body.top_p,
@ -310,7 +311,7 @@ export function toOpenaiRequest(body: CommonRequest) {
metadata: (body as any).metadata,
store: (body as any).store,
user: (body as any).user,
text: { verbosity: "low" },
text: { verbosity: body.model === "gpt-5-codex" ? "medium" : "low" },
reasoning: { effort: "medium" },
}
}

View file

@ -95,7 +95,7 @@ export interface CommonUsage {
}
export interface CommonRequest {
model?: string
model: string
max_tokens?: number
temperature?: number
top_p?: number

View file

@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/console-core",
"version": "0.15.24",
"version": "0.15.28",
"private": true,
"type": "module",
"dependencies": {

View file

@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-function",
"version": "0.15.24",
"version": "0.15.28",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",

View file

@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-mail",
"version": "0.15.24",
"version": "0.15.28",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",

View file

@ -1,6 +1,6 @@
{
"name": "@opencode-ai/desktop",
"version": "0.15.24",
"version": "0.15.28",
"description": "",
"type": "module",
"scripts": {

View file

@ -1,4 +1,4 @@
import type { Part, AssistantMessage, ReasoningPart, TextPart, ToolPart, Message } from "@opencode-ai/sdk"
import type { Part, ReasoningPart, TextPart, ToolPart, Message, AssistantMessage, UserMessage } from "@opencode-ai/sdk"
import { children, Component, createMemo, For, Match, Show, Switch, type JSX } from "solid-js"
import { Dynamic } from "solid-js/web"
import { Markdown } from "./markdown"
@ -17,7 +17,20 @@ import type { WriteTool } from "opencode/tool/write"
import type { TodoWriteTool } from "opencode/tool/todo"
import { DiffChanges } from "./diff-changes"
export function AssistantMessage(props: { message: AssistantMessage; parts: Part[] }) {
export function Message(props: { message: Message; parts: Part[] }) {
return (
<Switch>
<Match when={props.message.role === "user" && props.message}>
{(userMessage) => <UserMessage message={userMessage()} parts={props.parts} />}
</Match>
<Match when={props.message.role === "assistant" && props.message}>
{(assistantMessage) => <AssistantMessage message={assistantMessage()} parts={props.parts} />}
</Match>
</Switch>
)
}
function AssistantMessage(props: { message: AssistantMessage; parts: Part[] }) {
const filteredParts = createMemo(() => {
return props.parts?.filter((x) => {
if (x.type === "reasoning") return false
@ -31,11 +44,26 @@ export function AssistantMessage(props: { message: AssistantMessage; parts: Part
)
}
export function Part(props: { part: Part; message: Message; readonly?: boolean }) {
function UserMessage(props: { message: UserMessage; parts: Part[] }) {
const text = createMemo(() =>
props.parts
?.filter((p) => p.type === "text" && !p.synthetic)
?.map((p) => (p as TextPart).text)
?.join(""),
)
return <div class="text-12-regular text-text-base line-clamp-3">{text()}</div>
}
export function Part(props: { part: Part; message: Message; hideDetails?: boolean }) {
const component = createMemo(() => PART_MAPPING[props.part.type as keyof typeof PART_MAPPING])
return (
<Show when={component()}>
<Dynamic component={component()} part={props.part as any} message={props.message} readonly={props.readonly} />
<Dynamic
component={component()}
part={props.part as any}
message={props.message}
hideDetails={props.hideDetails}
/>
</Show>
)
}
@ -62,7 +90,7 @@ function TextPart(props: { part: TextPart; message: Message }) {
)
}
function ToolPart(props: { part: ToolPart; message: Message; readonly?: boolean }) {
function ToolPart(props: { part: ToolPart; message: Message; hideDetails?: boolean }) {
const component = createMemo(() => {
const render = ToolRegistry.render(props.part.tool) ?? GenericTool
const metadata = props.part.state.status === "pending" ? {} : (props.part.state.metadata ?? {})
@ -75,7 +103,7 @@ function ToolPart(props: { part: ToolPart; message: Message; readonly?: boolean
tool={props.part.tool}
metadata={metadata}
output={props.part.state.status === "completed" ? props.part.state.output : undefined}
readonly={props.readonly}
hideDetails={props.hideDetails}
/>
)
})
@ -101,7 +129,7 @@ function BasicTool(props: {
icon: IconProps["name"]
trigger: TriggerTitle | JSX.Element
children?: JSX.Element
readonly?: boolean
hideDetails?: boolean
}) {
const resolved = children(() => props.children)
return (
@ -157,12 +185,12 @@ function BasicTool(props: {
</Switch>
</div>
</div>
<Show when={resolved() && !props.readonly}>
<Show when={resolved() && !props.hideDetails}>
<Collapsible.Arrow />
</Show>
</div>
</Collapsible.Trigger>
<Show when={resolved() && !props.readonly}>
<Show when={resolved() && !props.hideDetails}>
<Collapsible.Content>{resolved()}</Collapsible.Content>
</Show>
</Collapsible>
@ -173,7 +201,7 @@ function BasicTool(props: {
}
function GenericTool(props: ToolProps<any>) {
return <BasicTool icon="mcp" trigger={{ title: props.tool }} readonly={props.readonly} />
return <BasicTool icon="mcp" trigger={{ title: props.tool }} hideDetails={props.hideDetails} />
}
type ToolProps<T extends Tool.Info> = {
@ -181,7 +209,7 @@ type ToolProps<T extends Tool.Info> = {
metadata: Partial<Tool.InferMetadata<T>>
tool: string
output?: string
readonly?: boolean
hideDetails?: boolean
}
const ToolRegistry = (() => {

View file

@ -33,7 +33,7 @@ import { Code } from "@/components/code"
import { useSync } from "@/context/sync"
import { useSDK } from "@/context/sdk"
import { ProgressCircle } from "@/components/progress-circle"
import { AssistantMessage, Part } from "@/components/assistant-message"
import { Message, Part } from "@/components/message"
import { type AssistantMessage as AssistantMessageType } from "@opencode-ai/sdk"
import { DiffChanges } from "@/components/diff-changes"
@ -198,6 +198,7 @@ export default function Page() {
}
if (!session) return
local.session.setActive(session.id)
const toAbsolutePath = (path: string) => (path.startsWith("/") ? path : sync.absolute(path))
const text = parts.map((part) => part.content).join("")
@ -259,7 +260,6 @@ export default function Page() {
],
},
})
local.session.setActive(session.id)
}
const handleNewSession = () => {
@ -639,8 +639,9 @@ export default function Page() {
<For each={local.session.userMessages()}>
{(message) => {
const [expanded, setExpanded] = createSignal(false)
const title = createMemo(() => message.summary?.title)
const parts = createMemo(() => sync.data.part[message.id])
const prompt = createMemo(() => local.session.getMessageText(message))
const title = createMemo(() => message.summary?.title)
const summary = createMemo(() => message.summary?.body)
const assistantMessages = createMemo(() => {
return sync.data.message[activeSession().id]?.filter(
@ -665,7 +666,9 @@ export default function Page() {
</h1>
</div>
<Show when={title}>
<div class="-mt-8 text-12-regular text-text-base line-clamp-3">{prompt()}</div>
<div class="-mt-8">
<Message message={message} parts={parts()} />
</div>
</Show>
{/* Response */}
<div class="w-full flex flex-col gap-2">
@ -686,7 +689,7 @@ export default function Page() {
<For each={assistantMessages()}>
{(assistantMessage) => {
const parts = createMemo(() => sync.data.part[assistantMessage.id])
return <AssistantMessage message={assistantMessage} parts={parts()} />
return <Message message={assistantMessage} parts={parts()} />
}}
</For>
</div>
@ -722,7 +725,9 @@ export default function Page() {
const lastTextPart = createMemo(() =>
sync.data.part[last().id].findLast((p) => p.type === "text"),
)
return <Part message={last()} part={lastTextPart()!} readonly />
return (
<Part message={last()} part={lastTextPart()!} hideDetails />
)
}}
</Match>
<Match when={lastMessageWithReasoning()}>
@ -733,7 +738,11 @@ export default function Page() {
),
)
return (
<Part message={last()} part={lastReasoningPart()!} readonly />
<Part
message={last()}
part={lastReasoningPart()!}
hideDetails
/>
)
}}
</Match>
@ -745,7 +754,7 @@ export default function Page() {
(p) => p.type === "tool" && p.state.status === "completed",
),
)
return <Part message={last()} part={lastToolPart()!} readonly />
return <Part message={last()} part={lastToolPart()!} hideDetails />
}}
</Show>
</div>

View file

@ -1,6 +1,6 @@
{
"name": "@opencode-ai/function",
"version": "0.15.24",
"version": "0.15.28",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",

View file

@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "0.15.24",
"version": "0.15.28",
"name": "opencode",
"type": "module",
"private": true,
@ -22,6 +22,10 @@
"@ai-sdk/google-vertex": "3.0.16",
"@babel/core": "7.28.4",
"@octokit/webhooks-types": "7.6.1",
"@parcel/watcher-darwin-arm64": "2.5.1",
"@parcel/watcher-darwin-x64": "2.5.1",
"@parcel/watcher-linux-arm64-glibc": "2.5.1",
"@parcel/watcher-linux-x64-glibc": "2.5.1",
"@parcel/watcher-win32-x64": "2.5.1",
"@standard-schema/spec": "1.0.0",
"@tsconfig/bun": "catalog:",

View file

@ -1506,6 +1506,9 @@ export namespace SessionPrompt {
})
export type CommandInput = z.infer<typeof CommandInput>
const bashRegex = /!`([^`]+)`/g
const argsRegex = /(?:[^\s"']+|"[^"]*"|'[^']*')+/g
const placeholderRegex = /\$(\d+)/g
const quoteTrimRegex = /^["']|["']$/g
/**
* Regular expression to match @ file references in text
* Matches @ followed by file paths, excluding commas, periods at end of sentences, and backticks
@ -1517,7 +1520,25 @@ export namespace SessionPrompt {
const command = await Command.get(input.command)
const agentName = command.agent ?? input.agent ?? "build"
let template = command.template.replaceAll("$ARGUMENTS", input.arguments)
const raw = input.arguments.match(argsRegex) ?? []
const args = raw.map((arg) => arg.replace(quoteTrimRegex, ""))
const placeholders = command.template.match(placeholderRegex) ?? []
let last = 0
for (const item of placeholders) {
const value = Number(item.slice(1))
if (value > last) last = value
}
// Let the final placeholder swallow any extra arguments so prompts read naturally
const withArgs = command.template.replaceAll(placeholderRegex, (_, index) => {
const position = Number(index)
const argIndex = position - 1
if (argIndex >= args.length) return ""
if (position === last) return args.slice(argIndex).join(" ")
return args[argIndex]
})
let template = withArgs.replaceAll("$ARGUMENTS", input.arguments)
const shell = ConfigMarkdown.shell(template)
if (shell.length > 0) {

View file

@ -98,23 +98,29 @@ export namespace SessionSummary {
m.parts.some((p) => p.type === "step-finish" && p.reason !== "tool-calls"),
)
) {
const result = await generateText({
model: small.language,
maxOutputTokens: 100,
messages: [
{
role: "user",
content: `
let summary = messages
.findLast((m) => m.info.role === "assistant")
?.parts.findLast((p) => p.type === "text")?.text
if (!summary || diffs.length > 0) {
const result = await generateText({
model: small.language,
maxOutputTokens: 100,
messages: [
{
role: "user",
content: `
Summarize the following conversation into 2 sentences MAX explaining what the assistant did and why. Do not explain the user's input. Do not speak in the third person about the assistant.
<conversation>
${JSON.stringify(MessageV2.toModelMessage(messages))}
</conversation>
`,
},
],
})
userMsg.summary.body = result.text
log.info("body", { body: result.text })
},
],
})
summary = result.text
}
userMsg.summary.body = summary
log.info("body", { body: summary })
await Session.updateMessage(userMsg)
}
}

View file

@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/plugin",
"version": "0.15.24",
"version": "0.15.28",
"type": "module",
"scripts": {
"typecheck": "tsgo --noEmit",

View file

@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/sdk",
"version": "0.15.24",
"version": "0.15.28",
"type": "module",
"scripts": {
"typecheck": "tsgo --noEmit",

View file

@ -1,6 +1,6 @@
{
"name": "@opencode-ai/slack",
"version": "0.15.24",
"version": "0.15.28",
"type": "module",
"scripts": {
"dev": "bun run src/index.ts",

View file

@ -1,6 +1,6 @@
{
"name": "@opencode-ai/ui",
"version": "0.15.24",
"version": "0.15.28",
"type": "module",
"exports": {
".": "./src/components/index.ts",

View file

@ -1,7 +1,7 @@
{
"name": "@opencode-ai/web",
"type": "module",
"version": "0.15.24",
"version": "0.15.28",
"scripts": {
"dev": "astro dev",
"dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev",

View file

@ -129,6 +129,36 @@ Run the command with arguments:
And `$ARGUMENTS` will be replaced with `Button`.
You can also access individual arguments using positional parameters:
- `$1` - First argument
- `$2` - Second argument
- `$3` - Third argument
- And so on...
For example:
```md title=".opencode/command/create-file.md"
---
description: Create a new file with content
---
Create a file named $1 in the directory $2
with the following content: $3
```
Run the command:
```bash frame="none"
/create-file config.json src "{ \"key\": \"value\" }"
```
This replaces:
- `$1` with `config.json`
- `$2` with `src`
- `$3` with `{ "key": "value" }`
---
### Shell output

View file

@ -55,6 +55,45 @@ We recommend you disable this for your trial.
---
## FAQ
<details>
<summary>What is OpenCode Enterprise?</summary>
OpenCode Enterprise provides self-hosted deployment options with enhanced security, SSO integration, and dedicated support for organizations that need to maintain full control over their development environment.
</details>
<details>
<summary>How does enterprise pricing work?</summary>
Enterprise pricing is based on team size and deployment requirements. Contact us at <a href={email}>{config.email}</a> for a custom quote based on your organization's needs.
</details>
<details>
<summary>What deployment options are available?</summary>
We offer cloud-hosted, on-premises, and air-gapped deployment options. Each includes SSO integration, private package registry support, and customizable security configurations.
</details>
<details>
<summary>Is my data secure with enterprise?</summary>
Yes. OpenCode does not store your code or context data. All processing happens locally or through direct API calls to your AI provider. Enterprise deployments add SSO protection and can be fully air-gapped for maximum security.
</details>
<details>
<summary>Can we integrate with existing tools?</summary>
Yes. OpenCode supports private npm registries, custom authentication providers, and can be integrated into your existing CI/CD pipelines and development workflows.
</details>
---
## Deployment
Once you have completed your trial and you are ready to self-host opencode at

View file

@ -2,7 +2,7 @@
"name": "opencode",
"displayName": "opencode",
"description": "opencode for VS Code",
"version": "0.15.24",
"version": "0.15.28",
"publisher": "sst-dev",
"repository": {
"type": "git",