bacluc-opencode-completion-check-command 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/src/completion-check-command.ts +75 -1
- package/src/index.ts +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bacluc-opencode-completion-check-command",
|
|
3
|
-
"version": "v0.
|
|
3
|
+
"version": "v0.7.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"opencode",
|
|
@@ -36,12 +36,12 @@
|
|
|
36
36
|
"format:check": "prettier --check ."
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@opencode-ai/plugin": "1.
|
|
39
|
+
"@opencode-ai/plugin": "1.17.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@types/bun": "1.3.14",
|
|
43
43
|
"@types/node": "25.9.2",
|
|
44
|
-
"prettier": "3.8.
|
|
44
|
+
"prettier": "3.8.4",
|
|
45
45
|
"typescript": "6.0.3",
|
|
46
46
|
"vitest": "4.1.8"
|
|
47
47
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Hooks, Plugin } from '@opencode-ai/plugin'
|
|
1
|
+
import type { Hooks, Plugin, PluginInput } from '@opencode-ai/plugin'
|
|
2
2
|
import type { Event } from '@opencode-ai/sdk'
|
|
3
3
|
import { promises as fs } from 'fs'
|
|
4
4
|
import { exec } from 'child_process'
|
|
@@ -123,6 +123,60 @@ export function buildFailureMessage(result: CommandResult): string {
|
|
|
123
123
|
return message
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
/**
|
|
127
|
+
* Detects whether an error attached to an assistant message means the provider's
|
|
128
|
+
* usage limit was used up (rate limit, quota or credits exhausted). When that
|
|
129
|
+
* happens the agent did not actually finish its task — it was cut off — so the
|
|
130
|
+
* completion check must not run and the agent must not be re-prompted (which
|
|
131
|
+
* would immediately hit the same limit again and burn the retry budget).
|
|
132
|
+
*/
|
|
133
|
+
export function isUsageLimitError(error: unknown): boolean {
|
|
134
|
+
if (!error || typeof error !== 'object') {
|
|
135
|
+
return false
|
|
136
|
+
}
|
|
137
|
+
const { name, data } = error as { name?: string; data?: Record<string, unknown> }
|
|
138
|
+
const statusCode = typeof data?.statusCode === 'number' ? data.statusCode : undefined
|
|
139
|
+
|
|
140
|
+
// HTTP 429 from the provider always means rate / usage limit reached.
|
|
141
|
+
if (name === 'APIError' && statusCode === 429) {
|
|
142
|
+
return true
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Otherwise fall back to matching the human-readable message / response body,
|
|
146
|
+
// which is how quota/credit exhaustion surfaces across providers.
|
|
147
|
+
const message = typeof data?.message === 'string' ? data.message : ''
|
|
148
|
+
const responseBody = typeof data?.responseBody === 'string' ? data.responseBody : ''
|
|
149
|
+
const haystack = `${message} ${responseBody}`.toLowerCase()
|
|
150
|
+
|
|
151
|
+
return /usage limit|rate limit|quota|too many requests|credit balance|out of credits|insufficient (?:credit|balance|funds|quota)/.test(
|
|
152
|
+
haystack,
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Returns true when the session's most recent assistant message ended with a
|
|
158
|
+
* usage-limit error, i.e. the model ran out of usage rather than finishing.
|
|
159
|
+
*/
|
|
160
|
+
export async function sessionHitUsageLimit(client: PluginInput['client'], sessionID: string): Promise<boolean> {
|
|
161
|
+
try {
|
|
162
|
+
const response = await client.session.messages({ path: { id: sessionID } })
|
|
163
|
+
const messages = response?.data
|
|
164
|
+
if (!Array.isArray(messages)) {
|
|
165
|
+
return false
|
|
166
|
+
}
|
|
167
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
168
|
+
const info = messages[i]?.info
|
|
169
|
+
if (info?.role === 'assistant') {
|
|
170
|
+
return isUsageLimitError(info.error)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return false
|
|
174
|
+
} catch {
|
|
175
|
+
// If we cannot determine the state, fall back to the normal behaviour.
|
|
176
|
+
return false
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
126
180
|
export async function readDefaultCommandFromAgentsMd(directory: string): Promise<string | null> {
|
|
127
181
|
try {
|
|
128
182
|
const content = await fs.readFile(`${directory}/AGENTS.md`, 'utf-8')
|
|
@@ -239,6 +293,26 @@ export const CompletionCheckCommandPlugin: Plugin = async (input, options) => {
|
|
|
239
293
|
processing.add(sessionID)
|
|
240
294
|
|
|
241
295
|
try {
|
|
296
|
+
if (await sessionHitUsageLimit(client, sessionID)) {
|
|
297
|
+
// The agent was cut off by the provider's usage limit rather than
|
|
298
|
+
// finishing. Skip the completion check (and the re-prompt) so we don't
|
|
299
|
+
// immediately hit the limit again. The command stays registered, so the
|
|
300
|
+
// check still runs once the session is able to continue.
|
|
301
|
+
try {
|
|
302
|
+
await client.tui.showToast({
|
|
303
|
+
body: {
|
|
304
|
+
title: 'Completion Check',
|
|
305
|
+
message: 'Skipped the completion check because the usage limit was reached.',
|
|
306
|
+
variant: 'warning',
|
|
307
|
+
duration: 10000,
|
|
308
|
+
},
|
|
309
|
+
})
|
|
310
|
+
} catch {
|
|
311
|
+
// Ignore feedback errors
|
|
312
|
+
}
|
|
313
|
+
return
|
|
314
|
+
}
|
|
315
|
+
|
|
242
316
|
const result = await executeCommand(command, input.directory)
|
|
243
317
|
|
|
244
318
|
if (result.exitCode === 0) {
|