Merge branch 'dev' into opentui

This commit is contained in:
Dax Raad 2025-10-08 02:26:48 -04:00
commit 61734ac21a
38 changed files with 1309 additions and 276 deletions

View file

@ -101,3 +101,4 @@
| 2025-10-04 | 452,561 (+5,732) | 370,386 (+10,449) | 822,947 (+16,181) |
| 2025-10-05 | 455,559 (+2,998) | 374,745 (+4,359) | 830,304 (+7,357) |
| 2025-10-06 | 460,927 (+5,368) | 379,489 (+4,744) | 840,416 (+10,112) |
| 2025-10-07 | 467,336 (+6,409) | 385,438 (+5,949) | 852,774 (+12,358) |

View file

@ -56,25 +56,30 @@ const removeMember = action(async (form: FormData) => {
)
}, "member.remove")
const updateMemberRole = action(async (form: FormData) => {
const updateMember = action(async (form: FormData) => {
"use server"
const id = form.get("id")?.toString()
if (!id) return { error: "ID is required" }
const workspaceID = form.get("workspaceID")?.toString()
if (!workspaceID) return { error: "Workspace ID is required" }
const role = form.get("role")?.toString() as (typeof UserRole)[number]
if (!role) return { error: "Role is required" }
const limit = form.get("limit")?.toString()
const monthlyLimit = limit && limit.trim() !== "" ? parseInt(limit) : null
if (monthlyLimit !== null && monthlyLimit < 0) return { error: "Set a valid monthly limit" }
return json(
await withActor(
() =>
User.updateRole({ id, role })
User.update({ id, role, monthlyLimit })
.then((data) => ({ error: undefined, data }))
.catch((e) => ({ error: e.message as string })),
workspaceID,
),
{ revalidate: listMembers.key },
)
}, "member.updateRole")
}, "member.update")
export function MemberCreateForm() {
const params = useParams()
@ -155,7 +160,7 @@ export function MemberCreateForm() {
function MemberRow(props: { member: any; workspaceID: string; currentUserID: string | null }) {
const [editing, setEditing] = createSignal(false)
const submission = useSubmission(updateMemberRole)
const submission = useSubmission(updateMember)
const isCurrentUser = () => props.currentUserID === props.member.id
createEffect(() => {
@ -164,6 +169,29 @@ function MemberRow(props: { member: any; workspaceID: string; currentUserID: str
}
})
function getUsageDisplay() {
const currentUsage = (() => {
const dateLastUsed = props.member.timeMonthlyUsageUpdated
if (!dateLastUsed) return 0
const current = new Date().toLocaleDateString("en-US", {
year: "numeric",
month: "long",
timeZone: "UTC",
})
const lastUsed = dateLastUsed.toLocaleDateString("en-US", {
year: "numeric",
month: "long",
timeZone: "UTC",
})
if (current !== lastUsed) return 0
return ((props.member.monthlyUsage ?? 0) / 100000000).toFixed(2)
})()
const limit = props.member.monthlyLimit ? `$${props.member.monthlyLimit}` : "no limit"
return `$${currentUsage} / ${limit}`
}
return (
<Show
when={editing()}
@ -171,6 +199,7 @@ function MemberRow(props: { member: any; workspaceID: string; currentUserID: str
<tr>
<td data-slot="member-email">{props.member.accountEmail ?? props.member.email}</td>
<td data-slot="member-role">{props.member.role}</td>
<td data-slot="member-usage">{getUsageDisplay()}</td>
<Show when={!props.member.timeSeen} fallback={<td data-slot="member-joined"></td>}>
<td data-slot="member-joined">invited</td>
</Show>
@ -190,12 +219,21 @@ function MemberRow(props: { member: any; workspaceID: string; currentUserID: str
}
>
<tr>
<td colspan="4">
<form action={updateMemberRole} method="post">
<td colspan="5">
<form action={updateMember} method="post">
<div data-slot="edit-member-email">{props.member.accountEmail ?? props.member.email}</div>
<input type="hidden" name="id" value={props.member.id} />
<input type="hidden" name="workspaceID" value={props.workspaceID} />
<Show when={!isCurrentUser()} fallback={<div data-slot="current-user-role">Role: {props.member.role}</div>}>
<Show
when={!isCurrentUser()}
fallback={
<>
<div data-slot="current-user-role">Role: {props.member.role}</div>
<input type="hidden" name="role" value={props.member.role} />
</>
}
>
<div data-slot="role-selector">
<label>
<input type="radio" name="role" value="admin" checked={props.member.role === "admin"} />
@ -213,18 +251,32 @@ function MemberRow(props: { member: any; workspaceID: string; currentUserID: str
</label>
</div>
</Show>
<div data-slot="limit-selector">
<label>
<strong>Monthly Limit</strong>
<input
type="number"
name="limit"
value={props.member.monthlyLimit ?? ""}
placeholder="No limit"
min="0"
/>
<p>Set a monthly spending limit for this user</p>
</label>
</div>
<Show when={submission.result && submission.result.error}>
{(err) => <div data-slot="form-error">{err()}</div>}
</Show>
<div data-slot="form-actions">
<button type="button" data-color="ghost" onClick={() => setEditing(false)}>
Cancel
</button>
<Show when={!isCurrentUser()}>
<button type="submit" data-color="primary" disabled={submission.pending}>
{submission.pending ? "Saving..." : "Save"}
</button>
</Show>
<button type="submit" data-color="primary" disabled={submission.pending}>
{submission.pending ? "Saving..." : "Save"}
</button>
</div>
</form>
</td>
@ -258,6 +310,7 @@ export function MemberSection() {
<tr>
<th>Email</th>
<th>Role</th>
<th>Usage</th>
<th></th>
<th></th>
</tr>

View file

@ -11,6 +11,7 @@ import { Billing } from "../../../../core/src/billing"
import { Actor } from "@opencode-ai/console-core/actor.js"
import { WorkspaceTable } from "@opencode-ai/console-core/schema/workspace.sql.js"
import { ZenModel } from "@opencode-ai/console-core/model.js"
import { UserTable } from "@opencode-ai/console-core/schema/user.sql.js"
export async function handler(
input: APIEvent,
@ -33,12 +34,14 @@ export async function handler(
class AuthError extends Error {}
class CreditsError extends Error {}
class MonthlyLimitError extends Error {}
class UserLimitError extends Error {}
class ModelError extends Error {}
type Model = z.infer<typeof ZenModel.ModelSchema>
const FREE_WORKSPACES = [
"wrk_01K46JDFR0E75SG2Q8K172KF3Y", // frank
"wrk_01K6W1A3VE0KMNVSCQT43BG2SX", // opencode bench
]
const logger = {
@ -181,6 +184,7 @@ export async function handler(
error instanceof AuthError ||
error instanceof CreditsError ||
error instanceof MonthlyLimitError ||
error instanceof UserLimitError ||
error instanceof ModelError
)
return new Response(
@ -243,10 +247,15 @@ export async function handler(
monthlyLimit: BillingTable.monthlyLimit,
monthlyUsage: BillingTable.monthlyUsage,
timeMonthlyUsageUpdated: BillingTable.timeMonthlyUsageUpdated,
userID: UserTable.id,
userMonthlyLimit: UserTable.monthlyLimit,
userMonthlyUsage: UserTable.monthlyUsage,
timeUserMonthlyUsageUpdated: UserTable.timeMonthlyUsageUpdated,
})
.from(KeyTable)
.innerJoin(WorkspaceTable, eq(WorkspaceTable.id, KeyTable.workspaceID))
.innerJoin(BillingTable, eq(BillingTable.workspaceID, KeyTable.workspaceID))
.innerJoin(UserTable, and(eq(UserTable.workspaceID, KeyTable.workspaceID), eq(UserTable.id, KeyTable.userID)))
.where(and(eq(KeyTable.key, apiKey), isNull(KeyTable.timeDeleted)))
.then((rows) => rows[0]),
)
@ -269,6 +278,12 @@ export async function handler(
monthlyUsage: data.monthlyUsage,
timeMonthlyUsageUpdated: data.timeMonthlyUsageUpdated,
},
user: {
id: data.userID,
monthlyLimit: data.userMonthlyLimit,
monthlyUsage: data.userMonthlyUsage,
timeMonthlyUsageUpdated: data.timeUserMonthlyUsageUpdated,
},
isFree,
}
}
@ -280,19 +295,34 @@ export async function handler(
const billing = authInfo.billing
if (!billing.paymentMethodID) throw new CreditsError("No payment method")
if (billing.balance <= 0) throw new CreditsError("Insufficient balance")
const now = new Date()
const currentYear = now.getUTCFullYear()
const currentMonth = now.getUTCMonth()
if (
billing.monthlyLimit &&
billing.monthlyUsage &&
billing.timeMonthlyUsageUpdated &&
billing.monthlyUsage >= centsToMicroCents(billing.monthlyLimit * 100)
) {
const now = new Date()
const currentYear = now.getUTCFullYear()
const currentMonth = now.getUTCMonth()
const dateYear = billing.timeMonthlyUsageUpdated.getUTCFullYear()
const dateMonth = billing.timeMonthlyUsageUpdated.getUTCMonth()
if (currentYear === dateYear && currentMonth === dateMonth)
throw new MonthlyLimitError(`You have reached your monthly spending limit of $${billing.monthlyLimit}.`)
throw new MonthlyLimitError(
`Your workspace has reached its monthly spending limit of $${billing.monthlyLimit}.`,
)
}
if (
authInfo.user.monthlyLimit &&
authInfo.user.monthlyUsage &&
authInfo.user.timeMonthlyUsageUpdated &&
authInfo.user.monthlyUsage >= centsToMicroCents(authInfo.user.monthlyLimit * 100)
) {
const dateYear = authInfo.user.timeMonthlyUsageUpdated.getUTCFullYear()
const dateMonth = authInfo.user.timeMonthlyUsageUpdated.getUTCMonth()
if (currentYear === dateYear && currentMonth === dateMonth)
throw new UserLimitError(`You have reached your monthly spending limit of $${authInfo.user.monthlyLimit}.`)
}
}
@ -386,6 +416,18 @@ export async function handler(
timeMonthlyUsageUpdated: sql`now()`,
})
.where(eq(BillingTable.workspaceID, authInfo.workspaceID))
await tx
.update(UserTable)
.set({
monthlyUsage: sql`
CASE
WHEN MONTH(${UserTable.timeMonthlyUsageUpdated}) = MONTH(now()) AND YEAR(${UserTable.timeMonthlyUsageUpdated}) = YEAR(now()) THEN ${UserTable.monthlyUsage} + ${cost}
ELSE ${cost}
END
`,
timeMonthlyUsageUpdated: sql`now()`,
})
.where(and(eq(UserTable.workspaceID, authInfo.workspaceID), eq(UserTable.id, authInfo.user.id)))
})
await Database.use((tx) =>

View file

@ -0,0 +1,3 @@
ALTER TABLE `user` ADD `monthly_limit` int;--> statement-breakpoint
ALTER TABLE `user` ADD `monthly_usage` bigint;--> statement-breakpoint
ALTER TABLE `user` ADD `time_monthly_usage_updated` timestamp(3);

View file

@ -0,0 +1,730 @@
{
"version": "5",
"dialect": "mysql",
"id": "33551b4c-fc2e-4753-8d9d-0971f333e65d",
"prevId": "a331e38c-c2e3-406d-a1ff-b0af7229cd85",
"tables": {
"account": {
"name": "account",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"email": {
"name": "email",
"columns": [
"email"
],
"isUnique": true
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraint": {}
},
"billing": {
"name": "billing",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"workspace_id": {
"name": "workspace_id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"customer_id": {
"name": "customer_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"payment_method_id": {
"name": "payment_method_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"payment_method_last4": {
"name": "payment_method_last4",
"type": "varchar(4)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"balance": {
"name": "balance",
"type": "bigint",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"monthly_limit": {
"name": "monthly_limit",
"type": "int",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"monthly_usage": {
"name": "monthly_usage",
"type": "bigint",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"time_monthly_usage_updated": {
"name": "time_monthly_usage_updated",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"reload": {
"name": "reload",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"reload_error": {
"name": "reload_error",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"time_reload_error": {
"name": "time_reload_error",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"time_reload_locked_till": {
"name": "time_reload_locked_till",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {
"global_customer_id": {
"name": "global_customer_id",
"columns": [
"customer_id"
],
"isUnique": true
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"billing_workspace_id_id_pk": {
"name": "billing_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
},
"payment": {
"name": "payment",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"workspace_id": {
"name": "workspace_id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"customer_id": {
"name": "customer_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"invoice_id": {
"name": "invoice_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"payment_id": {
"name": "payment_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"amount": {
"name": "amount",
"type": "bigint",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"time_refunded": {
"name": "time_refunded",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {
"payment_workspace_id_id_pk": {
"name": "payment_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
},
"usage": {
"name": "usage",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"workspace_id": {
"name": "workspace_id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"model": {
"name": "model",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"provider": {
"name": "provider",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"input_tokens": {
"name": "input_tokens",
"type": "int",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"output_tokens": {
"name": "output_tokens",
"type": "int",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"reasoning_tokens": {
"name": "reasoning_tokens",
"type": "int",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"cache_read_tokens": {
"name": "cache_read_tokens",
"type": "int",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"cache_write_5m_tokens": {
"name": "cache_write_5m_tokens",
"type": "int",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"cache_write_1h_tokens": {
"name": "cache_write_1h_tokens",
"type": "int",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"cost": {
"name": "cost",
"type": "bigint",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {
"usage_workspace_id_id_pk": {
"name": "usage_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
},
"key": {
"name": "key",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"workspace_id": {
"name": "workspace_id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"key": {
"name": "key",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"user_id": {
"name": "user_id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"time_used": {
"name": "time_used",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {
"global_key": {
"name": "global_key",
"columns": [
"key"
],
"isUnique": true
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"key_workspace_id_id_pk": {
"name": "key_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
},
"user": {
"name": "user",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"workspace_id": {
"name": "workspace_id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"account_id": {
"name": "account_id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"time_seen": {
"name": "time_seen",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"color": {
"name": "color",
"type": "int",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"role": {
"name": "role",
"type": "enum('admin','member')",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"monthly_limit": {
"name": "monthly_limit",
"type": "int",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"monthly_usage": {
"name": "monthly_usage",
"type": "bigint",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"time_monthly_usage_updated": {
"name": "time_monthly_usage_updated",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {
"user_account_id": {
"name": "user_account_id",
"columns": [
"workspace_id",
"account_id"
],
"isUnique": true
},
"user_email": {
"name": "user_email",
"columns": [
"workspace_id",
"email"
],
"isUnique": true
},
"global_account_id": {
"name": "global_account_id",
"columns": [
"account_id"
],
"isUnique": false
},
"global_email": {
"name": "global_email",
"columns": [
"email"
],
"isUnique": false
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"user_workspace_id_id_pk": {
"name": "user_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
},
"workspace": {
"name": "workspace",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"slug": {
"name": "slug",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {
"slug": {
"name": "slug",
"columns": [
"slug"
],
"isUnique": true
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"workspace_id": {
"name": "workspace_id",
"columns": [
"id"
]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
}
},
"views": {},
"_meta": {
"schemas": {},
"tables": {},
"columns": {}
},
"internal": {
"tables": {},
"indexes": {}
}
}

View file

@ -204,6 +204,13 @@
"when": 1759805025276,
"tag": "0028_careful_cerise",
"breakpoints": true
},
{
"idx": 29,
"version": "5",
"when": 1759811835558,
"tag": "0029_panoramic_harrier",
"breakpoints": true
}
]
}

View file

@ -1,5 +1,5 @@
import { Stripe } from "stripe"
import { and, Database, eq, sql } from "./drizzle"
import { Database, eq, sql } from "./drizzle"
import { BillingTable, PaymentTable, UsageTable } from "./schema/billing.sql"
import { Actor } from "./actor"
import { fn } from "./util/fn"

View file

@ -1,4 +1,4 @@
import { mysqlTable, uniqueIndex, varchar, int, mysqlEnum, index } from "drizzle-orm/mysql-core"
import { mysqlTable, uniqueIndex, varchar, int, mysqlEnum, index, bigint } from "drizzle-orm/mysql-core"
import { timestamps, ulid, utc, workspaceColumns } from "../drizzle/types"
import { workspaceIndexes } from "./workspace.sql"
@ -15,6 +15,9 @@ export const UserTable = mysqlTable(
timeSeen: utc("time_seen"),
color: int("color"),
role: mysqlEnum("role", UserRole).notNull(),
monthlyLimit: int("monthly_limit"),
monthlyUsage: bigint("monthly_usage", { mode: "number" }),
timeMonthlyUsageUpdated: utc("time_monthly_usage_updated"),
},
(table) => [
...workspaceIndexes(table),

View file

@ -174,18 +174,19 @@ export namespace User {
)
})
export const updateRole = fn(
export const update = fn(
z.object({
id: z.string(),
role: z.enum(UserRole),
monthlyLimit: z.number().nullable(),
}),
async ({ id, role }) => {
async ({ id, role, monthlyLimit }) => {
await assertAdmin()
if (role === "member") assertNotSelf(id)
return await Database.use((tx) =>
tx
.update(UserTable)
.set({ role })
.set({ role, monthlyLimit })
.where(and(eq(UserTable.id, id), eq(UserTable.workspaceID, Actor.workspace()))),
)
},

View file

@ -2,15 +2,15 @@ name: CI
on:
push:
branches-ignore:
- "generated"
- "codegen/**"
- "integrated/**"
- "stl-preview-head/**"
- "stl-preview-base/**"
- 'generated'
- 'codegen/**'
- 'integrated/**'
- 'stl-preview-head/**'
- 'stl-preview-base/**'
pull_request:
branches-ignore:
- "stl-preview-head/**"
- "stl-preview-base/**"
- 'stl-preview-head/**'
- 'stl-preview-base/**'
jobs:
lint:

View file

@ -1,3 +1,3 @@
{
".": "0.15.0"
}
".": "0.16.2"
}

View file

@ -1,4 +1,4 @@
configured_endpoints: 43
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-ad911ed0bdbeca62807509f364f25fcafd7a83e0b43e027ec0a85f72b7a4d963.yml
openapi_spec_hash: 15152513b4246bf4b5f8546fa6f1603f
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-273fc9fea965af661dfed0902d00f10d6ed844f0681ca861a58821c4902eac2f.yml
openapi_spec_hash: c6144f23a1bac75f79be86edd405552b
config_hash: 026ef000d34bf2f930e7b41e77d2d3ff

View file

@ -1,12 +1,43 @@
# Changelog
## 0.16.2 (2025-09-26)
Full Changelog: [v0.16.1...v0.16.2](https://github.com/sst/opencode-sdk-go/compare/v0.16.1...v0.16.2)
### Bug Fixes
* bugfix for setting JSON keys with special characters ([ac9a36f](https://github.com/sst/opencode-sdk-go/commit/ac9a36feb1c185ebf766d76909d0b86ac805e8a6))
## 0.16.1 (2025-09-20)
Full Changelog: [v0.16.0...v0.16.1](https://github.com/sst/opencode-sdk-go/compare/v0.16.0...v0.16.1)
### Bug Fixes
* use slices.Concat instead of sometimes modifying r.Options ([12e8b40](https://github.com/sst/opencode-sdk-go/commit/12e8b40809071095b0abb9b8031686353c8ac149))
### Chores
* bump minimum go version to 1.22 ([1a61c5c](https://github.com/sst/opencode-sdk-go/commit/1a61c5cc7e8f68cc1b0c219738cab530cb6aa3a2))
* do not install brew dependencies in ./scripts/bootstrap by default ([f6d3eaf](https://github.com/sst/opencode-sdk-go/commit/f6d3eafffc20e124bbfae6ac5ddc1b1122ad3e27))
* update more docs for 1.22 ([a3d0b0f](https://github.com/sst/opencode-sdk-go/commit/a3d0b0f26ed92ce1a6433f5bcf37a6436d268ba5))
## 0.16.0 (2025-09-17)
Full Changelog: [v0.15.0...v0.16.0](https://github.com/sst/opencode-sdk-go/compare/v0.15.0...v0.16.0)
### Features
* **api:** api update ([46e978e](https://github.com/sst/opencode-sdk-go/commit/46e978e43aee733d5c1c09dc5be6d8ac2a752427))
## 0.15.0 (2025-09-16)
Full Changelog: [v0.14.0...v0.15.0](https://github.com/sst/opencode-sdk-go/compare/v0.14.0...v0.15.0)
### Features
- **api:** api update ([397048f](https://github.com/sst/opencode-sdk-go/commit/397048faca7a1de7a028edd2254a0ad7797b151f))
* **api:** api update ([397048f](https://github.com/sst/opencode-sdk-go/commit/397048faca7a1de7a028edd2254a0ad7797b151f))
## 0.14.0 (2025-09-14)
@ -14,7 +45,7 @@ Full Changelog: [v0.13.0...v0.14.0](https://github.com/sst/opencode-sdk-go/compa
### Features
- **api:** api update ([dad0bc3](https://github.com/sst/opencode-sdk-go/commit/dad0bc3da99f20a0d002a6b94e049fb70f8e6a77))
* **api:** api update ([dad0bc3](https://github.com/sst/opencode-sdk-go/commit/dad0bc3da99f20a0d002a6b94e049fb70f8e6a77))
## 0.13.0 (2025-09-14)
@ -22,7 +53,7 @@ Full Changelog: [v0.12.0...v0.13.0](https://github.com/sst/opencode-sdk-go/compa
### Features
- **api:** api update ([80da4fb](https://github.com/sst/opencode-sdk-go/commit/80da4fb4ea9c6afb51a7e7135d9f5560ce6f2a6c))
* **api:** api update ([80da4fb](https://github.com/sst/opencode-sdk-go/commit/80da4fb4ea9c6afb51a7e7135d9f5560ce6f2a6c))
## 0.12.0 (2025-09-14)
@ -30,7 +61,7 @@ Full Changelog: [v0.11.0...v0.12.0](https://github.com/sst/opencode-sdk-go/compa
### Features
- **api:** api update ([7e3808b](https://github.com/sst/opencode-sdk-go/commit/7e3808ba349dc653174b32b48a1120c18d2975c2))
* **api:** api update ([7e3808b](https://github.com/sst/opencode-sdk-go/commit/7e3808ba349dc653174b32b48a1120c18d2975c2))
## 0.11.0 (2025-09-14)
@ -38,7 +69,7 @@ Full Changelog: [v0.10.0...v0.11.0](https://github.com/sst/opencode-sdk-go/compa
### Features
- **api:** api update ([a3d37f5](https://github.com/sst/opencode-sdk-go/commit/a3d37f5671545866547d351fc21b49809cc8b3c2))
* **api:** api update ([a3d37f5](https://github.com/sst/opencode-sdk-go/commit/a3d37f5671545866547d351fc21b49809cc8b3c2))
## 0.10.0 (2025-09-11)
@ -46,7 +77,7 @@ Full Changelog: [v0.9.0...v0.10.0](https://github.com/sst/opencode-sdk-go/compar
### Features
- **api:** api update ([0dc01f6](https://github.com/sst/opencode-sdk-go/commit/0dc01f6695c9b8400a4dc92166c5002bb120cf50))
* **api:** api update ([0dc01f6](https://github.com/sst/opencode-sdk-go/commit/0dc01f6695c9b8400a4dc92166c5002bb120cf50))
## 0.9.0 (2025-09-10)
@ -54,7 +85,7 @@ Full Changelog: [v0.8.0...v0.9.0](https://github.com/sst/opencode-sdk-go/compare
### Features
- **api:** api update ([2d3a28d](https://github.com/sst/opencode-sdk-go/commit/2d3a28df5657845aa4d73087e1737d1fc8c3ce1c))
* **api:** api update ([2d3a28d](https://github.com/sst/opencode-sdk-go/commit/2d3a28df5657845aa4d73087e1737d1fc8c3ce1c))
## 0.8.0 (2025-09-01)
@ -62,7 +93,7 @@ Full Changelog: [v0.7.0...v0.8.0](https://github.com/sst/opencode-sdk-go/compare
### Features
- **api:** api update ([ae87a71](https://github.com/sst/opencode-sdk-go/commit/ae87a71949994590ace8285a39f0991ef34b664d))
* **api:** api update ([ae87a71](https://github.com/sst/opencode-sdk-go/commit/ae87a71949994590ace8285a39f0991ef34b664d))
## 0.7.0 (2025-09-01)
@ -70,7 +101,7 @@ Full Changelog: [v0.6.0...v0.7.0](https://github.com/sst/opencode-sdk-go/compare
### Features
- **api:** api update ([64bb1b1](https://github.com/sst/opencode-sdk-go/commit/64bb1b1ee0cbe153abc6fb7bd9703b47911724d4))
* **api:** api update ([64bb1b1](https://github.com/sst/opencode-sdk-go/commit/64bb1b1ee0cbe153abc6fb7bd9703b47911724d4))
## 0.6.0 (2025-09-01)
@ -78,7 +109,7 @@ Full Changelog: [v0.5.0...v0.6.0](https://github.com/sst/opencode-sdk-go/compare
### Features
- **api:** api update ([928e384](https://github.com/sst/opencode-sdk-go/commit/928e3843355f96899f046f002b84372281dad0c8))
* **api:** api update ([928e384](https://github.com/sst/opencode-sdk-go/commit/928e3843355f96899f046f002b84372281dad0c8))
## 0.5.0 (2025-08-31)
@ -86,7 +117,7 @@ Full Changelog: [v0.4.0...v0.5.0](https://github.com/sst/opencode-sdk-go/compare
### Features
- **api:** api update ([44b281d](https://github.com/sst/opencode-sdk-go/commit/44b281d0bb39c5022a984ac9d0fca1529ccc0604))
* **api:** api update ([44b281d](https://github.com/sst/opencode-sdk-go/commit/44b281d0bb39c5022a984ac9d0fca1529ccc0604))
## 0.4.0 (2025-08-31)
@ -94,7 +125,7 @@ Full Changelog: [v0.3.0...v0.4.0](https://github.com/sst/opencode-sdk-go/compare
### Features
- **api:** api update ([fa9d6ec](https://github.com/sst/opencode-sdk-go/commit/fa9d6ec6472e62f4f6605d0a71a7aa8bf8a24559))
* **api:** api update ([fa9d6ec](https://github.com/sst/opencode-sdk-go/commit/fa9d6ec6472e62f4f6605d0a71a7aa8bf8a24559))
## 0.3.0 (2025-08-31)
@ -102,7 +133,7 @@ Full Changelog: [v0.2.0...v0.3.0](https://github.com/sst/opencode-sdk-go/compare
### Features
- **api:** api update ([aae1c06](https://github.com/sst/opencode-sdk-go/commit/aae1c06bb5a93a1cd9c589846a84b3f16246f5da))
* **api:** api update ([aae1c06](https://github.com/sst/opencode-sdk-go/commit/aae1c06bb5a93a1cd9c589846a84b3f16246f5da))
## 0.2.0 (2025-08-31)
@ -110,7 +141,7 @@ Full Changelog: [v0.1.0...v0.2.0](https://github.com/sst/opencode-sdk-go/compare
### Features
- **api:** api update ([1472790](https://github.com/sst/opencode-sdk-go/commit/1472790542515f47bd46e2a9e28d8afea024cf9c))
* **api:** api update ([1472790](https://github.com/sst/opencode-sdk-go/commit/1472790542515f47bd46e2a9e28d8afea024cf9c))
## 0.1.0 (2025-08-31)
@ -118,59 +149,61 @@ Full Changelog: [v0.0.1...v0.1.0](https://github.com/sst/opencode-sdk-go/compare
### Features
- **api:** api update ([3f03ddd](https://github.com/sst/opencode-sdk-go/commit/3f03dddd5ec0de98f99ce48679077dcae9ceffd6))
- **api:** api update ([e9f79c4](https://github.com/sst/opencode-sdk-go/commit/e9f79c4792b21ef64ab0431ffd76f5a71e04d182))
- **api:** api update ([139a686](https://github.com/sst/opencode-sdk-go/commit/139a6862d2f0ab0c8ea791663d736868be3e96e6))
- **api:** api update ([2ed0800](https://github.com/sst/opencode-sdk-go/commit/2ed0800b2c5b99877e9f7fde669a6c005fad6b77))
- **api:** api update ([88a87a4](https://github.com/sst/opencode-sdk-go/commit/88a87a458f56ce0c18b502c73da933f614f56e8b))
- **api:** api update ([0e5d65b](https://github.com/sst/opencode-sdk-go/commit/0e5d65b571e7b30dc6347e6730098878ebba3a42))
- **api:** api update ([ba381f1](https://github.com/sst/opencode-sdk-go/commit/ba381f1e07aad24e9824df7d53befae2a644f69f))
- **api:** api update ([3f429f5](https://github.com/sst/opencode-sdk-go/commit/3f429f5b4be5607433ef5fdc0d5bf67fe590d039))
- **api:** api update ([9f34787](https://github.com/sst/opencode-sdk-go/commit/9f347876b35b7f898060c1a5f71c322e95978e3e))
- **api:** api update ([379c8e0](https://github.com/sst/opencode-sdk-go/commit/379c8e00197e13aebaf2f2d61277b125f1f90011))
- **api:** api update ([550511c](https://github.com/sst/opencode-sdk-go/commit/550511c4c5b5055ac8ff22b7b11731331bd9d088))
- **api:** api update ([547f0c2](https://github.com/sst/opencode-sdk-go/commit/547f0c262f2df1ce83eaa7267d68be64bb29b841))
- **api:** api update ([b7b0720](https://github.com/sst/opencode-sdk-go/commit/b7b07204bff314da24b1819c128835a43ef64065))
- **api:** api update ([7250ffc](https://github.com/sst/opencode-sdk-go/commit/7250ffcba262b916c958ddecc2a42927982db39f))
- **api:** api update ([17fbab7](https://github.com/sst/opencode-sdk-go/commit/17fbab73111a3eae488737c69b12370bc69c65f7))
- **api:** api update ([1270b5c](https://github.com/sst/opencode-sdk-go/commit/1270b5cd81e6ac769dcd92ade6d877891bf51bd5))
- **api:** api update ([a238d4a](https://github.com/sst/opencode-sdk-go/commit/a238d4abd6ed7d15f3547d27a4b6ecf4aec8431e))
- **api:** api update ([7475655](https://github.com/sst/opencode-sdk-go/commit/7475655aca577fe4f807c2f02f92171f6a358e9c))
- **api:** api update ([429d258](https://github.com/sst/opencode-sdk-go/commit/429d258bb56e9cdeb1528be3944bf5537ac26a96))
- **api:** api update ([f250915](https://github.com/sst/opencode-sdk-go/commit/f2509157eaf1b453e741ee9482127cad2e3ace25))
- **api:** api update ([5efc987](https://github.com/sst/opencode-sdk-go/commit/5efc987353801d1e772c20edf162b1c75da32743))
- **api:** api update ([98a8350](https://github.com/sst/opencode-sdk-go/commit/98a83504f7cfc361e83314c3e79a4e9ff53f0560))
- **api:** api update ([6da8bf8](https://github.com/sst/opencode-sdk-go/commit/6da8bf8bfe91d45991fb580753d77c5534fc0b1b))
- **api:** api update ([f8c7148](https://github.com/sst/opencode-sdk-go/commit/f8c7148ae56143823186e2675a78e82676154956))
- **api:** manual updates ([7cf038f](https://github.com/sst/opencode-sdk-go/commit/7cf038ffae5da1b77e1cef11b5fa166a53b467f2))
- **api:** update via SDK Studio ([068a0eb](https://github.com/sst/opencode-sdk-go/commit/068a0eb025010da0c8d86fa1bb496a39dbedcef9))
- **api:** update via SDK Studio ([ca651ed](https://github.com/sst/opencode-sdk-go/commit/ca651edaf71d1f3678f929287474f5bc4f1aad10))
- **api:** update via SDK Studio ([13550a5](https://github.com/sst/opencode-sdk-go/commit/13550a5c65d77325e945ed99fe0799cd1107b775))
- **api:** update via SDK Studio ([7b73730](https://github.com/sst/opencode-sdk-go/commit/7b73730c7fa62ba966dda3541c3e97b49be8d2bf))
- **api:** update via SDK Studio ([9e39a59](https://github.com/sst/opencode-sdk-go/commit/9e39a59b3d5d1bd5e64633732521fb28362cc70e))
- **api:** update via SDK Studio ([9609d1b](https://github.com/sst/opencode-sdk-go/commit/9609d1b1db7806d00cb846c9914cb4935cdedf52))
- **api:** update via SDK Studio ([51315fa](https://github.com/sst/opencode-sdk-go/commit/51315fa2eae424743ea79701e67d44447c44144d))
- **api:** update via SDK Studio ([af07955](https://github.com/sst/opencode-sdk-go/commit/af0795543240aefaf04fc7663a348825541c79ed))
- **api:** update via SDK Studio ([5e3468a](https://github.com/sst/opencode-sdk-go/commit/5e3468a0aaa5ed3b13e019c3a24e0ba9147d1675))
- **api:** update via SDK Studio ([0a73e04](https://github.com/sst/opencode-sdk-go/commit/0a73e04c23c90b2061611edaa8fd6282dc0ce397))
- **api:** update via SDK Studio ([9b7883a](https://github.com/sst/opencode-sdk-go/commit/9b7883a144eeac526d9d04538e0876a9d18bb844))
- **client:** expand max streaming buffer size ([76303e5](https://github.com/sst/opencode-sdk-go/commit/76303e51067e78e732af26ced9d83b8bad7655c3))
- **client:** support optional json html escaping ([449748f](https://github.com/sst/opencode-sdk-go/commit/449748f35a1d8cb6f91dc36d25bf9489f4f371bd))
* **api:** api update ([3f03ddd](https://github.com/sst/opencode-sdk-go/commit/3f03dddd5ec0de98f99ce48679077dcae9ceffd6))
* **api:** api update ([e9f79c4](https://github.com/sst/opencode-sdk-go/commit/e9f79c4792b21ef64ab0431ffd76f5a71e04d182))
* **api:** api update ([139a686](https://github.com/sst/opencode-sdk-go/commit/139a6862d2f0ab0c8ea791663d736868be3e96e6))
* **api:** api update ([2ed0800](https://github.com/sst/opencode-sdk-go/commit/2ed0800b2c5b99877e9f7fde669a6c005fad6b77))
* **api:** api update ([88a87a4](https://github.com/sst/opencode-sdk-go/commit/88a87a458f56ce0c18b502c73da933f614f56e8b))
* **api:** api update ([0e5d65b](https://github.com/sst/opencode-sdk-go/commit/0e5d65b571e7b30dc6347e6730098878ebba3a42))
* **api:** api update ([ba381f1](https://github.com/sst/opencode-sdk-go/commit/ba381f1e07aad24e9824df7d53befae2a644f69f))
* **api:** api update ([3f429f5](https://github.com/sst/opencode-sdk-go/commit/3f429f5b4be5607433ef5fdc0d5bf67fe590d039))
* **api:** api update ([9f34787](https://github.com/sst/opencode-sdk-go/commit/9f347876b35b7f898060c1a5f71c322e95978e3e))
* **api:** api update ([379c8e0](https://github.com/sst/opencode-sdk-go/commit/379c8e00197e13aebaf2f2d61277b125f1f90011))
* **api:** api update ([550511c](https://github.com/sst/opencode-sdk-go/commit/550511c4c5b5055ac8ff22b7b11731331bd9d088))
* **api:** api update ([547f0c2](https://github.com/sst/opencode-sdk-go/commit/547f0c262f2df1ce83eaa7267d68be64bb29b841))
* **api:** api update ([b7b0720](https://github.com/sst/opencode-sdk-go/commit/b7b07204bff314da24b1819c128835a43ef64065))
* **api:** api update ([7250ffc](https://github.com/sst/opencode-sdk-go/commit/7250ffcba262b916c958ddecc2a42927982db39f))
* **api:** api update ([17fbab7](https://github.com/sst/opencode-sdk-go/commit/17fbab73111a3eae488737c69b12370bc69c65f7))
* **api:** api update ([1270b5c](https://github.com/sst/opencode-sdk-go/commit/1270b5cd81e6ac769dcd92ade6d877891bf51bd5))
* **api:** api update ([a238d4a](https://github.com/sst/opencode-sdk-go/commit/a238d4abd6ed7d15f3547d27a4b6ecf4aec8431e))
* **api:** api update ([7475655](https://github.com/sst/opencode-sdk-go/commit/7475655aca577fe4f807c2f02f92171f6a358e9c))
* **api:** api update ([429d258](https://github.com/sst/opencode-sdk-go/commit/429d258bb56e9cdeb1528be3944bf5537ac26a96))
* **api:** api update ([f250915](https://github.com/sst/opencode-sdk-go/commit/f2509157eaf1b453e741ee9482127cad2e3ace25))
* **api:** api update ([5efc987](https://github.com/sst/opencode-sdk-go/commit/5efc987353801d1e772c20edf162b1c75da32743))
* **api:** api update ([98a8350](https://github.com/sst/opencode-sdk-go/commit/98a83504f7cfc361e83314c3e79a4e9ff53f0560))
* **api:** api update ([6da8bf8](https://github.com/sst/opencode-sdk-go/commit/6da8bf8bfe91d45991fb580753d77c5534fc0b1b))
* **api:** api update ([f8c7148](https://github.com/sst/opencode-sdk-go/commit/f8c7148ae56143823186e2675a78e82676154956))
* **api:** manual updates ([7cf038f](https://github.com/sst/opencode-sdk-go/commit/7cf038ffae5da1b77e1cef11b5fa166a53b467f2))
* **api:** update via SDK Studio ([068a0eb](https://github.com/sst/opencode-sdk-go/commit/068a0eb025010da0c8d86fa1bb496a39dbedcef9))
* **api:** update via SDK Studio ([ca651ed](https://github.com/sst/opencode-sdk-go/commit/ca651edaf71d1f3678f929287474f5bc4f1aad10))
* **api:** update via SDK Studio ([13550a5](https://github.com/sst/opencode-sdk-go/commit/13550a5c65d77325e945ed99fe0799cd1107b775))
* **api:** update via SDK Studio ([7b73730](https://github.com/sst/opencode-sdk-go/commit/7b73730c7fa62ba966dda3541c3e97b49be8d2bf))
* **api:** update via SDK Studio ([9e39a59](https://github.com/sst/opencode-sdk-go/commit/9e39a59b3d5d1bd5e64633732521fb28362cc70e))
* **api:** update via SDK Studio ([9609d1b](https://github.com/sst/opencode-sdk-go/commit/9609d1b1db7806d00cb846c9914cb4935cdedf52))
* **api:** update via SDK Studio ([51315fa](https://github.com/sst/opencode-sdk-go/commit/51315fa2eae424743ea79701e67d44447c44144d))
* **api:** update via SDK Studio ([af07955](https://github.com/sst/opencode-sdk-go/commit/af0795543240aefaf04fc7663a348825541c79ed))
* **api:** update via SDK Studio ([5e3468a](https://github.com/sst/opencode-sdk-go/commit/5e3468a0aaa5ed3b13e019c3a24e0ba9147d1675))
* **api:** update via SDK Studio ([0a73e04](https://github.com/sst/opencode-sdk-go/commit/0a73e04c23c90b2061611edaa8fd6282dc0ce397))
* **api:** update via SDK Studio ([9b7883a](https://github.com/sst/opencode-sdk-go/commit/9b7883a144eeac526d9d04538e0876a9d18bb844))
* **client:** expand max streaming buffer size ([76303e5](https://github.com/sst/opencode-sdk-go/commit/76303e51067e78e732af26ced9d83b8bad7655c3))
* **client:** support optional json html escaping ([449748f](https://github.com/sst/opencode-sdk-go/commit/449748f35a1d8cb6f91dc36d25bf9489f4f371bd))
### Bug Fixes
- **client:** process custom base url ahead of time ([9b360d6](https://github.com/sst/opencode-sdk-go/commit/9b360d642cf6f302104308af5622e17099899e5f))
- **client:** resolve lint errors in streaming tests ([4d36cb0](https://github.com/sst/opencode-sdk-go/commit/4d36cb09fc9d436734d5dab1c499acaa88568ff7))
- close body before retrying ([4da3f7f](https://github.com/sst/opencode-sdk-go/commit/4da3f7f372bad222a189ba3eabcfde3373166ae5))
- don't try to deserialize as json when ResponseBodyInto is []byte ([595291f](https://github.com/sst/opencode-sdk-go/commit/595291f6dba6af472f160b9f8e3d145002f43a4a))
* **client:** process custom base url ahead of time ([9b360d6](https://github.com/sst/opencode-sdk-go/commit/9b360d642cf6f302104308af5622e17099899e5f))
* **client:** resolve lint errors in streaming tests ([4d36cb0](https://github.com/sst/opencode-sdk-go/commit/4d36cb09fc9d436734d5dab1c499acaa88568ff7))
* close body before retrying ([4da3f7f](https://github.com/sst/opencode-sdk-go/commit/4da3f7f372bad222a189ba3eabcfde3373166ae5))
* don't try to deserialize as json when ResponseBodyInto is []byte ([595291f](https://github.com/sst/opencode-sdk-go/commit/595291f6dba6af472f160b9f8e3d145002f43a4a))
### Chores
- **ci:** only run for pushes and fork pull requests ([bea59b8](https://github.com/sst/opencode-sdk-go/commit/bea59b886800ef555f89c47a9256d6392ed2e53d))
- **internal:** codegen related update ([6a22ce6](https://github.com/sst/opencode-sdk-go/commit/6a22ce6df155f5003e80b8a75686a9e513a5568a))
- **internal:** fix lint script for tests ([391c482](https://github.com/sst/opencode-sdk-go/commit/391c482148ed0a77c4ad52807abeb2d540b56797))
- **internal:** update comment in script ([b7f1c3e](https://github.com/sst/opencode-sdk-go/commit/b7f1c3e16935c71e243004b8f321d661cd8e9474))
- lint tests ([616796b](https://github.com/sst/opencode-sdk-go/commit/616796b761704bde6be5c6c2428f28c79c7f05ff))
- lint tests in subpackages ([50c82ff](https://github.com/sst/opencode-sdk-go/commit/50c82ff0757c973834b68adc22566b70f767b611))
- sync repo ([2f34d5d](https://github.com/sst/opencode-sdk-go/commit/2f34d5d53e56e9cdc3df99be7ee7efc83dd977a3))
- update @stainless-api/prism-cli to v5.15.0 ([2f24852](https://github.com/sst/opencode-sdk-go/commit/2f2485216d4f4891d1fbfbc23ff8410c2f35152a))
* **ci:** only run for pushes and fork pull requests ([bea59b8](https://github.com/sst/opencode-sdk-go/commit/bea59b886800ef555f89c47a9256d6392ed2e53d))
* **internal:** codegen related update ([6a22ce6](https://github.com/sst/opencode-sdk-go/commit/6a22ce6df155f5003e80b8a75686a9e513a5568a))
* **internal:** fix lint script for tests ([391c482](https://github.com/sst/opencode-sdk-go/commit/391c482148ed0a77c4ad52807abeb2d540b56797))
* **internal:** update comment in script ([b7f1c3e](https://github.com/sst/opencode-sdk-go/commit/b7f1c3e16935c71e243004b8f321d661cd8e9474))
* lint tests ([616796b](https://github.com/sst/opencode-sdk-go/commit/616796b761704bde6be5c6c2428f28c79c7f05ff))
* lint tests in subpackages ([50c82ff](https://github.com/sst/opencode-sdk-go/commit/50c82ff0757c973834b68adc22566b70f767b611))
* sync repo ([2f34d5d](https://github.com/sst/opencode-sdk-go/commit/2f34d5d53e56e9cdc3df99be7ee7efc83dd977a3))
* update @stainless-api/prism-cli to v5.15.0 ([2f24852](https://github.com/sst/opencode-sdk-go/commit/2f2485216d4f4891d1fbfbc23ff8410c2f35152a))

View file

@ -9,7 +9,7 @@ $ ./scripts/build
This will install all the required dependencies and build the SDK.
You can also [install go 1.18+ manually](https://go.dev/doc/install).
You can also [install go 1.22+ manually](https://go.dev/doc/install).
## Modifying/Adding code

View file

@ -24,14 +24,14 @@ Or to pin the version:
<!-- x-release-please-start-version -->
```sh
go get -u 'github.com/sst/opencode-sdk-go@v0.15.0'
go get -u 'github.com/sst/opencode-sdk-go@v0.16.2'
```
<!-- x-release-please-end -->
## Requirements
This library requires Go 1.18+.
This library requires Go 1.22+.
## Usage

View file

@ -6,6 +6,7 @@ import (
"context"
"net/http"
"net/url"
"slices"
"github.com/sst/opencode-sdk-go/internal/apijson"
"github.com/sst/opencode-sdk-go/internal/apiquery"
@ -35,7 +36,7 @@ func NewAgentService(opts ...option.RequestOption) (r *AgentService) {
// List all agents
func (r *AgentService) List(ctx context.Context, query AgentListParams, opts ...option.RequestOption) (res *[]Agent, err error) {
opts = append(r.Options[:], opts...)
opts = slices.Concat(r.Options, opts)
path := "agent"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
return

View file

@ -6,6 +6,7 @@ import (
"context"
"net/http"
"net/url"
"slices"
"github.com/sst/opencode-sdk-go/internal/apijson"
"github.com/sst/opencode-sdk-go/internal/apiquery"
@ -35,7 +36,7 @@ func NewAppService(opts ...option.RequestOption) (r *AppService) {
// Write a log entry to the server logs
func (r *AppService) Log(ctx context.Context, params AppLogParams, opts ...option.RequestOption) (res *bool, err error) {
opts = append(r.Options[:], opts...)
opts = slices.Concat(r.Options, opts)
path := "log"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, params, &res, opts...)
return
@ -43,7 +44,7 @@ func (r *AppService) Log(ctx context.Context, params AppLogParams, opts ...optio
// List all providers
func (r *AppService) Providers(ctx context.Context, query AppProvidersParams, opts ...option.RequestOption) (res *AppProvidersResponse, err error) {
opts = append(r.Options[:], opts...)
opts = slices.Concat(r.Options, opts)
path := "config/providers"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
return

View file

@ -6,6 +6,7 @@ import (
"context"
"net/http"
"os"
"slices"
"github.com/sst/opencode-sdk-go/internal/requestconfig"
"github.com/sst/opencode-sdk-go/option"
@ -95,7 +96,7 @@ func NewClient(opts ...option.RequestOption) (r *Client) {
// For even greater flexibility, see [option.WithResponseInto] and
// [option.WithResponseBodyInto].
func (r *Client) Execute(ctx context.Context, method string, path string, params interface{}, res interface{}, opts ...option.RequestOption) error {
opts = append(r.Options, opts...)
opts = slices.Concat(r.Options, opts)
return requestconfig.ExecuteNewRequest(ctx, method, path, params, res, opts...)
}

View file

@ -6,6 +6,7 @@ import (
"context"
"net/http"
"net/url"
"slices"
"github.com/sst/opencode-sdk-go/internal/apijson"
"github.com/sst/opencode-sdk-go/internal/apiquery"
@ -35,7 +36,7 @@ func NewCommandService(opts ...option.RequestOption) (r *CommandService) {
// List all commands
func (r *CommandService) List(ctx context.Context, query CommandListParams, opts ...option.RequestOption) (res *[]Command, err error) {
opts = append(r.Options[:], opts...)
opts = slices.Concat(r.Options, opts)
path := "command"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
return

View file

@ -7,6 +7,7 @@ import (
"net/http"
"net/url"
"reflect"
"slices"
"github.com/sst/opencode-sdk-go/internal/apijson"
"github.com/sst/opencode-sdk-go/internal/apiquery"
@ -38,7 +39,7 @@ func NewConfigService(opts ...option.RequestOption) (r *ConfigService) {
// Get config info
func (r *ConfigService) Get(ctx context.Context, query ConfigGetParams, opts ...option.RequestOption) (res *Config, err error) {
opts = append(r.Options[:], opts...)
opts = slices.Concat(r.Options, opts)
path := "config"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
return

View file

@ -7,6 +7,7 @@ import (
"net/http"
"net/url"
"reflect"
"slices"
"github.com/sst/opencode-sdk-go/internal/apijson"
"github.com/sst/opencode-sdk-go/internal/apiquery"
@ -43,7 +44,7 @@ func (r *EventService) ListStreaming(ctx context.Context, query EventListParams,
raw *http.Response
err error
)
opts = append(r.Options[:], opts...)
opts = slices.Concat(r.Options, opts)
opts = append([]option.RequestOption{option.WithHeader("Accept", "text/event-stream")}, opts...)
path := "event"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &raw, opts...)
@ -61,11 +62,12 @@ type EventListResponse struct {
// [EventListResponseEventSessionCompactedProperties], [Permission],
// [EventListResponseEventPermissionRepliedProperties],
// [EventListResponseEventFileEditedProperties],
// [EventListResponseEventFileWatcherUpdatedProperties],
// [EventListResponseEventTodoUpdatedProperties],
// [EventListResponseEventSessionIdleProperties],
// [EventListResponseEventSessionUpdatedProperties],
// [EventListResponseEventSessionDeletedProperties],
// [EventListResponseEventSessionErrorProperties], [interface{}],
// [EventListResponseEventFileWatcherUpdatedProperties],
// [EventListResponseEventIdeInstalledProperties].
Properties interface{} `json:"properties,required"`
Type EventListResponseType `json:"type,required"`
@ -107,11 +109,10 @@ func (r *EventListResponse) UnmarshalJSON(data []byte) (err error) {
// [EventListResponseEventSessionCompacted],
// [EventListResponseEventPermissionUpdated],
// [EventListResponseEventPermissionReplied], [EventListResponseEventFileEdited],
// [EventListResponseEventFileWatcherUpdated], [EventListResponseEventTodoUpdated],
// [EventListResponseEventSessionIdle], [EventListResponseEventSessionUpdated],
// [EventListResponseEventSessionDeleted], [EventListResponseEventSessionError],
// [EventListResponseEventServerConnected],
// [EventListResponseEventFileWatcherUpdated],
// [EventListResponseEventIdeInstalled].
// [EventListResponseEventServerConnected], [EventListResponseEventIdeInstalled].
func (r EventListResponse) AsUnion() EventListResponseUnion {
return r.union
}
@ -124,11 +125,10 @@ func (r EventListResponse) AsUnion() EventListResponseUnion {
// [EventListResponseEventSessionCompacted],
// [EventListResponseEventPermissionUpdated],
// [EventListResponseEventPermissionReplied], [EventListResponseEventFileEdited],
// [EventListResponseEventFileWatcherUpdated], [EventListResponseEventTodoUpdated],
// [EventListResponseEventSessionIdle], [EventListResponseEventSessionUpdated],
// [EventListResponseEventSessionDeleted], [EventListResponseEventSessionError],
// [EventListResponseEventServerConnected],
// [EventListResponseEventFileWatcherUpdated] or
// [EventListResponseEventIdeInstalled].
// [EventListResponseEventServerConnected] or [EventListResponseEventIdeInstalled].
type EventListResponseUnion interface {
implementsEventListResponse()
}
@ -177,6 +177,14 @@ func init() {
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(EventListResponseEventFileEdited{}),
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(EventListResponseEventFileWatcherUpdated{}),
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(EventListResponseEventTodoUpdated{}),
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(EventListResponseEventSessionIdle{}),
@ -197,10 +205,6 @@ func init() {
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(EventListResponseEventServerConnected{}),
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(EventListResponseEventFileWatcherUpdated{}),
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(EventListResponseEventIdeInstalled{}),
@ -799,6 +803,177 @@ func (r EventListResponseEventFileEditedType) IsKnown() bool {
return false
}
type EventListResponseEventFileWatcherUpdated struct {
Properties EventListResponseEventFileWatcherUpdatedProperties `json:"properties,required"`
Type EventListResponseEventFileWatcherUpdatedType `json:"type,required"`
JSON eventListResponseEventFileWatcherUpdatedJSON `json:"-"`
}
// eventListResponseEventFileWatcherUpdatedJSON contains the JSON metadata for the
// struct [EventListResponseEventFileWatcherUpdated]
type eventListResponseEventFileWatcherUpdatedJSON struct {
Properties apijson.Field
Type apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *EventListResponseEventFileWatcherUpdated) UnmarshalJSON(data []byte) (err error) {
return apijson.UnmarshalRoot(data, r)
}
func (r eventListResponseEventFileWatcherUpdatedJSON) RawJSON() string {
return r.raw
}
func (r EventListResponseEventFileWatcherUpdated) implementsEventListResponse() {}
type EventListResponseEventFileWatcherUpdatedProperties struct {
Event EventListResponseEventFileWatcherUpdatedPropertiesEvent `json:"event,required"`
File string `json:"file,required"`
JSON eventListResponseEventFileWatcherUpdatedPropertiesJSON `json:"-"`
}
// eventListResponseEventFileWatcherUpdatedPropertiesJSON contains the JSON
// metadata for the struct [EventListResponseEventFileWatcherUpdatedProperties]
type eventListResponseEventFileWatcherUpdatedPropertiesJSON struct {
Event apijson.Field
File apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *EventListResponseEventFileWatcherUpdatedProperties) UnmarshalJSON(data []byte) (err error) {
return apijson.UnmarshalRoot(data, r)
}
func (r eventListResponseEventFileWatcherUpdatedPropertiesJSON) RawJSON() string {
return r.raw
}
type EventListResponseEventFileWatcherUpdatedPropertiesEvent string
const (
EventListResponseEventFileWatcherUpdatedPropertiesEventAdd EventListResponseEventFileWatcherUpdatedPropertiesEvent = "add"
EventListResponseEventFileWatcherUpdatedPropertiesEventChange EventListResponseEventFileWatcherUpdatedPropertiesEvent = "change"
EventListResponseEventFileWatcherUpdatedPropertiesEventUnlink EventListResponseEventFileWatcherUpdatedPropertiesEvent = "unlink"
)
func (r EventListResponseEventFileWatcherUpdatedPropertiesEvent) IsKnown() bool {
switch r {
case EventListResponseEventFileWatcherUpdatedPropertiesEventAdd, EventListResponseEventFileWatcherUpdatedPropertiesEventChange, EventListResponseEventFileWatcherUpdatedPropertiesEventUnlink:
return true
}
return false
}
type EventListResponseEventFileWatcherUpdatedType string
const (
EventListResponseEventFileWatcherUpdatedTypeFileWatcherUpdated EventListResponseEventFileWatcherUpdatedType = "file.watcher.updated"
)
func (r EventListResponseEventFileWatcherUpdatedType) IsKnown() bool {
switch r {
case EventListResponseEventFileWatcherUpdatedTypeFileWatcherUpdated:
return true
}
return false
}
type EventListResponseEventTodoUpdated struct {
Properties EventListResponseEventTodoUpdatedProperties `json:"properties,required"`
Type EventListResponseEventTodoUpdatedType `json:"type,required"`
JSON eventListResponseEventTodoUpdatedJSON `json:"-"`
}
// eventListResponseEventTodoUpdatedJSON contains the JSON metadata for the struct
// [EventListResponseEventTodoUpdated]
type eventListResponseEventTodoUpdatedJSON struct {
Properties apijson.Field
Type apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *EventListResponseEventTodoUpdated) UnmarshalJSON(data []byte) (err error) {
return apijson.UnmarshalRoot(data, r)
}
func (r eventListResponseEventTodoUpdatedJSON) RawJSON() string {
return r.raw
}
func (r EventListResponseEventTodoUpdated) implementsEventListResponse() {}
type EventListResponseEventTodoUpdatedProperties struct {
SessionID string `json:"sessionID,required"`
Todos []EventListResponseEventTodoUpdatedPropertiesTodo `json:"todos,required"`
JSON eventListResponseEventTodoUpdatedPropertiesJSON `json:"-"`
}
// eventListResponseEventTodoUpdatedPropertiesJSON contains the JSON metadata for
// the struct [EventListResponseEventTodoUpdatedProperties]
type eventListResponseEventTodoUpdatedPropertiesJSON struct {
SessionID apijson.Field
Todos apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *EventListResponseEventTodoUpdatedProperties) UnmarshalJSON(data []byte) (err error) {
return apijson.UnmarshalRoot(data, r)
}
func (r eventListResponseEventTodoUpdatedPropertiesJSON) RawJSON() string {
return r.raw
}
type EventListResponseEventTodoUpdatedPropertiesTodo struct {
// Unique identifier for the todo item
ID string `json:"id,required"`
// Brief description of the task
Content string `json:"content,required"`
// Priority level of the task: high, medium, low
Priority string `json:"priority,required"`
// Current status of the task: pending, in_progress, completed, cancelled
Status string `json:"status,required"`
JSON eventListResponseEventTodoUpdatedPropertiesTodoJSON `json:"-"`
}
// eventListResponseEventTodoUpdatedPropertiesTodoJSON contains the JSON metadata
// for the struct [EventListResponseEventTodoUpdatedPropertiesTodo]
type eventListResponseEventTodoUpdatedPropertiesTodoJSON struct {
ID apijson.Field
Content apijson.Field
Priority apijson.Field
Status apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *EventListResponseEventTodoUpdatedPropertiesTodo) UnmarshalJSON(data []byte) (err error) {
return apijson.UnmarshalRoot(data, r)
}
func (r eventListResponseEventTodoUpdatedPropertiesTodoJSON) RawJSON() string {
return r.raw
}
type EventListResponseEventTodoUpdatedType string
const (
EventListResponseEventTodoUpdatedTypeTodoUpdated EventListResponseEventTodoUpdatedType = "todo.updated"
)
func (r EventListResponseEventTodoUpdatedType) IsKnown() bool {
switch r {
case EventListResponseEventTodoUpdatedTypeTodoUpdated:
return true
}
return false
}
type EventListResponseEventSessionIdle struct {
Properties EventListResponseEventSessionIdleProperties `json:"properties,required"`
Type EventListResponseEventSessionIdleType `json:"type,required"`
@ -1210,84 +1385,6 @@ func (r EventListResponseEventServerConnectedType) IsKnown() bool {
return false
}
type EventListResponseEventFileWatcherUpdated struct {
Properties EventListResponseEventFileWatcherUpdatedProperties `json:"properties,required"`
Type EventListResponseEventFileWatcherUpdatedType `json:"type,required"`
JSON eventListResponseEventFileWatcherUpdatedJSON `json:"-"`
}
// eventListResponseEventFileWatcherUpdatedJSON contains the JSON metadata for the
// struct [EventListResponseEventFileWatcherUpdated]
type eventListResponseEventFileWatcherUpdatedJSON struct {
Properties apijson.Field
Type apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *EventListResponseEventFileWatcherUpdated) UnmarshalJSON(data []byte) (err error) {
return apijson.UnmarshalRoot(data, r)
}
func (r eventListResponseEventFileWatcherUpdatedJSON) RawJSON() string {
return r.raw
}
func (r EventListResponseEventFileWatcherUpdated) implementsEventListResponse() {}
type EventListResponseEventFileWatcherUpdatedProperties struct {
Event EventListResponseEventFileWatcherUpdatedPropertiesEvent `json:"event,required"`
File string `json:"file,required"`
JSON eventListResponseEventFileWatcherUpdatedPropertiesJSON `json:"-"`
}
// eventListResponseEventFileWatcherUpdatedPropertiesJSON contains the JSON
// metadata for the struct [EventListResponseEventFileWatcherUpdatedProperties]
type eventListResponseEventFileWatcherUpdatedPropertiesJSON struct {
Event apijson.Field
File apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *EventListResponseEventFileWatcherUpdatedProperties) UnmarshalJSON(data []byte) (err error) {
return apijson.UnmarshalRoot(data, r)
}
func (r eventListResponseEventFileWatcherUpdatedPropertiesJSON) RawJSON() string {
return r.raw
}
type EventListResponseEventFileWatcherUpdatedPropertiesEvent string
const (
EventListResponseEventFileWatcherUpdatedPropertiesEventAdd EventListResponseEventFileWatcherUpdatedPropertiesEvent = "add"
EventListResponseEventFileWatcherUpdatedPropertiesEventChange EventListResponseEventFileWatcherUpdatedPropertiesEvent = "change"
EventListResponseEventFileWatcherUpdatedPropertiesEventUnlink EventListResponseEventFileWatcherUpdatedPropertiesEvent = "unlink"
)
func (r EventListResponseEventFileWatcherUpdatedPropertiesEvent) IsKnown() bool {
switch r {
case EventListResponseEventFileWatcherUpdatedPropertiesEventAdd, EventListResponseEventFileWatcherUpdatedPropertiesEventChange, EventListResponseEventFileWatcherUpdatedPropertiesEventUnlink:
return true
}
return false
}
type EventListResponseEventFileWatcherUpdatedType string
const (
EventListResponseEventFileWatcherUpdatedTypeFileWatcherUpdated EventListResponseEventFileWatcherUpdatedType = "file.watcher.updated"
)
func (r EventListResponseEventFileWatcherUpdatedType) IsKnown() bool {
switch r {
case EventListResponseEventFileWatcherUpdatedTypeFileWatcherUpdated:
return true
}
return false
}
type EventListResponseEventIdeInstalled struct {
Properties EventListResponseEventIdeInstalledProperties `json:"properties,required"`
Type EventListResponseEventIdeInstalledType `json:"type,required"`
@ -1361,18 +1458,19 @@ const (
EventListResponseTypePermissionUpdated EventListResponseType = "permission.updated"
EventListResponseTypePermissionReplied EventListResponseType = "permission.replied"
EventListResponseTypeFileEdited EventListResponseType = "file.edited"
EventListResponseTypeFileWatcherUpdated EventListResponseType = "file.watcher.updated"
EventListResponseTypeTodoUpdated EventListResponseType = "todo.updated"
EventListResponseTypeSessionIdle EventListResponseType = "session.idle"
EventListResponseTypeSessionUpdated EventListResponseType = "session.updated"
EventListResponseTypeSessionDeleted EventListResponseType = "session.deleted"
EventListResponseTypeSessionError EventListResponseType = "session.error"
EventListResponseTypeServerConnected EventListResponseType = "server.connected"
EventListResponseTypeFileWatcherUpdated EventListResponseType = "file.watcher.updated"
EventListResponseTypeIdeInstalled EventListResponseType = "ide.installed"
)
func (r EventListResponseType) IsKnown() bool {
switch r {
case EventListResponseTypeInstallationUpdated, EventListResponseTypeLspClientDiagnostics, EventListResponseTypeMessageUpdated, EventListResponseTypeMessageRemoved, EventListResponseTypeMessagePartUpdated, EventListResponseTypeMessagePartRemoved, EventListResponseTypeSessionCompacted, EventListResponseTypePermissionUpdated, EventListResponseTypePermissionReplied, EventListResponseTypeFileEdited, EventListResponseTypeSessionIdle, EventListResponseTypeSessionUpdated, EventListResponseTypeSessionDeleted, EventListResponseTypeSessionError, EventListResponseTypeServerConnected, EventListResponseTypeFileWatcherUpdated, EventListResponseTypeIdeInstalled:
case EventListResponseTypeInstallationUpdated, EventListResponseTypeLspClientDiagnostics, EventListResponseTypeMessageUpdated, EventListResponseTypeMessageRemoved, EventListResponseTypeMessagePartUpdated, EventListResponseTypeMessagePartRemoved, EventListResponseTypeSessionCompacted, EventListResponseTypePermissionUpdated, EventListResponseTypePermissionReplied, EventListResponseTypeFileEdited, EventListResponseTypeFileWatcherUpdated, EventListResponseTypeTodoUpdated, EventListResponseTypeSessionIdle, EventListResponseTypeSessionUpdated, EventListResponseTypeSessionDeleted, EventListResponseTypeSessionError, EventListResponseTypeServerConnected, EventListResponseTypeIdeInstalled:
return true
}
return false

View file

@ -6,6 +6,7 @@ import (
"context"
"net/http"
"net/url"
"slices"
"github.com/sst/opencode-sdk-go/internal/apijson"
"github.com/sst/opencode-sdk-go/internal/apiquery"
@ -35,7 +36,7 @@ func NewFileService(opts ...option.RequestOption) (r *FileService) {
// List files and directories
func (r *FileService) List(ctx context.Context, query FileListParams, opts ...option.RequestOption) (res *[]FileNode, err error) {
opts = append(r.Options[:], opts...)
opts = slices.Concat(r.Options, opts)
path := "file"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
return
@ -43,7 +44,7 @@ func (r *FileService) List(ctx context.Context, query FileListParams, opts ...op
// Read a file
func (r *FileService) Read(ctx context.Context, query FileReadParams, opts ...option.RequestOption) (res *FileReadResponse, err error) {
opts = append(r.Options[:], opts...)
opts = slices.Concat(r.Options, opts)
path := "file/content"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
return
@ -51,7 +52,7 @@ func (r *FileService) Read(ctx context.Context, query FileReadParams, opts ...op
// Get file status
func (r *FileService) Status(ctx context.Context, query FileStatusParams, opts ...option.RequestOption) (res *[]File, err error) {
opts = append(r.Options[:], opts...)
opts = slices.Concat(r.Options, opts)
path := "file/status"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
return

View file

@ -6,6 +6,7 @@ import (
"context"
"net/http"
"net/url"
"slices"
"github.com/sst/opencode-sdk-go/internal/apijson"
"github.com/sst/opencode-sdk-go/internal/apiquery"
@ -35,7 +36,7 @@ func NewFindService(opts ...option.RequestOption) (r *FindService) {
// Find files
func (r *FindService) Files(ctx context.Context, query FindFilesParams, opts ...option.RequestOption) (res *[]string, err error) {
opts = append(r.Options[:], opts...)
opts = slices.Concat(r.Options, opts)
path := "find/file"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
return
@ -43,7 +44,7 @@ func (r *FindService) Files(ctx context.Context, query FindFilesParams, opts ...
// Find workspace symbols
func (r *FindService) Symbols(ctx context.Context, query FindSymbolsParams, opts ...option.RequestOption) (res *[]Symbol, err error) {
opts = append(r.Options[:], opts...)
opts = slices.Concat(r.Options, opts)
path := "find/symbol"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
return
@ -51,7 +52,7 @@ func (r *FindService) Symbols(ctx context.Context, query FindSymbolsParams, opts
// Find text in files
func (r *FindService) Text(ctx context.Context, query FindTextParams, opts ...option.RequestOption) (res *[]FindTextResponse, err error) {
opts = append(r.Options[:], opts...)
opts = slices.Concat(r.Options, opts)
path := "find"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
return

View file

@ -1,6 +1,6 @@
module github.com/sst/opencode-sdk-go
go 1.21
go 1.22
require (
github.com/tidwall/gjson v1.14.4

View file

@ -224,7 +224,7 @@ func (d *decoderBuilder) newUnionDecoder(t reflect.Type) decoderFunc {
}
if len(unionEntry.discriminatorKey) != 0 {
discriminatorValue := n.Get(unionEntry.discriminatorKey).Value()
discriminatorValue := n.Get(EscapeSJSONKey(unionEntry.discriminatorKey)).Value()
if discriminatorValue == variant.DiscriminatorValue {
inner := reflect.New(variant.Type).Elem()
err := decoder(n, inner, state)

View file

@ -18,6 +18,10 @@ import (
var encoders sync.Map // map[encoderEntry]encoderFunc
// If we want to set a literal key value into JSON using sjson, we need to make sure it doesn't have
// special characters that sjson interprets as a path.
var EscapeSJSONKey = strings.NewReplacer("\\", "\\\\", "|", "\\|", "#", "\\#", "@", "\\@", "*", "\\*", ".", "\\.", ":", "\\:", "?", "\\?").Replace
func Marshal(value interface{}) ([]byte, error) {
e := &encoder{dateFormat: time.RFC3339}
return e.marshal(value)
@ -276,7 +280,7 @@ func (e *encoder) newStructTypeEncoder(t reflect.Type) encoderFunc {
if encoded == nil {
continue
}
json, err = sjson.SetRawBytes(json, ef.tag.name, encoded)
json, err = sjson.SetRawBytes(json, EscapeSJSONKey(ef.tag.name), encoded)
if err != nil {
return nil, err
}
@ -354,7 +358,7 @@ func (e *encoder) encodeMapEntries(json []byte, v reflect.Value) ([]byte, error)
}
encodedKeyString = string(encodedKeyBytes)
}
encodedKey := []byte(sjsonReplacer.Replace(encodedKeyString))
encodedKey := []byte(encodedKeyString)
pairs = append(pairs, mapPair{key: encodedKey, value: iter.Value()})
}
@ -372,7 +376,7 @@ func (e *encoder) encodeMapEntries(json []byte, v reflect.Value) ([]byte, error)
if len(encodedValue) == 0 {
continue
}
json, err = sjson.SetRawBytes(json, string(p.key), encodedValue)
json, err = sjson.SetRawBytes(json, EscapeSJSONKey(string(p.key)), encodedValue)
if err != nil {
return nil, err
}
@ -392,7 +396,3 @@ func (e *encoder) newMapEncoder(t reflect.Type) encoderFunc {
return json, nil
}
}
// If we want to set a literal key value into JSON using sjson, we need to make sure it doesn't have
// special characters that sjson interprets as a path.
var sjsonReplacer *strings.Replacer = strings.NewReplacer(".", "\\.", ":", "\\:", "*", "\\*")

View file

@ -362,7 +362,7 @@ var tests = map[string]struct {
"date_time_nano_missing_t_coerce": {`"2007-03-01 13:03:05.123456789Z"`, time.Date(2007, time.March, 1, 13, 3, 5, 123456789, time.UTC)},
"map_string": {`{"foo":"bar"}`, map[string]string{"foo": "bar"}},
"map_string_with_sjson_path_chars": {`{":a.b.c*:d*-1e.f":"bar"}`, map[string]string{":a.b.c*:d*-1e.f": "bar"}},
"map_string_with_sjson_path_chars": {`{":a.b.c*:d*-1e.f@g?h":"bar"}`, map[string]string{":a.b.c*:d*-1e.f@g?h": "bar"}},
"map_interface": {`{"a":1,"b":"str","c":false}`, map[string]interface{}{"a": float64(1), "b": "str", "c": false}},
"primitive_struct": {

View file

@ -2,4 +2,4 @@
package internal
const PackageVersion = "0.15.0" // x-release-please-version
const PackageVersion = "0.16.2" // x-release-please-version

View file

@ -6,6 +6,7 @@ import (
"context"
"net/http"
"net/url"
"slices"
"github.com/sst/opencode-sdk-go/internal/apijson"
"github.com/sst/opencode-sdk-go/internal/apiquery"
@ -35,7 +36,7 @@ func NewPathService(opts ...option.RequestOption) (r *PathService) {
// Get the current path
func (r *PathService) Get(ctx context.Context, query PathGetParams, opts ...option.RequestOption) (res *Path, err error) {
opts = append(r.Options[:], opts...)
opts = slices.Concat(r.Options, opts)
path := "path"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
return

View file

@ -6,6 +6,7 @@ import (
"context"
"net/http"
"net/url"
"slices"
"github.com/sst/opencode-sdk-go/internal/apijson"
"github.com/sst/opencode-sdk-go/internal/apiquery"
@ -35,7 +36,7 @@ func NewProjectService(opts ...option.RequestOption) (r *ProjectService) {
// List all projects
func (r *ProjectService) List(ctx context.Context, query ProjectListParams, opts ...option.RequestOption) (res *[]Project, err error) {
opts = append(r.Options[:], opts...)
opts = slices.Concat(r.Options, opts)
path := "project"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
return
@ -43,7 +44,7 @@ func (r *ProjectService) List(ctx context.Context, query ProjectListParams, opts
// Get the current project
func (r *ProjectService) Current(ctx context.Context, query ProjectCurrentParams, opts ...option.RequestOption) (res *Project, err error) {
opts = append(r.Options[:], opts...)
opts = slices.Concat(r.Options, opts)
path := "project/current"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
return

View file

@ -60,5 +60,8 @@
}
],
"release-type": "go",
"extra-files": ["internal/version.go", "README.md"]
}
"extra-files": [
"internal/version.go",
"README.md"
]
}

View file

@ -4,10 +4,18 @@ set -e
cd "$(dirname "$0")/.."
if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "$SKIP_BREW" != "1" ]; then
if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "$SKIP_BREW" != "1" ] && [ -t 0 ]; then
brew bundle check >/dev/null 2>&1 || {
echo "==> Installing Homebrew dependencies…"
brew bundle
echo -n "==> Install Homebrew dependencies? (y/N): "
read -r response
case "$response" in
[yY][eE][sS]|[yY])
brew bundle
;;
*)
;;
esac
echo
}
fi

View file

@ -9,6 +9,7 @@ import (
"net/http"
"net/url"
"reflect"
"slices"
"github.com/sst/opencode-sdk-go/internal/apijson"
"github.com/sst/opencode-sdk-go/internal/apiquery"
@ -42,7 +43,7 @@ func NewSessionService(opts ...option.RequestOption) (r *SessionService) {
// Create a new session
func (r *SessionService) New(ctx context.Context, params SessionNewParams, opts ...option.RequestOption) (res *Session, err error) {
opts = append(r.Options[:], opts...)
opts = slices.Concat(r.Options, opts)
path := "session"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, params, &res, opts...)
return
@ -50,7 +51,7 @@ func (r *SessionService) New(ctx context.Context, params SessionNewParams, opts
// Update session properties
func (r *SessionService) Update(ctx context.Context, id string, params SessionUpdateParams, opts ...option.RequestOption) (res *Session, err error) {
opts = append(r.Options[:], opts...)
opts = slices.Concat(r.Options, opts)
if id == "" {
err = errors.New("missing required id parameter")
return
@ -62,7 +63,7 @@ func (r *SessionService) Update(ctx context.Context, id string, params SessionUp
// List all sessions
func (r *SessionService) List(ctx context.Context, query SessionListParams, opts ...option.RequestOption) (res *[]Session, err error) {
opts = append(r.Options[:], opts...)
opts = slices.Concat(r.Options, opts)
path := "session"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
return
@ -70,7 +71,7 @@ func (r *SessionService) List(ctx context.Context, query SessionListParams, opts
// Delete a session and all its data
func (r *SessionService) Delete(ctx context.Context, id string, body SessionDeleteParams, opts ...option.RequestOption) (res *bool, err error) {
opts = append(r.Options[:], opts...)
opts = slices.Concat(r.Options, opts)
if id == "" {
err = errors.New("missing required id parameter")
return
@ -82,7 +83,7 @@ func (r *SessionService) Delete(ctx context.Context, id string, body SessionDele
// Abort a session
func (r *SessionService) Abort(ctx context.Context, id string, body SessionAbortParams, opts ...option.RequestOption) (res *bool, err error) {
opts = append(r.Options[:], opts...)
opts = slices.Concat(r.Options, opts)
if id == "" {
err = errors.New("missing required id parameter")
return
@ -94,7 +95,7 @@ func (r *SessionService) Abort(ctx context.Context, id string, body SessionAbort
// 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...)
opts = slices.Concat(r.Options, opts)
if id == "" {
err = errors.New("missing required id parameter")
return
@ -106,7 +107,7 @@ func (r *SessionService) Children(ctx context.Context, id string, query SessionC
// Send a new command to a session
func (r *SessionService) Command(ctx context.Context, id string, params SessionCommandParams, opts ...option.RequestOption) (res *SessionCommandResponse, err error) {
opts = append(r.Options[:], opts...)
opts = slices.Concat(r.Options, opts)
if id == "" {
err = errors.New("missing required id parameter")
return
@ -118,7 +119,7 @@ func (r *SessionService) Command(ctx context.Context, id string, params SessionC
// Get session
func (r *SessionService) Get(ctx context.Context, id string, query SessionGetParams, opts ...option.RequestOption) (res *Session, err error) {
opts = append(r.Options[:], opts...)
opts = slices.Concat(r.Options, opts)
if id == "" {
err = errors.New("missing required id parameter")
return
@ -130,7 +131,7 @@ func (r *SessionService) Get(ctx context.Context, id string, query SessionGetPar
// Analyze the app and create an AGENTS.md file
func (r *SessionService) Init(ctx context.Context, id string, params SessionInitParams, opts ...option.RequestOption) (res *bool, err error) {
opts = append(r.Options[:], opts...)
opts = slices.Concat(r.Options, opts)
if id == "" {
err = errors.New("missing required id parameter")
return
@ -142,7 +143,7 @@ func (r *SessionService) Init(ctx context.Context, id string, params SessionInit
// Get a message from a session
func (r *SessionService) Message(ctx context.Context, id string, messageID string, query SessionMessageParams, opts ...option.RequestOption) (res *SessionMessageResponse, err error) {
opts = append(r.Options[:], opts...)
opts = slices.Concat(r.Options, opts)
if id == "" {
err = errors.New("missing required id parameter")
return
@ -158,7 +159,7 @@ func (r *SessionService) Message(ctx context.Context, id string, messageID strin
// List messages for a session
func (r *SessionService) Messages(ctx context.Context, id string, query SessionMessagesParams, opts ...option.RequestOption) (res *[]SessionMessagesResponse, err error) {
opts = append(r.Options[:], opts...)
opts = slices.Concat(r.Options, opts)
if id == "" {
err = errors.New("missing required id parameter")
return
@ -170,7 +171,7 @@ func (r *SessionService) Messages(ctx context.Context, id string, query SessionM
// 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...)
opts = slices.Concat(r.Options, opts)
if id == "" {
err = errors.New("missing required id parameter")
return
@ -182,7 +183,7 @@ func (r *SessionService) Prompt(ctx context.Context, id string, params SessionPr
// 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...)
opts = slices.Concat(r.Options, opts)
if id == "" {
err = errors.New("missing required id parameter")
return
@ -194,7 +195,7 @@ func (r *SessionService) Revert(ctx context.Context, id string, params SessionRe
// Share a session
func (r *SessionService) Share(ctx context.Context, id string, body SessionShareParams, opts ...option.RequestOption) (res *Session, err error) {
opts = append(r.Options[:], opts...)
opts = slices.Concat(r.Options, opts)
if id == "" {
err = errors.New("missing required id parameter")
return
@ -206,7 +207,7 @@ func (r *SessionService) Share(ctx context.Context, id string, body SessionShare
// Run a shell command
func (r *SessionService) Shell(ctx context.Context, id string, params SessionShellParams, opts ...option.RequestOption) (res *AssistantMessage, err error) {
opts = append(r.Options[:], opts...)
opts = slices.Concat(r.Options, opts)
if id == "" {
err = errors.New("missing required id parameter")
return
@ -218,7 +219,7 @@ func (r *SessionService) Shell(ctx context.Context, id string, params SessionShe
// Summarize the session
func (r *SessionService) Summarize(ctx context.Context, id string, params SessionSummarizeParams, opts ...option.RequestOption) (res *bool, err error) {
opts = append(r.Options[:], opts...)
opts = slices.Concat(r.Options, opts)
if id == "" {
err = errors.New("missing required id parameter")
return
@ -230,7 +231,7 @@ func (r *SessionService) Summarize(ctx context.Context, id string, params Sessio
// Restore all reverted messages
func (r *SessionService) Unrevert(ctx context.Context, id string, body SessionUnrevertParams, opts ...option.RequestOption) (res *Session, err error) {
opts = append(r.Options[:], opts...)
opts = slices.Concat(r.Options, opts)
if id == "" {
err = errors.New("missing required id parameter")
return
@ -242,7 +243,7 @@ func (r *SessionService) Unrevert(ctx context.Context, id string, body SessionUn
// Unshare the session
func (r *SessionService) Unshare(ctx context.Context, id string, body SessionUnshareParams, opts ...option.RequestOption) (res *Session, err error) {
opts = append(r.Options[:], opts...)
opts = slices.Concat(r.Options, opts)
if id == "" {
err = errors.New("missing required id parameter")
return
@ -1724,14 +1725,15 @@ func (r SymbolSourceRangeStartParam) MarshalJSON() (data []byte, err error) {
}
type TextPart struct {
ID string `json:"id,required"`
MessageID string `json:"messageID,required"`
SessionID string `json:"sessionID,required"`
Text string `json:"text,required"`
Type TextPartType `json:"type,required"`
Synthetic bool `json:"synthetic"`
Time TextPartTime `json:"time"`
JSON textPartJSON `json:"-"`
ID string `json:"id,required"`
MessageID string `json:"messageID,required"`
SessionID string `json:"sessionID,required"`
Text string `json:"text,required"`
Type TextPartType `json:"type,required"`
Metadata map[string]interface{} `json:"metadata"`
Synthetic bool `json:"synthetic"`
Time TextPartTime `json:"time"`
JSON textPartJSON `json:"-"`
}
// textPartJSON contains the JSON metadata for the struct [TextPart]
@ -1741,6 +1743,7 @@ type textPartJSON struct {
SessionID apijson.Field
Text apijson.Field
Type apijson.Field
Metadata apijson.Field
Synthetic apijson.Field
Time apijson.Field
raw string
@ -1797,6 +1800,7 @@ type TextPartInputParam struct {
Text param.Field[string] `json:"text,required"`
Type param.Field[TextPartInputType] `json:"type,required"`
ID param.Field[string] `json:"id"`
Metadata param.Field[map[string]interface{}] `json:"metadata"`
Synthetic param.Field[bool] `json:"synthetic"`
Time param.Field[TextPartInputTimeParam] `json:"time"`
}
@ -1831,14 +1835,15 @@ func (r TextPartInputTimeParam) MarshalJSON() (data []byte, err error) {
}
type ToolPart struct {
ID string `json:"id,required"`
CallID string `json:"callID,required"`
MessageID string `json:"messageID,required"`
SessionID string `json:"sessionID,required"`
State ToolPartState `json:"state,required"`
Tool string `json:"tool,required"`
Type ToolPartType `json:"type,required"`
JSON toolPartJSON `json:"-"`
ID string `json:"id,required"`
CallID string `json:"callID,required"`
MessageID string `json:"messageID,required"`
SessionID string `json:"sessionID,required"`
State ToolPartState `json:"state,required"`
Tool string `json:"tool,required"`
Type ToolPartType `json:"type,required"`
Metadata map[string]interface{} `json:"metadata"`
JSON toolPartJSON `json:"-"`
}
// toolPartJSON contains the JSON metadata for the struct [ToolPart]
@ -1850,6 +1855,7 @@ type toolPartJSON struct {
State apijson.Field
Tool apijson.Field
Type apijson.Field
Metadata apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
@ -2556,6 +2562,7 @@ type SessionPromptParamsPart struct {
Type param.Field[SessionPromptParamsPartsType] `json:"type,required"`
ID param.Field[string] `json:"id"`
Filename param.Field[string] `json:"filename"`
Metadata param.Field[interface{}] `json:"metadata"`
Mime param.Field[string] `json:"mime"`
Name param.Field[string] `json:"name"`
Source param.Field[interface{}] `json:"source"`

View file

@ -27,7 +27,7 @@ func TestSessionNewWithOptionalParams(t *testing.T) {
)
_, err := client.Session.New(context.TODO(), opencode.SessionNewParams{
Directory: opencode.F("directory"),
ParentID: opencode.F("parentID"),
ParentID: opencode.F("sesJ!"),
Title: opencode.F("title"),
})
if err != nil {
@ -106,7 +106,7 @@ func TestSessionDeleteWithOptionalParams(t *testing.T) {
)
_, err := client.Session.Delete(
context.TODO(),
"id",
"sesJ!",
opencode.SessionDeleteParams{
Directory: opencode.F("directory"),
},
@ -162,7 +162,7 @@ func TestSessionChildrenWithOptionalParams(t *testing.T) {
)
_, err := client.Session.Children(
context.TODO(),
"id",
"sesJ!",
opencode.SessionChildrenParams{
Directory: opencode.F("directory"),
},
@ -223,7 +223,7 @@ func TestSessionGetWithOptionalParams(t *testing.T) {
)
_, err := client.Session.Get(
context.TODO(),
"id",
"sesJ!",
opencode.SessionGetParams{
Directory: opencode.F("directory"),
},
@ -253,7 +253,7 @@ func TestSessionInitWithOptionalParams(t *testing.T) {
context.TODO(),
"id",
opencode.SessionInitParams{
MessageID: opencode.F("messageID"),
MessageID: opencode.F("msgJ!"),
ModelID: opencode.F("modelID"),
ProviderID: opencode.F("providerID"),
Directory: opencode.F("directory"),
@ -342,9 +342,12 @@ func TestSessionPromptWithOptionalParams(t *testing.T) {
"id",
opencode.SessionPromptParams{
Parts: opencode.F([]opencode.SessionPromptParamsPartUnion{opencode.TextPartInputParam{
Text: opencode.F("text"),
Type: opencode.F(opencode.TextPartInputTypeText),
ID: opencode.F("id"),
Text: opencode.F("text"),
Type: opencode.F(opencode.TextPartInputTypeText),
ID: opencode.F("id"),
Metadata: opencode.F(map[string]interface{}{
"foo": "bar",
}),
Synthetic: opencode.F(true),
Time: opencode.F(opencode.TextPartInputTimeParam{
Start: opencode.F(0.000000),
@ -533,7 +536,7 @@ func TestSessionUnshareWithOptionalParams(t *testing.T) {
)
_, err := client.Session.Unshare(
context.TODO(),
"id",
"sesJ!",
opencode.SessionUnshareParams{
Directory: opencode.F("directory"),
},

View file

@ -9,6 +9,7 @@ import (
"net/http"
"net/url"
"reflect"
"slices"
"github.com/sst/opencode-sdk-go/internal/apijson"
"github.com/sst/opencode-sdk-go/internal/apiquery"
@ -40,7 +41,7 @@ func NewSessionPermissionService(opts ...option.RequestOption) (r *SessionPermis
// Respond to a permission request
func (r *SessionPermissionService) Respond(ctx context.Context, id string, permissionID string, params SessionPermissionRespondParams, opts ...option.RequestOption) (res *bool, err error) {
opts = append(r.Options[:], opts...)
opts = slices.Concat(r.Options, opts)
if id == "" {
err = errors.New("missing required id parameter")
return

View file

@ -6,6 +6,7 @@ import (
"context"
"net/http"
"net/url"
"slices"
"github.com/sst/opencode-sdk-go/internal/apijson"
"github.com/sst/opencode-sdk-go/internal/apiquery"
@ -35,7 +36,7 @@ func NewTuiService(opts ...option.RequestOption) (r *TuiService) {
// Append prompt to the TUI
func (r *TuiService) AppendPrompt(ctx context.Context, params TuiAppendPromptParams, opts ...option.RequestOption) (res *bool, err error) {
opts = append(r.Options[:], opts...)
opts = slices.Concat(r.Options, opts)
path := "tui/append-prompt"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, params, &res, opts...)
return
@ -43,7 +44,7 @@ func (r *TuiService) AppendPrompt(ctx context.Context, params TuiAppendPromptPar
// Clear the prompt
func (r *TuiService) ClearPrompt(ctx context.Context, body TuiClearPromptParams, opts ...option.RequestOption) (res *bool, err error) {
opts = append(r.Options[:], opts...)
opts = slices.Concat(r.Options, opts)
path := "tui/clear-prompt"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
return
@ -51,7 +52,7 @@ func (r *TuiService) ClearPrompt(ctx context.Context, body TuiClearPromptParams,
// Execute a TUI command (e.g. agent_cycle)
func (r *TuiService) ExecuteCommand(ctx context.Context, params TuiExecuteCommandParams, opts ...option.RequestOption) (res *bool, err error) {
opts = append(r.Options[:], opts...)
opts = slices.Concat(r.Options, opts)
path := "tui/execute-command"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, params, &res, opts...)
return
@ -59,7 +60,7 @@ func (r *TuiService) ExecuteCommand(ctx context.Context, params TuiExecuteComman
// Open the help dialog
func (r *TuiService) OpenHelp(ctx context.Context, body TuiOpenHelpParams, opts ...option.RequestOption) (res *bool, err error) {
opts = append(r.Options[:], opts...)
opts = slices.Concat(r.Options, opts)
path := "tui/open-help"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
return
@ -67,7 +68,7 @@ func (r *TuiService) OpenHelp(ctx context.Context, body TuiOpenHelpParams, opts
// Open the model dialog
func (r *TuiService) OpenModels(ctx context.Context, body TuiOpenModelsParams, opts ...option.RequestOption) (res *bool, err error) {
opts = append(r.Options[:], opts...)
opts = slices.Concat(r.Options, opts)
path := "tui/open-models"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
return
@ -75,7 +76,7 @@ func (r *TuiService) OpenModels(ctx context.Context, body TuiOpenModelsParams, o
// Open the session dialog
func (r *TuiService) OpenSessions(ctx context.Context, body TuiOpenSessionsParams, opts ...option.RequestOption) (res *bool, err error) {
opts = append(r.Options[:], opts...)
opts = slices.Concat(r.Options, opts)
path := "tui/open-sessions"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
return
@ -83,7 +84,7 @@ func (r *TuiService) OpenSessions(ctx context.Context, body TuiOpenSessionsParam
// Open the theme dialog
func (r *TuiService) OpenThemes(ctx context.Context, body TuiOpenThemesParams, opts ...option.RequestOption) (res *bool, err error) {
opts = append(r.Options[:], opts...)
opts = slices.Concat(r.Options, opts)
path := "tui/open-themes"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
return
@ -91,7 +92,7 @@ func (r *TuiService) OpenThemes(ctx context.Context, body TuiOpenThemesParams, o
// Show a toast notification in the TUI
func (r *TuiService) ShowToast(ctx context.Context, params TuiShowToastParams, opts ...option.RequestOption) (res *bool, err error) {
opts = append(r.Options[:], opts...)
opts = slices.Concat(r.Options, opts)
path := "tui/show-toast"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, params, &res, opts...)
return
@ -99,7 +100,7 @@ func (r *TuiService) ShowToast(ctx context.Context, params TuiShowToastParams, o
// Submit the prompt
func (r *TuiService) SubmitPrompt(ctx context.Context, body TuiSubmitPromptParams, opts ...option.RequestOption) (res *bool, err error) {
opts = append(r.Options[:], opts...)
opts = slices.Concat(r.Options, opts)
path := "tui/submit-prompt"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
return

View file

@ -111,5 +111,23 @@ For example.
}
}
```
This configuration allows all commands by default (`"*": "allow"`) but requires approval for `git push` commands.
### Agents
Configure agent specific permissions
```json
{
"$schema": "https://opencode.ai/config.json",
"agent": {
"plan": {
"permission": {
"bash": {
"echo *": "allow"
}
}
}
}
}
```

View file

@ -86,6 +86,18 @@ Here are some common issues and how to resolve them.
2. Verify the model name in your config is correct
3. Some models may require specific access or subscriptions
If you encounter `ProviderModelNotFoundError` you are most likely incorrectly
referencing a model somewhere.
Models should be referenced like so: `<providerId>/<modelId>`
Examples:
- `openai/gpt-4.1`
- `openrouter/google/gemini-2.5-flash`
- `opencode/kimi-k2`
To figure out what models you have access to, run `opencode models`
---
### ProviderInitError