Skip to content

Commit 021fd3f

Browse files
committed
sync
1 parent fa3253d commit 021fd3f

8 files changed

Lines changed: 688 additions & 152 deletions

File tree

packages/opencode/src/lsp/server.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@ export namespace LSPServer {
3838
? path.resolve(process.cwd(), process.argv0)
3939
: process.argv0
4040
return spawn(root, ["x", "typescript-language-server", "--stdio"], {
41+
argv0: "bun",
4142
env: {
43+
...process.env,
4244
BUN_BE_BUN: "1",
4345
},
4446
})

packages/opencode/src/provider/provider.ts

Lines changed: 75 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { WriteTool } from "../tool/write"
2323
import { TodoReadTool, TodoWriteTool } from "../tool/todo"
2424
import { AuthAnthropic } from "../auth/anthropic"
2525
import { ModelsDev } from "./models"
26+
import { NamedError } from "../util/error"
2627

2728
export namespace Provider {
2829
const log = Log.create({ service: "provider" })
@@ -75,26 +76,34 @@ export namespace Provider {
7576
string,
7677
(provider: Info) => Promise<Record<string, any> | false>
7778
> = {
78-
anthropic: async () => {
79+
async anthropic(provider) {
7980
const access = await AuthAnthropic.access()
80-
if (access)
81+
if (access) {
82+
// claude sub doesn't have usage cost
83+
for (const model of Object.values(provider.models)) {
84+
model.cost = {
85+
input: 0,
86+
inputCached: 0,
87+
output: 0,
88+
outputCached: 0,
89+
}
90+
}
8191
return {
8292
apiKey: "",
8393
headers: {
8494
authorization: `Bearer ${access}`,
8595
"anthropic-beta": "oauth-2025-04-20",
8696
},
8797
}
88-
return env("ANTHROPIC_API_KEY")
98+
}
99+
return env("ANTHROPIC_API_KEY")(provider)
89100
},
90101
google: env("GOOGLE_GENERATIVE_AI_API_KEY"),
91102
openai: env("OPENAI_API_KEY"),
92103
}
93104

94105
const state = App.state("provider", async () => {
95-
log.info("loading config")
96106
const config = await Config.get()
97-
log.info("loading providers")
98107
const database: Record<string, Provider.Info> = await ModelsDev.get()
99108

100109
const providers: {
@@ -134,6 +143,10 @@ export namespace Provider {
134143
}
135144
}
136145

146+
for (const providerID of Object.keys(providers)) {
147+
log.info("loaded", { providerID })
148+
}
149+
137150
return {
138151
models,
139152
providers,
@@ -148,28 +161,32 @@ export namespace Provider {
148161
}
149162

150163
async function getSDK(providerID: string) {
151-
const s = await state()
152-
if (s.sdk.has(providerID)) return s.sdk.get(providerID)!
153-
154-
const dir = path.join(
155-
Global.Path.cache,
156-
`node_modules`,
157-
`@ai-sdk`,
158-
providerID,
159-
)
160-
if (!(await Bun.file(path.join(dir, "package.json")).exists())) {
161-
log.info("installing", {
164+
return (async () => {
165+
const s = await state()
166+
const existing = s.sdk.get(providerID)
167+
if (existing) return existing
168+
const dir = path.join(
169+
Global.Path.cache,
170+
`node_modules`,
171+
`@ai-sdk`,
162172
providerID,
163-
})
164-
await BunProc.run(["add", `@ai-sdk/${providerID}@alpha`], {
165-
cwd: Global.Path.cache,
166-
})
167-
}
168-
const mod = await import(path.join(dir))
169-
const fn = mod[Object.keys(mod).find((key) => key.startsWith("create"))!]
170-
const loaded = fn(s.providers[providerID]?.options)
171-
s.sdk.set(providerID, loaded)
172-
return loaded as SDK
173+
)
174+
if (!(await Bun.file(path.join(dir, "package.json")).exists())) {
175+
log.info("installing", {
176+
providerID,
177+
})
178+
await BunProc.run(["add", `@ai-sdk/${providerID}@alpha`], {
179+
cwd: Global.Path.cache,
180+
})
181+
}
182+
const mod = await import(path.join(dir))
183+
const fn = mod[Object.keys(mod).find((key) => key.startsWith("create"))!]
184+
const loaded = fn(s.providers[providerID]?.options)
185+
s.sdk.set(providerID, loaded)
186+
return loaded as SDK
187+
})().catch((e) => {
188+
throw new InitError({ providerID: providerID }, { cause: e })
189+
})
173190
}
174191

175192
export async function getModel(providerID: string, modelID: string) {
@@ -183,12 +200,11 @@ export namespace Provider {
183200
})
184201

185202
const provider = s.providers[providerID]
186-
if (!provider) throw new ModelNotFoundError(modelID)
203+
if (!provider) throw new ModelNotFoundError({ providerID, modelID })
187204
const info = provider.info.models[modelID]
188-
if (!info) throw new ModelNotFoundError(modelID)
205+
if (!info) throw new ModelNotFoundError({ providerID, modelID })
189206

190207
const sdk = await getSDK(providerID)
191-
if (!sdk) throw new ModelNotFoundError(modelID)
192208

193209
try {
194210
const language = sdk.languageModel(modelID)
@@ -202,7 +218,14 @@ export namespace Provider {
202218
language,
203219
}
204220
} catch (e) {
205-
if (e instanceof NoSuchModelError) throw new ModelNotFoundError(modelID)
221+
if (e instanceof NoSuchModelError)
222+
throw new ModelNotFoundError(
223+
{
224+
modelID: modelID,
225+
providerID,
226+
},
227+
{ cause: e },
228+
)
206229
throw e
207230
}
208231
}
@@ -259,9 +282,26 @@ export namespace Provider {
259282
return TOOL_MAPPING[providerID] ?? TOOLS
260283
}
261284

262-
class ModelNotFoundError extends Error {
263-
constructor(public readonly model: string) {
264-
super()
265-
}
266-
}
285+
export const ModelNotFoundError = NamedError.create(
286+
"ProviderModelNotFoundError",
287+
z.object({
288+
providerID: z.string(),
289+
modelID: z.string(),
290+
}),
291+
)
292+
293+
export const InitError = NamedError.create(
294+
"ProviderInitError",
295+
z.object({
296+
providerID: z.string(),
297+
}),
298+
)
299+
300+
export const AuthError = NamedError.create(
301+
"ProviderAuthError",
302+
z.object({
303+
providerID: z.string(),
304+
message: z.string(),
305+
}),
306+
)
267307
}

packages/opencode/src/server/server.ts

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,26 @@ import { Provider } from "../provider/provider"
1111
import { App } from "../app/app"
1212
import { Global } from "../global"
1313
import { mapValues } from "remeda"
14+
import { NamedError } from "../util/error"
15+
16+
const ERRORS = {
17+
400: {
18+
description: "Bad request",
19+
content: {
20+
"application/json": {
21+
schema: resolver(
22+
z
23+
.object({
24+
data: z.record(z.string(), z.any()),
25+
})
26+
.openapi({
27+
ref: "Error",
28+
}),
29+
),
30+
},
31+
},
32+
},
33+
} as const
1434

1535
export namespace Server {
1636
const log = Log.create({ service: "server" })
@@ -22,13 +42,15 @@ export namespace Server {
2242

2343
const result = app
2444
.onError((err, c) => {
25-
log.error("error", err)
45+
if (err instanceof NamedError) {
46+
return c.json(err.toObject(), {
47+
status: 400,
48+
})
49+
}
2650
return c.json(
51+
new NamedError.Unknown({ message: err.toString() }).toObject(),
2752
{
28-
error: err.toString(),
29-
},
30-
{
31-
status: 500,
53+
status: 400,
3254
},
3355
)
3456
})
@@ -197,6 +219,7 @@ export namespace Server {
197219
describeRoute({
198220
description: "Create a new session",
199221
responses: {
222+
...ERRORS,
200223
200: {
201224
description: "Successfully created session",
202225
content: {

packages/opencode/src/session/index.ts

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { Log } from "../util/log"
66
import {
77
convertToModelMessages,
88
generateText,
9+
LoadAPIKeyError,
910
stepCountIs,
1011
streamText,
1112
tool,
@@ -28,6 +29,7 @@ import { Provider } from "../provider/provider"
2829
import { SessionContext } from "./context"
2930
import { ListTool } from "../tool/ls"
3031
import { MCP } from "../mcp"
32+
import { NamedError } from "../util/error"
3133

3234
export namespace Session {
3335
const log = Log.create({ service: "session" })
@@ -59,6 +61,12 @@ export namespace Session {
5961
info: Info,
6062
}),
6163
),
64+
Error: Bus.event(
65+
"session.error",
66+
z.object({
67+
error: Message.Info.shape.metadata.shape.error,
68+
}),
69+
),
6270
}
6371

6472
const state = App.state("session", () => {
@@ -296,11 +304,13 @@ export namespace Session {
296304
},
297305
]),
298306
model: model.language,
299-
}).then((result) => {
300-
return Session.update(input.sessionID, (draft) => {
301-
draft.title = result.text
302-
})
303307
})
308+
.then((result) => {
309+
return Session.update(input.sessionID, (draft) => {
310+
draft.title = result.text
311+
})
312+
})
313+
.catch(() => {})
304314
await updateMessage(system)
305315
}
306316
const msg: Message.Info = {
@@ -506,11 +516,27 @@ export namespace Session {
506516
assistant.cost = usage.cost
507517
await updateMessage(next)
508518
},
509-
onError(input) {
510-
log.error("error", input)
511-
if (input.error instanceof Error) {
512-
next.metadata.error = input.error.toString()
519+
onError(err) {
520+
log.error("error", err)
521+
switch (true) {
522+
case LoadAPIKeyError.isInstance(err.error):
523+
next.metadata.error = new Provider.AuthError(
524+
{
525+
providerID: input.providerID,
526+
message: err.error.message,
527+
},
528+
{ cause: err.error },
529+
).toObject()
530+
break
531+
case err.error instanceof Error:
532+
next.metadata.error = new NamedError.Unknown(
533+
{ message: err.error.toString() },
534+
{ cause: err.error },
535+
).toObject()
513536
}
537+
Bus.publish(Event.Error, {
538+
error: next.metadata.error,
539+
})
514540
},
515541
async prepareStep(step) {
516542
next.parts.push({
@@ -532,7 +558,7 @@ export namespace Session {
532558
})
533559
await result.consumeStream({
534560
onError: (err) => {
535-
log.error("error", {
561+
log.error("stream error", {
536562
err,
537563
})
538564
},

packages/opencode/src/session/message.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import z from "zod"
22
import { Bus } from "../bus"
3+
import { Provider } from "../provider/provider"
4+
import { NamedError } from "../util/error"
35

46
export namespace Message {
57
export const ToolCall = z
@@ -138,7 +140,10 @@ export namespace Message {
138140
created: z.number(),
139141
completed: z.number().optional(),
140142
}),
141-
error: z.string().optional(),
143+
error: z.discriminatedUnion("name", [
144+
Provider.AuthError.Schema,
145+
NamedError.Unknown.Schema,
146+
]),
142147
sessionID: z.string(),
143148
tool: z.record(z.string(), z.any()),
144149
assistant: z

0 commit comments

Comments
 (0)