Skip to content

Commit 685f17e

Browse files
0xMinkedelauna
andauthored
chore: re-enable the prefer-const ESLint rule (#250)
* chore: re-enable the prefer-const ESLint rule prefer-const was disabled in src/eslint.config.mjs under a "TODO: re-enable" comment. Re-enabled with { destructuring: "all" } so it only flags a destructuring declaration when every binding can be const. Most violations were auto-fixed (let -> const); five declare-then-assign cases were merged by hand into a single const declaration. No behavior change — tsc and the tests for the touched modules pass. * fix(McpHub): pass cancellationDisposable to cleanup() to avoid TDZ on sync cancellation When CancellationToken.onCancellationRequested() registers a listener on an already-cancelled token, VS Code fires the callback synchronously inside the registration call — before the `const cancellationDisposable = onCancellationRequested(...)` assignment completes. The old `let`- predeclared shape was no-op-safe via optional chaining, but the prefer-const consolidation in this PR put `cancellationDisposable` into the temporal dead zone for the duration of the sync callback, turning the already-cancelled path into a ReferenceError. Restructured cleanup() to receive the disposable as a parameter instead of capturing it from outer scope: - cleanup signature now takes `disposable?: vscode.Disposable` - non-callback call sites pass cancellationDisposable explicitly - the cancellation callback calls cleanup() with no arg; VS Code's CancellationToken cleans up its own listener after firing, so disposing from inside the listener was already redundant Keeps cancellationDisposable as a const, so prefer-const remains satisfied. Includes a regression test that fires the cancellation listener synchronously during registration and asserts the flow resolves cleanly. * fix(McpHub): register the oauth watcher before installing the cancellation callback The previous TDZ fix made cancellationDisposable safe under synchronous callback firing, but the _oauthWatchers.set(watcherKey, ...) call after the onCancellationRequested(...) registration is still racy. If the token is already cancelled at registration time and the callback fires synchronously, cleanup() runs (which tries to _oauthWatchers.delete the entry) before the entry has been added, leaving an orphan watcher in the map after the flow has otherwise torn itself down. Moved the watcher registration above the onCancellationRequested call so the entry exists in the map before the callback can fire. A sync-firing cleanup() now finds and deletes it cleanly. Extended the existing already-cancelled-token regression test to assert _oauthWatchers is empty after the flow resolves. * test: increasing coverage of touched files * fix(vitest): addressing test flake on teardown --------- Co-authored-by: 0xMink <260166390+0xMink@users.noreply.github.com> Co-authored-by: Elliott de Launay <edelauna@gmail.com> Co-authored-by: Elliott de Launay <edelaunay@wealthsimple.com>
1 parent 44e7bee commit 685f17e

56 files changed

Lines changed: 490 additions & 204 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.changeset/fix-multiline-quoted-command-parsing.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@ Quote masking is comment-aware: a quote character inside a `#` comment is not pa
1414

1515
**Pattern selector (UI)**: the command pattern breakdown shown after execution now uses the same heredoc- and quote-aware parser (`parseCommand`) before extracting patterns, so an unterminated or terminated heredoc no longer produces spurious tokens like `EOF`, body-line words, or `<<` fragments in the allow/deny selector.
1616

17-
Note: this change only prevents *auto-approval* of fragments from a malformed command; it does not reject malformed commands before execution, which will be addressed in a separate PR to keep the scope focused here.
17+
Note: this change only prevents _auto-approval_ of fragments from a malformed command; it does not reject malformed commands before execution, which will be addressed in a separate PR to keep the scope focused here.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ for this exact support, so if you are having problems or if you have question, j
8989
- [简体中文](locales/zh-CN/README.md)
9090
- [繁體中文](locales/zh-TW/README.md)
9191
- ...
92-
</details>
92+
</details>
9393

9494
---
9595

src/api/providers/__tests__/unbound.spec.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,4 +181,24 @@ describe("UnboundHandler", () => {
181181
}),
182182
)
183183
})
184+
185+
it("completePrompt returns the response text", async () => {
186+
const mockCreate = (OpenAI as unknown as any)().chat.completions.create
187+
mockCreate.mockResolvedValue({
188+
choices: [{ message: { content: "completed text" } }],
189+
})
190+
191+
const handler = new UnboundHandler({
192+
unboundApiKey: "test-key",
193+
unboundModelId: "openai/gpt-4o",
194+
})
195+
196+
const result = await handler.completePrompt("Write a haiku")
197+
expect(result).toBe("completed text")
198+
expect(mockCreate).toHaveBeenCalledWith(
199+
expect.objectContaining({
200+
messages: [{ role: "system", content: "Write a haiku" }],
201+
}),
202+
)
203+
})
184204
})

src/api/providers/anthropic-vertex.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ export class AnthropicVertexHandler extends BaseProvider implements SingleComple
7171
messages: Anthropic.Messages.MessageParam[],
7272
metadata?: ApiHandlerCreateMessageMetadata,
7373
): ApiStream {
74-
let { id, info, temperature, maxTokens, reasoning: thinking, betas } = this.getModel()
74+
const { id, info, temperature, maxTokens, reasoning: thinking, betas } = this.getModel()
7575

7676
const { supportsPromptCache } = info
7777

@@ -210,7 +210,7 @@ export class AnthropicVertexHandler extends BaseProvider implements SingleComple
210210

211211
getModel() {
212212
const modelId = this.options.apiModelId
213-
let id = modelId && modelId in vertexModels ? (modelId as VertexModelId) : vertexDefaultModelId
213+
const id = modelId && modelId in vertexModels ? (modelId as VertexModelId) : vertexDefaultModelId
214214
let info: ModelInfo = vertexModels[id]
215215

216216
// Check if 1M context beta should be enabled for supported models
@@ -270,7 +270,7 @@ export class AnthropicVertexHandler extends BaseProvider implements SingleComple
270270

271271
async completePrompt(prompt: string) {
272272
try {
273-
let {
273+
const {
274274
id,
275275
info: { supportsPromptCache },
276276
temperature,

src/api/providers/anthropic.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export class AnthropicHandler extends BaseProvider implements SingleCompletionHa
5454
): ApiStream {
5555
let stream: AnthropicStream<Anthropic.Messages.RawMessageStreamEvent>
5656
const cacheControl: CacheControlEphemeral = { type: "ephemeral" }
57-
let {
57+
const {
5858
id: modelId,
5959
betas = ["fine-grained-tool-streaming-2025-05-14"],
6060
maxTokens,
@@ -352,7 +352,7 @@ export class AnthropicHandler extends BaseProvider implements SingleCompletionHa
352352

353353
getModel() {
354354
const modelId = this.options.apiModelId
355-
let id = modelId && modelId in anthropicModels ? (modelId as AnthropicModelId) : anthropicDefaultModelId
355+
const id = modelId && modelId in anthropicModels ? (modelId as AnthropicModelId) : anthropicDefaultModelId
356356
let info: ModelInfo = anthropicModels[id]
357357

358358
// If 1M context beta is enabled for supported models, update the model info
@@ -398,7 +398,7 @@ export class AnthropicHandler extends BaseProvider implements SingleCompletionHa
398398
}
399399

400400
async completePrompt(prompt: string) {
401-
let { id: model, temperature } = this.getModel()
401+
const { id: model, temperature } = this.getModel()
402402

403403
let message
404404
try {

src/api/providers/bedrock.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ export class AwsBedrockHandler extends BaseProvider implements SingleCompletionH
216216
constructor(options: ProviderSettings) {
217217
super()
218218
this.options = options
219-
let region = this.options.awsRegion
219+
const region = this.options.awsRegion
220220

221221
// process the various user input options, be opinionated about the intent of the options
222222
// and determine the model to use during inference and for cost calculations
@@ -591,8 +591,11 @@ export class AwsBedrockHandler extends BaseProvider implements SingleCompletionH
591591
//so that pricing, context window, caching etc have values that can be used
592592
//However, we want to keep the id of the model to be the ID for the router for
593593
//subsequent requests so they are sent back through the router
594-
let invokedArnInfo = this.parseArn(streamEvent.trace.promptRouter.invokedModelId)
595-
let invokedModel = this.getModelById(invokedArnInfo.modelId as string, invokedArnInfo.modelType)
594+
const invokedArnInfo = this.parseArn(streamEvent.trace.promptRouter.invokedModelId)
595+
const invokedModel = this.getModelById(
596+
invokedArnInfo.modelId as string,
597+
invokedArnInfo.modelType,
598+
)
596599
if (invokedModel) {
597600
invokedModel.id = modelConfig.id
598601
this.costModelConfig = invokedModel
@@ -934,7 +937,7 @@ export class AwsBedrockHandler extends BaseProvider implements SingleCompletionH
934937
}
935938

936939
// Get cache point placements
937-
let strategy = new MultiPointStrategy(config)
940+
const strategy = new MultiPointStrategy(config)
938941
const cacheResult = strategy.determineOptimalCachePoints()
939942

940943
// Store cache point placements for future use if conversation ID is provided
@@ -998,7 +1001,7 @@ export class AwsBedrockHandler extends BaseProvider implements SingleCompletionH
9981001
*/
9991002

10001003
const arnRegex = /^arn:[^:]+:(?:bedrock|sagemaker):([^:]+):([^:]*):(?:([^\/]+)\/([\w\.\-:]+)|([^\/]+))$/
1001-
let match = arn.match(arnRegex)
1004+
const match = arn.match(arnRegex)
10021005

10031006
if (match && match[1] && match[3] && match[4]) {
10041007
// Create the result object
@@ -1025,7 +1028,7 @@ export class AwsBedrockHandler extends BaseProvider implements SingleCompletionH
10251028
// Check if the original model ID had a region prefix
10261029
if (originalModelId && result.modelId !== originalModelId) {
10271030
// If the model ID changed after parsing, it had a region prefix
1028-
let prefix = originalModelId.replace(result.modelId, "")
1031+
const prefix = originalModelId.replace(result.modelId, "")
10291032
result.crossRegionInference = AwsBedrockHandler.isSystemInferenceProfile(prefix)
10301033
}
10311034

src/api/providers/fake-ai.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ interface FakeAI {
3838
*
3939
* We use the ID to lookup the original FakeAI object in the mapping.
4040
*/
41-
let fakeAiMap: Map<string, FakeAI> = new Map()
41+
const fakeAiMap: Map<string, FakeAI> = new Map()
4242

4343
export class FakeAIHandler implements ApiHandler, SingleCompletionHandler {
4444
private ai: FakeAI

src/api/providers/fetchers/ollama.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ export async function getOllamaModels(
8181

8282
const response = await axios.get<OllamaModelsResponse>(`${baseUrl}/api/tags`, { headers })
8383
const parsedResponse = OllamaModelsResponseSchema.safeParse(response.data)
84-
let modelInfoPromises = []
84+
const modelInfoPromises = []
8585

8686
if (parsedResponse.success) {
8787
for (const ollamaModel of parsedResponse.data.models) {

src/api/providers/fetchers/opencode-go.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,9 @@ export async function getOpencodeGoModels(apiKey?: string): Promise<Record<strin
7272
const data = Array.isArray(rawData) ? rawData : []
7373

7474
if (!result.success) {
75-
console.warn(`Opencode Go models response did not match expected schema; falling back to per-item parsing: ${JSON.stringify(result.error.format())}`)
75+
console.warn(
76+
`Opencode Go models response did not match expected schema; falling back to per-item parsing: ${JSON.stringify(result.error.format())}`,
77+
)
7678
}
7779

7880
for (const rawModel of data) {

src/api/providers/gemini.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -678,7 +678,7 @@ export class GeminiHandler extends BaseProvider implements SingleCompletionHandl
678678
// Bill both completion and reasoning ("thoughts") tokens as output.
679679
const billedOutputTokens = outputTokens + reasoningTokens
680680

681-
let cacheReadCost = cacheReadTokens > 0 ? cacheReadsPrice * (cacheReadTokens / 1_000_000) : 0
681+
const cacheReadCost = cacheReadTokens > 0 ? cacheReadsPrice * (cacheReadTokens / 1_000_000) : 0
682682

683683
const inputTokensCost = inputPrice * (uncachedInputTokens / 1_000_000)
684684
const outputTokensCost = outputPrice * (billedOutputTokens / 1_000_000)

0 commit comments

Comments
 (0)