From 7c0d928bbe4dc614816d5b95f4f64d6c5ef58ad1 Mon Sep 17 00:00:00 2001 From: Agney Date: Mon, 29 Dec 2025 17:43:32 +0530 Subject: [PATCH] added queue priority support --- package-lock.json | 85 +++++++++++++++++++++++++++++++++++++++++++++++ src/index.ts | 64 ++++++++++++++++++++++++++++++++--- 2 files changed, 145 insertions(+), 4 deletions(-) create mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..8118028 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,85 @@ +{ + "name": "@0xsero/open-queue", + "version": "1.0.11", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@0xsero/open-queue", + "version": "1.0.11", + "license": "MIT", + "bin": { + "open-queue": "bin/install.js" + }, + "devDependencies": { + "@opencode-ai/plugin": "^1.0.150", + "@opencode-ai/sdk": "^1.0.150", + "@types/node": "^20.14.10", + "typescript": "^5.9.3" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@opencode-ai/plugin": ">=1.0.0" + } + }, + "node_modules/@opencode-ai/plugin": { + "version": "1.0.207", + "resolved": "https://registry.npmjs.org/@opencode-ai/plugin/-/plugin-1.0.207.tgz", + "integrity": "sha512-UOjZ09lVwWv0MRHUcluLm3eeYC7rmQyuSRfaaK6RLKn1bdsypp8lnuw9HZ2nrR5kmkiF12+9yMM4bVgER6lTBw==", + "dev": true, + "dependencies": { + "@opencode-ai/sdk": "1.0.207", + "zod": "4.1.8" + } + }, + "node_modules/@opencode-ai/sdk": { + "version": "1.0.207", + "resolved": "https://registry.npmjs.org/@opencode-ai/sdk/-/sdk-1.0.207.tgz", + "integrity": "sha512-/t4C3+PXZ5NpN7+HwKZmVwJsJmQ1eyub0eQcV8GZh7fuPo2wCxKHCD/lzk82CobW+3jVp2TSBKeDBBTtVvEFeQ==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.19.27", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.27.tgz", + "integrity": "sha512-N2clP5pJhB2YnZJ3PIHFk5RkygRX5WO/5f0WC08tp0wd+sv0rsJk3MqWn3CbNmT2J505a5336jaQj4ph1AdMug==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/zod": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.8.tgz", + "integrity": "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/src/index.ts b/src/index.ts index cfbf2bf..db377b9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,11 +6,15 @@ import type { SubtaskPartInput, TextPartInput, } from "@opencode-ai/sdk" +import fs from "fs" + type ModelRef = { providerID: string; modelID: string } type PromptPart = TextPartInput | FilePartInput | AgentPartInput | SubtaskPartInput +type QueuePriority = "low" | "normal" | "high" | any + type QueuedMessage = { sessionID: string agent?: string @@ -19,7 +23,10 @@ type QueuedMessage = { tools?: Record parts: PromptPart[] preview: string + priority: QueuePriority + enqueuedAt: number status: "queued" | "sending" | "sent" + } type QueueMode = "immediate" | "hold" @@ -50,6 +57,32 @@ const EMPTY_TOAST_DURATION_MS = (() => { })() const INTERNAL_METADATA_KEY = "__open_queue_internal" +const PRIORITY_REGEX = /^\s*\[\s*priority\s*:\s*(low|normal|high)\s*\]\s*/i + +function extractPriority( + parts: PromptPart[], +): { priority: QueuePriority; parts: PromptPart[] } { + const firstText = parts.find((p) => p.type === "text") + if (!firstText || firstText.type !== "text") { + return { priority: "normal", parts } + } + + const match = firstText.text.match(PRIORITY_REGEX) + if (!match) { + return { priority: "normal", parts } + } + + const priority = match[1].toLowerCase() as QueuePriority + + const cleanedText = firstText.text.replace(PRIORITY_REGEX, "") + + const cleanedParts = parts.map((p) => + p === firstText ? { ...p, text: cleanedText } : p, + ) + + return { priority, parts: cleanedParts } +} + function toPromptPart(part: Part): PromptPart | null { switch (part.type) { case "text": @@ -120,11 +153,24 @@ function getPendingCount(queue: QueuedMessage[]) { return queue.filter((item) => item.status !== "sent").length } +function priorityRank(priority: QueuePriority): number { + switch (priority) { + case "high": + return 2 + case "normal": + return 1 + case "low": + return 0 + default: + return 0 + } +} + function buildToastMessage(queue: QueuedMessage[]) { const pendingCount = getPendingCount(queue) const previewCount = Math.min(queue.length, TOAST_MAX_PREVIEWS) const previews = queue.slice(0, previewCount).map((item, index) => { - const text = buildPreview(item) + const text = `[${item.priority}] ${buildPreview(item)}` if (item.status === "sent") return ` ${index + 1}. [x] ~~${text}~~` if (item.status === "sending") return ` ${index + 1}. [>] ${text}` return ` ${index + 1}. [ ] ${text}` @@ -222,7 +268,13 @@ export const MessageQueuePlugin: Plugin = async ({ client }) => { try { let showedEmptyToast = false while (true) { - const next = queue.find((item) => item.status === "queued") + const next = queue + .filter((item) => item.status === "queued") + .sort( + (a, b) => + priorityRank(b.priority) - priorityRank(a.priority) || + a.enqueuedAt - b.enqueuedAt, + )[0] if (!next) break next.status = "sending" @@ -349,11 +401,13 @@ export const MessageQueuePlugin: Plugin = async ({ client }) => { const existingQueue = queueBySession.get(input.sessionID) const pendingCount = existingQueue ? getPendingCount(existingQueue) : 0 const busy = busyBySession.get(input.sessionID) ?? false - const shouldQueue = busy || draining.has(input.sessionID) || pendingCount > 0 + const shouldQueue = busy || draining.has(input.sessionID) || pendingCount >= 0//Set to >= to ensure first message is also included in the queue toast if (!shouldQueue) return const originalParts = [...output.parts] const queuedParts = originalParts.map(toPromptPart).filter((part): part is PromptPart => part !== null) + + const { priority, parts: cleanedParts } = extractPriority(queuedParts) const preview = extractPreview(queuedParts) enqueue(input.sessionID, { @@ -362,7 +416,9 @@ export const MessageQueuePlugin: Plugin = async ({ client }) => { model: input.model ?? output.message.model, system: output.message.system, tools: output.message.tools, - parts: queuedParts, + parts: cleanedParts, + priority, + enqueuedAt: Date.now(), preview, status: "queued", })