Merge branch 'dev' into effect-sync-event
This commit is contained in:
@@ -120,6 +120,10 @@ class TokenRefreshRequest extends Schema.Class<TokenRefreshRequest>("TokenRefres
|
||||
|
||||
const clientId = "opencode-cli"
|
||||
const eagerRefreshThreshold = Duration.minutes(5)
|
||||
const eagerRefreshThresholdMs = Duration.toMillis(eagerRefreshThreshold)
|
||||
|
||||
const isTokenFresh = (tokenExpiry: number | null, now: number) =>
|
||||
tokenExpiry != null && tokenExpiry > now + eagerRefreshThresholdMs
|
||||
|
||||
const mapAccountServiceError =
|
||||
(message = "Account service operation failed") =>
|
||||
@@ -219,7 +223,7 @@ export namespace Account {
|
||||
|
||||
const account = maybeAccount.value
|
||||
const now = yield* Clock.currentTimeMillis
|
||||
if (account.token_expiry && account.token_expiry > now + Duration.toMillis(eagerRefreshThreshold)) {
|
||||
if (isTokenFresh(account.token_expiry, now)) {
|
||||
return account.access_token
|
||||
}
|
||||
|
||||
@@ -229,7 +233,7 @@ export namespace Account {
|
||||
|
||||
const resolveToken = Effect.fnUntraced(function* (row: AccountRow) {
|
||||
const now = yield* Clock.currentTimeMillis
|
||||
if (row.token_expiry && row.token_expiry > now + Duration.toMillis(eagerRefreshThreshold)) {
|
||||
if (isTokenFresh(row.token_expiry, now)) {
|
||||
return row.access_token
|
||||
}
|
||||
|
||||
|
||||
@@ -12,3 +12,4 @@ Focus on information that would be helpful for continuing the conversation, incl
|
||||
Your summary should be comprehensive enough to provide context but concise enough to be quickly understood.
|
||||
|
||||
Do not respond to any questions in the conversation, only output the summary.
|
||||
Respond in the same language the user used in the conversation.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import z from "zod"
|
||||
import { EOL } from "os"
|
||||
import { NamedError } from "@opencode-ai/util/error"
|
||||
import { logo as glyphs } from "./logo"
|
||||
|
||||
export namespace UI {
|
||||
const wordmark = [
|
||||
@@ -47,12 +48,60 @@ export namespace UI {
|
||||
}
|
||||
|
||||
export function logo(pad?: string) {
|
||||
const result = []
|
||||
for (const row of wordmark) {
|
||||
if (pad) result.push(pad)
|
||||
result.push(row)
|
||||
result.push(EOL)
|
||||
if (!process.stdout.isTTY && !process.stderr.isTTY) {
|
||||
const result = []
|
||||
for (const row of wordmark) {
|
||||
if (pad) result.push(pad)
|
||||
result.push(row)
|
||||
result.push(EOL)
|
||||
}
|
||||
return result.join("").trimEnd()
|
||||
}
|
||||
|
||||
const result: string[] = []
|
||||
const reset = "\x1b[0m"
|
||||
const left = {
|
||||
fg: "\x1b[90m",
|
||||
shadow: "\x1b[38;5;235m",
|
||||
bg: "\x1b[48;5;235m",
|
||||
}
|
||||
const right = {
|
||||
fg: reset,
|
||||
shadow: "\x1b[38;5;238m",
|
||||
bg: "\x1b[48;5;238m",
|
||||
}
|
||||
const gap = " "
|
||||
const draw = (line: string, fg: string, shadow: string, bg: string) => {
|
||||
const parts: string[] = []
|
||||
for (const char of line) {
|
||||
if (char === "_") {
|
||||
parts.push(bg, " ", reset)
|
||||
continue
|
||||
}
|
||||
if (char === "^") {
|
||||
parts.push(fg, bg, "▀", reset)
|
||||
continue
|
||||
}
|
||||
if (char === "~") {
|
||||
parts.push(shadow, "▀", reset)
|
||||
continue
|
||||
}
|
||||
if (char === " ") {
|
||||
parts.push(" ")
|
||||
continue
|
||||
}
|
||||
parts.push(fg, char, reset)
|
||||
}
|
||||
return parts.join("")
|
||||
}
|
||||
glyphs.left.forEach((row, index) => {
|
||||
if (pad) result.push(pad)
|
||||
result.push(draw(row, left.fg, left.shadow, left.bg))
|
||||
result.push(gap)
|
||||
const other = glyphs.right[index] ?? ""
|
||||
result.push(draw(other, right.fg, right.shadow, right.bg))
|
||||
result.push(EOL)
|
||||
})
|
||||
return result.join("").trimEnd()
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,19 @@ process.on("uncaughtException", (e) => {
|
||||
})
|
||||
})
|
||||
|
||||
const cli = yargs(hideBin(process.argv))
|
||||
const args = hideBin(process.argv)
|
||||
|
||||
function show(out: string) {
|
||||
const text = out.trimStart()
|
||||
if (!text.startsWith("opencode ")) {
|
||||
process.stderr.write(UI.logo() + EOL + EOL)
|
||||
process.stderr.write(text)
|
||||
return
|
||||
}
|
||||
process.stderr.write(out)
|
||||
}
|
||||
|
||||
const cli = yargs(args)
|
||||
.parserConfiguration({ "populate--": true })
|
||||
.scriptName("opencode")
|
||||
.wrap(100)
|
||||
@@ -130,7 +142,7 @@ const cli = yargs(hideBin(process.argv))
|
||||
process.stderr.write("Database migration complete." + EOL)
|
||||
}
|
||||
})
|
||||
.usage("\n" + UI.logo())
|
||||
.usage("")
|
||||
.completion("completion", "generate shell completion script")
|
||||
.command(AcpCommand)
|
||||
.command(McpCommand)
|
||||
@@ -162,7 +174,7 @@ const cli = yargs(hideBin(process.argv))
|
||||
msg?.startsWith("Invalid values:")
|
||||
) {
|
||||
if (err) throw err
|
||||
cli.showHelp("log")
|
||||
cli.showHelp(show)
|
||||
}
|
||||
if (err) throw err
|
||||
process.exit(1)
|
||||
@@ -170,7 +182,15 @@ const cli = yargs(hideBin(process.argv))
|
||||
.strict()
|
||||
|
||||
try {
|
||||
await cli.parse()
|
||||
if (args.includes("-h") || args.includes("--help")) {
|
||||
await cli.parse(args, (err: Error | undefined, _argv: unknown, out: string) => {
|
||||
if (err) throw err
|
||||
if (!out) return
|
||||
show(out)
|
||||
})
|
||||
} else {
|
||||
await cli.parse()
|
||||
}
|
||||
} catch (e) {
|
||||
let data: Record<string, any> = {}
|
||||
if (e instanceof NamedError) {
|
||||
|
||||
@@ -44,6 +44,7 @@ import { createGateway } from "@ai-sdk/gateway"
|
||||
import { createTogetherAI } from "@ai-sdk/togetherai"
|
||||
import { createPerplexity } from "@ai-sdk/perplexity"
|
||||
import { createVercel } from "@ai-sdk/vercel"
|
||||
import { createVenice } from "venice-ai-sdk-provider"
|
||||
import {
|
||||
createGitLab,
|
||||
VERSION as GITLAB_PROVIDER_VERSION,
|
||||
@@ -139,6 +140,7 @@ export namespace Provider {
|
||||
"@ai-sdk/vercel": createVercel,
|
||||
"gitlab-ai-provider": createGitLab,
|
||||
"@ai-sdk/github-copilot": createGitHubCopilotOpenAICompatible,
|
||||
"venice-ai-sdk-provider": createVenice,
|
||||
}
|
||||
|
||||
type CustomModelLoader = (sdk: any, modelID: string, options?: Record<string, any>) => Promise<any>
|
||||
|
||||
@@ -190,6 +190,7 @@ export namespace SessionCompaction {
|
||||
Focus on information that would be helpful for continuing the conversation, including what we did, what we're doing, which files we're working on, and what we're going to do next.
|
||||
The summary that you construct will be used so that another agent can read it and continue the work.
|
||||
Do not call any tools. Respond only with the summary text.
|
||||
Respond in the same language as the user's messages in the conversation.
|
||||
|
||||
When constructing the summary, try to stick to this template:
|
||||
---
|
||||
|
||||
@@ -756,7 +756,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the
|
||||
}
|
||||
const model = input.model ?? agent.model ?? (yield* lastModel(input.sessionID))
|
||||
const userMsg: MessageV2.User = {
|
||||
id: MessageID.ascending(),
|
||||
id: input.messageID ?? MessageID.ascending(),
|
||||
sessionID: input.sessionID,
|
||||
time: { created: Date.now() },
|
||||
role: "user",
|
||||
@@ -1362,9 +1362,18 @@ NOTE: At any point in time through this workflow you should feel free to ask the
|
||||
}
|
||||
|
||||
if (!lastUser) throw new Error("No user message found in stream. This should never happen.")
|
||||
|
||||
const lastAssistantMsg = msgs.findLast(
|
||||
(msg) => msg.info.role === "assistant" && msg.info.id === lastAssistant?.id,
|
||||
)
|
||||
// Some providers return "stop" even when the assistant message contains tool calls.
|
||||
// Keep the loop running so tool results can be sent back to the model.
|
||||
const hasToolCalls = lastAssistantMsg?.parts.some((part) => part.type === "tool") ?? false
|
||||
|
||||
if (
|
||||
lastAssistant?.finish &&
|
||||
!["tool-calls"].includes(lastAssistant.finish) &&
|
||||
!hasToolCalls &&
|
||||
lastUser.id < lastAssistant.id
|
||||
) {
|
||||
log.info("exiting loop", { sessionID })
|
||||
@@ -1818,6 +1827,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the
|
||||
|
||||
export const ShellInput = z.object({
|
||||
sessionID: SessionID.zod,
|
||||
messageID: MessageID.zod.optional(),
|
||||
agent: z.string(),
|
||||
model: z
|
||||
.object({
|
||||
|
||||
@@ -54,7 +54,7 @@ export namespace SessionRetry {
|
||||
if (MessageV2.APIError.isInstance(error)) {
|
||||
if (!error.data.isRetryable) return undefined
|
||||
if (error.data.responseBody?.includes("FreeUsageLimitError"))
|
||||
return `Free usage exceeded, add credits https://opencode.ai/zen`
|
||||
return `Free usage exceeded, subscribe to Go https://opencode.ai/go`
|
||||
return error.data.message.includes("Overloaded") ? "Provider is overloaded" : error.data.message
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user