Compare commits
35
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b62c10553 | ||
|
|
61c7196bd9 | ||
|
|
365fdd9ff8 | ||
|
|
f6bc9238df | ||
|
|
26f75d4e68 | ||
|
|
8ba8d3c7e3 | ||
|
|
f993541e0b | ||
|
|
e2df3eb44d | ||
|
|
38f9ce05f6 | ||
|
|
a6e09363b8 | ||
|
|
49629bb58e | ||
|
|
2bb5b9b13a | ||
|
|
41338d1bf9 | ||
|
|
41ee9c94c7 | ||
|
|
9c16db0f36 | ||
|
|
721869353b | ||
|
|
6d22ade771 | ||
|
|
fbcceeb781 | ||
|
|
95775d68b7 | ||
|
|
cf11669618 | ||
|
|
65dc19e85a | ||
|
|
cfcfceca6d | ||
|
|
9f8899a9f9 | ||
|
|
449a063fe2 | ||
|
|
37530359ee | ||
|
|
65f0bea146 | ||
|
|
e4cc05a975 | ||
|
|
029612d8d5 | ||
|
|
e9826e8a22 | ||
|
|
ad5f209dc8 | ||
|
|
fcfeac57c5 | ||
|
|
2946898934 | ||
|
|
b4d95545e0 | ||
|
|
d3bbaa141c | ||
|
|
8714f23509 |
@@ -64,3 +64,5 @@
|
||||
| 2025-08-28 | 252,796 (+4,387) | 205,242 (+0) | 458,038 (+4,387) |
|
||||
| 2025-08-29 | 256,045 (+3,249) | 211,075 (+5,833) | 467,120 (+9,082) |
|
||||
| 2025-08-30 | 258,863 (+2,818) | 212,397 (+1,322) | 471,260 (+4,140) |
|
||||
| 2025-08-31 | 262,004 (+3,141) | 213,944 (+1,547) | 475,948 (+4,688) |
|
||||
| 2025-09-01 | 265,359 (+3,355) | 215,115 (+1,171) | 480,474 (+4,526) |
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"dev:remote": "VITE_AUTH_URL=https://auth.dev.opencode.ai bun sst shell --stage=dev bun dev",
|
||||
"build": "vinxi build",
|
||||
"start": "vinxi start",
|
||||
"version": "0.5.29"
|
||||
"version": "0.6.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ibm/plex": "6.4.1",
|
||||
|
||||
@@ -52,7 +52,7 @@ export const getActor = query(async (): Promise<Actor.Info> => {
|
||||
}
|
||||
}
|
||||
const workspaceHint = splits[1]
|
||||
const accounts = Object.keys(auth.data.account)
|
||||
const accounts = Object.keys(auth.data.account ?? {})
|
||||
const result = await Database.transaction(async (tx) => {
|
||||
return await tx
|
||||
.select({
|
||||
|
||||
@@ -20,4 +20,6 @@ export default createHandler(() => (
|
||||
</html>
|
||||
)}
|
||||
/>
|
||||
))
|
||||
), {
|
||||
mode: "sync",
|
||||
})
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import type { APIEvent } from "@solidjs/start/server"
|
||||
import { Resource } from "@opencode/cloud-resource"
|
||||
import { json } from "@solidjs/router"
|
||||
import { Database } from "@opencode/cloud-core/drizzle/index.js"
|
||||
import { UserTable } from "@opencode/cloud-core/schema/user.sql.js"
|
||||
|
||||
export async function GET(evt: APIEvent) {
|
||||
return json({
|
||||
data: Resource.Database.host,
|
||||
data: await Database.use(async (tx) => {
|
||||
const result = await tx.$count(UserTable)
|
||||
return result
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -54,7 +54,10 @@
|
||||
|
||||
a,
|
||||
button {
|
||||
appearance: none;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
color: var(--color-text);
|
||||
text-decoration: underline;
|
||||
|
||||
@@ -1,7 +1,20 @@
|
||||
import "./workspace.css"
|
||||
import { useAuthSession } from "~/context/auth.session"
|
||||
import { IconLogo } from "../component/icon"
|
||||
import { action, redirect, RouteSectionProps } from "@solidjs/router"
|
||||
import { withActor } from "~/context/auth.withActor"
|
||||
import "./workspace.css"
|
||||
import { query, action, redirect, createAsync, RouteSectionProps } from "@solidjs/router"
|
||||
import { User } from "@opencode/cloud-core/user.js"
|
||||
import { Actor } from "@opencode/cloud-core/actor.js"
|
||||
|
||||
const getUserInfo = query(async () => {
|
||||
"use server"
|
||||
return withActor(async () => {
|
||||
const actor = Actor.assert("user")
|
||||
const user = await User.fromID(actor.properties.userID)
|
||||
return { user }
|
||||
})
|
||||
}, "userInfo")
|
||||
|
||||
const logout = action(async () => {
|
||||
"use server"
|
||||
@@ -17,6 +30,9 @@ const logout = action(async () => {
|
||||
})
|
||||
|
||||
export default function WorkspaceLayout(props: RouteSectionProps) {
|
||||
const userInfo = createAsync(() => getUserInfo(), {
|
||||
deferStream: true,
|
||||
})
|
||||
return (
|
||||
<main data-page="workspace">
|
||||
<header data-component="workspace-header">
|
||||
@@ -26,7 +42,7 @@ export default function WorkspaceLayout(props: RouteSectionProps) {
|
||||
</a>
|
||||
</div>
|
||||
<div data-slot="header-actions">
|
||||
<span>name@example.com</span>
|
||||
<span>{userInfo()?.user.email}</span>
|
||||
<form action={logout} method="post">
|
||||
<button type="submit" formaction={logout}>Logout</button>
|
||||
</form>
|
||||
|
||||
@@ -23,453 +23,450 @@
|
||||
padding-bottom: var(--space-16);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Common elements */
|
||||
button {
|
||||
padding: var(--space-3) var(--space-4);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius-sm);
|
||||
background-color: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
font-size: var(--font-size-sm);
|
||||
font-family: var(--font-sans);
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-surface-hover);
|
||||
border-color: var(--color-accent);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-bg);
|
||||
border-color: var(--color-border);
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
&[color="primary"] {
|
||||
background-color: var(--color-primary);
|
||||
border-color: var(--color-primary);
|
||||
color: var(--color-primary-text);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-primary-hover);
|
||||
border-color: var(--color-primary-hover);
|
||||
}
|
||||
}
|
||||
|
||||
&[color="ghost"] {
|
||||
background-color: transparent;
|
||||
border-color: transparent;
|
||||
color: var(--color-text-muted);
|
||||
/* Common elements */
|
||||
button {
|
||||
padding: var(--space-3) var(--space-4);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius-sm);
|
||||
background-color: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
font-size: var(--font-size-sm);
|
||||
font-family: var(--font-sans);
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-surface-hover);
|
||||
border-color: var(--color-border);
|
||||
color: var(--color-text);
|
||||
border-color: var(--color-accent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--color-text);
|
||||
text-decoration: underline;
|
||||
text-underline-offset: var(--space-0-75);
|
||||
text-decoration-thickness: 1px;
|
||||
}
|
||||
&:active {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
[data-slot="empty-state"] {
|
||||
padding: var(--space-20) var(--space-6);
|
||||
text-align: center;
|
||||
border: 1px dashed var(--color-border);
|
||||
border-radius: var(--border-radius-sm);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-2);
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
|
||||
p {
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--color-text-muted);
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
background-color: var(--color-bg);
|
||||
border-color: var(--color-border);
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Title section */
|
||||
[data-slot="title-section"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-2);
|
||||
padding-bottom: var(--space-8);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
&[data-color="primary"] {
|
||||
background-color: var(--color-primary);
|
||||
border-color: var(--color-primary);
|
||||
color: var(--color-primary-text);
|
||||
|
||||
h1 {
|
||||
font-size: var(--font-size-2xl);
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
letter-spacing: -0.03125rem;
|
||||
margin: 0;
|
||||
text-transform: uppercase;
|
||||
&:hover {
|
||||
background-color: var(--color-primary-hover);
|
||||
border-color: var(--color-primary-hover);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 30rem) {
|
||||
font-size: var(--font-size-xl);
|
||||
line-height: 1.25;
|
||||
&[data-color="ghost"] {
|
||||
background-color: transparent;
|
||||
border-color: transparent;
|
||||
color: var(--color-text-muted);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-surface-hover);
|
||||
border-color: var(--color-border);
|
||||
color: var(--color-text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: var(--font-size-md);
|
||||
color: var(--color-text-muted);
|
||||
a {
|
||||
color: var(--color-text);
|
||||
text-decoration: underline;
|
||||
text-underline-offset: var(--space-0-75);
|
||||
text-decoration-thickness: 1px;
|
||||
}
|
||||
|
||||
a {
|
||||
[data-slot="empty-state"] {
|
||||
padding: var(--space-20) var(--space-6);
|
||||
text-align: center;
|
||||
border: 1px dashed var(--color-border);
|
||||
border-radius: var(--border-radius-sm);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-2);
|
||||
|
||||
p {
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--color-text-muted);
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Title section */
|
||||
[data-slot="title-section"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-2);
|
||||
padding-bottom: var(--space-8);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
|
||||
h1 {
|
||||
font-size: var(--font-size-2xl);
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
letter-spacing: -0.03125rem;
|
||||
margin: 0;
|
||||
text-transform: uppercase;
|
||||
|
||||
@media (max-width: 30rem) {
|
||||
font-size: var(--font-size-xl);
|
||||
line-height: 1.25;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: var(--font-size-md);
|
||||
color: var(--color-text-muted);
|
||||
|
||||
a {
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Section titles */
|
||||
[data-slot="section-title"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-1);
|
||||
|
||||
h2 {
|
||||
font-size: var(--font-size-md);
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
letter-spacing: -0.03125rem;
|
||||
margin: 0;
|
||||
color: var(--color-text-secondary);
|
||||
text-transform: uppercase;
|
||||
|
||||
@media (max-width: 30rem) {
|
||||
font-size: var(--font-size-lg);
|
||||
line-height: 1.25;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Section titles */
|
||||
[data-slot="section-title"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-1);
|
||||
|
||||
h2 {
|
||||
font-size: var(--font-size-md);
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
letter-spacing: -0.03125rem;
|
||||
margin: 0;
|
||||
color: var(--color-text-secondary);
|
||||
text-transform: uppercase;
|
||||
|
||||
@media (max-width: 30rem) {
|
||||
font-size: var(--font-size-lg);
|
||||
line-height: 1.25;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
}
|
||||
|
||||
/* API Keys Section */
|
||||
[data-slot="api-keys-section"] {
|
||||
[data-slot="create-form"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-4);
|
||||
background-color: var(--color-bg-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius-sm);
|
||||
max-width: 32rem;
|
||||
|
||||
input {
|
||||
padding: var(--space-2) var(--space-3);
|
||||
/* API Keys Section */
|
||||
[data-slot="api-keys-section"] {
|
||||
[data-slot="create-form"] {
|
||||
display: flex;
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-4);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius-sm);
|
||||
background-color: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
|
||||
input {
|
||||
flex: 1;
|
||||
padding: var(--space-2) var(--space-3);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius-sm);
|
||||
background-color: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
font-size: var(--font-size-sm);
|
||||
font-family: var(--font-mono);
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-accent);
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: var(--color-text-disabled);
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="form-actions"] {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="api-keys-table"] {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
[data-slot="api-keys-table-element"] {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: var(--font-size-sm);
|
||||
font-family: var(--font-mono);
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-accent);
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: var(--color-text-disabled);
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="form-actions"] {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="api-keys-table"] {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
[data-slot="api-keys-table-element"] {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: var(--font-size-sm);
|
||||
|
||||
thead {
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
th {
|
||||
padding: var(--space-3) var(--space-4);
|
||||
text-align: left;
|
||||
font-weight: normal;
|
||||
color: var(--color-text-muted);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: var(--space-3) var(--space-4);
|
||||
border-bottom: 1px solid var(--color-border-muted);
|
||||
color: var(--color-text-muted);
|
||||
font-family: var(--font-mono);
|
||||
|
||||
&[data-slot="key-name"] {
|
||||
color: var(--color-text);
|
||||
font-family: var(--font-sans);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&[data-slot="key-value"] {
|
||||
font-family: var(--font-mono);
|
||||
|
||||
div {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
}
|
||||
|
||||
&[data-slot="key-date"] {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
&[data-slot="key-actions"] {
|
||||
font-family: var(--font-sans);
|
||||
}
|
||||
}
|
||||
|
||||
tbody tr {
|
||||
&:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 40rem) {
|
||||
th,
|
||||
td {
|
||||
padding: var(--space-2) var(--space-3);
|
||||
font-size: var(--font-size-xs);
|
||||
thead {
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
th {
|
||||
&:nth-child(3) /* Date */ {
|
||||
display: none;
|
||||
}
|
||||
padding: var(--space-3) var(--space-4);
|
||||
text-align: left;
|
||||
font-weight: normal;
|
||||
color: var(--color-text-muted);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
td {
|
||||
&:nth-child(3) /* Date */ {
|
||||
display: none;
|
||||
padding: var(--space-3) var(--space-4);
|
||||
border-bottom: 1px solid var(--color-border-muted);
|
||||
color: var(--color-text-muted);
|
||||
font-family: var(--font-mono);
|
||||
|
||||
&[data-slot="key-name"] {
|
||||
color: var(--color-text);
|
||||
font-family: var(--font-sans);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&[data-slot="key-value"] {
|
||||
font-family: var(--font-mono);
|
||||
|
||||
div {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
}
|
||||
|
||||
&[data-slot="key-date"] {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
&[data-slot="key-actions"] {
|
||||
font-family: var(--font-sans);
|
||||
}
|
||||
}
|
||||
|
||||
tbody tr {
|
||||
&:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 40rem) {
|
||||
th,
|
||||
td {
|
||||
padding: var(--space-2) var(--space-3);
|
||||
font-size: var(--font-size-xs);
|
||||
}
|
||||
|
||||
th {
|
||||
&:nth-child(3) /* Date */ {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
&:nth-child(3) /* Date */ {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Balance Section */
|
||||
[data-slot="balance-section"] {
|
||||
[data-slot="balance"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-4);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius-sm);
|
||||
min-width: 14.5rem;
|
||||
width: fit-content;
|
||||
|
||||
[data-slot="amount"] {
|
||||
padding: var(--space-3-5) var(--space-4);
|
||||
background-color: var(--color-bg-surface);
|
||||
/* Balance Section */
|
||||
[data-slot="balance-section"] {
|
||||
[data-slot="balance"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-4);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius-sm);
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: var(--space-1);
|
||||
justify-content: flex-end;
|
||||
min-width: 14.5rem;
|
||||
width: fit-content;
|
||||
|
||||
[data-slot="amount"] {
|
||||
padding: var(--space-3-5) var(--space-4);
|
||||
background-color: var(--color-bg-surface);
|
||||
border-radius: var(--border-radius-sm);
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: var(--space-1);
|
||||
justify-content: flex-end;
|
||||
|
||||
&.danger {
|
||||
[data-slot="value"] {
|
||||
color: var(--color-danger);
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="currency"] {
|
||||
position: relative;
|
||||
bottom: 2px;
|
||||
font-size: var(--font-size-lg);
|
||||
color: var(--color-text-muted);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
&.danger {
|
||||
[data-slot="value"] {
|
||||
color: var(--color-danger);
|
||||
font-size: var(--font-size-3xl);
|
||||
font-weight: 500;
|
||||
color: var(--color-text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="currency"] {
|
||||
position: relative;
|
||||
bottom: 2px;
|
||||
font-size: var(--font-size-lg);
|
||||
/* Payments Section */
|
||||
[data-slot="payments-section"] {
|
||||
[data-slot="payments-table"] {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
[data-slot="payments-table-element"] {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: var(--font-size-sm);
|
||||
|
||||
thead {
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
th {
|
||||
padding: var(--space-3) var(--space-4);
|
||||
text-align: left;
|
||||
font-weight: normal;
|
||||
color: var(--color-text-muted);
|
||||
font-weight: 400;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
[data-slot="value"] {
|
||||
font-size: var(--font-size-3xl);
|
||||
font-weight: 500;
|
||||
color: var(--color-text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Payments Section */
|
||||
[data-slot="payments-section"] {
|
||||
[data-slot="payments-table"] {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
[data-slot="payments-table-element"] {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: var(--font-size-sm);
|
||||
|
||||
thead {
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
th {
|
||||
padding: var(--space-3) var(--space-4);
|
||||
text-align: left;
|
||||
font-weight: normal;
|
||||
color: var(--color-text-muted);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: var(--space-3) var(--space-4);
|
||||
border-bottom: 1px solid var(--color-border-muted);
|
||||
color: var(--color-text-muted);
|
||||
font-family: var(--font-mono);
|
||||
|
||||
&[data-slot="payment-date"] {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
&[data-slot="payment-id"] {
|
||||
td {
|
||||
padding: var(--space-3) var(--space-4);
|
||||
border-bottom: 1px solid var(--color-border-muted);
|
||||
color: var(--color-text-muted);
|
||||
font-family: var(--font-mono);
|
||||
font-weight: 400;
|
||||
|
||||
&[data-slot="payment-date"] {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
&[data-slot="payment-id"] {
|
||||
font-family: var(--font-mono);
|
||||
font-weight: 400;
|
||||
color: var(--color-text-muted);
|
||||
max-width: 200px;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
&[data-slot="payment-amount"] {
|
||||
color: var(--color-text);
|
||||
}
|
||||
}
|
||||
|
||||
tbody tr {
|
||||
&:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 40rem) {
|
||||
th,
|
||||
td {
|
||||
padding: var(--space-2) var(--space-3);
|
||||
font-size: var(--font-size-xs);
|
||||
}
|
||||
|
||||
th {
|
||||
&:nth-child(2) /* Payment ID */ {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
&:nth-child(2) /* Payment ID */ {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Usage Section */
|
||||
[data-slot="usage-section"] {
|
||||
[data-slot="usage-table"] {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
[data-slot="usage-table-element"] {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: var(--font-size-sm);
|
||||
|
||||
thead {
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
th {
|
||||
padding: var(--space-3) var(--space-4);
|
||||
text-align: left;
|
||||
font-weight: normal;
|
||||
color: var(--color-text-muted);
|
||||
max-width: 200px;
|
||||
word-break: break-word;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
&[data-slot="payment-amount"] {
|
||||
color: var(--color-text);
|
||||
}
|
||||
}
|
||||
|
||||
tbody tr {
|
||||
&:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 40rem) {
|
||||
th,
|
||||
td {
|
||||
padding: var(--space-2) var(--space-3);
|
||||
font-size: var(--font-size-xs);
|
||||
}
|
||||
padding: var(--space-3) var(--space-4);
|
||||
border-bottom: 1px solid var(--color-border-muted);
|
||||
color: var(--color-text-muted);
|
||||
font-family: var(--font-mono);
|
||||
|
||||
th {
|
||||
&:nth-child(2) /* Payment ID */ {
|
||||
display: none;
|
||||
&[data-slot="usage-date"] {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
&[data-slot="usage-model"] {
|
||||
font-family: var(--font-sans);
|
||||
font-weight: 400;
|
||||
color: var(--color-text-secondary);
|
||||
max-width: 200px;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
&[data-slot="usage-cost"] {
|
||||
color: var(--color-text);
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
&:nth-child(2) /* Payment ID */ {
|
||||
display: none;
|
||||
tbody tr {
|
||||
&:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Usage Section */
|
||||
[data-slot="usage-section"] {
|
||||
[data-slot="usage-table"] {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
[data-slot="usage-table-element"] {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: var(--font-size-sm);
|
||||
|
||||
thead {
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
th {
|
||||
padding: var(--space-3) var(--space-4);
|
||||
text-align: left;
|
||||
font-weight: normal;
|
||||
color: var(--color-text-muted);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: var(--space-3) var(--space-4);
|
||||
border-bottom: 1px solid var(--color-border-muted);
|
||||
color: var(--color-text-muted);
|
||||
font-family: var(--font-mono);
|
||||
|
||||
&[data-slot="usage-date"] {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
&[data-slot="usage-model"] {
|
||||
font-family: var(--font-sans);
|
||||
font-weight: 400;
|
||||
color: var(--color-text-secondary);
|
||||
max-width: 200px;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
&[data-slot="usage-cost"] {
|
||||
color: var(--color-text);
|
||||
}
|
||||
}
|
||||
|
||||
tbody tr {
|
||||
&:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 40rem) {
|
||||
th,
|
||||
td {
|
||||
padding: var(--space-2) var(--space-3);
|
||||
font-size: var(--font-size-xs);
|
||||
}
|
||||
@media (max-width: 40rem) {
|
||||
th,
|
||||
td {
|
||||
padding: var(--space-2) var(--space-3);
|
||||
font-size: var(--font-size-xs);
|
||||
}
|
||||
|
||||
th {
|
||||
&:nth-child(2) /* Model */ {
|
||||
display: none;
|
||||
th {
|
||||
&:nth-child(2) /* Model */ {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
&:nth-child(2) /* Model */ {
|
||||
display: none;
|
||||
td {
|
||||
&:nth-child(2) /* Model */ {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,121 +63,14 @@ const createPortalUrl = action(async (returnUrl: string) => {
|
||||
return withActor(() => Billing.generatePortalUrl({ returnUrl }))
|
||||
}, "portalUrl")
|
||||
|
||||
const dummyUsageData = [
|
||||
{
|
||||
model: "claude-3-5-sonnet-20241022",
|
||||
inputTokens: 1250,
|
||||
outputTokens: 890,
|
||||
reasoningTokens: 150,
|
||||
cacheReadTokens: 0,
|
||||
cacheWriteTokens: 45,
|
||||
cost: 12340000,
|
||||
timeCreated: new Date("2025-01-28T10:30:00Z"),
|
||||
},
|
||||
{
|
||||
model: "claude-3-haiku-20240307",
|
||||
inputTokens: 2100,
|
||||
outputTokens: 450,
|
||||
reasoningTokens: null,
|
||||
cacheReadTokens: 120,
|
||||
cacheWriteTokens: 0,
|
||||
cost: 5670000,
|
||||
timeCreated: new Date("2025-01-27T15:22:00Z"),
|
||||
},
|
||||
{
|
||||
model: "claude-3-5-sonnet-20241022",
|
||||
inputTokens: 850,
|
||||
outputTokens: 1200,
|
||||
reasoningTokens: 220,
|
||||
cacheReadTokens: 30,
|
||||
cacheWriteTokens: 15,
|
||||
cost: 18990000,
|
||||
timeCreated: new Date("2025-01-27T09:15:00Z"),
|
||||
},
|
||||
{
|
||||
model: "claude-3-opus-20240229",
|
||||
inputTokens: 3200,
|
||||
outputTokens: 1800,
|
||||
reasoningTokens: 400,
|
||||
cacheReadTokens: 0,
|
||||
cacheWriteTokens: 100,
|
||||
cost: 45670000,
|
||||
timeCreated: new Date("2025-01-26T14:45:00Z"),
|
||||
},
|
||||
{
|
||||
model: "claude-3-haiku-20240307",
|
||||
inputTokens: 650,
|
||||
outputTokens: 280,
|
||||
reasoningTokens: null,
|
||||
cacheReadTokens: 200,
|
||||
cacheWriteTokens: 0,
|
||||
cost: 2340000,
|
||||
timeCreated: new Date("2025-01-25T16:18:00Z"),
|
||||
},
|
||||
]
|
||||
|
||||
const dummyPaymentData = [
|
||||
{
|
||||
id: "pay_1Ab2Cd3Ef4Gh5678",
|
||||
amount: 2000000000,
|
||||
timeCreated: new Date("2025-01-28T14:32:00Z"),
|
||||
},
|
||||
{
|
||||
id: "pay_9Ij8Kl7Mn6Op5432",
|
||||
amount: 1000000000,
|
||||
timeCreated: new Date("2025-01-25T09:18:00Z"),
|
||||
},
|
||||
{
|
||||
id: "pay_5Qr4St3Uv2Wx1098",
|
||||
amount: 5000000000,
|
||||
timeCreated: new Date("2025-01-20T16:45:00Z"),
|
||||
},
|
||||
{
|
||||
id: "pay_7Yz6Ab5Cd4Ef3210",
|
||||
amount: 1500000000,
|
||||
timeCreated: new Date("2025-01-15T11:22:00Z"),
|
||||
},
|
||||
{
|
||||
id: "pay_3Gh2Ij1Kl0Mn9876",
|
||||
amount: 3000000000,
|
||||
timeCreated: new Date("2025-01-10T13:55:00Z"),
|
||||
},
|
||||
]
|
||||
|
||||
const dummyApiKeyData = [
|
||||
{
|
||||
id: "key_1Ab2Cd3Ef4Gh5678",
|
||||
name: "Production API",
|
||||
key: "oc_live_sk_1Ab2Cd3Ef4Gh567890123456789012345678901234567890",
|
||||
timeCreated: new Date("2025-01-28T14:32:00Z"),
|
||||
timeUsed: new Date("2025-01-29T09:15:00Z"),
|
||||
},
|
||||
{
|
||||
id: "key_9Ij8Kl7Mn6Op5432",
|
||||
name: "Development Key",
|
||||
key: "oc_test_sk_9Ij8Kl7Mn6Op543210987654321098765432109876543210",
|
||||
timeCreated: new Date("2025-01-25T09:18:00Z"),
|
||||
timeUsed: null,
|
||||
},
|
||||
{
|
||||
id: "key_5Qr4St3Uv2Wx1098",
|
||||
name: "CI/CD Pipeline",
|
||||
key: "oc_live_sk_5Qr4St3Uv2Wx109876543210987654321098765432109876",
|
||||
timeCreated: new Date("2025-01-20T16:45:00Z"),
|
||||
timeUsed: new Date("2025-01-28T12:30:00Z"),
|
||||
},
|
||||
]
|
||||
|
||||
export default function () {
|
||||
const actor = createAsync(() => getActor())
|
||||
onMount(() => {
|
||||
console.log("MOUNTED", actor())
|
||||
})
|
||||
|
||||
/////////////////
|
||||
// Keys section
|
||||
/////////////////
|
||||
const keys = createAsync(() => listKeys())
|
||||
const keys = createAsync(() => listKeys(), {
|
||||
deferStream: true,
|
||||
})
|
||||
const createKeyAction = useAction(createKey)
|
||||
const removeKeyAction = useAction(removeKey)
|
||||
const createKeySubmission = useSubmission(createKey)
|
||||
@@ -265,7 +158,9 @@ export default function () {
|
||||
/////////////////
|
||||
// Billing section
|
||||
/////////////////
|
||||
const billingInfo = createAsync(() => getBillingInfo())
|
||||
const billingInfo = createAsync(() => getBillingInfo(), {
|
||||
deferStream: true,
|
||||
})
|
||||
const createCheckoutUrlAction = useAction(createCheckoutUrl)
|
||||
const createCheckoutUrlSubmission = useSubmission(createCheckoutUrl)
|
||||
|
||||
@@ -292,15 +187,6 @@ export default function () {
|
||||
</section>
|
||||
|
||||
<div data-slot="sections">
|
||||
{/* Actor Section */}
|
||||
<section data-slot="actor-section">
|
||||
<div data-slot="section-title">
|
||||
<h2>Actor</h2>
|
||||
<p>Current authenticated user information and session details.</p>
|
||||
</div>
|
||||
<div>{JSON.stringify(actor())}</div>
|
||||
</section>
|
||||
|
||||
{/* API Keys Section */}
|
||||
<section data-slot="api-keys-section">
|
||||
<div data-slot="section-title">
|
||||
@@ -321,14 +207,7 @@ export default function () {
|
||||
/>
|
||||
<div data-slot="form-actions">
|
||||
<button
|
||||
color="primary"
|
||||
disabled={createKeySubmission.pending || !keyName().trim()}
|
||||
onClick={handleCreateKey}
|
||||
>
|
||||
{createKeySubmission.pending ? "Creating..." : "Create"}
|
||||
</button>
|
||||
<button
|
||||
color="ghost"
|
||||
data-color="ghost"
|
||||
onClick={() => {
|
||||
setShowCreateForm(false)
|
||||
setKeyName("")
|
||||
@@ -336,12 +215,19 @@ export default function () {
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
data-color="primary"
|
||||
disabled={createKeySubmission.pending || !keyName().trim()}
|
||||
onClick={handleCreateKey}
|
||||
>
|
||||
{createKeySubmission.pending ? "Creating..." : "Create"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<button
|
||||
color="primary"
|
||||
data-color="primary"
|
||||
onClick={() => {
|
||||
console.log("clicked")
|
||||
setShowCreateForm(true)
|
||||
@@ -370,7 +256,6 @@ export default function () {
|
||||
</thead>
|
||||
<tbody>
|
||||
<For each={keys()!}>
|
||||
{/* Real data: keys() */}
|
||||
{(key) => (
|
||||
<tr>
|
||||
<td data-slot="key-name">{key.name}</td>
|
||||
@@ -389,7 +274,7 @@ export default function () {
|
||||
{formatDateForTable(key.timeCreated)}
|
||||
</td>
|
||||
<td data-slot="key-actions">
|
||||
<button color="ghost" onClick={() => handleDeleteKey(key.id)} title="Delete API key">
|
||||
<button data-color="ghost" onClick={() => handleDeleteKey(key.id)} title="Delete API key">
|
||||
Delete
|
||||
</button>
|
||||
</td>
|
||||
@@ -426,51 +311,12 @@ export default function () {
|
||||
})()}
|
||||
</span>
|
||||
</div>
|
||||
<button color="primary" disabled={createCheckoutUrlSubmission.pending} onClick={handleBuyCredits}>
|
||||
<button data-color="primary" disabled={createCheckoutUrlSubmission.pending} onClick={handleBuyCredits}>
|
||||
{createCheckoutUrlSubmission.pending ? "Loading..." : "Buy Credits"}
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Payments Section */}
|
||||
<Show when={dummyPaymentData.length > 0}>
|
||||
{/* Real data condition: billingInfo() && billingInfo()!.payments.length > 0 */}
|
||||
<section data-slot="payments-section">
|
||||
<div data-slot="section-title">
|
||||
<h2>Payments History</h2>
|
||||
<p>Recent payment transactions.</p>
|
||||
</div>
|
||||
<div data-slot="payments-table">
|
||||
<table data-slot="payments-table-element">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Payment ID</th>
|
||||
<th>Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<For each={dummyPaymentData}>
|
||||
{/* Real data: billingInfo()?.payments */}
|
||||
{(payment) => {
|
||||
const date = new Date(payment.timeCreated)
|
||||
return (
|
||||
<tr>
|
||||
<td data-slot="payment-date" title={formatDateUTC(date)}>
|
||||
{formatDateForTable(date)}
|
||||
</td>
|
||||
<td data-slot="payment-id">{payment.id}</td>
|
||||
<td data-slot="payment-amount">${((payment.amount ?? 0) / 100000000).toFixed(2)}</td>
|
||||
</tr>
|
||||
)
|
||||
}}
|
||||
</For>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</Show>
|
||||
|
||||
{/* Usage Section */}
|
||||
<section data-slot="usage-section">
|
||||
<div data-slot="section-title">
|
||||
@@ -479,7 +325,7 @@ export default function () {
|
||||
</div>
|
||||
<div data-slot="usage-table">
|
||||
<Show
|
||||
when={dummyUsageData.length > 0}
|
||||
when={billingInfo() && billingInfo()!.usage.length > 0}
|
||||
fallback={
|
||||
<div data-slot="empty-state">
|
||||
<p>Make your first API call to get started.</p>
|
||||
@@ -496,7 +342,7 @@ export default function () {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<For each={dummyUsageData}>
|
||||
<For each={billingInfo()!.usage}>
|
||||
{(usage) => {
|
||||
const totalTokens = usage.inputTokens + usage.outputTokens + (usage.reasoningTokens || 0)
|
||||
const date = new Date(usage.timeCreated)
|
||||
@@ -517,6 +363,44 @@ export default function () {
|
||||
</Show>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Payments Section */}
|
||||
<Show when={billingInfo() && billingInfo()!.payments.length > 0}>
|
||||
<section data-slot="payments-section">
|
||||
<div data-slot="section-title">
|
||||
<h2>Payments History</h2>
|
||||
<p>Recent payment transactions.</p>
|
||||
</div>
|
||||
<div data-slot="payments-table">
|
||||
<table data-slot="payments-table-element">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Payment ID</th>
|
||||
<th>Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<For each={billingInfo()!.payments}>
|
||||
{(payment) => {
|
||||
const date = new Date(payment.timeCreated)
|
||||
return (
|
||||
<tr>
|
||||
<td data-slot="payment-date" title={formatDateUTC(date)}>
|
||||
{formatDateForTable(date)}
|
||||
</td>
|
||||
<td data-slot="payment-id">{payment.id}</td>
|
||||
<td data-slot="payment-amount">${((payment.amount ?? 0) / 100000000).toFixed(2)}</td>
|
||||
</tr>
|
||||
)
|
||||
}}
|
||||
</For>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</Show>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode/cloud-core",
|
||||
"version": "0.5.29",
|
||||
"version": "0.6.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
|
||||
@@ -15,12 +15,12 @@ const init = () => {
|
||||
ssl: {
|
||||
rejectUnauthorized: false,
|
||||
},
|
||||
max: 1,
|
||||
max: 6,
|
||||
})
|
||||
return drizzle(client, {})
|
||||
}
|
||||
|
||||
const createClient = "NODE_ENV" in process.env ? memo(init) : init
|
||||
const createClient = init
|
||||
|
||||
import { PgTransaction, type PgTransactionConfig } from "drizzle-orm/pg-core"
|
||||
import type { ExtractTablesWithRelations } from "drizzle-orm"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode/cloud-function",
|
||||
"version": "0.5.29",
|
||||
"version": "0.6.1",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ export const database = new sst.Linkable("Database", {
|
||||
database: "postgres",
|
||||
username: DATABASE_USERNAME.value,
|
||||
password: DATABASE_PASSWORD.value,
|
||||
port: 5432,
|
||||
port: 6432,
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode/function",
|
||||
"version": "0.5.29",
|
||||
"version": "0.6.1",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "0.5.29",
|
||||
"version": "0.6.1",
|
||||
"name": "opencode",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
|
||||
@@ -10,6 +10,7 @@ import { bootstrap } from "../bootstrap"
|
||||
import { MessageV2 } from "../../session/message-v2"
|
||||
import { Identifier } from "../../id/id"
|
||||
import { Agent } from "../../agent/agent"
|
||||
import { Command } from "../../command"
|
||||
|
||||
const TOOL: Record<string, [string, string]> = {
|
||||
todowrite: ["Todo", UI.Style.TEXT_WARNING_BOLD],
|
||||
@@ -35,6 +36,10 @@ export const RunCommand = cmd({
|
||||
array: true,
|
||||
default: [],
|
||||
})
|
||||
.option("command", {
|
||||
describe: "the command to run, use message for args",
|
||||
type: "string",
|
||||
})
|
||||
.option("continue", {
|
||||
alias: ["c"],
|
||||
describe: "continue the last session",
|
||||
@@ -64,12 +69,19 @@ export const RunCommand = cmd({
|
||||
|
||||
if (!process.stdin.isTTY) message += "\n" + (await Bun.stdin.text())
|
||||
|
||||
if (message.trim().length === 0) {
|
||||
UI.error("Message cannot be empty")
|
||||
if (message.trim().length === 0 && !args.command) {
|
||||
UI.error("You must provide a message or a command")
|
||||
return
|
||||
}
|
||||
|
||||
await bootstrap(process.cwd(), async () => {
|
||||
if (args.command) {
|
||||
const exists = await Command.get(args.command)
|
||||
if (!exists) {
|
||||
UI.error(`Command "${args.command}" not found`)
|
||||
return
|
||||
}
|
||||
}
|
||||
const session = await (async () => {
|
||||
if (args.continue) {
|
||||
const it = Session.list()
|
||||
@@ -172,12 +184,26 @@ export const RunCommand = cmd({
|
||||
UI.error(err)
|
||||
})
|
||||
|
||||
if (args.command) {
|
||||
await Session.command({
|
||||
messageID: Identifier.ascending("message"),
|
||||
sessionID: session.id,
|
||||
agent: agent.name,
|
||||
model: providerID + "/" + modelID,
|
||||
command: args.command,
|
||||
arguments: message,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const messageID = Identifier.ascending("message")
|
||||
const result = await Session.chat({
|
||||
const result = await Session.prompt({
|
||||
sessionID: session.id,
|
||||
messageID,
|
||||
providerID,
|
||||
modelID,
|
||||
model: {
|
||||
providerID,
|
||||
modelID,
|
||||
},
|
||||
agent: agent.name,
|
||||
parts: [
|
||||
{
|
||||
@@ -190,7 +216,7 @@ export const RunCommand = cmd({
|
||||
|
||||
const isPiped = !process.stdout.isTTY
|
||||
if (isPiped) {
|
||||
const match = result.parts.findLast((x) => x.type === "text")
|
||||
const match = result.parts.findLast((x: any) => x.type === "text") as any
|
||||
if (match) process.stdout.write(UI.markdown(match.text))
|
||||
if (errorMsg) process.stdout.write(errorMsg)
|
||||
}
|
||||
|
||||
@@ -397,6 +397,21 @@ export namespace Config {
|
||||
.object({
|
||||
apiKey: z.string().optional(),
|
||||
baseURL: z.string().optional(),
|
||||
timeout: z
|
||||
.union([
|
||||
z
|
||||
.number()
|
||||
.int()
|
||||
.positive()
|
||||
.describe(
|
||||
"Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout.",
|
||||
),
|
||||
z.literal(false).describe("Disable timeout for this provider entirely."),
|
||||
])
|
||||
.optional()
|
||||
.describe(
|
||||
"Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout.",
|
||||
),
|
||||
})
|
||||
.catchall(z.any())
|
||||
.optional(),
|
||||
|
||||
@@ -59,7 +59,8 @@ export namespace LSPClient {
|
||||
return null
|
||||
})
|
||||
connection.onRequest("workspace/configuration", async () => {
|
||||
return [{}]
|
||||
// Return server initialization options
|
||||
return [input.server.initialization ?? {}]
|
||||
})
|
||||
connection.listen()
|
||||
|
||||
@@ -108,6 +109,12 @@ export namespace LSPClient {
|
||||
|
||||
await connection.sendNotification("initialized", {})
|
||||
|
||||
if (input.server.initialization) {
|
||||
await connection.sendNotification("workspace/didChangeConfiguration", {
|
||||
settings: input.server.initialization,
|
||||
})
|
||||
}
|
||||
|
||||
const files: {
|
||||
[path: string]: number
|
||||
} = {}
|
||||
|
||||
@@ -193,7 +193,7 @@ export namespace LSPServer {
|
||||
}
|
||||
|
||||
export const Gopls: Info = {
|
||||
id: "golang",
|
||||
id: "gopls",
|
||||
root: async (file) => {
|
||||
const work = await NearestRoot(["go.work"])(file)
|
||||
if (work) return work
|
||||
@@ -298,6 +298,23 @@ export namespace LSPServer {
|
||||
args.push(...["run", js])
|
||||
}
|
||||
args.push("--stdio")
|
||||
|
||||
const initialization: Record<string, string> = {}
|
||||
|
||||
const potentialVenvPaths = [process.env["VIRTUAL_ENV"], path.join(root, ".venv"), path.join(root, "venv")].filter(
|
||||
(p): p is string => p !== undefined,
|
||||
)
|
||||
for (const venvPath of potentialVenvPaths) {
|
||||
const isWindows = process.platform === "win32"
|
||||
const potentialPythonPath = isWindows
|
||||
? path.join(venvPath, "Scripts", "python.exe")
|
||||
: path.join(venvPath, "bin", "python")
|
||||
if (await Bun.file(potentialPythonPath).exists()) {
|
||||
initialization["pythonPath"] = potentialPythonPath
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const proc = spawn(binary, args, {
|
||||
cwd: root,
|
||||
env: {
|
||||
@@ -307,6 +324,7 @@ export namespace LSPServer {
|
||||
})
|
||||
return {
|
||||
process: proc,
|
||||
initialization,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -153,7 +153,10 @@ export namespace Provider {
|
||||
options: Record<string, any>
|
||||
}
|
||||
} = {}
|
||||
const models = new Map<string, { info: ModelsDev.Model; language: LanguageModel }>()
|
||||
const models = new Map<
|
||||
string,
|
||||
{ providerID: string; modelID: string; info: ModelsDev.Model; language: LanguageModel }
|
||||
>()
|
||||
const sdk = new Map<string, SDK>()
|
||||
|
||||
log.info("init")
|
||||
@@ -320,9 +323,16 @@ export namespace Provider {
|
||||
const pkg = provider.npm ?? provider.id
|
||||
const mod = await import(await BunProc.install(pkg, "latest"))
|
||||
const fn = mod[Object.keys(mod).find((key) => key.startsWith("create"))!]
|
||||
let options = { ...s.providers[provider.id]?.options }
|
||||
if (options["timeout"] !== undefined) {
|
||||
// Only override fetch if user explicitly sets timeout
|
||||
options["fetch"] = async (input: any, init?: any) => {
|
||||
return await fetch(input, { ...init, timeout: options["timeout"] })
|
||||
}
|
||||
}
|
||||
const loaded = fn({
|
||||
name: provider.id,
|
||||
...s.providers[provider.id]?.options,
|
||||
...options,
|
||||
})
|
||||
s.sdk.set(provider.id, loaded)
|
||||
return loaded as SDK
|
||||
@@ -355,10 +365,14 @@ export namespace Provider {
|
||||
const language = provider.getModel ? await provider.getModel(sdk, modelID) : sdk.languageModel(modelID)
|
||||
log.info("found", { providerID, modelID })
|
||||
s.models.set(key, {
|
||||
providerID,
|
||||
modelID,
|
||||
info,
|
||||
language,
|
||||
})
|
||||
return {
|
||||
modelID,
|
||||
providerID,
|
||||
info,
|
||||
language,
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ export const ProjectRoute = new Hono()
|
||||
"/current",
|
||||
describeRoute({
|
||||
description: "Get the current project",
|
||||
operationId: "butt.current",
|
||||
operationId: "project.current",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Current project",
|
||||
|
||||
@@ -53,6 +53,9 @@ export namespace Server {
|
||||
const app = new Hono()
|
||||
export const App = app
|
||||
.onError((err, c) => {
|
||||
log.error("failed", {
|
||||
error: err,
|
||||
})
|
||||
if (err instanceof NamedError) {
|
||||
return c.json(err.toObject(), {
|
||||
status: 400,
|
||||
@@ -606,7 +609,7 @@ export namespace Server {
|
||||
"/session/:id/message",
|
||||
describeRoute({
|
||||
description: "Create and send a new message to a session",
|
||||
operationId: "session.chat",
|
||||
operationId: "session.prompt",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Created message",
|
||||
@@ -629,11 +632,11 @@ export namespace Server {
|
||||
id: z.string().openapi({ description: "Session ID" }),
|
||||
}),
|
||||
),
|
||||
zValidator("json", Session.ChatInput.omit({ sessionID: true })),
|
||||
zValidator("json", Session.PromptInput.omit({ sessionID: true })),
|
||||
async (c) => {
|
||||
const sessionID = c.req.valid("param").id
|
||||
const body = c.req.valid("json")
|
||||
const msg = await Session.chat({ ...body, sessionID })
|
||||
const msg = await Session.prompt({ ...body, sessionID })
|
||||
return c.json(msg)
|
||||
},
|
||||
)
|
||||
|
||||
@@ -364,11 +364,15 @@ export namespace Session {
|
||||
return part
|
||||
}
|
||||
|
||||
export const ChatInput = z.object({
|
||||
export const PromptInput = z.object({
|
||||
sessionID: Identifier.schema("session"),
|
||||
messageID: Identifier.schema("message").optional(),
|
||||
providerID: z.string(),
|
||||
modelID: z.string(),
|
||||
model: z
|
||||
.object({
|
||||
providerID: z.string(),
|
||||
modelID: z.string(),
|
||||
})
|
||||
.optional(),
|
||||
agent: z.string().optional(),
|
||||
system: z.string().optional(),
|
||||
tools: z.record(z.boolean()).optional(),
|
||||
@@ -407,10 +411,10 @@ export namespace Session {
|
||||
]),
|
||||
),
|
||||
})
|
||||
export type ChatInput = z.infer<typeof ChatInput>
|
||||
export type ChatInput = z.infer<typeof PromptInput>
|
||||
|
||||
export async function chat(
|
||||
input: z.infer<typeof ChatInput>,
|
||||
export async function prompt(
|
||||
input: z.infer<typeof PromptInput>,
|
||||
): Promise<{ info: MessageV2.Assistant; parts: MessageV2.Part[] }> {
|
||||
const l = log.clone().tag("session", input.sessionID)
|
||||
l.info("chatting")
|
||||
@@ -652,7 +656,16 @@ export namespace Session {
|
||||
})
|
||||
}
|
||||
|
||||
const model = await Provider.getModel(input.providerID, input.modelID)
|
||||
const agent = await Agent.get(inputAgent)
|
||||
const model = await (async () => {
|
||||
if (input.model) {
|
||||
return input.model
|
||||
}
|
||||
if (agent.model) {
|
||||
return agent.model
|
||||
}
|
||||
return Provider.defaultModel()
|
||||
})().then((x) => Provider.getModel(x.providerID, x.modelID))
|
||||
let msgs = await messages(input.sessionID)
|
||||
|
||||
const previous = msgs.filter((x) => x.info.role === "assistant").at(-1)?.info as MessageV2.Assistant
|
||||
@@ -667,10 +680,10 @@ export namespace Session {
|
||||
|
||||
await summarize({
|
||||
sessionID: input.sessionID,
|
||||
providerID: input.providerID,
|
||||
modelID: input.modelID,
|
||||
providerID: model.providerID,
|
||||
modelID: model.info.id,
|
||||
})
|
||||
return chat(input)
|
||||
return prompt(input)
|
||||
}
|
||||
}
|
||||
using abort = lock(input.sessionID)
|
||||
@@ -679,17 +692,17 @@ export namespace Session {
|
||||
if (lastSummary) msgs = msgs.filter((msg) => msg.info.id >= lastSummary.info.id)
|
||||
|
||||
if (msgs.filter((m) => m.info.role === "user").length === 1 && !session.parentID && isDefaultTitle(session.title)) {
|
||||
const small = (await Provider.getSmallModel(input.providerID)) ?? model
|
||||
const small = (await Provider.getSmallModel(model.providerID)) ?? model
|
||||
generateText({
|
||||
maxOutputTokens: small.info.reasoning ? 1024 : 20,
|
||||
providerOptions: {
|
||||
[input.providerID]: {
|
||||
[model.providerID]: {
|
||||
...small.info.options,
|
||||
...ProviderTransform.options(input.providerID, small.info.id, input.sessionID),
|
||||
...ProviderTransform.options(small.providerID, small.modelID, input.sessionID),
|
||||
},
|
||||
},
|
||||
messages: [
|
||||
...SystemPrompt.title(input.providerID).map(
|
||||
...SystemPrompt.title(model.providerID).map(
|
||||
(x): ModelMessage => ({
|
||||
role: "system",
|
||||
content: x,
|
||||
@@ -724,7 +737,6 @@ export namespace Session {
|
||||
})
|
||||
}
|
||||
|
||||
const agent = await Agent.get(inputAgent)
|
||||
if (agent.name === "plan") {
|
||||
msgs.at(-1)?.parts.push({
|
||||
id: Identifier.ascending("part"),
|
||||
@@ -747,12 +759,12 @@ export namespace Session {
|
||||
synthetic: true,
|
||||
})
|
||||
}
|
||||
let system = SystemPrompt.header(input.providerID)
|
||||
let system = SystemPrompt.header(model.providerID)
|
||||
system.push(
|
||||
...(() => {
|
||||
if (input.system) return [input.system]
|
||||
if (agent.prompt) return [agent.prompt]
|
||||
return SystemPrompt.provider(input.modelID)
|
||||
return SystemPrompt.provider(model.modelID)
|
||||
})(),
|
||||
)
|
||||
system.push(...(await SystemPrompt.environment()))
|
||||
@@ -777,8 +789,8 @@ export namespace Session {
|
||||
reasoning: 0,
|
||||
cache: { read: 0, write: 0 },
|
||||
},
|
||||
modelID: input.modelID,
|
||||
providerID: input.providerID,
|
||||
modelID: model.modelID,
|
||||
providerID: model.providerID,
|
||||
time: {
|
||||
created: Date.now(),
|
||||
},
|
||||
@@ -796,10 +808,10 @@ export namespace Session {
|
||||
|
||||
const enabledTools = pipe(
|
||||
agent.tools,
|
||||
mergeDeep(await ToolRegistry.enabled(input.providerID, input.modelID, agent)),
|
||||
mergeDeep(await ToolRegistry.enabled(model.providerID, model.modelID, agent)),
|
||||
mergeDeep(input.tools ?? {}),
|
||||
)
|
||||
for (const item of await ToolRegistry.tools(input.providerID, input.modelID)) {
|
||||
for (const item of await ToolRegistry.tools(model.providerID, model.modelID)) {
|
||||
if (Wildcard.all(item.id, enabledTools) === false) continue
|
||||
tools[item.id] = tool({
|
||||
id: item.id as any,
|
||||
@@ -909,16 +921,16 @@ export namespace Session {
|
||||
"chat.params",
|
||||
{
|
||||
model: model.info,
|
||||
provider: await Provider.getProvider(input.providerID),
|
||||
provider: await Provider.getProvider(model.providerID),
|
||||
message: userMsg,
|
||||
},
|
||||
{
|
||||
temperature: model.info.temperature
|
||||
? (agent.temperature ?? ProviderTransform.temperature(input.providerID, input.modelID))
|
||||
? (agent.temperature ?? ProviderTransform.temperature(model.providerID, model.modelID))
|
||||
: undefined,
|
||||
topP: agent.topP ?? ProviderTransform.topP(input.providerID, input.modelID),
|
||||
topP: agent.topP ?? ProviderTransform.topP(model.providerID, model.modelID),
|
||||
options: {
|
||||
...ProviderTransform.options(input.providerID, input.modelID, input.sessionID),
|
||||
...ProviderTransform.options(model.providerID, model.modelID, input.sessionID),
|
||||
...model.info.options,
|
||||
...agent.options,
|
||||
},
|
||||
@@ -962,8 +974,8 @@ export namespace Session {
|
||||
reasoning: 0,
|
||||
cache: { read: 0, write: 0 },
|
||||
},
|
||||
modelID: input.modelID,
|
||||
providerID: input.providerID,
|
||||
modelID: model.modelID,
|
||||
providerID: model.providerID,
|
||||
mode: inputAgent,
|
||||
time: {
|
||||
created: Date.now(),
|
||||
@@ -987,7 +999,7 @@ export namespace Session {
|
||||
}
|
||||
},
|
||||
headers:
|
||||
input.providerID === "opencode"
|
||||
model.providerID === "opencode"
|
||||
? {
|
||||
"x-opencode-session": input.sessionID,
|
||||
"x-opencode-request": userMsg.id,
|
||||
@@ -1010,7 +1022,7 @@ export namespace Session {
|
||||
return false
|
||||
},
|
||||
providerOptions: {
|
||||
[input.providerID]: params.options,
|
||||
[model.providerID]: params.options,
|
||||
},
|
||||
temperature: params.temperature,
|
||||
topP: params.topP,
|
||||
@@ -1031,7 +1043,7 @@ export namespace Session {
|
||||
async transformParams(args) {
|
||||
if (args.type === "stream") {
|
||||
// @ts-expect-error
|
||||
args.params.prompt = ProviderTransform.message(args.params.prompt, input.providerID, input.modelID)
|
||||
args.params.prompt = ProviderTransform.message(args.params.prompt, model.providerID, model.modelID)
|
||||
}
|
||||
return args.params
|
||||
},
|
||||
@@ -1044,7 +1056,7 @@ export namespace Session {
|
||||
const unprocessed = queued.find((x) => !x.processed)
|
||||
if (unprocessed) {
|
||||
unprocessed.processed = true
|
||||
return chat(unprocessed.input)
|
||||
return prompt(unprocessed.input)
|
||||
}
|
||||
for (const item of queued) {
|
||||
item.callback(result)
|
||||
@@ -1143,6 +1155,7 @@ export namespace Session {
|
||||
const proc = spawn(shell, args, {
|
||||
cwd: Instance.directory,
|
||||
signal: abort.signal,
|
||||
detached: true,
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
env: {
|
||||
...process.env,
|
||||
@@ -1150,6 +1163,11 @@ export namespace Session {
|
||||
},
|
||||
})
|
||||
|
||||
abort.signal.addEventListener("abort", () => {
|
||||
if (!proc.pid) return
|
||||
process.kill(-proc.pid)
|
||||
})
|
||||
|
||||
let output = ""
|
||||
|
||||
proc.stdout?.on("data", (chunk) => {
|
||||
@@ -1214,16 +1232,9 @@ export namespace Session {
|
||||
const fileRegex = /@([^\s]+)/g
|
||||
|
||||
export async function command(input: CommandInput) {
|
||||
log.info("command", input)
|
||||
const command = await Command.get(input.command)
|
||||
const agent = command.agent ?? input.agent ?? "build"
|
||||
const fmtModel = (model: { providerID: string; modelID: string }) => `${model.providerID}/${model.modelID}`
|
||||
|
||||
const model =
|
||||
command.model ??
|
||||
(command.agent && (await Agent.get(command.agent).then((x) => (x.model ? fmtModel(x.model) : undefined)))) ??
|
||||
input.model ??
|
||||
(input.agent && (await Agent.get(input.agent).then((x) => (x.model ? fmtModel(x.model) : undefined)))) ??
|
||||
fmtModel(await Provider.defaultModel())
|
||||
|
||||
let template = command.template.replace("$ARGUMENTS", input.arguments)
|
||||
|
||||
@@ -1267,10 +1278,18 @@ export namespace Session {
|
||||
})
|
||||
}
|
||||
|
||||
return chat({
|
||||
return prompt({
|
||||
sessionID: input.sessionID,
|
||||
messageID: input.messageID,
|
||||
...Provider.parseModel(model!),
|
||||
model: (() => {
|
||||
if (input.model) {
|
||||
return Provider.parseModel(input.model)
|
||||
}
|
||||
if (command.model) {
|
||||
return Provider.parseModel(command.model)
|
||||
}
|
||||
return undefined
|
||||
})(),
|
||||
agent,
|
||||
parts,
|
||||
})
|
||||
@@ -1637,7 +1656,7 @@ export namespace Session {
|
||||
const filtered = msgs.filter((msg) => !lastSummary || msg.info.id >= lastSummary.info.id)
|
||||
const model = await Provider.getModel(input.providerID, input.modelID)
|
||||
const system = [
|
||||
...SystemPrompt.summarize(input.providerID),
|
||||
...SystemPrompt.summarize(model.providerID),
|
||||
...(await SystemPrompt.environment()),
|
||||
...(await SystemPrompt.custom()),
|
||||
]
|
||||
@@ -1655,7 +1674,7 @@ export namespace Session {
|
||||
summary: true,
|
||||
cost: 0,
|
||||
modelID: input.modelID,
|
||||
providerID: input.providerID,
|
||||
providerID: model.providerID,
|
||||
tokens: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
@@ -1764,11 +1783,13 @@ export namespace Session {
|
||||
providerID: string
|
||||
messageID: string
|
||||
}) {
|
||||
await Session.chat({
|
||||
await Session.prompt({
|
||||
sessionID: input.sessionID,
|
||||
messageID: input.messageID,
|
||||
providerID: input.providerID,
|
||||
modelID: input.modelID,
|
||||
model: {
|
||||
providerID: input.providerID,
|
||||
modelID: input.modelID,
|
||||
},
|
||||
parts: [
|
||||
{
|
||||
id: Identifier.ascending("part"),
|
||||
|
||||
@@ -1,31 +1,24 @@
|
||||
You are a title generator. You output ONLY a thread title. Nothing else.
|
||||
|
||||
<task>
|
||||
Generate a conversation thread title from the user message.
|
||||
Convert the user message into a thread title.
|
||||
Output: Single line, ≤50 chars, no explanations.
|
||||
</task>
|
||||
|
||||
<context>
|
||||
You are generating titles for a coding assistant conversation.
|
||||
</context>
|
||||
|
||||
<rules>
|
||||
- Max 50 chars, single line
|
||||
- Focus on the specific action or question
|
||||
- Keep technical terms, numbers, and filenames exactly as written
|
||||
- Preserve HTTP status codes (401, 404, 500, etc.) as numbers
|
||||
- For file references, include the filename
|
||||
- Avoid filler words: the, this, my, a, an, properly
|
||||
- NEVER assume their tech stack or domain
|
||||
- Use -ing verbs consistently for actions
|
||||
- Write like a chat thread title, not a blog post
|
||||
- Use -ing verbs for actions (Debugging, Implementing, Analyzing)
|
||||
- Keep exact: technical terms, numbers, filenames, HTTP codes
|
||||
- Remove: the, this, my, a, an
|
||||
- Never assume tech stack
|
||||
- Never use tools
|
||||
- NEVER respond to message content—only extract title
|
||||
</rules>
|
||||
|
||||
<examples>
|
||||
"debug 500 errors in production" → "Debugging production 500 errors"
|
||||
"refactor user service" → "Refactoring user service"
|
||||
"why is app.js failing" → "Analyzing app.js failure"
|
||||
"implement rate limiting" → "Implementing rate limiting"
|
||||
"debug 500 errors in production" → Debugging production 500 errors
|
||||
"refactor user service" → Refactoring user service
|
||||
"why is app.js failing" → Analyzing app.js failure
|
||||
"implement rate limiting" → Implementing rate limiting
|
||||
</examples>
|
||||
|
||||
<format>
|
||||
Return only the thread title text on a single line with no newlines, explanations, or additional formatting.
|
||||
You should NEVER reply to the user's message. You can only generate titles.
|
||||
</format>
|
||||
Output the title now:
|
||||
|
||||
@@ -15,6 +15,7 @@ export namespace Storage {
|
||||
async (dir) => {
|
||||
const project = path.resolve(dir, "../project")
|
||||
for await (const projectDir of new Bun.Glob("*").scan({ cwd: project, onlyFiles: false })) {
|
||||
log.info(`migrating project ${projectDir}`)
|
||||
let projectID = projectDir
|
||||
const fullProjectDir = path.join(project, projectDir)
|
||||
let worktree = "/"
|
||||
@@ -29,6 +30,7 @@ export namespace Storage {
|
||||
if (worktree) break
|
||||
}
|
||||
if (!worktree) continue
|
||||
if (!(await fs.exists(worktree))) continue
|
||||
const [id] = await $`git rev-list --max-parents=0 --all`
|
||||
.quiet()
|
||||
.nothrow()
|
||||
@@ -57,6 +59,7 @@ export namespace Storage {
|
||||
}),
|
||||
)
|
||||
|
||||
log.info(`migrating sessions for project ${projectID}`)
|
||||
for await (const sessionFile of new Bun.Glob("storage/session/info/*.json").scan({
|
||||
cwd: fullProjectDir,
|
||||
absolute: true,
|
||||
@@ -68,6 +71,7 @@ export namespace Storage {
|
||||
})
|
||||
const session = await Bun.file(sessionFile).json()
|
||||
await Bun.write(dest, JSON.stringify(session))
|
||||
log.info(`migrating messages for session ${session.id}`)
|
||||
for await (const msgFile of new Bun.Glob(`storage/session/message/${session.id}/*.json`).scan({
|
||||
cwd: fullProjectDir,
|
||||
absolute: true,
|
||||
@@ -80,6 +84,7 @@ export namespace Storage {
|
||||
const message = await Bun.file(msgFile).json()
|
||||
await Bun.write(dest, JSON.stringify(message))
|
||||
|
||||
log.info(`migrating parts for message ${message.id}`)
|
||||
for await (const partFile of new Bun.Glob(`storage/session/part/${session.id}/${message.id}/*.json`).scan(
|
||||
{
|
||||
cwd: fullProjectDir,
|
||||
@@ -110,7 +115,9 @@ export namespace Storage {
|
||||
for (let index = migration; index < MIGRATIONS.length; index++) {
|
||||
log.info("running migration", { index })
|
||||
const migration = MIGRATIONS[index]
|
||||
await migration(dir)
|
||||
await migration(dir).catch((e) => {
|
||||
log.error("failed to run migration", { error: e, index })
|
||||
})
|
||||
await Bun.write(path.join(dir, "migration"), (index + 1).toString())
|
||||
}
|
||||
return {
|
||||
|
||||
@@ -51,11 +51,13 @@ export const TaskTool = Tool.define("task", async () => {
|
||||
ctx.abort.addEventListener("abort", () => {
|
||||
Session.abort(session.id)
|
||||
})
|
||||
const result = await Session.chat({
|
||||
const result = await Session.prompt({
|
||||
messageID,
|
||||
sessionID: session.id,
|
||||
modelID: model.modelID,
|
||||
providerID: model.providerID,
|
||||
model: {
|
||||
modelID: model.modelID,
|
||||
providerID: model.providerID,
|
||||
},
|
||||
agent: agent.name,
|
||||
tools: {
|
||||
todowrite: false,
|
||||
@@ -75,9 +77,9 @@ export const TaskTool = Tool.define("task", async () => {
|
||||
return {
|
||||
title: params.description,
|
||||
metadata: {
|
||||
summary: result.parts.filter((x) => x.type === "tool"),
|
||||
summary: result.parts.filter((x: any) => x.type === "tool"),
|
||||
},
|
||||
output: result.parts.findLast((x) => x.type === "text")?.text ?? "",
|
||||
output: (result.parts.findLast((x: any) => x.type === "text") as any)?.text ?? "",
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -83,6 +83,13 @@ export namespace Log {
|
||||
await Promise.all(filesToDelete.map((file) => fs.unlink(file).catch(() => {})))
|
||||
}
|
||||
|
||||
function formatError(error: Error, depth = 0): string {
|
||||
const result = error.message
|
||||
return error.cause instanceof Error && depth < 10
|
||||
? result + " Caused by: " + formatError(error.cause, depth + 1)
|
||||
: result
|
||||
}
|
||||
|
||||
let last = Date.now()
|
||||
export function create(tags?: Record<string, any>) {
|
||||
tags = tags || {}
|
||||
@@ -103,7 +110,7 @@ export namespace Log {
|
||||
.filter(([_, value]) => value !== undefined && value !== null)
|
||||
.map(([key, value]) => {
|
||||
const prefix = `${key}=`
|
||||
if (value instanceof Error) return prefix + value.message
|
||||
if (value instanceof Error) return prefix + formatError(value)
|
||||
if (typeof value === "object") return prefix + JSON.stringify(value)
|
||||
return prefix + value
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "0.5.29",
|
||||
"version": "0.6.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"typecheck": "tsc --noEmit"
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
import { Plugin } from "./index"
|
||||
|
||||
export const ExamplePlugin: Plugin = async ({ client, $ }) => {
|
||||
export const ExamplePlugin: Plugin = async ({
|
||||
client: _client,
|
||||
$: _shell,
|
||||
project: _project,
|
||||
directory: _directory,
|
||||
worktree: _worktree,
|
||||
}) => {
|
||||
return {
|
||||
permission: {},
|
||||
async "chat.params"(input, output) {
|
||||
async "chat.params"(_input, output) {
|
||||
output.topP = 1
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
".": "0.5.0"
|
||||
".": "0.8.0"
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
configured_endpoints: 43
|
||||
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-24d6bfcc66bba2e3a826fdad7e48a474c5aa9193a6bb89dc8ab1feb1f3d9bf72.yml
|
||||
openapi_spec_hash: db8b553192d9027e1f9254096406cfc2
|
||||
config_hash: 1b0d220e033fe9f683abf7048e7ad076
|
||||
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-97b61518d8666ea7cb310af04248e00bcf8dc9753ba3c7e84471df72b3232004.yml
|
||||
openapi_spec_hash: a3500531973ad999c350b87c21aa3ab8
|
||||
config_hash: 026ef000d34bf2f930e7b41e77d2d3ff
|
||||
|
||||
@@ -1,5 +1,29 @@
|
||||
# Changelog
|
||||
|
||||
## 0.8.0 (2025-09-01)
|
||||
|
||||
Full Changelog: [v0.7.0...v0.8.0](https://github.com/sst/opencode-sdk-go/compare/v0.7.0...v0.8.0)
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** api update ([ae87a71](https://github.com/sst/opencode-sdk-go/commit/ae87a71949994590ace8285a39f0991ef34b664d))
|
||||
|
||||
## 0.7.0 (2025-09-01)
|
||||
|
||||
Full Changelog: [v0.6.0...v0.7.0](https://github.com/sst/opencode-sdk-go/compare/v0.6.0...v0.7.0)
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** api update ([64bb1b1](https://github.com/sst/opencode-sdk-go/commit/64bb1b1ee0cbe153abc6fb7bd9703b47911724d4))
|
||||
|
||||
## 0.6.0 (2025-09-01)
|
||||
|
||||
Full Changelog: [v0.5.0...v0.6.0](https://github.com/sst/opencode-sdk-go/compare/v0.5.0...v0.6.0)
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** api update ([928e384](https://github.com/sst/opencode-sdk-go/commit/928e3843355f96899f046f002b84372281dad0c8))
|
||||
|
||||
## 0.5.0 (2025-08-31)
|
||||
|
||||
Full Changelog: [v0.4.0...v0.5.0](https://github.com/sst/opencode-sdk-go/compare/v0.4.0...v0.5.0)
|
||||
|
||||
@@ -24,7 +24,7 @@ Or to pin the version:
|
||||
<!-- x-release-please-start-version -->
|
||||
|
||||
```sh
|
||||
go get -u 'github.com/sst/opencode-sdk-go@v0.5.0'
|
||||
go get -u 'github.com/sst/opencode-sdk-go@v0.8.0'
|
||||
```
|
||||
|
||||
<!-- x-release-please-end -->
|
||||
|
||||
@@ -143,10 +143,10 @@ Response Types:
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#ToolStatePending">ToolStatePending</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#ToolStateRunning">ToolStateRunning</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#UserMessage">UserMessage</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionChatResponse">SessionChatResponse</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionCommandResponse">SessionCommandResponse</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionMessageResponse">SessionMessageResponse</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionMessagesResponse">SessionMessagesResponse</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionPromptResponse">SessionPromptResponse</a>
|
||||
|
||||
Methods:
|
||||
|
||||
@@ -155,13 +155,13 @@ Methods:
|
||||
- <code title="get /session">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.List">List</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, query <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionListParams">SessionListParams</a>) ([]<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="delete /session/{id}">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Delete">Delete</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionDeleteParams">SessionDeleteParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="post /session/{id}/abort">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Abort">Abort</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionAbortParams">SessionAbortParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="post /session/{id}/message">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Chat">Chat</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, params <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionChatParams">SessionChatParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionChatResponse">SessionChatResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="get /session/{id}/children">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Children">Children</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, query <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionChildrenParams">SessionChildrenParams</a>) ([]<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="post /session/{id}/command">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Command">Command</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, params <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionCommandParams">SessionCommandParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionCommandResponse">SessionCommandResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="get /session/{id}">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Get">Get</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, query <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionGetParams">SessionGetParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="post /session/{id}/init">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Init">Init</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, params <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionInitParams">SessionInitParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="get /session/{id}/message/{messageID}">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Message">Message</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, messageID <a href="https://pkg.go.dev/builtin#string">string</a>, query <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionMessageParams">SessionMessageParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionMessageResponse">SessionMessageResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="get /session/{id}/message">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Messages">Messages</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, query <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionMessagesParams">SessionMessagesParams</a>) ([]<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionMessagesResponse">SessionMessagesResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="post /session/{id}/message">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Prompt">Prompt</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, params <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionPromptParams">SessionPromptParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionPromptResponse">SessionPromptResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="post /session/{id}/revert">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Revert">Revert</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, params <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionRevertParams">SessionRevertParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="post /session/{id}/share">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Share">Share</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionShareParams">SessionShareParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="post /session/{id}/shell">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Shell">Shell</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, params <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionShellParams">SessionShellParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#AssistantMessage">AssistantMessage</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/sst/opencode-sdk-go/internal/param"
|
||||
"github.com/sst/opencode-sdk-go/internal/requestconfig"
|
||||
"github.com/sst/opencode-sdk-go/option"
|
||||
"github.com/sst/opencode-sdk-go/shared"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
@@ -1650,10 +1651,13 @@ func (r configProviderModelsLimitJSON) RawJSON() string {
|
||||
}
|
||||
|
||||
type ConfigProviderOptions struct {
|
||||
APIKey string `json:"apiKey"`
|
||||
BaseURL string `json:"baseURL"`
|
||||
ExtraFields map[string]interface{} `json:"-,extras"`
|
||||
JSON configProviderOptionsJSON `json:"-"`
|
||||
APIKey string `json:"apiKey"`
|
||||
BaseURL string `json:"baseURL"`
|
||||
// Timeout in milliseconds for requests to this provider. Default is 300000 (5
|
||||
// minutes). Set to false to disable timeout.
|
||||
Timeout ConfigProviderOptionsTimeoutUnion `json:"timeout"`
|
||||
ExtraFields map[string]interface{} `json:"-,extras"`
|
||||
JSON configProviderOptionsJSON `json:"-"`
|
||||
}
|
||||
|
||||
// configProviderOptionsJSON contains the JSON metadata for the struct
|
||||
@@ -1661,6 +1665,7 @@ type ConfigProviderOptions struct {
|
||||
type configProviderOptionsJSON struct {
|
||||
APIKey apijson.Field
|
||||
BaseURL apijson.Field
|
||||
Timeout apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
@@ -1673,6 +1678,33 @@ func (r configProviderOptionsJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
// Timeout in milliseconds for requests to this provider. Default is 300000 (5
|
||||
// minutes). Set to false to disable timeout.
|
||||
//
|
||||
// Union satisfied by [shared.UnionInt] or [shared.UnionBool].
|
||||
type ConfigProviderOptionsTimeoutUnion interface {
|
||||
ImplementsConfigProviderOptionsTimeoutUnion()
|
||||
}
|
||||
|
||||
func init() {
|
||||
apijson.RegisterUnion(
|
||||
reflect.TypeOf((*ConfigProviderOptionsTimeoutUnion)(nil)).Elem(),
|
||||
"",
|
||||
apijson.UnionVariant{
|
||||
TypeFilter: gjson.Number,
|
||||
Type: reflect.TypeOf(shared.UnionInt(0)),
|
||||
},
|
||||
apijson.UnionVariant{
|
||||
TypeFilter: gjson.True,
|
||||
Type: reflect.TypeOf(shared.UnionBool(false)),
|
||||
},
|
||||
apijson.UnionVariant{
|
||||
TypeFilter: gjson.False,
|
||||
Type: reflect.TypeOf(shared.UnionBool(false)),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// Control sharing behavior:'manual' allows manual sharing via commands, 'auto'
|
||||
// enables automatic sharing, 'disabled' disables all sharing
|
||||
type ConfigShare string
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
package internal
|
||||
|
||||
const PackageVersion = "0.5.0" // x-release-please-version
|
||||
const PackageVersion = "0.8.0" // x-release-please-version
|
||||
|
||||
@@ -42,15 +42,19 @@ func (r *PathService) Get(ctx context.Context, query PathGetParams, opts ...opti
|
||||
}
|
||||
|
||||
type Path struct {
|
||||
Config string `json:"config,required"`
|
||||
State string `json:"state,required"`
|
||||
JSON pathJSON `json:"-"`
|
||||
Config string `json:"config,required"`
|
||||
Directory string `json:"directory,required"`
|
||||
State string `json:"state,required"`
|
||||
Worktree string `json:"worktree,required"`
|
||||
JSON pathJSON `json:"-"`
|
||||
}
|
||||
|
||||
// pathJSON contains the JSON metadata for the struct [Path]
|
||||
type pathJSON struct {
|
||||
Config apijson.Field
|
||||
Directory apijson.Field
|
||||
State apijson.Field
|
||||
Worktree apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
+110
-102
@@ -92,18 +92,6 @@ func (r *SessionService) Abort(ctx context.Context, id string, body SessionAbort
|
||||
return
|
||||
}
|
||||
|
||||
// Create and send a new message to a session
|
||||
func (r *SessionService) Chat(ctx context.Context, id string, params SessionChatParams, opts ...option.RequestOption) (res *SessionChatResponse, err error) {
|
||||
opts = append(r.Options[:], opts...)
|
||||
if id == "" {
|
||||
err = errors.New("missing required id parameter")
|
||||
return
|
||||
}
|
||||
path := fmt.Sprintf("session/%s/message", id)
|
||||
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, params, &res, opts...)
|
||||
return
|
||||
}
|
||||
|
||||
// Get a session's children
|
||||
func (r *SessionService) Children(ctx context.Context, id string, query SessionChildrenParams, opts ...option.RequestOption) (res *[]Session, err error) {
|
||||
opts = append(r.Options[:], opts...)
|
||||
@@ -180,6 +168,18 @@ func (r *SessionService) Messages(ctx context.Context, id string, query SessionM
|
||||
return
|
||||
}
|
||||
|
||||
// Create and send a new message to a session
|
||||
func (r *SessionService) Prompt(ctx context.Context, id string, params SessionPromptParams, opts ...option.RequestOption) (res *SessionPromptResponse, err error) {
|
||||
opts = append(r.Options[:], opts...)
|
||||
if id == "" {
|
||||
err = errors.New("missing required id parameter")
|
||||
return
|
||||
}
|
||||
path := fmt.Sprintf("session/%s/message", id)
|
||||
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, params, &res, opts...)
|
||||
return
|
||||
}
|
||||
|
||||
// Revert a message
|
||||
func (r *SessionService) Revert(ctx context.Context, id string, params SessionRevertParams, opts ...option.RequestOption) (res *Session, err error) {
|
||||
opts = append(r.Options[:], opts...)
|
||||
@@ -333,7 +333,7 @@ func (r AgentPartInputParam) MarshalJSON() (data []byte, err error) {
|
||||
return apijson.MarshalRoot(r)
|
||||
}
|
||||
|
||||
func (r AgentPartInputParam) implementsSessionChatParamsPartUnion() {}
|
||||
func (r AgentPartInputParam) implementsSessionPromptParamsPartUnion() {}
|
||||
|
||||
type AgentPartInputType string
|
||||
|
||||
@@ -709,7 +709,7 @@ func (r FilePartInputParam) MarshalJSON() (data []byte, err error) {
|
||||
return apijson.MarshalRoot(r)
|
||||
}
|
||||
|
||||
func (r FilePartInputParam) implementsSessionChatParamsPartUnion() {}
|
||||
func (r FilePartInputParam) implementsSessionPromptParamsPartUnion() {}
|
||||
|
||||
type FilePartInputType string
|
||||
|
||||
@@ -1820,7 +1820,7 @@ func (r TextPartInputParam) MarshalJSON() (data []byte, err error) {
|
||||
return apijson.MarshalRoot(r)
|
||||
}
|
||||
|
||||
func (r TextPartInputParam) implementsSessionChatParamsPartUnion() {}
|
||||
func (r TextPartInputParam) implementsSessionPromptParamsPartUnion() {}
|
||||
|
||||
type TextPartInputType string
|
||||
|
||||
@@ -2296,29 +2296,6 @@ func (r userMessageTimeJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
type SessionChatResponse struct {
|
||||
Info AssistantMessage `json:"info,required"`
|
||||
Parts []Part `json:"parts,required"`
|
||||
JSON sessionChatResponseJSON `json:"-"`
|
||||
}
|
||||
|
||||
// sessionChatResponseJSON contains the JSON metadata for the struct
|
||||
// [SessionChatResponse]
|
||||
type sessionChatResponseJSON struct {
|
||||
Info apijson.Field
|
||||
Parts apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *SessionChatResponse) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r sessionChatResponseJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
type SessionCommandResponse struct {
|
||||
Info AssistantMessage `json:"info,required"`
|
||||
Parts []Part `json:"parts,required"`
|
||||
@@ -2388,6 +2365,29 @@ func (r sessionMessagesResponseJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
type SessionPromptResponse struct {
|
||||
Info AssistantMessage `json:"info,required"`
|
||||
Parts []Part `json:"parts,required"`
|
||||
JSON sessionPromptResponseJSON `json:"-"`
|
||||
}
|
||||
|
||||
// sessionPromptResponseJSON contains the JSON metadata for the struct
|
||||
// [SessionPromptResponse]
|
||||
type sessionPromptResponseJSON struct {
|
||||
Info apijson.Field
|
||||
Parts apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *SessionPromptResponse) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r sessionPromptResponseJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
type SessionNewParams struct {
|
||||
Directory param.Field[string] `query:"directory"`
|
||||
ParentID param.Field[string] `json:"parentID"`
|
||||
@@ -2459,70 +2459,6 @@ func (r SessionAbortParams) URLQuery() (v url.Values) {
|
||||
})
|
||||
}
|
||||
|
||||
type SessionChatParams struct {
|
||||
ModelID param.Field[string] `json:"modelID,required"`
|
||||
Parts param.Field[[]SessionChatParamsPartUnion] `json:"parts,required"`
|
||||
ProviderID param.Field[string] `json:"providerID,required"`
|
||||
Directory param.Field[string] `query:"directory"`
|
||||
Agent param.Field[string] `json:"agent"`
|
||||
MessageID param.Field[string] `json:"messageID"`
|
||||
System param.Field[string] `json:"system"`
|
||||
Tools param.Field[map[string]bool] `json:"tools"`
|
||||
}
|
||||
|
||||
func (r SessionChatParams) MarshalJSON() (data []byte, err error) {
|
||||
return apijson.MarshalRoot(r)
|
||||
}
|
||||
|
||||
// URLQuery serializes [SessionChatParams]'s query parameters as `url.Values`.
|
||||
func (r SessionChatParams) URLQuery() (v url.Values) {
|
||||
return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
|
||||
ArrayFormat: apiquery.ArrayQueryFormatComma,
|
||||
NestedFormat: apiquery.NestedQueryFormatBrackets,
|
||||
})
|
||||
}
|
||||
|
||||
type SessionChatParamsPart struct {
|
||||
Type param.Field[SessionChatParamsPartsType] `json:"type,required"`
|
||||
ID param.Field[string] `json:"id"`
|
||||
Filename param.Field[string] `json:"filename"`
|
||||
Mime param.Field[string] `json:"mime"`
|
||||
Name param.Field[string] `json:"name"`
|
||||
Source param.Field[interface{}] `json:"source"`
|
||||
Synthetic param.Field[bool] `json:"synthetic"`
|
||||
Text param.Field[string] `json:"text"`
|
||||
Time param.Field[interface{}] `json:"time"`
|
||||
URL param.Field[string] `json:"url"`
|
||||
}
|
||||
|
||||
func (r SessionChatParamsPart) MarshalJSON() (data []byte, err error) {
|
||||
return apijson.MarshalRoot(r)
|
||||
}
|
||||
|
||||
func (r SessionChatParamsPart) implementsSessionChatParamsPartUnion() {}
|
||||
|
||||
// Satisfied by [TextPartInputParam], [FilePartInputParam], [AgentPartInputParam],
|
||||
// [SessionChatParamsPart].
|
||||
type SessionChatParamsPartUnion interface {
|
||||
implementsSessionChatParamsPartUnion()
|
||||
}
|
||||
|
||||
type SessionChatParamsPartsType string
|
||||
|
||||
const (
|
||||
SessionChatParamsPartsTypeText SessionChatParamsPartsType = "text"
|
||||
SessionChatParamsPartsTypeFile SessionChatParamsPartsType = "file"
|
||||
SessionChatParamsPartsTypeAgent SessionChatParamsPartsType = "agent"
|
||||
)
|
||||
|
||||
func (r SessionChatParamsPartsType) IsKnown() bool {
|
||||
switch r {
|
||||
case SessionChatParamsPartsTypeText, SessionChatParamsPartsTypeFile, SessionChatParamsPartsTypeAgent:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type SessionChildrenParams struct {
|
||||
Directory param.Field[string] `query:"directory"`
|
||||
}
|
||||
@@ -2611,6 +2547,78 @@ func (r SessionMessagesParams) URLQuery() (v url.Values) {
|
||||
})
|
||||
}
|
||||
|
||||
type SessionPromptParams struct {
|
||||
Parts param.Field[[]SessionPromptParamsPartUnion] `json:"parts,required"`
|
||||
Directory param.Field[string] `query:"directory"`
|
||||
Agent param.Field[string] `json:"agent"`
|
||||
MessageID param.Field[string] `json:"messageID"`
|
||||
Model param.Field[SessionPromptParamsModel] `json:"model"`
|
||||
System param.Field[string] `json:"system"`
|
||||
Tools param.Field[map[string]bool] `json:"tools"`
|
||||
}
|
||||
|
||||
func (r SessionPromptParams) MarshalJSON() (data []byte, err error) {
|
||||
return apijson.MarshalRoot(r)
|
||||
}
|
||||
|
||||
// URLQuery serializes [SessionPromptParams]'s query parameters as `url.Values`.
|
||||
func (r SessionPromptParams) URLQuery() (v url.Values) {
|
||||
return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
|
||||
ArrayFormat: apiquery.ArrayQueryFormatComma,
|
||||
NestedFormat: apiquery.NestedQueryFormatBrackets,
|
||||
})
|
||||
}
|
||||
|
||||
type SessionPromptParamsPart struct {
|
||||
Type param.Field[SessionPromptParamsPartsType] `json:"type,required"`
|
||||
ID param.Field[string] `json:"id"`
|
||||
Filename param.Field[string] `json:"filename"`
|
||||
Mime param.Field[string] `json:"mime"`
|
||||
Name param.Field[string] `json:"name"`
|
||||
Source param.Field[interface{}] `json:"source"`
|
||||
Synthetic param.Field[bool] `json:"synthetic"`
|
||||
Text param.Field[string] `json:"text"`
|
||||
Time param.Field[interface{}] `json:"time"`
|
||||
URL param.Field[string] `json:"url"`
|
||||
}
|
||||
|
||||
func (r SessionPromptParamsPart) MarshalJSON() (data []byte, err error) {
|
||||
return apijson.MarshalRoot(r)
|
||||
}
|
||||
|
||||
func (r SessionPromptParamsPart) implementsSessionPromptParamsPartUnion() {}
|
||||
|
||||
// Satisfied by [TextPartInputParam], [FilePartInputParam], [AgentPartInputParam],
|
||||
// [SessionPromptParamsPart].
|
||||
type SessionPromptParamsPartUnion interface {
|
||||
implementsSessionPromptParamsPartUnion()
|
||||
}
|
||||
|
||||
type SessionPromptParamsPartsType string
|
||||
|
||||
const (
|
||||
SessionPromptParamsPartsTypeText SessionPromptParamsPartsType = "text"
|
||||
SessionPromptParamsPartsTypeFile SessionPromptParamsPartsType = "file"
|
||||
SessionPromptParamsPartsTypeAgent SessionPromptParamsPartsType = "agent"
|
||||
)
|
||||
|
||||
func (r SessionPromptParamsPartsType) IsKnown() bool {
|
||||
switch r {
|
||||
case SessionPromptParamsPartsTypeText, SessionPromptParamsPartsTypeFile, SessionPromptParamsPartsTypeAgent:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type SessionPromptParamsModel struct {
|
||||
ModelID param.Field[string] `json:"modelID,required"`
|
||||
ProviderID param.Field[string] `json:"providerID,required"`
|
||||
}
|
||||
|
||||
func (r SessionPromptParamsModel) MarshalJSON() (data []byte, err error) {
|
||||
return apijson.MarshalRoot(r)
|
||||
}
|
||||
|
||||
type SessionRevertParams struct {
|
||||
MessageID param.Field[string] `json:"messageID,required"`
|
||||
Directory param.Field[string] `query:"directory"`
|
||||
|
||||
@@ -148,52 +148,6 @@ func TestSessionAbortWithOptionalParams(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionChatWithOptionalParams(t *testing.T) {
|
||||
t.Skip("Prism tests are disabled")
|
||||
baseURL := "http://localhost:4010"
|
||||
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
|
||||
baseURL = envURL
|
||||
}
|
||||
if !testutil.CheckTestServer(t, baseURL) {
|
||||
return
|
||||
}
|
||||
client := opencode.NewClient(
|
||||
option.WithBaseURL(baseURL),
|
||||
)
|
||||
_, err := client.Session.Chat(
|
||||
context.TODO(),
|
||||
"id",
|
||||
opencode.SessionChatParams{
|
||||
ModelID: opencode.F("modelID"),
|
||||
Parts: opencode.F([]opencode.SessionChatParamsPartUnion{opencode.TextPartInputParam{
|
||||
Text: opencode.F("text"),
|
||||
Type: opencode.F(opencode.TextPartInputTypeText),
|
||||
ID: opencode.F("id"),
|
||||
Synthetic: opencode.F(true),
|
||||
Time: opencode.F(opencode.TextPartInputTimeParam{
|
||||
Start: opencode.F(0.000000),
|
||||
End: opencode.F(0.000000),
|
||||
}),
|
||||
}}),
|
||||
ProviderID: opencode.F("providerID"),
|
||||
Directory: opencode.F("directory"),
|
||||
Agent: opencode.F("agent"),
|
||||
MessageID: opencode.F("msg"),
|
||||
System: opencode.F("system"),
|
||||
Tools: opencode.F(map[string]bool{
|
||||
"foo": true,
|
||||
}),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
var apierr *opencode.Error
|
||||
if errors.As(err, &apierr) {
|
||||
t.Log(string(apierr.DumpRequest(true)))
|
||||
}
|
||||
t.Fatalf("err should be nil: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionChildrenWithOptionalParams(t *testing.T) {
|
||||
t.Skip("Prism tests are disabled")
|
||||
baseURL := "http://localhost:4010"
|
||||
@@ -371,6 +325,54 @@ func TestSessionMessagesWithOptionalParams(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionPromptWithOptionalParams(t *testing.T) {
|
||||
t.Skip("Prism tests are disabled")
|
||||
baseURL := "http://localhost:4010"
|
||||
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
|
||||
baseURL = envURL
|
||||
}
|
||||
if !testutil.CheckTestServer(t, baseURL) {
|
||||
return
|
||||
}
|
||||
client := opencode.NewClient(
|
||||
option.WithBaseURL(baseURL),
|
||||
)
|
||||
_, err := client.Session.Prompt(
|
||||
context.TODO(),
|
||||
"id",
|
||||
opencode.SessionPromptParams{
|
||||
Parts: opencode.F([]opencode.SessionPromptParamsPartUnion{opencode.TextPartInputParam{
|
||||
Text: opencode.F("text"),
|
||||
Type: opencode.F(opencode.TextPartInputTypeText),
|
||||
ID: opencode.F("id"),
|
||||
Synthetic: opencode.F(true),
|
||||
Time: opencode.F(opencode.TextPartInputTimeParam{
|
||||
Start: opencode.F(0.000000),
|
||||
End: opencode.F(0.000000),
|
||||
}),
|
||||
}}),
|
||||
Directory: opencode.F("directory"),
|
||||
Agent: opencode.F("agent"),
|
||||
MessageID: opencode.F("msg"),
|
||||
Model: opencode.F(opencode.SessionPromptParamsModel{
|
||||
ModelID: opencode.F("modelID"),
|
||||
ProviderID: opencode.F("providerID"),
|
||||
}),
|
||||
System: opencode.F("system"),
|
||||
Tools: opencode.F(map[string]bool{
|
||||
"foo": true,
|
||||
}),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
var apierr *opencode.Error
|
||||
if errors.As(err, &apierr) {
|
||||
t.Log(string(apierr.DumpRequest(true)))
|
||||
}
|
||||
t.Fatalf("err should be nil: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionRevertWithOptionalParams(t *testing.T) {
|
||||
t.Skip("Prism tests are disabled")
|
||||
baseURL := "http://localhost:4010"
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
|
||||
|
||||
package shared
|
||||
|
||||
type UnionBool bool
|
||||
|
||||
func (UnionBool) ImplementsConfigProviderOptionsTimeoutUnion() {}
|
||||
|
||||
type UnionInt int64
|
||||
|
||||
func (UnionInt) ImplementsConfigProviderOptionsTimeoutUnion() {}
|
||||
@@ -0,0 +1,55 @@
|
||||
import { createOpencodeClient, createOpencodeServer } from "@opencode-ai/sdk"
|
||||
|
||||
const server = await createOpencodeServer()
|
||||
const client = createOpencodeClient({ baseUrl: server.url })
|
||||
|
||||
const input = await Array.fromAsync(new Bun.Glob("packages/core/*.ts").scan())
|
||||
|
||||
const tasks: Promise<void>[] = []
|
||||
for await (const file of input) {
|
||||
console.log("processing", file)
|
||||
const session = await client.session.create()
|
||||
tasks.push(
|
||||
client.session.prompt({
|
||||
path: { id: session.data.id },
|
||||
body: {
|
||||
parts: [
|
||||
{
|
||||
type: "file",
|
||||
mime: "text/plain",
|
||||
url: `file://${file}`,
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
text: `Write tests for every public function in this file.`,
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
)
|
||||
console.log("done", file)
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
input.map(async (file) => {
|
||||
const session = await client.session.create()
|
||||
console.log("processing", file)
|
||||
await client.session.prompt({
|
||||
path: { id: session.data.id },
|
||||
body: {
|
||||
parts: [
|
||||
{
|
||||
type: "file",
|
||||
mime: "text/plain",
|
||||
url: `file://${file}`,
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
text: `Write tests for every public function in this file.`,
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
console.log("done", file)
|
||||
}),
|
||||
)
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "0.5.29",
|
||||
"version": "0.6.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"typecheck": "tsc --noEmit"
|
||||
|
||||
@@ -4,6 +4,8 @@ import type { Options as ClientOptions, TDataShape, Client } from "./client/inde
|
||||
import type {
|
||||
ProjectListData,
|
||||
ProjectListResponses,
|
||||
ProjectCurrentData,
|
||||
ProjectCurrentResponses,
|
||||
EventSubscribeData,
|
||||
EventSubscribeResponses,
|
||||
ConfigGetData,
|
||||
@@ -35,8 +37,8 @@ import type {
|
||||
SessionSummarizeResponses,
|
||||
SessionMessagesData,
|
||||
SessionMessagesResponses,
|
||||
SessionChatData,
|
||||
SessionChatResponses,
|
||||
SessionPromptData,
|
||||
SessionPromptResponses,
|
||||
SessionMessageData,
|
||||
SessionMessageResponses,
|
||||
SessionCommandData,
|
||||
@@ -130,6 +132,16 @@ class Project extends _HeyApiClient {
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current project
|
||||
*/
|
||||
public current<ThrowOnError extends boolean = false>(options?: Options<ProjectCurrentData, ThrowOnError>) {
|
||||
return (options?.client ?? this._client).get<ProjectCurrentResponses, unknown, ThrowOnError>({
|
||||
url: "/project/current",
|
||||
...options,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class Event extends _HeyApiClient {
|
||||
@@ -318,8 +330,8 @@ class Session extends _HeyApiClient {
|
||||
/**
|
||||
* Create and send a new message to a session
|
||||
*/
|
||||
public chat<ThrowOnError extends boolean = false>(options: Options<SessionChatData, ThrowOnError>) {
|
||||
return (options.client ?? this._client).post<SessionChatResponses, unknown, ThrowOnError>({
|
||||
public prompt<ThrowOnError extends boolean = false>(options: Options<SessionPromptData, ThrowOnError>) {
|
||||
return (options.client ?? this._client).post<SessionPromptResponses, unknown, ThrowOnError>({
|
||||
url: "/session/{id}/message",
|
||||
...options,
|
||||
headers: {
|
||||
|
||||
@@ -645,7 +645,11 @@ export type Config = {
|
||||
options?: {
|
||||
apiKey?: string
|
||||
baseURL?: string
|
||||
[key: string]: unknown | string | undefined
|
||||
/**
|
||||
* Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout.
|
||||
*/
|
||||
timeout?: number | false
|
||||
[key: string]: unknown | string | (number | false) | undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1052,6 +1056,8 @@ export type LayoutConfig = "auto" | "stretch"
|
||||
export type Path = {
|
||||
state: string
|
||||
config: string
|
||||
worktree: string
|
||||
directory: string
|
||||
}
|
||||
|
||||
export type _Error = {
|
||||
@@ -1196,6 +1202,24 @@ export type ProjectListResponses = {
|
||||
|
||||
export type ProjectListResponse = ProjectListResponses[keyof ProjectListResponses]
|
||||
|
||||
export type ProjectCurrentData = {
|
||||
body?: never
|
||||
path?: never
|
||||
query?: {
|
||||
directory?: string
|
||||
}
|
||||
url: "/project/current"
|
||||
}
|
||||
|
||||
export type ProjectCurrentResponses = {
|
||||
/**
|
||||
* Current project
|
||||
*/
|
||||
200: Project
|
||||
}
|
||||
|
||||
export type ProjectCurrentResponse = ProjectCurrentResponses[keyof ProjectCurrentResponses]
|
||||
|
||||
export type EventSubscribeData = {
|
||||
body?: never
|
||||
path?: never
|
||||
@@ -1519,11 +1543,13 @@ export type SessionMessagesResponses = {
|
||||
|
||||
export type SessionMessagesResponse = SessionMessagesResponses[keyof SessionMessagesResponses]
|
||||
|
||||
export type SessionChatData = {
|
||||
export type SessionPromptData = {
|
||||
body?: {
|
||||
messageID?: string
|
||||
providerID: string
|
||||
modelID: string
|
||||
model?: {
|
||||
providerID: string
|
||||
modelID: string
|
||||
}
|
||||
agent?: string
|
||||
system?: string
|
||||
tools?: {
|
||||
@@ -1553,7 +1579,7 @@ export type SessionChatData = {
|
||||
url: "/session/{id}/message"
|
||||
}
|
||||
|
||||
export type SessionChatResponses = {
|
||||
export type SessionPromptResponses = {
|
||||
/**
|
||||
* Created message
|
||||
*/
|
||||
@@ -1563,7 +1589,7 @@ export type SessionChatResponses = {
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionChatResponse = SessionChatResponses[keyof SessionChatResponses]
|
||||
export type SessionPromptResponse = SessionPromptResponses[keyof SessionPromptResponses]
|
||||
|
||||
export type SessionMessageData = {
|
||||
body?: never
|
||||
|
||||
@@ -147,7 +147,7 @@ resources:
|
||||
summarize: post /session/{id}/summarize
|
||||
message: get /session/{id}/message/{messageID}
|
||||
messages: get /session/{id}/message
|
||||
chat: post /session/{id}/message
|
||||
prompt: post /session/{id}/message
|
||||
command: post /session/{id}/command
|
||||
shell: post /session/{id}/shell
|
||||
update: patch /session/{id}
|
||||
|
||||
@@ -786,12 +786,14 @@ func (a *App) SendPrompt(ctx context.Context, prompt Prompt) (*App, tea.Cmd) {
|
||||
a.Messages = append(a.Messages, message)
|
||||
|
||||
cmds = append(cmds, func() tea.Msg {
|
||||
_, err := a.Client.Session.Chat(ctx, a.Session.ID, opencode.SessionChatParams{
|
||||
ProviderID: opencode.F(a.Provider.ID),
|
||||
ModelID: opencode.F(a.Model.ID),
|
||||
Agent: opencode.F(a.Agent().Name),
|
||||
MessageID: opencode.F(messageID),
|
||||
Parts: opencode.F(message.ToSessionChatParams()),
|
||||
_, err := a.Client.Session.Prompt(ctx, a.Session.ID, opencode.SessionPromptParams{
|
||||
Model: opencode.F(opencode.SessionPromptParamsModel{
|
||||
ProviderID: opencode.F(a.Provider.ID),
|
||||
ModelID: opencode.F(a.Model.ID),
|
||||
}),
|
||||
Agent: opencode.F(a.Agent().Name),
|
||||
MessageID: opencode.F(messageID),
|
||||
Parts: opencode.F(message.ToSessionChatParams()),
|
||||
})
|
||||
if err != nil {
|
||||
errormsg := fmt.Sprintf("failed to send message: %v", err)
|
||||
@@ -818,15 +820,18 @@ func (a *App) SendCommand(ctx context.Context, command string, args string) (*Ap
|
||||
}
|
||||
|
||||
cmds = append(cmds, func() tea.Msg {
|
||||
params := opencode.SessionCommandParams{
|
||||
Command: opencode.F(command),
|
||||
Arguments: opencode.F(args),
|
||||
Agent: opencode.F(a.Agents[a.AgentIndex].Name),
|
||||
}
|
||||
if a.Provider != nil && a.Model != nil {
|
||||
params.Model = opencode.F(a.Provider.ID + "/" + a.Model.ID)
|
||||
}
|
||||
_, err := a.Client.Session.Command(
|
||||
context.Background(),
|
||||
a.Session.ID,
|
||||
opencode.SessionCommandParams{
|
||||
Command: opencode.F(command),
|
||||
Arguments: opencode.F(args),
|
||||
Agent: opencode.F(a.Agents[a.AgentIndex].Name),
|
||||
Model: opencode.F(a.State.Provider + "/" + a.State.Model),
|
||||
},
|
||||
params,
|
||||
)
|
||||
if err != nil {
|
||||
slog.Error("Failed to execute command", "error", err)
|
||||
|
||||
@@ -43,6 +43,9 @@ func (p Prompt) ToMessage(
|
||||
}
|
||||
for _, att := range textAttachments {
|
||||
if source, ok := att.GetTextSource(); ok {
|
||||
if att.StartIndex > att.EndIndex || att.EndIndex > len(text) {
|
||||
continue
|
||||
}
|
||||
text = text[:att.StartIndex] + source.Value + text[att.EndIndex:]
|
||||
}
|
||||
}
|
||||
@@ -204,8 +207,8 @@ func (m Message) ToPrompt() (*Prompt, error) {
|
||||
return nil, errors.New("unknown message type")
|
||||
}
|
||||
|
||||
func (m Message) ToSessionChatParams() []opencode.SessionChatParamsPartUnion {
|
||||
parts := []opencode.SessionChatParamsPartUnion{}
|
||||
func (m Message) ToSessionChatParams() []opencode.SessionPromptParamsPartUnion {
|
||||
parts := []opencode.SessionPromptParamsPartUnion{}
|
||||
for _, part := range m.Parts {
|
||||
switch p := part.(type) {
|
||||
case opencode.TextPart:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@opencode/web",
|
||||
"type": "module",
|
||||
"version": "0.5.29",
|
||||
"version": "0.6.1",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"dev:remote": "sst shell --stage=dev --target=Web astro dev",
|
||||
|
||||
@@ -9,7 +9,7 @@ you to use opencode at your organization.
|
||||
To get started, we recommend:
|
||||
|
||||
1. Do a trial internally with your team.
|
||||
2. [**Contact us**](mailto:hello@anoma.ly) to discuss pricing and implementation options.
|
||||
2. [**Contact us**](mailto:contact@anoma.ly) to discuss pricing and implementation options.
|
||||
|
||||
---
|
||||
|
||||
@@ -55,7 +55,7 @@ We recommend you disable this for your trial.
|
||||
## Deployment
|
||||
|
||||
Once you have completed your trial and you are ready to self-host opencode at
|
||||
your organization, you can [**contact us**](mailto:hello@anoma.ly) to discuss
|
||||
your organization, you can [**contact us**](mailto:contact@anoma.ly) to discuss
|
||||
pricing and implementation options.
|
||||
|
||||
---
|
||||
|
||||
@@ -26,7 +26,7 @@ Plugins are loaded from:
|
||||
### Basic structure
|
||||
|
||||
```js title=".opencode/plugin/example.js"
|
||||
export const MyPlugin = async ({ app, client, $ }) => {
|
||||
export const MyPlugin = async ({ project, client, $, directory, worktree }) => {
|
||||
console.log("Plugin initialized!")
|
||||
|
||||
return {
|
||||
@@ -37,7 +37,9 @@ export const MyPlugin = async ({ app, client, $ }) => {
|
||||
|
||||
The plugin function receives:
|
||||
|
||||
- `app`: The opencode application instance.
|
||||
- `project`: The current project information.
|
||||
- `directory`: The current working directory.
|
||||
- `worktree`: The git worktree path.
|
||||
- `client`: An opencode SDK client for interacting with the AI.
|
||||
- `$`: Bun's [shell API](https://bun.com/docs/runtime/shell) for executing commands.
|
||||
|
||||
@@ -50,7 +52,7 @@ For TypeScript plugins, you can import types from the plugin package:
|
||||
```ts title="my-plugin.ts" {1}
|
||||
import type { Plugin } from "@opencode-ai/plugin"
|
||||
|
||||
export const MyPlugin: Plugin = async ({ app, client, $ }) => {
|
||||
export const MyPlugin: Plugin = async ({ project, client, $, directory, worktree }) => {
|
||||
return {
|
||||
// Type-safe hook implementations
|
||||
}
|
||||
@@ -70,7 +72,7 @@ Here are some examples of plugins you can use to extend opencode.
|
||||
Send notifications when certain events occur:
|
||||
|
||||
```js title=".opencode/plugin/notification.js"
|
||||
export const NotificationPlugin = async ({ client, $ }) => {
|
||||
export const NotificationPlugin = async ({ project, client, $, directory, worktree }) => {
|
||||
return {
|
||||
event: async ({ event }) => {
|
||||
// Send notification on session completion
|
||||
@@ -91,13 +93,13 @@ We are using `osascript` to run AppleScript on macOS. Here we are using it to se
|
||||
Prevent opencode from reading `.env` files:
|
||||
|
||||
```javascript title=".opencode/plugin/env-protection.js"
|
||||
export const EnvProtection = async ({ client, $ }) => {
|
||||
export const EnvProtection = async ({ project, client, $, directory, worktree }) => {
|
||||
return {
|
||||
"tool.execute.before": async (input, output) => {
|
||||
if (input.tool === "read" && output.args.filePath.includes(".env")) {
|
||||
throw new Error("Do not read .env files")
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -10,6 +10,8 @@ The opencode [JS/TS SDK](https://www.npmjs.com/package/@opencode-ai/sdk) provide
|
||||
|
||||
[Learn more](/docs/server) about how the opencode server works.
|
||||
|
||||
> **Note**: Many API endpoints now require a `directory` query parameter to specify the working directory context.
|
||||
|
||||
---
|
||||
|
||||
## Install
|
||||
@@ -65,12 +67,12 @@ server.close()
|
||||
|
||||
#### Options
|
||||
|
||||
| Option | Type | Description | Default |
|
||||
| ---------- | -------------- | ------------------------------ | ------------- |
|
||||
| `hostname` | `string` | Server hostname | `127.0.0.1` |
|
||||
| `port` | `number` | Server port | `4096` |
|
||||
| `signal` | `AbortSignal` | Abort signal for cancellation | `undefined` |
|
||||
| `timeout` | `number` | Timeout in ms for server start | `5000` |
|
||||
| Option | Type | Description | Default |
|
||||
| ---------- | ------------- | ------------------------------ | ----------- |
|
||||
| `hostname` | `string` | Server hostname | `127.0.0.1` |
|
||||
| `port` | `number` | Server port | `4096` |
|
||||
| `signal` | `AbortSignal` | Abort signal for cancellation | `undefined` |
|
||||
| `timeout` | `number` | Timeout in ms for server start | `5000` |
|
||||
|
||||
---
|
||||
|
||||
@@ -80,7 +82,7 @@ The SDK includes TypeScript definitions for all API types. Import them directly:
|
||||
|
||||
```typescript
|
||||
import type { Session, Message, Part } from "@opencode-ai/sdk"
|
||||
````
|
||||
```
|
||||
|
||||
All types are generated from the server's OpenAPI specification and available in the <a href={typesUrl}>types file</a>.
|
||||
|
||||
@@ -108,18 +110,63 @@ The SDK exposes all server APIs through a type-safe client interface.
|
||||
|
||||
### App
|
||||
|
||||
| Method | Description | Response |
|
||||
| ------------ | ------------------ | --------------------------------------- |
|
||||
| `app.get()` | Get app info | <a href={typesUrl}><code>App</code></a> |
|
||||
| `app.init()` | Initialize the app | `boolean` |
|
||||
| Method | Description | Response |
|
||||
| -------------- | ------------------------- | ------------------------------------------- |
|
||||
| `app.log()` | Write a log entry | `boolean` |
|
||||
| `app.agents()` | List all available agents | <a href={typesUrl}><code>Agent[]</code></a> |
|
||||
|
||||
---
|
||||
|
||||
#### Examples
|
||||
|
||||
```javascript
|
||||
const app = await client.app.get()
|
||||
await client.app.init()
|
||||
// Log an entry
|
||||
await client.app.log({
|
||||
service: "my-app",
|
||||
level: "info",
|
||||
message: "Operation completed",
|
||||
})
|
||||
|
||||
// List available agents
|
||||
const agents = await client.app.agents()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Project
|
||||
|
||||
| Method | Description | Response |
|
||||
| ------------------- | ------------------- | --------------------------------------------- |
|
||||
| `project.list()` | List all projects | <a href={typesUrl}><code>Project[]</code></a> |
|
||||
| `project.current()` | Get current project | <a href={typesUrl}><code>Project</code></a> |
|
||||
|
||||
---
|
||||
|
||||
#### Examples
|
||||
|
||||
```javascript
|
||||
// List all projects
|
||||
const projects = await client.project.list()
|
||||
|
||||
// Get current project
|
||||
const currentProject = await client.project.current()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Path
|
||||
|
||||
| Method | Description | Response |
|
||||
| ------------ | ---------------- | ---------------------------------------- |
|
||||
| `path.get()` | Get current path | <a href={typesUrl}><code>Path</code></a> |
|
||||
|
||||
---
|
||||
|
||||
#### Examples
|
||||
|
||||
```javascript
|
||||
// Get current path information
|
||||
const pathInfo = await client.path.get()
|
||||
```
|
||||
|
||||
---
|
||||
@@ -159,7 +206,7 @@ const { providers, default: defaults } = await client.config.providers()
|
||||
| `session.summarize({ id, providerID, modelID })` | Summarize session | Returns `boolean` |
|
||||
| `session.messages({ id })` | List messages in a session | Returns `{ info: `<a href={typesUrl}><code>Message</code></a>`, parts: `<a href={typesUrl}><code>Part[]</code></a>`}[]` |
|
||||
| `session.message({ id, messageID })` | Get message details | Returns `{ info: `<a href={typesUrl}><code>Message</code></a>`, parts: `<a href={typesUrl}><code>Part[]</code></a>`}` |
|
||||
| `session.chat({ id, ...chatInput })` | Send chat message | Returns <a href={typesUrl}><code>Message</code></a> |
|
||||
| `session.prompt({ id, ...promptInput })` | Send prompt message | Returns <a href={typesUrl}><code>Message</code></a> |
|
||||
| `session.shell({ id, agent, command })` | Run a shell command | Returns <a href={typesUrl}><code>Message</code></a> |
|
||||
| `session.revert({ id, messageID, partID? })` | Revert a message | Returns <a href={typesUrl}><code>Session</code></a> |
|
||||
| `session.unrevert({ id })` | Restore reverted messages | Returns <a href={typesUrl}><code>Session</code></a> |
|
||||
@@ -175,10 +222,12 @@ const session = await client.session.create({ title: "My session" })
|
||||
const sessions = await client.session.list()
|
||||
|
||||
// Send messages
|
||||
const message = await client.session.chat({
|
||||
const message = await client.session.prompt({
|
||||
id: session.id,
|
||||
providerID: "anthropic",
|
||||
modelID: "claude-3-5-sonnet-20241022",
|
||||
model: {
|
||||
providerID: "anthropic",
|
||||
modelID: "claude-3-5-sonnet-20241022",
|
||||
},
|
||||
parts: [{ type: "text", text: "Hello!" }],
|
||||
})
|
||||
```
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "opencode",
|
||||
"displayName": "opencode",
|
||||
"description": "opencode for VS Code",
|
||||
"version": "0.5.29",
|
||||
"version": "0.6.1",
|
||||
"publisher": "sst-dev",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
Reference in New Issue
Block a user