Compare commits
10 Commits
c2fa28c1be
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
84f0c63fa1 | ||
|
|
3e9b451fb4 | ||
|
|
4ccf683527 | ||
|
|
b236ca9047 | ||
|
|
aa9ebe5d7c | ||
|
|
4c94753eda | ||
|
|
c3a55c35bb | ||
|
|
d5275010d5 | ||
|
|
dedfa563c2 | ||
|
|
7aa57accf5 |
1
STATS.md
1
STATS.md
@@ -75,3 +75,4 @@
|
|||||||
| 2025-09-08 | 293,341 (+4,718) | 227,073 (+1,207) | 520,414 (+5,925) |
|
| 2025-09-08 | 293,341 (+4,718) | 227,073 (+1,207) | 520,414 (+5,925) |
|
||||||
| 2025-09-09 | 300,036 (+6,695) | 229,788 (+2,715) | 529,824 (+9,410) |
|
| 2025-09-09 | 300,036 (+6,695) | 229,788 (+2,715) | 529,824 (+9,410) |
|
||||||
| 2025-09-10 | 307,287 (+7,251) | 233,435 (+3,647) | 540,722 (+10,898) |
|
| 2025-09-10 | 307,287 (+7,251) | 233,435 (+3,647) | 540,722 (+10,898) |
|
||||||
|
| 2025-09-11 | 314,083 (+6,796) | 237,356 (+3,921) | 551,439 (+10,717) |
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
"dev:remote": "VITE_AUTH_URL=https://auth.dev.opencode.ai bun sst shell --stage=dev bun dev",
|
"dev:remote": "VITE_AUTH_URL=https://auth.dev.opencode.ai bun sst shell --stage=dev bun dev",
|
||||||
"build": "vinxi build && ../../packages/opencode/script/schema.ts ./.output/public/config.json",
|
"build": "vinxi build && ../../packages/opencode/script/schema.ts ./.output/public/config.json",
|
||||||
"start": "vinxi start",
|
"start": "vinxi start",
|
||||||
"version": "0.7.1"
|
"version": "0.7.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ibm/plex": "6.4.1",
|
"@ibm/plex": "6.4.1",
|
||||||
|
|||||||
@@ -551,7 +551,7 @@ function NewUserSection() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function() {
|
export default function () {
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
const keys = createAsync(() => listKeys(params.id))
|
const keys = createAsync(() => listKeys(params.id))
|
||||||
const usage = createAsync(() => getUsageInfo(params.id))
|
const usage = createAsync(() => getUsageInfo(params.id))
|
||||||
|
|||||||
@@ -6,9 +6,7 @@ export function POST(input: APIEvent) {
|
|||||||
return handler(input, {
|
return handler(input, {
|
||||||
modifyBody: (body: any) => ({
|
modifyBody: (body: any) => ({
|
||||||
...body,
|
...body,
|
||||||
stream_options: {
|
...(body.stream ? { stream_options: { include_usage: true } } : {}),
|
||||||
include_usage: true,
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
setAuthHeader: (headers: Headers, apiKey: string) => {
|
setAuthHeader: (headers: Headers, apiKey: string) => {
|
||||||
headers.set("authorization", `Bearer ${apiKey}`)
|
headers.set("authorization", `Bearer ${apiKey}`)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { APIEvent } from "@solidjs/start/server"
|
import type { APIEvent } from "@solidjs/start/server"
|
||||||
|
import path from "node:path"
|
||||||
import { Database, eq, sql } from "@opencode/cloud-core/drizzle/index.js"
|
import { Database, eq, sql } from "@opencode/cloud-core/drizzle/index.js"
|
||||||
import { KeyTable } from "@opencode/cloud-core/schema/key.sql.js"
|
import { KeyTable } from "@opencode/cloud-core/schema/key.sql.js"
|
||||||
import { BillingTable, UsageTable } from "@opencode/cloud-core/schema/billing.sql.js"
|
import { BillingTable, UsageTable } from "@opencode/cloud-core/schema/billing.sql.js"
|
||||||
@@ -6,6 +7,29 @@ import { centsToMicroCents } from "@opencode/cloud-core/util/price.js"
|
|||||||
import { Identifier } from "@opencode/cloud-core/identifier.js"
|
import { Identifier } from "@opencode/cloud-core/identifier.js"
|
||||||
import { Resource } from "@opencode/cloud-resource"
|
import { Resource } from "@opencode/cloud-resource"
|
||||||
|
|
||||||
|
type ModelCost = {
|
||||||
|
input: number
|
||||||
|
output: number
|
||||||
|
cacheRead: number
|
||||||
|
cacheWrite5m: number
|
||||||
|
cacheWrite1h: number
|
||||||
|
}
|
||||||
|
type Model = {
|
||||||
|
id: string
|
||||||
|
auth: boolean
|
||||||
|
cost: ModelCost | ((usage: any) => ModelCost)
|
||||||
|
headerMappings: Record<string, string>
|
||||||
|
providers: Record<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
api: string
|
||||||
|
apiKey: string
|
||||||
|
model: string
|
||||||
|
weight?: number
|
||||||
|
}
|
||||||
|
>
|
||||||
|
}
|
||||||
|
|
||||||
export async function handler(
|
export async function handler(
|
||||||
input: APIEvent,
|
input: APIEvent,
|
||||||
opts: {
|
opts: {
|
||||||
@@ -28,13 +52,10 @@ export async function handler(
|
|||||||
class CreditsError extends Error {}
|
class CreditsError extends Error {}
|
||||||
class ModelError extends Error {}
|
class ModelError extends Error {}
|
||||||
|
|
||||||
const MODELS = {
|
const MODELS: Record<string, Model> = {
|
||||||
"claude-opus-4-1": {
|
"claude-opus-4-1": {
|
||||||
id: "claude-opus-4-1" as const,
|
id: "claude-opus-4-1" as const,
|
||||||
auth: true,
|
auth: true,
|
||||||
api: "https://api.anthropic.com",
|
|
||||||
apiKey: Resource.ANTHROPIC_API_KEY.value,
|
|
||||||
model: "claude-opus-4-1-20250805",
|
|
||||||
cost: {
|
cost: {
|
||||||
input: 0.000015,
|
input: 0.000015,
|
||||||
output: 0.000075,
|
output: 0.000075,
|
||||||
@@ -43,13 +64,17 @@ export async function handler(
|
|||||||
cacheWrite1h: 0.00003,
|
cacheWrite1h: 0.00003,
|
||||||
},
|
},
|
||||||
headerMappings: {},
|
headerMappings: {},
|
||||||
|
providers: {
|
||||||
|
anthropic: {
|
||||||
|
api: "https://api.anthropic.com",
|
||||||
|
apiKey: Resource.ANTHROPIC_API_KEY.value,
|
||||||
|
model: "claude-opus-4-1-20250805",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"claude-sonnet-4": {
|
"claude-sonnet-4": {
|
||||||
id: "claude-sonnet-4" as const,
|
id: "claude-sonnet-4" as const,
|
||||||
auth: true,
|
auth: true,
|
||||||
api: "https://api.anthropic.com",
|
|
||||||
apiKey: Resource.ANTHROPIC_API_KEY.value,
|
|
||||||
model: "claude-sonnet-4-20250514",
|
|
||||||
cost: (usage: any) => {
|
cost: (usage: any) => {
|
||||||
const totalInputTokens =
|
const totalInputTokens =
|
||||||
usage.inputTokens + usage.cacheReadTokens + usage.cacheWrite5mTokens + usage.cacheWrite1hTokens
|
usage.inputTokens + usage.cacheReadTokens + usage.cacheWrite5mTokens + usage.cacheWrite1hTokens
|
||||||
@@ -70,13 +95,17 @@ export async function handler(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
headerMappings: {},
|
headerMappings: {},
|
||||||
|
providers: {
|
||||||
|
anthropic: {
|
||||||
|
api: "https://api.anthropic.com",
|
||||||
|
apiKey: Resource.ANTHROPIC_API_KEY.value,
|
||||||
|
model: "claude-sonnet-4-20250514",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"claude-3-5-haiku": {
|
"claude-3-5-haiku": {
|
||||||
id: "claude-3-5-haiku" as const,
|
id: "claude-3-5-haiku" as const,
|
||||||
auth: true,
|
auth: true,
|
||||||
api: "https://api.anthropic.com",
|
|
||||||
apiKey: Resource.ANTHROPIC_API_KEY.value,
|
|
||||||
model: "claude-3-5-haiku-20241022",
|
|
||||||
cost: {
|
cost: {
|
||||||
input: 0.0000008,
|
input: 0.0000008,
|
||||||
output: 0.000004,
|
output: 0.000004,
|
||||||
@@ -85,13 +114,17 @@ export async function handler(
|
|||||||
cacheWrite1h: 0.0000016,
|
cacheWrite1h: 0.0000016,
|
||||||
},
|
},
|
||||||
headerMappings: {},
|
headerMappings: {},
|
||||||
|
providers: {
|
||||||
|
anthropic: {
|
||||||
|
api: "https://api.anthropic.com",
|
||||||
|
apiKey: Resource.ANTHROPIC_API_KEY.value,
|
||||||
|
model: "claude-3-5-haiku-20241022",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"gpt-5": {
|
"gpt-5": {
|
||||||
id: "gpt-5" as const,
|
id: "gpt-5" as const,
|
||||||
auth: true,
|
auth: true,
|
||||||
api: "https://api.openai.com",
|
|
||||||
apiKey: Resource.OPENAI_API_KEY.value,
|
|
||||||
model: "gpt-5",
|
|
||||||
cost: {
|
cost: {
|
||||||
input: 0.00000125,
|
input: 0.00000125,
|
||||||
output: 0.00001,
|
output: 0.00001,
|
||||||
@@ -100,28 +133,43 @@ export async function handler(
|
|||||||
cacheWrite1h: 0,
|
cacheWrite1h: 0,
|
||||||
},
|
},
|
||||||
headerMappings: {},
|
headerMappings: {},
|
||||||
|
providers: {
|
||||||
|
openai: {
|
||||||
|
api: "https://api.openai.com",
|
||||||
|
apiKey: Resource.OPENAI_API_KEY.value,
|
||||||
|
model: "gpt-5",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"qwen3-coder": {
|
"qwen3-coder": {
|
||||||
id: "qwen3-coder" as const,
|
id: "qwen3-coder" as const,
|
||||||
auth: true,
|
auth: true,
|
||||||
api: "https://inference.baseten.co",
|
|
||||||
apiKey: Resource.BASETEN_API_KEY.value,
|
|
||||||
model: "Qwen/Qwen3-Coder-480B-A35B-Instruct",
|
|
||||||
cost: {
|
cost: {
|
||||||
input: 0.00000038,
|
input: 0.00000045,
|
||||||
output: 0.00000153,
|
output: 0.0000018,
|
||||||
cacheRead: 0,
|
cacheRead: 0,
|
||||||
cacheWrite5m: 0,
|
cacheWrite5m: 0,
|
||||||
cacheWrite1h: 0,
|
cacheWrite1h: 0,
|
||||||
},
|
},
|
||||||
headerMappings: {},
|
headerMappings: {},
|
||||||
|
providers: {
|
||||||
|
baseten: {
|
||||||
|
api: "https://inference.baseten.co",
|
||||||
|
apiKey: Resource.BASETEN_API_KEY.value,
|
||||||
|
model: "Qwen/Qwen3-Coder-480B-A35B-Instruct",
|
||||||
|
weight: 4,
|
||||||
|
},
|
||||||
|
fireworks: {
|
||||||
|
api: "https://api.fireworks.ai/inference",
|
||||||
|
apiKey: Resource.FIREWORKS_API_KEY.value,
|
||||||
|
model: "accounts/fireworks/models/qwen3-coder-480b-a35b-instruct",
|
||||||
|
weight: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"kimi-k2": {
|
"kimi-k2": {
|
||||||
id: "kimi-k2" as const,
|
id: "kimi-k2" as const,
|
||||||
auth: true,
|
auth: true,
|
||||||
api: "https://inference.baseten.co",
|
|
||||||
apiKey: Resource.BASETEN_API_KEY.value,
|
|
||||||
model: "moonshotai/Kimi-K2-Instruct-0905",
|
|
||||||
cost: {
|
cost: {
|
||||||
input: 0.0000006,
|
input: 0.0000006,
|
||||||
output: 0.0000025,
|
output: 0.0000025,
|
||||||
@@ -130,13 +178,24 @@ export async function handler(
|
|||||||
cacheWrite1h: 0,
|
cacheWrite1h: 0,
|
||||||
},
|
},
|
||||||
headerMappings: {},
|
headerMappings: {},
|
||||||
|
providers: {
|
||||||
|
baseten: {
|
||||||
|
api: "https://inference.baseten.co",
|
||||||
|
apiKey: Resource.BASETEN_API_KEY.value,
|
||||||
|
model: "moonshotai/Kimi-K2-Instruct-0905",
|
||||||
|
weight: 4,
|
||||||
|
},
|
||||||
|
fireworks: {
|
||||||
|
api: "https://api.fireworks.ai/inference",
|
||||||
|
apiKey: Resource.FIREWORKS_API_KEY.value,
|
||||||
|
model: "accounts/fireworks/models/kimi-k2-instruct-0905",
|
||||||
|
weight: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"grok-code": {
|
"grok-code": {
|
||||||
id: "grok-code" as const,
|
id: "grok-code" as const,
|
||||||
auth: false,
|
auth: false,
|
||||||
api: "https://api.x.ai",
|
|
||||||
apiKey: Resource.XAI_API_KEY.value,
|
|
||||||
model: "grok-code",
|
|
||||||
cost: {
|
cost: {
|
||||||
input: 0,
|
input: 0,
|
||||||
output: 0,
|
output: 0,
|
||||||
@@ -148,14 +207,18 @@ export async function handler(
|
|||||||
"x-grok-conv-id": "x-opencode-session",
|
"x-grok-conv-id": "x-opencode-session",
|
||||||
"x-grok-req-id": "x-opencode-request",
|
"x-grok-req-id": "x-opencode-request",
|
||||||
},
|
},
|
||||||
|
providers: {
|
||||||
|
xai: {
|
||||||
|
api: "https://api.x.ai",
|
||||||
|
apiKey: Resource.XAI_API_KEY.value,
|
||||||
|
model: "grok-code",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
// deprecated
|
// deprecated
|
||||||
"qwen/qwen3-coder": {
|
"qwen/qwen3-coder": {
|
||||||
id: "qwen/qwen3-coder" as const,
|
id: "qwen/qwen3-coder" as const,
|
||||||
auth: true,
|
auth: true,
|
||||||
api: "https://inference.baseten.co",
|
|
||||||
apiKey: Resource.BASETEN_API_KEY.value,
|
|
||||||
model: "Qwen/Qwen3-Coder-480B-A35B-Instruct",
|
|
||||||
cost: {
|
cost: {
|
||||||
input: 0.00000038,
|
input: 0.00000038,
|
||||||
output: 0.00000153,
|
output: 0.00000153,
|
||||||
@@ -164,6 +227,20 @@ export async function handler(
|
|||||||
cacheWrite1h: 0,
|
cacheWrite1h: 0,
|
||||||
},
|
},
|
||||||
headerMappings: {},
|
headerMappings: {},
|
||||||
|
providers: {
|
||||||
|
baseten: {
|
||||||
|
api: "https://inference.baseten.co",
|
||||||
|
apiKey: Resource.BASETEN_API_KEY.value,
|
||||||
|
model: "Qwen/Qwen3-Coder-480B-A35B-Instruct",
|
||||||
|
weight: 5,
|
||||||
|
},
|
||||||
|
fireworks: {
|
||||||
|
api: "https://api.fireworks.ai/inference",
|
||||||
|
apiKey: Resource.FIREWORKS_API_KEY.value,
|
||||||
|
model: "accounts/fireworks/models/qwen3-coder-480b-a35b-instruct",
|
||||||
|
weight: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,15 +272,19 @@ export async function handler(
|
|||||||
const apiKey = await authenticate()
|
const apiKey = await authenticate()
|
||||||
const isFree = FREE_WORKSPACES.includes(apiKey?.workspaceID ?? "")
|
const isFree = FREE_WORKSPACES.includes(apiKey?.workspaceID ?? "")
|
||||||
await checkCredits()
|
await checkCredits()
|
||||||
|
const providerName = selectProvider()
|
||||||
|
const providerData = MODEL.providers[providerName]
|
||||||
|
logger.metric({ provider: providerName })
|
||||||
|
|
||||||
// Request to model provider
|
// Request to model provider
|
||||||
const res = await fetch(new URL(url.pathname.replace(/^\/zen/, "") + url.search, MODEL.api), {
|
const startTimestamp = Date.now()
|
||||||
|
const res = await fetch(path.posix.join(providerData.api, url.pathname.replace(/^\/zen/, "") + url.search), {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: (() => {
|
headers: (() => {
|
||||||
const headers = input.request.headers
|
const headers = input.request.headers
|
||||||
headers.delete("host")
|
headers.delete("host")
|
||||||
headers.delete("content-length")
|
headers.delete("content-length")
|
||||||
opts.setAuthHeader(headers, MODEL.apiKey)
|
opts.setAuthHeader(headers, providerData.apiKey)
|
||||||
Object.entries(MODEL.headerMappings ?? {}).forEach(([k, v]) => {
|
Object.entries(MODEL.headerMappings ?? {}).forEach(([k, v]) => {
|
||||||
headers.set(k, headers.get(v)!)
|
headers.set(k, headers.get(v)!)
|
||||||
})
|
})
|
||||||
@@ -211,7 +292,7 @@ export async function handler(
|
|||||||
})(),
|
})(),
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
...(opts.modifyBody?.(body) ?? body),
|
...(opts.modifyBody?.(body) ?? body),
|
||||||
model: MODEL.model,
|
model: providerData.model,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -245,7 +326,6 @@ export async function handler(
|
|||||||
const decoder = new TextDecoder()
|
const decoder = new TextDecoder()
|
||||||
let buffer = ""
|
let buffer = ""
|
||||||
let responseLength = 0
|
let responseLength = 0
|
||||||
let startTimestamp = Date.now()
|
|
||||||
|
|
||||||
function pump(): Promise<void> {
|
function pump(): Promise<void> {
|
||||||
return (
|
return (
|
||||||
@@ -262,7 +342,6 @@ export async function handler(
|
|||||||
logger.metric({ time_to_first_byte: Date.now() - startTimestamp })
|
logger.metric({ time_to_first_byte: Date.now() - startTimestamp })
|
||||||
}
|
}
|
||||||
responseLength += value.length
|
responseLength += value.length
|
||||||
|
|
||||||
buffer += decoder.decode(value, { stream: true })
|
buffer += decoder.decode(value, { stream: true })
|
||||||
|
|
||||||
const parts = buffer.split("\n\n")
|
const parts = buffer.split("\n\n")
|
||||||
@@ -344,6 +423,13 @@ export async function handler(
|
|||||||
if (billing.balance <= 0) throw new CreditsError("Insufficient balance")
|
if (billing.balance <= 0) throw new CreditsError("Insufficient balance")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function selectProvider() {
|
||||||
|
const picks = Object.entries(MODEL.providers).flatMap(([name, provider]) =>
|
||||||
|
Array<string>(provider.weight ?? 1).fill(name),
|
||||||
|
)
|
||||||
|
return picks[Math.floor(Math.random() * picks.length)]
|
||||||
|
}
|
||||||
|
|
||||||
async function trackUsage(usage: any) {
|
async function trackUsage(usage: any) {
|
||||||
const { inputTokens, outputTokens, reasoningTokens, cacheReadTokens, cacheWrite5mTokens, cacheWrite1hTokens } =
|
const { inputTokens, outputTokens, reasoningTokens, cacheReadTokens, cacheWrite5mTokens, cacheWrite1hTokens } =
|
||||||
opts.normalizeUsage(usage)
|
opts.normalizeUsage(usage)
|
||||||
@@ -416,9 +502,25 @@ export async function handler(
|
|||||||
"error.message": error.message,
|
"error.message": error.message,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Note: both top level "type" and "error.type" fields are used by the @ai-sdk/anthropic client to render the error message.
|
||||||
if (error instanceof AuthError || error instanceof CreditsError || error instanceof ModelError)
|
if (error instanceof AuthError || error instanceof CreditsError || error instanceof ModelError)
|
||||||
return new Response(JSON.stringify({ error: { message: error.message } }), { status: 401 })
|
return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
type: "error",
|
||||||
|
error: { type: error.constructor.name, message: error.message },
|
||||||
|
}),
|
||||||
|
{ status: 401 },
|
||||||
|
)
|
||||||
|
|
||||||
return new Response(JSON.stringify({ error: { message: error.message } }), { status: 500 })
|
return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
type: "error",
|
||||||
|
error: {
|
||||||
|
type: "error",
|
||||||
|
message: error.message,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
{ status: 500 },
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"name": "@opencode/cloud-core",
|
"name": "@opencode/cloud-core",
|
||||||
"version": "0.7.1",
|
"version": "0.7.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode/cloud-function",
|
"name": "@opencode/cloud-function",
|
||||||
"version": "0.7.1",
|
"version": "0.7.2",
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
4
cloud/function/sst-env.d.ts
vendored
4
cloud/function/sst-env.d.ts
vendored
@@ -30,6 +30,10 @@ declare module "sst" {
|
|||||||
type: "sst.sst.Linkable"
|
type: "sst.sst.Linkable"
|
||||||
username: string
|
username: string
|
||||||
}
|
}
|
||||||
|
FIREWORKS_API_KEY: {
|
||||||
|
type: "sst.sst.Secret"
|
||||||
|
value: string
|
||||||
|
}
|
||||||
GITHUB_APP_ID: {
|
GITHUB_APP_ID: {
|
||||||
type: "sst.sst.Secret"
|
type: "sst.sst.Secret"
|
||||||
value: string
|
value: string
|
||||||
|
|||||||
4
cloud/resource/sst-env.d.ts
vendored
4
cloud/resource/sst-env.d.ts
vendored
@@ -30,6 +30,10 @@ declare module "sst" {
|
|||||||
type: "sst.sst.Linkable"
|
type: "sst.sst.Linkable"
|
||||||
username: string
|
username: string
|
||||||
}
|
}
|
||||||
|
FIREWORKS_API_KEY: {
|
||||||
|
type: "sst.sst.Secret"
|
||||||
|
value: string
|
||||||
|
}
|
||||||
GITHUB_APP_ID: {
|
GITHUB_APP_ID: {
|
||||||
type: "sst.sst.Secret"
|
type: "sst.sst.Secret"
|
||||||
value: string
|
value: string
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode/cloud-scripts",
|
"name": "@opencode/cloud-scripts",
|
||||||
"version": "0.7.1",
|
"version": "0.7.2",
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ const ANTHROPIC_API_KEY = new sst.Secret("ANTHROPIC_API_KEY")
|
|||||||
const OPENAI_API_KEY = new sst.Secret("OPENAI_API_KEY")
|
const OPENAI_API_KEY = new sst.Secret("OPENAI_API_KEY")
|
||||||
const XAI_API_KEY = new sst.Secret("XAI_API_KEY")
|
const XAI_API_KEY = new sst.Secret("XAI_API_KEY")
|
||||||
const BASETEN_API_KEY = new sst.Secret("BASETEN_API_KEY")
|
const BASETEN_API_KEY = new sst.Secret("BASETEN_API_KEY")
|
||||||
|
const FIREWORKS_API_KEY = new sst.Secret("FIREWORKS_API_KEY")
|
||||||
const STRIPE_SECRET_KEY = new sst.Secret("STRIPE_SECRET_KEY")
|
const STRIPE_SECRET_KEY = new sst.Secret("STRIPE_SECRET_KEY")
|
||||||
const AUTH_API_URL = new sst.Linkable("AUTH_API_URL", {
|
const AUTH_API_URL = new sst.Linkable("AUTH_API_URL", {
|
||||||
properties: { value: auth.url.apply((url) => url!) },
|
properties: { value: auth.url.apply((url) => url!) },
|
||||||
@@ -136,6 +137,7 @@ new sst.cloudflare.x.SolidStart("Console", {
|
|||||||
OPENAI_API_KEY,
|
OPENAI_API_KEY,
|
||||||
XAI_API_KEY,
|
XAI_API_KEY,
|
||||||
BASETEN_API_KEY,
|
BASETEN_API_KEY,
|
||||||
|
FIREWORKS_API_KEY,
|
||||||
],
|
],
|
||||||
environment: {
|
environment: {
|
||||||
//VITE_DOCS_URL: web.url.apply((url) => url!),
|
//VITE_DOCS_URL: web.url.apply((url) => url!),
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode/function",
|
"name": "@opencode/function",
|
||||||
"version": "0.7.1",
|
"version": "0.7.2",
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
4
packages/function/sst-env.d.ts
vendored
4
packages/function/sst-env.d.ts
vendored
@@ -30,6 +30,10 @@ declare module "sst" {
|
|||||||
type: "sst.sst.Linkable"
|
type: "sst.sst.Linkable"
|
||||||
username: string
|
username: string
|
||||||
}
|
}
|
||||||
|
FIREWORKS_API_KEY: {
|
||||||
|
type: "sst.sst.Secret"
|
||||||
|
value: string
|
||||||
|
}
|
||||||
GITHUB_APP_ID: {
|
GITHUB_APP_ID: {
|
||||||
type: "sst.sst.Secret"
|
type: "sst.sst.Secret"
|
||||||
value: string
|
value: string
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"version": "0.7.1",
|
"version": "0.7.2",
|
||||||
"name": "opencode",
|
"name": "opencode",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { Ide } from "../../ide"
|
|||||||
import { Flag } from "../../flag/flag"
|
import { Flag } from "../../flag/flag"
|
||||||
import { Session } from "../../session"
|
import { Session } from "../../session"
|
||||||
import { Instance } from "../../project/instance"
|
import { Instance } from "../../project/instance"
|
||||||
|
import { $ } from "bun"
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
const OPENCODE_TUI_PATH: string
|
const OPENCODE_TUI_PATH: string
|
||||||
@@ -111,8 +112,7 @@ export const TuiCommand = cmd({
|
|||||||
hostname: args.hostname,
|
hostname: args.hostname,
|
||||||
})
|
})
|
||||||
|
|
||||||
let cmd = ["go", "run", "./main.go"]
|
let cmd = [] as string[]
|
||||||
let cwd = Bun.fileURLToPath(new URL("../../../../tui/cmd/opencode", import.meta.url))
|
|
||||||
const tui = Bun.embeddedFiles.find((item) => (item as File).name.includes("tui")) as File
|
const tui = Bun.embeddedFiles.find((item) => (item as File).name.includes("tui")) as File
|
||||||
if (tui) {
|
if (tui) {
|
||||||
let binaryName = tui.name
|
let binaryName = tui.name
|
||||||
@@ -125,9 +125,13 @@ export const TuiCommand = cmd({
|
|||||||
await Bun.write(file, tui, { mode: 0o755 })
|
await Bun.write(file, tui, { mode: 0o755 })
|
||||||
await fs.chmod(binary, 0o755)
|
await fs.chmod(binary, 0o755)
|
||||||
}
|
}
|
||||||
cwd = process.cwd()
|
|
||||||
cmd = [binary]
|
cmd = [binary]
|
||||||
}
|
}
|
||||||
|
if (!tui) {
|
||||||
|
const dir = Bun.fileURLToPath(new URL("../../../../tui/cmd/opencode", import.meta.url))
|
||||||
|
await $`go build -o ./dist/tui ./main.go`.cwd(dir)
|
||||||
|
cmd = [path.join(dir, "dist/tui")]
|
||||||
|
}
|
||||||
Log.Default.info("tui", {
|
Log.Default.info("tui", {
|
||||||
cmd,
|
cmd,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export namespace Identifier {
|
|||||||
|
|
||||||
function generateID(prefix: keyof typeof prefixes, descending: boolean, given?: string): string {
|
function generateID(prefix: keyof typeof prefixes, descending: boolean, given?: string): string {
|
||||||
if (!given) {
|
if (!given) {
|
||||||
return generateNewID(prefix, descending)
|
return create(prefix, descending)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!given.startsWith(prefixes[prefix])) {
|
if (!given.startsWith(prefixes[prefix])) {
|
||||||
@@ -49,8 +49,8 @@ export namespace Identifier {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateNewID(prefix: keyof typeof prefixes, descending: boolean): string {
|
export function create(prefix: keyof typeof prefixes, descending: boolean, timestamp?: number): string {
|
||||||
const currentTimestamp = Date.now()
|
const currentTimestamp = timestamp ?? Date.now()
|
||||||
|
|
||||||
if (currentTimestamp !== lastTimestamp) {
|
if (currentTimestamp !== lastTimestamp) {
|
||||||
lastTimestamp = currentTimestamp
|
lastTimestamp = currentTimestamp
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ export namespace Session {
|
|||||||
time: z.object({
|
time: z.object({
|
||||||
created: z.number(),
|
created: z.number(),
|
||||||
updated: z.number(),
|
updated: z.number(),
|
||||||
|
compacting: z.number().optional(),
|
||||||
}),
|
}),
|
||||||
revert: z
|
revert: z
|
||||||
.object({
|
.object({
|
||||||
@@ -137,12 +138,17 @@ export namespace Session {
|
|||||||
error: MessageV2.Assistant.shape.error,
|
error: MessageV2.Assistant.shape.error,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
Compacted: Bus.event(
|
||||||
|
"session.compacted",
|
||||||
|
z.object({
|
||||||
|
sessionID: z.string(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
const state = Instance.state(
|
const state = Instance.state(
|
||||||
() => {
|
() => {
|
||||||
const pending = new Map<string, AbortController>()
|
const pending = new Map<string, AbortController>()
|
||||||
const autoCompacting = new Map<string, boolean>()
|
|
||||||
const queued = new Map<
|
const queued = new Map<
|
||||||
string,
|
string,
|
||||||
{
|
{
|
||||||
@@ -156,7 +162,6 @@ export namespace Session {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
pending,
|
pending,
|
||||||
autoCompacting,
|
|
||||||
queued,
|
queued,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -714,24 +719,8 @@ export namespace Session {
|
|||||||
})().then((x) => Provider.getModel(x.providerID, x.modelID))
|
})().then((x) => Provider.getModel(x.providerID, x.modelID))
|
||||||
let msgs = await messages(input.sessionID)
|
let msgs = await messages(input.sessionID)
|
||||||
|
|
||||||
const previous = msgs.filter((x) => x.info.role === "assistant").at(-1)?.info as MessageV2.Assistant
|
|
||||||
const outputLimit = Math.min(model.info.limit.output, OUTPUT_TOKEN_MAX) || OUTPUT_TOKEN_MAX
|
const outputLimit = Math.min(model.info.limit.output, OUTPUT_TOKEN_MAX) || OUTPUT_TOKEN_MAX
|
||||||
|
|
||||||
// auto summarize if too long
|
|
||||||
if (previous && previous.tokens) {
|
|
||||||
const tokens =
|
|
||||||
previous.tokens.input + previous.tokens.cache.read + previous.tokens.cache.write + previous.tokens.output
|
|
||||||
if (model.info.limit.context && tokens > Math.max((model.info.limit.context - outputLimit) * 0.9, 0)) {
|
|
||||||
state().autoCompacting.set(input.sessionID, true)
|
|
||||||
|
|
||||||
await summarize({
|
|
||||||
sessionID: input.sessionID,
|
|
||||||
providerID: model.providerID,
|
|
||||||
modelID: model.info.id,
|
|
||||||
})
|
|
||||||
return prompt(input)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
using abort = lock(input.sessionID)
|
using abort = lock(input.sessionID)
|
||||||
|
|
||||||
const lastSummary = msgs.findLast((msg) => msg.info.role === "assistant" && msg.info.summary === true)
|
const lastSummary = msgs.findLast((msg) => msg.info.role === "assistant" && msg.info.summary === true)
|
||||||
@@ -999,7 +988,38 @@ export namespace Session {
|
|||||||
error: e,
|
error: e,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
async prepareStep({ messages }) {
|
async prepareStep({ messages, steps }) {
|
||||||
|
// Auto compact if too long
|
||||||
|
const tokens = (() => {
|
||||||
|
if (steps.length) {
|
||||||
|
const previous = steps.at(-1)
|
||||||
|
if (previous) return getUsage(model.info, previous.usage, previous.providerMetadata).tokens
|
||||||
|
}
|
||||||
|
const msg = msgs.findLast((x) => x.info.role === "assistant")?.info as MessageV2.Assistant
|
||||||
|
if (msg && msg.tokens) {
|
||||||
|
return msg.tokens
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
if (tokens) {
|
||||||
|
log.info("compact check", tokens)
|
||||||
|
const count = tokens.input + tokens.cache.read + tokens.cache.write + tokens.output
|
||||||
|
if (model.info.limit.context && count > Math.max((model.info.limit.context - outputLimit) * 0.9, 0)) {
|
||||||
|
log.info("compacting in prepareStep")
|
||||||
|
const summarized = await summarize({
|
||||||
|
sessionID: input.sessionID,
|
||||||
|
providerID: model.providerID,
|
||||||
|
modelID: model.info.id,
|
||||||
|
})
|
||||||
|
const msgs = await Session.messages(input.sessionID).then((x) =>
|
||||||
|
x.filter((x) => x.info.id >= summarized.id),
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
messages: MessageV2.toModelMessage(msgs),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add queued messages to the stream
|
||||||
const queue = (state().queued.get(input.sessionID) ?? []).filter((x) => !x.processed)
|
const queue = (state().queued.get(input.sessionID) ?? []).filter((x) => !x.processed)
|
||||||
if (queue.length) {
|
if (queue.length) {
|
||||||
for (const item of queue) {
|
for (const item of queue) {
|
||||||
@@ -1756,10 +1776,22 @@ export namespace Session {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function summarize(input: { sessionID: string; providerID: string; modelID: string }) {
|
export async function summarize(input: { sessionID: string; providerID: string; modelID: string }) {
|
||||||
using abort = lock(input.sessionID)
|
await update(input.sessionID, (draft) => {
|
||||||
|
draft.time.compacting = Date.now()
|
||||||
|
})
|
||||||
|
await using _ = defer(async () => {
|
||||||
|
await update(input.sessionID, (draft) => {
|
||||||
|
draft.time.compacting = undefined
|
||||||
|
})
|
||||||
|
})
|
||||||
const msgs = await messages(input.sessionID)
|
const msgs = await messages(input.sessionID)
|
||||||
const lastSummary = msgs.findLast((msg) => msg.info.role === "assistant" && msg.info.summary === true)
|
const start = Math.max(
|
||||||
const filtered = msgs.filter((msg) => !lastSummary || msg.info.id >= lastSummary.info.id)
|
0,
|
||||||
|
msgs.findLastIndex((msg) => msg.info.role === "assistant" && msg.info.summary === true),
|
||||||
|
)
|
||||||
|
const split = start + Math.floor((msgs.length - start) / 2)
|
||||||
|
log.info("summarizing", { start, split })
|
||||||
|
const toSummarize = msgs.slice(start, split)
|
||||||
const model = await Provider.getModel(input.providerID, input.modelID)
|
const model = await Provider.getModel(input.providerID, input.modelID)
|
||||||
const system = [
|
const system = [
|
||||||
...SystemPrompt.summarize(model.providerID),
|
...SystemPrompt.summarize(model.providerID),
|
||||||
@@ -1767,36 +1799,8 @@ export namespace Session {
|
|||||||
...(await SystemPrompt.custom()),
|
...(await SystemPrompt.custom()),
|
||||||
]
|
]
|
||||||
|
|
||||||
const next: MessageV2.Info = {
|
const generated = await generateText({
|
||||||
id: Identifier.ascending("message"),
|
|
||||||
role: "assistant",
|
|
||||||
sessionID: input.sessionID,
|
|
||||||
system,
|
|
||||||
mode: "build",
|
|
||||||
path: {
|
|
||||||
cwd: Instance.directory,
|
|
||||||
root: Instance.worktree,
|
|
||||||
},
|
|
||||||
summary: true,
|
|
||||||
cost: 0,
|
|
||||||
modelID: input.modelID,
|
|
||||||
providerID: model.providerID,
|
|
||||||
tokens: {
|
|
||||||
input: 0,
|
|
||||||
output: 0,
|
|
||||||
reasoning: 0,
|
|
||||||
cache: { read: 0, write: 0 },
|
|
||||||
},
|
|
||||||
time: {
|
|
||||||
created: Date.now(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
await updateMessage(next)
|
|
||||||
|
|
||||||
const processor = createProcessor(next, model.info)
|
|
||||||
const stream = streamText({
|
|
||||||
maxRetries: 10,
|
maxRetries: 10,
|
||||||
abortSignal: abort.signal,
|
|
||||||
model: model.language,
|
model: model.language,
|
||||||
messages: [
|
messages: [
|
||||||
...system.map(
|
...system.map(
|
||||||
@@ -1805,7 +1809,7 @@ export namespace Session {
|
|||||||
content: x,
|
content: x,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
...MessageV2.toModelMessage(filtered),
|
...MessageV2.toModelMessage(toSummarize),
|
||||||
{
|
{
|
||||||
role: "user",
|
role: "user",
|
||||||
content: [
|
content: [
|
||||||
@@ -1817,9 +1821,45 @@ export namespace Session {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
const usage = getUsage(model.info, generated.usage, generated.providerMetadata)
|
||||||
|
const msg: MessageV2.Info = {
|
||||||
|
id: Identifier.create("message", false, toSummarize.at(-1)!.info.time.created + 1),
|
||||||
|
role: "assistant",
|
||||||
|
sessionID: input.sessionID,
|
||||||
|
system,
|
||||||
|
mode: "build",
|
||||||
|
path: {
|
||||||
|
cwd: Instance.directory,
|
||||||
|
root: Instance.worktree,
|
||||||
|
},
|
||||||
|
summary: true,
|
||||||
|
cost: usage.cost,
|
||||||
|
tokens: usage.tokens,
|
||||||
|
modelID: input.modelID,
|
||||||
|
providerID: model.providerID,
|
||||||
|
time: {
|
||||||
|
created: Date.now(),
|
||||||
|
completed: Date.now(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
await updateMessage(msg)
|
||||||
|
await updatePart({
|
||||||
|
type: "text",
|
||||||
|
sessionID: input.sessionID,
|
||||||
|
messageID: msg.id,
|
||||||
|
id: Identifier.ascending("part"),
|
||||||
|
text: generated.text,
|
||||||
|
time: {
|
||||||
|
start: Date.now(),
|
||||||
|
end: Date.now(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
const result = await processor.process(stream)
|
Bus.publish(Event.Compacted, {
|
||||||
return result
|
sessionID: input.sessionID,
|
||||||
|
})
|
||||||
|
|
||||||
|
return msg
|
||||||
}
|
}
|
||||||
|
|
||||||
function isLocked(sessionID: string) {
|
function isLocked(sessionID: string) {
|
||||||
@@ -1837,12 +1877,6 @@ export namespace Session {
|
|||||||
log.info("unlocking", { sessionID })
|
log.info("unlocking", { sessionID })
|
||||||
state().pending.delete(sessionID)
|
state().pending.delete(sessionID)
|
||||||
|
|
||||||
const isAutoCompacting = state().autoCompacting.get(sessionID) ?? false
|
|
||||||
if (isAutoCompacting) {
|
|
||||||
state().autoCompacting.delete(sessionID)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const session = await get(sessionID)
|
const session = await get(sessionID)
|
||||||
if (session.parentID) return
|
if (session.parentID) return
|
||||||
|
|
||||||
|
|||||||
@@ -107,14 +107,12 @@ export const EditTool = Tool.define("edit", {
|
|||||||
for (const [file, issues] of Object.entries(diagnostics)) {
|
for (const [file, issues] of Object.entries(diagnostics)) {
|
||||||
if (issues.length === 0) continue
|
if (issues.length === 0) continue
|
||||||
if (file === filePath) {
|
if (file === filePath) {
|
||||||
output += `\nThis file has errors, please fix\n<file_diagnostics>\n${issues.map(LSP.Diagnostic.pretty).join("\n")}\n</file_diagnostics>\n`
|
output += `\nThis file has errors, please fix\n<file_diagnostics>\n${issues
|
||||||
|
.filter((item) => item.severity === 1)
|
||||||
|
.map(LSP.Diagnostic.pretty)
|
||||||
|
.join("\n")}\n</file_diagnostics>\n`
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
output += `\n<project_diagnostics>\n${file}\n${issues
|
|
||||||
// TODO: may want to make more leniant for eslint
|
|
||||||
.filter((item) => item.severity === 1)
|
|
||||||
.map(LSP.Diagnostic.pretty)
|
|
||||||
.join("\n")}\n</project_diagnostics>\n`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -599,7 +597,7 @@ export function replace(content: string, oldString: string, newString: string, r
|
|||||||
for (const replacer of [
|
for (const replacer of [
|
||||||
SimpleReplacer,
|
SimpleReplacer,
|
||||||
LineTrimmedReplacer,
|
LineTrimmedReplacer,
|
||||||
BlockAnchorReplacer,
|
// BlockAnchorReplacer,
|
||||||
WhitespaceNormalizedReplacer,
|
WhitespaceNormalizedReplacer,
|
||||||
IndentationFlexibleReplacer,
|
IndentationFlexibleReplacer,
|
||||||
EscapeNormalizedReplacer,
|
EscapeNormalizedReplacer,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"name": "@opencode-ai/plugin",
|
"name": "@opencode-ai/plugin",
|
||||||
"version": "0.7.1",
|
"version": "0.7.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit"
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
".": "0.8.0"
|
".": "0.9.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
configured_endpoints: 43
|
configured_endpoints: 43
|
||||||
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-97b61518d8666ea7cb310af04248e00bcf8dc9753ba3c7e84471df72b3232004.yml
|
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-46826ba8640557721614b0c9a3f1860681d825ca8d8b12869652fa25aacb0b4c.yml
|
||||||
openapi_spec_hash: a3500531973ad999c350b87c21aa3ab8
|
openapi_spec_hash: 33b8db6fde3021579b21325ce910197d
|
||||||
config_hash: 026ef000d34bf2f930e7b41e77d2d3ff
|
config_hash: 026ef000d34bf2f930e7b41e77d2d3ff
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.9.0 (2025-09-10)
|
||||||
|
|
||||||
|
Full Changelog: [v0.8.0...v0.9.0](https://github.com/sst/opencode-sdk-go/compare/v0.8.0...v0.9.0)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- **api:** api update ([2d3a28d](https://github.com/sst/opencode-sdk-go/commit/2d3a28df5657845aa4d73087e1737d1fc8c3ce1c))
|
||||||
|
|
||||||
## 0.8.0 (2025-09-01)
|
## 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)
|
Full Changelog: [v0.7.0...v0.8.0](https://github.com/sst/opencode-sdk-go/compare/v0.7.0...v0.8.0)
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ Or to pin the version:
|
|||||||
<!-- x-release-please-start-version -->
|
<!-- x-release-please-start-version -->
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
go get -u 'github.com/sst/opencode-sdk-go@v0.8.0'
|
go get -u 'github.com/sst/opencode-sdk-go@v0.9.0'
|
||||||
```
|
```
|
||||||
|
|
||||||
<!-- x-release-please-end -->
|
<!-- x-release-please-end -->
|
||||||
|
|||||||
@@ -50,33 +50,37 @@ func (r *AppService) Providers(ctx context.Context, query AppProvidersParams, op
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Model struct {
|
type Model struct {
|
||||||
ID string `json:"id,required"`
|
ID string `json:"id,required"`
|
||||||
Attachment bool `json:"attachment,required"`
|
Attachment bool `json:"attachment,required"`
|
||||||
Cost ModelCost `json:"cost,required"`
|
Cost ModelCost `json:"cost,required"`
|
||||||
Limit ModelLimit `json:"limit,required"`
|
Limit ModelLimit `json:"limit,required"`
|
||||||
Name string `json:"name,required"`
|
Name string `json:"name,required"`
|
||||||
Options map[string]interface{} `json:"options,required"`
|
Options map[string]interface{} `json:"options,required"`
|
||||||
Reasoning bool `json:"reasoning,required"`
|
Reasoning bool `json:"reasoning,required"`
|
||||||
ReleaseDate string `json:"release_date,required"`
|
ReleaseDate string `json:"release_date,required"`
|
||||||
Temperature bool `json:"temperature,required"`
|
Temperature bool `json:"temperature,required"`
|
||||||
ToolCall bool `json:"tool_call,required"`
|
ToolCall bool `json:"tool_call,required"`
|
||||||
JSON modelJSON `json:"-"`
|
Experimental bool `json:"experimental"`
|
||||||
|
Provider ModelProvider `json:"provider"`
|
||||||
|
JSON modelJSON `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// modelJSON contains the JSON metadata for the struct [Model]
|
// modelJSON contains the JSON metadata for the struct [Model]
|
||||||
type modelJSON struct {
|
type modelJSON struct {
|
||||||
ID apijson.Field
|
ID apijson.Field
|
||||||
Attachment apijson.Field
|
Attachment apijson.Field
|
||||||
Cost apijson.Field
|
Cost apijson.Field
|
||||||
Limit apijson.Field
|
Limit apijson.Field
|
||||||
Name apijson.Field
|
Name apijson.Field
|
||||||
Options apijson.Field
|
Options apijson.Field
|
||||||
Reasoning apijson.Field
|
Reasoning apijson.Field
|
||||||
ReleaseDate apijson.Field
|
ReleaseDate apijson.Field
|
||||||
Temperature apijson.Field
|
Temperature apijson.Field
|
||||||
ToolCall apijson.Field
|
ToolCall apijson.Field
|
||||||
raw string
|
Experimental apijson.Field
|
||||||
ExtraFields map[string]apijson.Field
|
Provider apijson.Field
|
||||||
|
raw string
|
||||||
|
ExtraFields map[string]apijson.Field
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Model) UnmarshalJSON(data []byte) (err error) {
|
func (r *Model) UnmarshalJSON(data []byte) (err error) {
|
||||||
@@ -135,6 +139,26 @@ func (r modelLimitJSON) RawJSON() string {
|
|||||||
return r.raw
|
return r.raw
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ModelProvider struct {
|
||||||
|
Npm string `json:"npm,required"`
|
||||||
|
JSON modelProviderJSON `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// modelProviderJSON contains the JSON metadata for the struct [ModelProvider]
|
||||||
|
type modelProviderJSON struct {
|
||||||
|
Npm apijson.Field
|
||||||
|
raw string
|
||||||
|
ExtraFields map[string]apijson.Field
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ModelProvider) UnmarshalJSON(data []byte) (err error) {
|
||||||
|
return apijson.UnmarshalRoot(data, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r modelProviderJSON) RawJSON() string {
|
||||||
|
return r.raw
|
||||||
|
}
|
||||||
|
|
||||||
type Provider struct {
|
type Provider struct {
|
||||||
ID string `json:"id,required"`
|
ID string `json:"id,required"`
|
||||||
Env []string `json:"env,required"`
|
Env []string `json:"env,required"`
|
||||||
|
|||||||
@@ -1562,34 +1562,38 @@ func (r configProviderJSON) RawJSON() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ConfigProviderModel struct {
|
type ConfigProviderModel struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Attachment bool `json:"attachment"`
|
Attachment bool `json:"attachment"`
|
||||||
Cost ConfigProviderModelsCost `json:"cost"`
|
Cost ConfigProviderModelsCost `json:"cost"`
|
||||||
Limit ConfigProviderModelsLimit `json:"limit"`
|
Experimental bool `json:"experimental"`
|
||||||
Name string `json:"name"`
|
Limit ConfigProviderModelsLimit `json:"limit"`
|
||||||
Options map[string]interface{} `json:"options"`
|
Name string `json:"name"`
|
||||||
Reasoning bool `json:"reasoning"`
|
Options map[string]interface{} `json:"options"`
|
||||||
ReleaseDate string `json:"release_date"`
|
Provider ConfigProviderModelsProvider `json:"provider"`
|
||||||
Temperature bool `json:"temperature"`
|
Reasoning bool `json:"reasoning"`
|
||||||
ToolCall bool `json:"tool_call"`
|
ReleaseDate string `json:"release_date"`
|
||||||
JSON configProviderModelJSON `json:"-"`
|
Temperature bool `json:"temperature"`
|
||||||
|
ToolCall bool `json:"tool_call"`
|
||||||
|
JSON configProviderModelJSON `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// configProviderModelJSON contains the JSON metadata for the struct
|
// configProviderModelJSON contains the JSON metadata for the struct
|
||||||
// [ConfigProviderModel]
|
// [ConfigProviderModel]
|
||||||
type configProviderModelJSON struct {
|
type configProviderModelJSON struct {
|
||||||
ID apijson.Field
|
ID apijson.Field
|
||||||
Attachment apijson.Field
|
Attachment apijson.Field
|
||||||
Cost apijson.Field
|
Cost apijson.Field
|
||||||
Limit apijson.Field
|
Experimental apijson.Field
|
||||||
Name apijson.Field
|
Limit apijson.Field
|
||||||
Options apijson.Field
|
Name apijson.Field
|
||||||
Reasoning apijson.Field
|
Options apijson.Field
|
||||||
ReleaseDate apijson.Field
|
Provider apijson.Field
|
||||||
Temperature apijson.Field
|
Reasoning apijson.Field
|
||||||
ToolCall apijson.Field
|
ReleaseDate apijson.Field
|
||||||
raw string
|
Temperature apijson.Field
|
||||||
ExtraFields map[string]apijson.Field
|
ToolCall apijson.Field
|
||||||
|
raw string
|
||||||
|
ExtraFields map[string]apijson.Field
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ConfigProviderModel) UnmarshalJSON(data []byte) (err error) {
|
func (r *ConfigProviderModel) UnmarshalJSON(data []byte) (err error) {
|
||||||
@@ -1650,6 +1654,27 @@ func (r configProviderModelsLimitJSON) RawJSON() string {
|
|||||||
return r.raw
|
return r.raw
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ConfigProviderModelsProvider struct {
|
||||||
|
Npm string `json:"npm,required"`
|
||||||
|
JSON configProviderModelsProviderJSON `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// configProviderModelsProviderJSON contains the JSON metadata for the struct
|
||||||
|
// [ConfigProviderModelsProvider]
|
||||||
|
type configProviderModelsProviderJSON struct {
|
||||||
|
Npm apijson.Field
|
||||||
|
raw string
|
||||||
|
ExtraFields map[string]apijson.Field
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ConfigProviderModelsProvider) UnmarshalJSON(data []byte) (err error) {
|
||||||
|
return apijson.UnmarshalRoot(data, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r configProviderModelsProviderJSON) RawJSON() string {
|
||||||
|
return r.raw
|
||||||
|
}
|
||||||
|
|
||||||
type ConfigProviderOptions struct {
|
type ConfigProviderOptions struct {
|
||||||
APIKey string `json:"apiKey"`
|
APIKey string `json:"apiKey"`
|
||||||
BaseURL string `json:"baseURL"`
|
BaseURL string `json:"baseURL"`
|
||||||
|
|||||||
@@ -63,7 +63,8 @@ type EventListResponse struct {
|
|||||||
// [EventListResponseEventSessionUpdatedProperties],
|
// [EventListResponseEventSessionUpdatedProperties],
|
||||||
// [EventListResponseEventSessionDeletedProperties],
|
// [EventListResponseEventSessionDeletedProperties],
|
||||||
// [EventListResponseEventSessionIdleProperties],
|
// [EventListResponseEventSessionIdleProperties],
|
||||||
// [EventListResponseEventSessionErrorProperties], [interface{}].
|
// [EventListResponseEventSessionErrorProperties],
|
||||||
|
// [EventListResponseEventSessionCompactedProperties], [interface{}].
|
||||||
Properties interface{} `json:"properties,required"`
|
Properties interface{} `json:"properties,required"`
|
||||||
Type EventListResponseType `json:"type,required"`
|
Type EventListResponseType `json:"type,required"`
|
||||||
JSON eventListResponseJSON `json:"-"`
|
JSON eventListResponseJSON `json:"-"`
|
||||||
@@ -105,6 +106,7 @@ func (r *EventListResponse) UnmarshalJSON(data []byte) (err error) {
|
|||||||
// [EventListResponseEventPermissionReplied], [EventListResponseEventFileEdited],
|
// [EventListResponseEventPermissionReplied], [EventListResponseEventFileEdited],
|
||||||
// [EventListResponseEventSessionUpdated], [EventListResponseEventSessionDeleted],
|
// [EventListResponseEventSessionUpdated], [EventListResponseEventSessionDeleted],
|
||||||
// [EventListResponseEventSessionIdle], [EventListResponseEventSessionError],
|
// [EventListResponseEventSessionIdle], [EventListResponseEventSessionError],
|
||||||
|
// [EventListResponseEventSessionCompacted],
|
||||||
// [EventListResponseEventServerConnected].
|
// [EventListResponseEventServerConnected].
|
||||||
func (r EventListResponse) AsUnion() EventListResponseUnion {
|
func (r EventListResponse) AsUnion() EventListResponseUnion {
|
||||||
return r.union
|
return r.union
|
||||||
@@ -118,7 +120,8 @@ func (r EventListResponse) AsUnion() EventListResponseUnion {
|
|||||||
// [EventListResponseEventPermissionUpdated],
|
// [EventListResponseEventPermissionUpdated],
|
||||||
// [EventListResponseEventPermissionReplied], [EventListResponseEventFileEdited],
|
// [EventListResponseEventPermissionReplied], [EventListResponseEventFileEdited],
|
||||||
// [EventListResponseEventSessionUpdated], [EventListResponseEventSessionDeleted],
|
// [EventListResponseEventSessionUpdated], [EventListResponseEventSessionDeleted],
|
||||||
// [EventListResponseEventSessionIdle], [EventListResponseEventSessionError] or
|
// [EventListResponseEventSessionIdle], [EventListResponseEventSessionError],
|
||||||
|
// [EventListResponseEventSessionCompacted] or
|
||||||
// [EventListResponseEventServerConnected].
|
// [EventListResponseEventServerConnected].
|
||||||
type EventListResponseUnion interface {
|
type EventListResponseUnion interface {
|
||||||
implementsEventListResponse()
|
implementsEventListResponse()
|
||||||
@@ -193,6 +196,11 @@ func init() {
|
|||||||
Type: reflect.TypeOf(EventListResponseEventSessionError{}),
|
Type: reflect.TypeOf(EventListResponseEventSessionError{}),
|
||||||
DiscriminatorValue: "session.error",
|
DiscriminatorValue: "session.error",
|
||||||
},
|
},
|
||||||
|
apijson.UnionVariant{
|
||||||
|
TypeFilter: gjson.JSON,
|
||||||
|
Type: reflect.TypeOf(EventListResponseEventSessionCompacted{}),
|
||||||
|
DiscriminatorValue: "session.compacted",
|
||||||
|
},
|
||||||
apijson.UnionVariant{
|
apijson.UnionVariant{
|
||||||
TypeFilter: gjson.JSON,
|
TypeFilter: gjson.JSON,
|
||||||
Type: reflect.TypeOf(EventListResponseEventServerConnected{}),
|
Type: reflect.TypeOf(EventListResponseEventServerConnected{}),
|
||||||
@@ -1108,6 +1116,66 @@ func (r EventListResponseEventSessionErrorType) IsKnown() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type EventListResponseEventSessionCompacted struct {
|
||||||
|
Properties EventListResponseEventSessionCompactedProperties `json:"properties,required"`
|
||||||
|
Type EventListResponseEventSessionCompactedType `json:"type,required"`
|
||||||
|
JSON eventListResponseEventSessionCompactedJSON `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// eventListResponseEventSessionCompactedJSON contains the JSON metadata for the
|
||||||
|
// struct [EventListResponseEventSessionCompacted]
|
||||||
|
type eventListResponseEventSessionCompactedJSON struct {
|
||||||
|
Properties apijson.Field
|
||||||
|
Type apijson.Field
|
||||||
|
raw string
|
||||||
|
ExtraFields map[string]apijson.Field
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *EventListResponseEventSessionCompacted) UnmarshalJSON(data []byte) (err error) {
|
||||||
|
return apijson.UnmarshalRoot(data, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r eventListResponseEventSessionCompactedJSON) RawJSON() string {
|
||||||
|
return r.raw
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r EventListResponseEventSessionCompacted) implementsEventListResponse() {}
|
||||||
|
|
||||||
|
type EventListResponseEventSessionCompactedProperties struct {
|
||||||
|
SessionID string `json:"sessionID,required"`
|
||||||
|
JSON eventListResponseEventSessionCompactedPropertiesJSON `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// eventListResponseEventSessionCompactedPropertiesJSON contains the JSON metadata
|
||||||
|
// for the struct [EventListResponseEventSessionCompactedProperties]
|
||||||
|
type eventListResponseEventSessionCompactedPropertiesJSON struct {
|
||||||
|
SessionID apijson.Field
|
||||||
|
raw string
|
||||||
|
ExtraFields map[string]apijson.Field
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *EventListResponseEventSessionCompactedProperties) UnmarshalJSON(data []byte) (err error) {
|
||||||
|
return apijson.UnmarshalRoot(data, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r eventListResponseEventSessionCompactedPropertiesJSON) RawJSON() string {
|
||||||
|
return r.raw
|
||||||
|
}
|
||||||
|
|
||||||
|
type EventListResponseEventSessionCompactedType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
EventListResponseEventSessionCompactedTypeSessionCompacted EventListResponseEventSessionCompactedType = "session.compacted"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r EventListResponseEventSessionCompactedType) IsKnown() bool {
|
||||||
|
switch r {
|
||||||
|
case EventListResponseEventSessionCompactedTypeSessionCompacted:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
type EventListResponseEventServerConnected struct {
|
type EventListResponseEventServerConnected struct {
|
||||||
Properties interface{} `json:"properties,required"`
|
Properties interface{} `json:"properties,required"`
|
||||||
Type EventListResponseEventServerConnectedType `json:"type,required"`
|
Type EventListResponseEventServerConnectedType `json:"type,required"`
|
||||||
@@ -1163,12 +1231,13 @@ const (
|
|||||||
EventListResponseTypeSessionDeleted EventListResponseType = "session.deleted"
|
EventListResponseTypeSessionDeleted EventListResponseType = "session.deleted"
|
||||||
EventListResponseTypeSessionIdle EventListResponseType = "session.idle"
|
EventListResponseTypeSessionIdle EventListResponseType = "session.idle"
|
||||||
EventListResponseTypeSessionError EventListResponseType = "session.error"
|
EventListResponseTypeSessionError EventListResponseType = "session.error"
|
||||||
|
EventListResponseTypeSessionCompacted EventListResponseType = "session.compacted"
|
||||||
EventListResponseTypeServerConnected EventListResponseType = "server.connected"
|
EventListResponseTypeServerConnected EventListResponseType = "server.connected"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r EventListResponseType) IsKnown() bool {
|
func (r EventListResponseType) IsKnown() bool {
|
||||||
switch r {
|
switch r {
|
||||||
case EventListResponseTypeInstallationUpdated, EventListResponseTypeLspClientDiagnostics, EventListResponseTypeMessageUpdated, EventListResponseTypeMessageRemoved, EventListResponseTypeMessagePartUpdated, EventListResponseTypeMessagePartRemoved, EventListResponseTypePermissionUpdated, EventListResponseTypePermissionReplied, EventListResponseTypeFileEdited, EventListResponseTypeSessionUpdated, EventListResponseTypeSessionDeleted, EventListResponseTypeSessionIdle, EventListResponseTypeSessionError, EventListResponseTypeServerConnected:
|
case EventListResponseTypeInstallationUpdated, EventListResponseTypeLspClientDiagnostics, EventListResponseTypeMessageUpdated, EventListResponseTypeMessageRemoved, EventListResponseTypeMessagePartUpdated, EventListResponseTypeMessagePartRemoved, EventListResponseTypePermissionUpdated, EventListResponseTypePermissionReplied, EventListResponseTypeFileEdited, EventListResponseTypeSessionUpdated, EventListResponseTypeSessionDeleted, EventListResponseTypeSessionIdle, EventListResponseTypeSessionError, EventListResponseTypeSessionCompacted, EventListResponseTypeServerConnected:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -100,15 +100,17 @@ func (r FileStatus) IsKnown() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type FileNode struct {
|
type FileNode struct {
|
||||||
Ignored bool `json:"ignored,required"`
|
Absolute string `json:"absolute,required"`
|
||||||
Name string `json:"name,required"`
|
Ignored bool `json:"ignored,required"`
|
||||||
Path string `json:"path,required"`
|
Name string `json:"name,required"`
|
||||||
Type FileNodeType `json:"type,required"`
|
Path string `json:"path,required"`
|
||||||
JSON fileNodeJSON `json:"-"`
|
Type FileNodeType `json:"type,required"`
|
||||||
|
JSON fileNodeJSON `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// fileNodeJSON contains the JSON metadata for the struct [FileNode]
|
// fileNodeJSON contains the JSON metadata for the struct [FileNode]
|
||||||
type fileNodeJSON struct {
|
type fileNodeJSON struct {
|
||||||
|
Absolute apijson.Field
|
||||||
Ignored apijson.Field
|
Ignored apijson.Field
|
||||||
Name apijson.Field
|
Name apijson.Field
|
||||||
Path apijson.Field
|
Path apijson.Field
|
||||||
@@ -141,16 +143,18 @@ func (r FileNodeType) IsKnown() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type FileReadResponse struct {
|
type FileReadResponse struct {
|
||||||
Content string `json:"content,required"`
|
Content string `json:"content,required"`
|
||||||
Type FileReadResponseType `json:"type,required"`
|
Diff string `json:"diff"`
|
||||||
JSON fileReadResponseJSON `json:"-"`
|
Patch FileReadResponsePatch `json:"patch"`
|
||||||
|
JSON fileReadResponseJSON `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// fileReadResponseJSON contains the JSON metadata for the struct
|
// fileReadResponseJSON contains the JSON metadata for the struct
|
||||||
// [FileReadResponse]
|
// [FileReadResponse]
|
||||||
type fileReadResponseJSON struct {
|
type fileReadResponseJSON struct {
|
||||||
Content apijson.Field
|
Content apijson.Field
|
||||||
Type apijson.Field
|
Diff apijson.Field
|
||||||
|
Patch apijson.Field
|
||||||
raw string
|
raw string
|
||||||
ExtraFields map[string]apijson.Field
|
ExtraFields map[string]apijson.Field
|
||||||
}
|
}
|
||||||
@@ -163,19 +167,64 @@ func (r fileReadResponseJSON) RawJSON() string {
|
|||||||
return r.raw
|
return r.raw
|
||||||
}
|
}
|
||||||
|
|
||||||
type FileReadResponseType string
|
type FileReadResponsePatch struct {
|
||||||
|
Hunks []FileReadResponsePatchHunk `json:"hunks,required"`
|
||||||
|
NewFileName string `json:"newFileName,required"`
|
||||||
|
OldFileName string `json:"oldFileName,required"`
|
||||||
|
Index string `json:"index"`
|
||||||
|
NewHeader string `json:"newHeader"`
|
||||||
|
OldHeader string `json:"oldHeader"`
|
||||||
|
JSON fileReadResponsePatchJSON `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
// fileReadResponsePatchJSON contains the JSON metadata for the struct
|
||||||
FileReadResponseTypeRaw FileReadResponseType = "raw"
|
// [FileReadResponsePatch]
|
||||||
FileReadResponseTypePatch FileReadResponseType = "patch"
|
type fileReadResponsePatchJSON struct {
|
||||||
)
|
Hunks apijson.Field
|
||||||
|
NewFileName apijson.Field
|
||||||
|
OldFileName apijson.Field
|
||||||
|
Index apijson.Field
|
||||||
|
NewHeader apijson.Field
|
||||||
|
OldHeader apijson.Field
|
||||||
|
raw string
|
||||||
|
ExtraFields map[string]apijson.Field
|
||||||
|
}
|
||||||
|
|
||||||
func (r FileReadResponseType) IsKnown() bool {
|
func (r *FileReadResponsePatch) UnmarshalJSON(data []byte) (err error) {
|
||||||
switch r {
|
return apijson.UnmarshalRoot(data, r)
|
||||||
case FileReadResponseTypeRaw, FileReadResponseTypePatch:
|
}
|
||||||
return true
|
|
||||||
}
|
func (r fileReadResponsePatchJSON) RawJSON() string {
|
||||||
return false
|
return r.raw
|
||||||
|
}
|
||||||
|
|
||||||
|
type FileReadResponsePatchHunk struct {
|
||||||
|
Lines []string `json:"lines,required"`
|
||||||
|
NewLines float64 `json:"newLines,required"`
|
||||||
|
NewStart float64 `json:"newStart,required"`
|
||||||
|
OldLines float64 `json:"oldLines,required"`
|
||||||
|
OldStart float64 `json:"oldStart,required"`
|
||||||
|
JSON fileReadResponsePatchHunkJSON `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// fileReadResponsePatchHunkJSON contains the JSON metadata for the struct
|
||||||
|
// [FileReadResponsePatchHunk]
|
||||||
|
type fileReadResponsePatchHunkJSON struct {
|
||||||
|
Lines apijson.Field
|
||||||
|
NewLines apijson.Field
|
||||||
|
NewStart apijson.Field
|
||||||
|
OldLines apijson.Field
|
||||||
|
OldStart apijson.Field
|
||||||
|
raw string
|
||||||
|
ExtraFields map[string]apijson.Field
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FileReadResponsePatchHunk) UnmarshalJSON(data []byte) (err error) {
|
||||||
|
return apijson.UnmarshalRoot(data, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r fileReadResponsePatchHunkJSON) RawJSON() string {
|
||||||
|
return r.raw
|
||||||
}
|
}
|
||||||
|
|
||||||
type FileListParams struct {
|
type FileListParams struct {
|
||||||
|
|||||||
@@ -2,4 +2,4 @@
|
|||||||
|
|
||||||
package internal
|
package internal
|
||||||
|
|
||||||
const PackageVersion = "0.8.0" // x-release-please-version
|
const PackageVersion = "0.9.0" // x-release-please-version
|
||||||
|
|||||||
@@ -1332,15 +1332,17 @@ func (r sessionJSON) RawJSON() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SessionTime struct {
|
type SessionTime struct {
|
||||||
Created float64 `json:"created,required"`
|
Created float64 `json:"created,required"`
|
||||||
Updated float64 `json:"updated,required"`
|
Updated float64 `json:"updated,required"`
|
||||||
JSON sessionTimeJSON `json:"-"`
|
Compacting float64 `json:"compacting"`
|
||||||
|
JSON sessionTimeJSON `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// sessionTimeJSON contains the JSON metadata for the struct [SessionTime]
|
// sessionTimeJSON contains the JSON metadata for the struct [SessionTime]
|
||||||
type sessionTimeJSON struct {
|
type sessionTimeJSON struct {
|
||||||
Created apijson.Field
|
Created apijson.Field
|
||||||
Updated apijson.Field
|
Updated apijson.Field
|
||||||
|
Compacting apijson.Field
|
||||||
raw string
|
raw string
|
||||||
ExtraFields map[string]apijson.Field
|
ExtraFields map[string]apijson.Field
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"name": "@opencode-ai/sdk",
|
"name": "@opencode-ai/sdk",
|
||||||
"version": "0.7.1",
|
"version": "0.7.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit"
|
||||||
|
|||||||
@@ -50,6 +50,9 @@ export type Event =
|
|||||||
| ({
|
| ({
|
||||||
type: "session.error"
|
type: "session.error"
|
||||||
} & EventSessionError)
|
} & EventSessionError)
|
||||||
|
| ({
|
||||||
|
type: "session.compacted"
|
||||||
|
} & EventSessionCompacted)
|
||||||
| ({
|
| ({
|
||||||
type: "server.connected"
|
type: "server.connected"
|
||||||
} & EventServerConnected)
|
} & EventServerConnected)
|
||||||
@@ -478,6 +481,7 @@ export type Session = {
|
|||||||
time: {
|
time: {
|
||||||
created: number
|
created: number
|
||||||
updated: number
|
updated: number
|
||||||
|
compacting?: number
|
||||||
}
|
}
|
||||||
revert?: {
|
revert?: {
|
||||||
messageID: string
|
messageID: string
|
||||||
@@ -521,6 +525,13 @@ export type EventSessionError = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type EventSessionCompacted = {
|
||||||
|
type: "session.compacted"
|
||||||
|
properties: {
|
||||||
|
sessionID: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export type EventServerConnected = {
|
export type EventServerConnected = {
|
||||||
type: "server.connected"
|
type: "server.connected"
|
||||||
properties: {
|
properties: {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
|
||||||
@@ -656,6 +657,9 @@ func (a *App) IsBusy() bool {
|
|||||||
if len(a.Messages) == 0 {
|
if len(a.Messages) == 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if a.IsCompacting() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
lastMessage := a.Messages[len(a.Messages)-1]
|
lastMessage := a.Messages[len(a.Messages)-1]
|
||||||
if casted, ok := lastMessage.Info.(opencode.AssistantMessage); ok {
|
if casted, ok := lastMessage.Info.(opencode.AssistantMessage); ok {
|
||||||
return casted.Time.Completed == 0
|
return casted.Time.Completed == 0
|
||||||
@@ -663,6 +667,13 @@ func (a *App) IsBusy() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *App) IsCompacting() bool {
|
||||||
|
if time.Since(time.UnixMilli(int64(a.Session.Time.Compacting))) < time.Second*30 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (a *App) HasAnimatingWork() bool {
|
func (a *App) HasAnimatingWork() bool {
|
||||||
for _, msg := range a.Messages {
|
for _, msg := range a.Messages {
|
||||||
switch casted := msg.Info.(type) {
|
switch casted := msg.Info.(type) {
|
||||||
|
|||||||
@@ -385,6 +385,9 @@ func (m *editorComponent) Content() string {
|
|||||||
} else if m.app.IsBusy() {
|
} else if m.app.IsBusy() {
|
||||||
keyText := m.getInterruptKeyText()
|
keyText := m.getInterruptKeyText()
|
||||||
status := "working"
|
status := "working"
|
||||||
|
if m.app.IsCompacting() {
|
||||||
|
status = "compacting"
|
||||||
|
}
|
||||||
if m.app.CurrentPermission.ID != "" {
|
if m.app.CurrentPermission.ID != "" {
|
||||||
status = "waiting for permission"
|
status = "waiting for permission"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -365,6 +365,9 @@ func (m *messagesComponent) renderView() tea.Cmd {
|
|||||||
lastAssistantMessage := "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"
|
lastAssistantMessage := "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"
|
||||||
for _, msg := range slices.Backward(m.app.Messages) {
|
for _, msg := range slices.Backward(m.app.Messages) {
|
||||||
if assistant, ok := msg.Info.(opencode.AssistantMessage); ok {
|
if assistant, ok := msg.Info.(opencode.AssistantMessage); ok {
|
||||||
|
if assistant.Time.Completed > 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
lastAssistantMessage = assistant.ID
|
lastAssistantMessage = assistant.ID
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -475,6 +478,9 @@ func (m *messagesComponent) renderView() tea.Cmd {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case opencode.AssistantMessage:
|
case opencode.AssistantMessage:
|
||||||
|
if casted.Summary {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if casted.ID == m.app.Session.Revert.MessageID {
|
if casted.ID == m.app.Session.Revert.MessageID {
|
||||||
reverted = true
|
reverted = true
|
||||||
revertedMessageCount = 1
|
revertedMessageCount = 1
|
||||||
|
|||||||
@@ -592,10 +592,40 @@ func (a Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if matchIndex == -1 {
|
if matchIndex == -1 {
|
||||||
a.app.Messages = append(a.app.Messages, app.Message{
|
// Extract the new message ID
|
||||||
|
var newMessageID string
|
||||||
|
switch casted := msg.Properties.Info.AsUnion().(type) {
|
||||||
|
case opencode.UserMessage:
|
||||||
|
newMessageID = casted.ID
|
||||||
|
case opencode.AssistantMessage:
|
||||||
|
newMessageID = casted.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the correct insertion index by scanning backwards
|
||||||
|
// Most messages are added to the end, so start from the end
|
||||||
|
insertIndex := len(a.app.Messages)
|
||||||
|
for i := len(a.app.Messages) - 1; i >= 0; i-- {
|
||||||
|
var existingID string
|
||||||
|
switch casted := a.app.Messages[i].Info.(type) {
|
||||||
|
case opencode.UserMessage:
|
||||||
|
existingID = casted.ID
|
||||||
|
case opencode.AssistantMessage:
|
||||||
|
existingID = casted.ID
|
||||||
|
}
|
||||||
|
if existingID < newMessageID {
|
||||||
|
insertIndex = i + 1
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the new message
|
||||||
|
newMessage := app.Message{
|
||||||
Info: msg.Properties.Info.AsUnion(),
|
Info: msg.Properties.Info.AsUnion(),
|
||||||
Parts: []opencode.PartUnion{},
|
Parts: []opencode.PartUnion{},
|
||||||
})
|
}
|
||||||
|
|
||||||
|
// Insert at the correct position
|
||||||
|
a.app.Messages = append(a.app.Messages[:insertIndex], append([]app.Message{newMessage}, a.app.Messages[insertIndex:]...)...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case opencode.EventListResponseEventPermissionUpdated:
|
case opencode.EventListResponseEventPermissionUpdated:
|
||||||
@@ -627,6 +657,10 @@ func (a Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
slog.Error("Server error", "name", err.Name, "message", err.Data.Message)
|
slog.Error("Server error", "name", err.Name, "message", err.Data.Message)
|
||||||
return a, toast.NewErrorToast(err.Data.Message, toast.WithTitle(string(err.Name)))
|
return a, toast.NewErrorToast(err.Data.Message, toast.WithTitle(string(err.Name)))
|
||||||
}
|
}
|
||||||
|
case opencode.EventListResponseEventSessionCompacted:
|
||||||
|
if msg.Properties.SessionID == a.app.Session.ID {
|
||||||
|
return a, toast.NewSuccessToast("Session compacted successfully")
|
||||||
|
}
|
||||||
case tea.WindowSizeMsg:
|
case tea.WindowSizeMsg:
|
||||||
msg.Height -= 2 // Make space for the status bar
|
msg.Height -= 2 // Make space for the status bar
|
||||||
a.width, a.height = msg.Width, msg.Height
|
a.width, a.height = msg.Width, msg.Height
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode/web",
|
"name": "@opencode/web",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "0.7.1",
|
"version": "0.7.2",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "astro dev",
|
"dev": "astro dev",
|
||||||
"dev:remote": "sst shell --stage=dev --target=Web astro dev",
|
"dev:remote": "sst shell --stage=dev --target=Web astro dev",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "opencode",
|
"name": "opencode",
|
||||||
"displayName": "opencode",
|
"displayName": "opencode",
|
||||||
"description": "opencode for VS Code",
|
"description": "opencode for VS Code",
|
||||||
"version": "0.7.1",
|
"version": "0.7.2",
|
||||||
"publisher": "sst-dev",
|
"publisher": "sst-dev",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
4
sst-env.d.ts
vendored
4
sst-env.d.ts
vendored
@@ -44,6 +44,10 @@ declare module "sst" {
|
|||||||
type: "sst.sst.Linkable"
|
type: "sst.sst.Linkable"
|
||||||
username: string
|
username: string
|
||||||
}
|
}
|
||||||
|
FIREWORKS_API_KEY: {
|
||||||
|
type: "sst.sst.Secret"
|
||||||
|
value: string
|
||||||
|
}
|
||||||
GITHUB_APP_ID: {
|
GITHUB_APP_ID: {
|
||||||
type: "sst.sst.Secret"
|
type: "sst.sst.Secret"
|
||||||
value: string
|
value: string
|
||||||
|
|||||||
Reference in New Issue
Block a user