Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e3ff34c76e | ||
|
|
8440dce902 | ||
|
|
5dba5992b4 | ||
|
|
662bae2454 | ||
|
|
c37d41edb2 |
@@ -24,7 +24,7 @@
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"OmO",
|
||||
"Sisyphus",
|
||||
"oracle",
|
||||
"librarian",
|
||||
"explore",
|
||||
@@ -288,7 +288,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"OmO": {
|
||||
"Sisyphus": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"model": {
|
||||
@@ -399,7 +399,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"OmO-Plan": {
|
||||
"Planner-Sisyphus": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"model": {
|
||||
@@ -1201,13 +1201,27 @@
|
||||
"google_auth": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"omo_agent": {
|
||||
"sisyphus_agent": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"disabled": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"experimental": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"aggressive_truncation": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"empty_message_recovery": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"auto_resume": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "oh-my-opencode",
|
||||
"version": "2.3.0",
|
||||
"version": "2.3.1",
|
||||
"description": "OpenCode plugin - custom agents (oracle, librarian) and enhanced features",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
||||
@@ -3,6 +3,49 @@ import * as path from "node:path"
|
||||
import { CACHE_DIR, PACKAGE_NAME } from "./constants"
|
||||
import { log } from "../../shared/logger"
|
||||
|
||||
interface BunLockfile {
|
||||
workspaces?: {
|
||||
""?: {
|
||||
dependencies?: Record<string, string>
|
||||
}
|
||||
}
|
||||
packages?: Record<string, unknown>
|
||||
}
|
||||
|
||||
function stripTrailingCommas(json: string): string {
|
||||
return json.replace(/,(\s*[}\]])/g, "$1")
|
||||
}
|
||||
|
||||
function removeFromBunLock(packageName: string): boolean {
|
||||
const lockPath = path.join(CACHE_DIR, "bun.lock")
|
||||
if (!fs.existsSync(lockPath)) return false
|
||||
|
||||
try {
|
||||
const content = fs.readFileSync(lockPath, "utf-8")
|
||||
const lock = JSON.parse(stripTrailingCommas(content)) as BunLockfile
|
||||
let modified = false
|
||||
|
||||
if (lock.workspaces?.[""]?.dependencies?.[packageName]) {
|
||||
delete lock.workspaces[""].dependencies[packageName]
|
||||
modified = true
|
||||
}
|
||||
|
||||
if (lock.packages?.[packageName]) {
|
||||
delete lock.packages[packageName]
|
||||
modified = true
|
||||
}
|
||||
|
||||
if (modified) {
|
||||
fs.writeFileSync(lockPath, JSON.stringify(lock, null, 2))
|
||||
log(`[auto-update-checker] Removed from bun.lock: ${packageName}`)
|
||||
}
|
||||
|
||||
return modified
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export function invalidatePackage(packageName: string = PACKAGE_NAME): boolean {
|
||||
try {
|
||||
const pkgDir = path.join(CACHE_DIR, "node_modules", packageName)
|
||||
@@ -10,6 +53,7 @@ export function invalidatePackage(packageName: string = PACKAGE_NAME): boolean {
|
||||
|
||||
let packageRemoved = false
|
||||
let dependencyRemoved = false
|
||||
let lockRemoved = false
|
||||
|
||||
if (fs.existsSync(pkgDir)) {
|
||||
fs.rmSync(pkgDir, { recursive: true, force: true })
|
||||
@@ -28,7 +72,9 @@ export function invalidatePackage(packageName: string = PACKAGE_NAME): boolean {
|
||||
}
|
||||
}
|
||||
|
||||
if (!packageRemoved && !dependencyRemoved) {
|
||||
lockRemoved = removeFromBunLock(packageName)
|
||||
|
||||
if (!packageRemoved && !dependencyRemoved && !lockRemoved) {
|
||||
log(`[auto-update-checker] Package not found, nothing to invalidate: ${packageName}`)
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -1,131 +0,0 @@
|
||||
import type { PluginInput } from "@opencode-ai/plugin"
|
||||
|
||||
const ANTHROPIC_ACTUAL_LIMIT = 200_000
|
||||
const CHARS_PER_TOKEN_ESTIMATE = 4
|
||||
const TARGET_MAX_TOKENS = 50_000
|
||||
|
||||
interface AssistantMessageInfo {
|
||||
role: "assistant"
|
||||
tokens: {
|
||||
input: number
|
||||
output: number
|
||||
reasoning: number
|
||||
cache: { read: number; write: number }
|
||||
}
|
||||
}
|
||||
|
||||
interface MessageWrapper {
|
||||
info: { role: string } & Partial<AssistantMessageInfo>
|
||||
}
|
||||
|
||||
function estimateTokens(text: string): number {
|
||||
return Math.ceil(text.length / CHARS_PER_TOKEN_ESTIMATE)
|
||||
}
|
||||
|
||||
function truncateToTokenLimit(output: string, maxTokens: number): { result: string; truncated: boolean } {
|
||||
const currentTokens = estimateTokens(output)
|
||||
|
||||
if (currentTokens <= maxTokens) {
|
||||
return { result: output, truncated: false }
|
||||
}
|
||||
|
||||
const lines = output.split("\n")
|
||||
|
||||
if (lines.length <= 3) {
|
||||
const maxChars = maxTokens * CHARS_PER_TOKEN_ESTIMATE
|
||||
return {
|
||||
result: output.slice(0, maxChars) + "\n\n[Output truncated due to context window limit]",
|
||||
truncated: true,
|
||||
}
|
||||
}
|
||||
|
||||
const headerLines = lines.slice(0, 3)
|
||||
const contentLines = lines.slice(3)
|
||||
|
||||
const headerText = headerLines.join("\n")
|
||||
const headerTokens = estimateTokens(headerText)
|
||||
const availableTokens = maxTokens - headerTokens - 50
|
||||
|
||||
if (availableTokens <= 0) {
|
||||
return {
|
||||
result: headerText + "\n\n[Content truncated due to context window limit]",
|
||||
truncated: true,
|
||||
}
|
||||
}
|
||||
|
||||
let resultLines: string[] = []
|
||||
let currentTokenCount = 0
|
||||
|
||||
for (const line of contentLines) {
|
||||
const lineTokens = estimateTokens(line + "\n")
|
||||
if (currentTokenCount + lineTokens > availableTokens) {
|
||||
break
|
||||
}
|
||||
resultLines.push(line)
|
||||
currentTokenCount += lineTokens
|
||||
}
|
||||
|
||||
const truncatedContent = [...headerLines, ...resultLines].join("\n")
|
||||
const removedCount = contentLines.length - resultLines.length
|
||||
|
||||
return {
|
||||
result: truncatedContent + `\n\n[${removedCount} more lines truncated due to context window limit]`,
|
||||
truncated: true,
|
||||
}
|
||||
}
|
||||
|
||||
export function createGrepOutputTruncatorHook(ctx: PluginInput) {
|
||||
const GREP_TOOLS = ["grep", "Grep", "safe_grep"]
|
||||
|
||||
const toolExecuteAfter = async (
|
||||
input: { tool: string; sessionID: string; callID: string },
|
||||
output: { title: string; output: string; metadata: unknown }
|
||||
) => {
|
||||
if (!GREP_TOOLS.includes(input.tool)) return
|
||||
|
||||
const { sessionID } = input
|
||||
|
||||
try {
|
||||
const response = await ctx.client.session.messages({
|
||||
path: { id: sessionID },
|
||||
})
|
||||
|
||||
const messages = (response.data ?? response) as MessageWrapper[]
|
||||
|
||||
const assistantMessages = messages
|
||||
.filter((m) => m.info.role === "assistant")
|
||||
.map((m) => m.info as AssistantMessageInfo)
|
||||
|
||||
if (assistantMessages.length === 0) return
|
||||
|
||||
// Use only the last assistant message's input tokens
|
||||
// This reflects the ACTUAL current context window usage (post-compaction)
|
||||
const lastAssistant = assistantMessages[assistantMessages.length - 1]
|
||||
const lastTokens = lastAssistant.tokens
|
||||
const totalInputTokens = (lastTokens?.input ?? 0) + (lastTokens?.cache?.read ?? 0)
|
||||
|
||||
const remainingTokens = ANTHROPIC_ACTUAL_LIMIT - totalInputTokens
|
||||
|
||||
const maxOutputTokens = Math.min(
|
||||
remainingTokens * 0.5,
|
||||
TARGET_MAX_TOKENS
|
||||
)
|
||||
|
||||
if (maxOutputTokens <= 0) {
|
||||
output.output = "[Output suppressed - context window exhausted]"
|
||||
return
|
||||
}
|
||||
|
||||
const { result, truncated } = truncateToTokenLimit(output.output, maxOutputTokens)
|
||||
if (truncated) {
|
||||
output.output = result
|
||||
}
|
||||
} catch {
|
||||
// Graceful degradation
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
"tool.execute.after": toolExecuteAfter,
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ export { createContextWindowMonitorHook } from "./context-window-monitor";
|
||||
export { createSessionNotification } from "./session-notification";
|
||||
export { createSessionRecoveryHook, type SessionRecoveryHook, type SessionRecoveryOptions } from "./session-recovery";
|
||||
export { createCommentCheckerHooks } from "./comment-checker";
|
||||
export { createGrepOutputTruncatorHook } from "./grep-output-truncator";
|
||||
export { createToolOutputTruncatorHook } from "./tool-output-truncator";
|
||||
export { createDirectoryAgentsInjectorHook } from "./directory-agents-injector";
|
||||
export { createDirectoryReadmeInjectorHook } from "./directory-readme-injector";
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import type { PluginInput } from "@opencode-ai/plugin"
|
||||
import { createDynamicTruncator } from "../shared/dynamic-truncator"
|
||||
|
||||
// Note: "grep" and "Grep" are handled by dedicated grep-output-truncator.ts
|
||||
const TRUNCATABLE_TOOLS = [
|
||||
"grep",
|
||||
"Grep",
|
||||
"safe_grep",
|
||||
"glob",
|
||||
"Glob",
|
||||
|
||||
@@ -109,6 +109,10 @@ export const BUILTIN_SERVERS: Record<string, Omit<LSPServerConfig, "id">> = {
|
||||
command: ["astro-ls", "--stdio"],
|
||||
extensions: [".astro"],
|
||||
},
|
||||
"bash-ls": {
|
||||
command: ["bash-language-server", "start"],
|
||||
extensions: [".sh", ".bash", ".zsh", ".ksh"],
|
||||
},
|
||||
jdtls: {
|
||||
command: ["jdtls"],
|
||||
extensions: [".java"],
|
||||
|
||||
Reference in New Issue
Block a user