правильная организация директории плагина
This commit is contained in:
parent
26e28361cc
commit
5fdb2510b4
|
|
@ -21,7 +21,6 @@ function isV2Context(ctx: any): boolean {
|
|||
async function runPlugin(ctx: any, options: any, sdkClient?: any) {
|
||||
const isV2 = isV2Context(ctx)
|
||||
const eventController = new AbortController()
|
||||
let expiryInterval: ReturnType<typeof setInterval> | undefined
|
||||
let cleanup: (() => void) | undefined
|
||||
|
||||
try {
|
||||
|
|
@ -39,6 +38,58 @@ async function runPlugin(ctx: any, options: any, sdkClient?: any) {
|
|||
const matrix = new MatrixBotClient(options)
|
||||
const sessionManager = new SessionManager(options.rateLimitSeconds || 5)
|
||||
|
||||
// Track pending tool calls per session: sessionID -> Map<callID, {roomId, threadRootId, context}>
|
||||
const pendingToolCalls = new Map<string, Map<string, { roomId: string; threadRootId: string; context: any }>>()
|
||||
|
||||
// Track sent messages per session to avoid duplicates
|
||||
const sentMessageIds = new Map<string, Set<string>>()
|
||||
|
||||
// Track Matrix context per OpenCode session
|
||||
const sessionContextMap = new Map<string, { roomId: string; threadRootId: string; context: any }>()
|
||||
|
||||
// Helper: send a message to Matrix
|
||||
async function sendMatrixMessage(roomId: string, threadRootId: string, context: any, text: string): Promise<void> {
|
||||
if (!text.trim()) return
|
||||
await matrix.sendReply(context, text)
|
||||
log(`Matrix: Sent to ${roomId}:${threadRootId} (${text.length} chars)`)
|
||||
}
|
||||
|
||||
// Helper: extract content from parts
|
||||
function extractContent(parts: any[]): { reasoningText: string; mainText: string; toolCallText: string } {
|
||||
let reasoningText = ""
|
||||
let mainText = ""
|
||||
let toolCallText = ""
|
||||
const reasoningTypes = new Set(["reasoning", "thinking", "redwood", "reason"])
|
||||
|
||||
for (const p of parts) {
|
||||
const content = p?.content || p?.text || ""
|
||||
const pType = p?.type || ""
|
||||
if (reasoningTypes.has(pType)) {
|
||||
reasoningText += content
|
||||
} else if (pType === "tool_call" || pType === "tool") {
|
||||
toolCallText += content
|
||||
} else {
|
||||
mainText += content
|
||||
}
|
||||
}
|
||||
|
||||
return { reasoningText, mainText, toolCallText }
|
||||
}
|
||||
|
||||
// Helper: format message text
|
||||
function formatMessage(toolCallText: string, reasoningText: string, mainText: string): string {
|
||||
if (toolCallText.trim()) {
|
||||
return `Tool call: ${toolCallText.trim()}`
|
||||
}
|
||||
|
||||
let responseText = mainText
|
||||
if (reasoningText.trim()) {
|
||||
responseText = `<details><summary>Thoughts</summary>\n\n${reasoningText.trim()}\n\n</details>\n\n---\n\n${mainText}`
|
||||
}
|
||||
|
||||
return responseText
|
||||
}
|
||||
|
||||
// V2 Event subscription for response streaming
|
||||
if (isV2) {
|
||||
void (async () => {
|
||||
|
|
@ -50,6 +101,7 @@ async function runPlugin(ctx: any, options: any, sdkClient?: any) {
|
|||
if (!sessionId) continue
|
||||
const update = (event as { update?: { type: string; content?: string; toolName?: string } }).update
|
||||
if (!update) continue
|
||||
log(`V2 event: session=${sessionId}, update.type=${update.type}`)
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
|
|
@ -62,30 +114,6 @@ async function runPlugin(ctx: any, options: any, sdkClient?: any) {
|
|||
})()
|
||||
}
|
||||
|
||||
// V1 Event subscription for response streaming
|
||||
if (!isV2 && sdkClient) {
|
||||
void (async () => {
|
||||
try {
|
||||
const events = await sdkClient.event.subscribe()
|
||||
for await (const event of events.stream) {
|
||||
const eventType = event.type || ""
|
||||
if (eventType === "session.updated") {
|
||||
const sessionId = event.properties?.sessionID
|
||||
if (!sessionId) continue
|
||||
const update = event.properties?.update
|
||||
if (!update) continue
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
if (String(err).includes("AbortError") || (err as { name?: string }).name === "AbortError") {
|
||||
// expected during cleanup
|
||||
} else {
|
||||
log(`V1 event subscription error: ${String(err)}`)
|
||||
}
|
||||
}
|
||||
})()
|
||||
}
|
||||
|
||||
// Message handler
|
||||
matrix.on(async (data: any) => {
|
||||
const { context, query, sender, roomId, eventId, timestamp } = data
|
||||
|
|
@ -133,6 +161,7 @@ async function runPlugin(ctx: any, options: any, sdkClient?: any) {
|
|||
}
|
||||
session.opencodeSessionId = opencodeSessionId
|
||||
sessionManager.saveSession(roomId, threadRootId, opencodeSessionId)
|
||||
sessionContextMap.set(opencodeSessionId, { roomId, threadRootId, context })
|
||||
} catch (createErr: any) {
|
||||
log(`Failed to create session: ${String(createErr)}`)
|
||||
session.isActive = false
|
||||
|
|
@ -157,66 +186,87 @@ async function runPlugin(ctx: any, options: any, sdkClient?: any) {
|
|||
// V1: use SDK client.session.prompt()
|
||||
log(`V1: Sending prompt to session ${opencodeSessionId}: ${query}`)
|
||||
|
||||
// Start polling for new messages
|
||||
const pollInterval = setInterval(async () => {
|
||||
if (!session.isActive) {
|
||||
clearInterval(pollInterval)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const existingIds = sentMessageIds.get(opencodeSessionId) || new Set()
|
||||
const messagesResult = await sdkClient.session.messages({
|
||||
path: { id: opencodeSessionId },
|
||||
})
|
||||
|
||||
const messages = messagesResult?.data || []
|
||||
if (Array.isArray(messages)) {
|
||||
for (const msg of messages) {
|
||||
const msgId = msg?.info?.id || msg?.id
|
||||
if (!msgId || existingIds.has(msgId)) continue
|
||||
|
||||
const role = msg?.info?.role || ""
|
||||
if (role !== "assistant" && role !== "tool_call") continue
|
||||
|
||||
const parts = msg?.parts || msg?.info?.parts || []
|
||||
const { reasoningText, mainText, toolCallText } = extractContent(parts)
|
||||
const responseText = formatMessage(toolCallText, reasoningText, mainText)
|
||||
|
||||
if (responseText.trim()) {
|
||||
await matrix.sendReply(context, responseText)
|
||||
log(`V1: Sent message ${msgId} (role=${role}, ${responseText.length} chars)`)
|
||||
existingIds.add(msgId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sentMessageIds.set(opencodeSessionId, existingIds)
|
||||
} catch (err) {
|
||||
log(`V1: Error during polling: ${String(err)}`)
|
||||
}
|
||||
}, 1000)
|
||||
|
||||
try {
|
||||
const result = await sdkClient.session.prompt({
|
||||
path: { id: opencodeSessionId },
|
||||
body: { parts: [{ type: "text", text: query }] },
|
||||
})
|
||||
log(`V1: prompt result keys=${Object.keys(result || {})}`)
|
||||
log(`V1: prompt result.data.keys=${result?.data ? Object.keys(result.data).join(",") : "null"}`)
|
||||
if (result?.data?.parts) {
|
||||
log(`V1: data.parts count=${result.data.parts.length}`)
|
||||
log(`V1: data.parts.types=${result.data.parts.map((p: any) => p.type).join(",")}`)
|
||||
log(`V1: data.parts[0]=${JSON.stringify(result.data.parts[0]).slice(0, 200)}`)
|
||||
}
|
||||
|
||||
// Try to extract content from various locations, separating reasoning from main text
|
||||
let reasoningText = ""
|
||||
let mainText = ""
|
||||
const reasoningTypes = new Set(["reasoning", "thinking", "redwood", "reason"])
|
||||
if (result?.data?.parts) {
|
||||
for (const p of result.data.parts) {
|
||||
const content = p.content || p.text || ""
|
||||
if (reasoningTypes.has(p.type)) {
|
||||
reasoningText += content
|
||||
} else {
|
||||
mainText += content
|
||||
}
|
||||
}
|
||||
} else if (result?.data?.info?.parts) {
|
||||
for (const p of result.data.info.parts) {
|
||||
const content = p.content || p.text || ""
|
||||
if (reasoningTypes.has(p.type)) {
|
||||
reasoningText += content
|
||||
} else {
|
||||
mainText += content
|
||||
}
|
||||
}
|
||||
} else if (result?.data?.message?.parts) {
|
||||
for (const p of result.data.message.parts) {
|
||||
const content = p.content || p.text || ""
|
||||
if (reasoningTypes.has(p.type)) {
|
||||
reasoningText += content
|
||||
} else {
|
||||
mainText += content
|
||||
}
|
||||
}
|
||||
} else if (result?.data?.content) {
|
||||
mainText = result.data.content
|
||||
} else if (result?.response?.content) {
|
||||
mainText = result.response.content
|
||||
} else if (result?.content) {
|
||||
mainText = result.content
|
||||
}
|
||||
// Stop polling
|
||||
clearInterval(pollInterval)
|
||||
|
||||
let responseText = mainText
|
||||
if (reasoningText.trim()) {
|
||||
responseText = `<details><summary>Thoughts</summary>\n\n${reasoningText.trim()}\n\n</details>\n\n---\n\n${mainText}`
|
||||
}
|
||||
// Send any remaining messages
|
||||
try {
|
||||
const existingIds = sentMessageIds.get(opencodeSessionId) || new Set()
|
||||
const messagesResult = await sdkClient.session.messages({
|
||||
path: { id: opencodeSessionId },
|
||||
})
|
||||
|
||||
session.outputChars += responseText.length
|
||||
const messages = messagesResult?.data || []
|
||||
if (Array.isArray(messages)) {
|
||||
for (const msg of messages) {
|
||||
const msgId = msg?.info?.id || msg?.id
|
||||
if (!msgId || existingIds.has(msgId)) continue
|
||||
|
||||
const role = msg?.info?.role || ""
|
||||
if (role !== "assistant" && role !== "tool_call") continue
|
||||
|
||||
const parts = msg?.parts || msg?.info?.parts || []
|
||||
const { reasoningText, mainText, toolCallText } = extractContent(parts)
|
||||
const responseText = formatMessage(toolCallText, reasoningText, mainText)
|
||||
|
||||
if (responseText.trim()) {
|
||||
await matrix.sendReply(context, responseText)
|
||||
log(`V1: Response sent (${responseText.length} chars)`)
|
||||
log(`V1: Sent message ${msgId} (role=${role}, ${responseText.length} chars)`)
|
||||
existingIds.add(msgId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sentMessageIds.set(opencodeSessionId, existingIds)
|
||||
} catch (err) {
|
||||
log(`V1: Error sending final messages: ${String(err)}`)
|
||||
}
|
||||
} catch (promptErr: any) {
|
||||
log(`V1 prompt error: ${String(promptErr)}`)
|
||||
const errMsg = promptErr?.response?.error?.message || promptErr?.error?.message || String(promptErr)
|
||||
|
|
@ -236,6 +286,7 @@ async function runPlugin(ctx: any, options: any, sdkClient?: any) {
|
|||
} finally {
|
||||
session.isActive = false
|
||||
session.lastActivity = Date.now()
|
||||
sessionContextMap.delete(opencodeSessionId)
|
||||
releaseQuery()
|
||||
}
|
||||
})
|
||||
|
|
@ -317,7 +368,7 @@ async function runPlugin(ctx: any, options: any, sdkClient?: any) {
|
|||
}
|
||||
})
|
||||
|
||||
// Start Matrix client
|
||||
// V2 Storage - defensive access
|
||||
if (isV2 && ctx?.storage) {
|
||||
await ctx.storage.set("matrix/plugin_version", "1.0.0")
|
||||
await ctx.storage.set("matrix/started_at", new Date().toISOString())
|
||||
Loading…
Reference in New Issue