Skip to content

Commit ec3e02e

Browse files
Merge main tip efc22ed into the first rung of the declared chain order
Main has already spoken on three paths this branch also rewrites, so on those three paths main's landed shape is kept and the branch's addition is re-expressed on top of it rather than the reverse: - src/core/task/observationRegistry.ts keeps main's retired flag, its close() and its early return in observe(), and gains the branch's forget(absolutePath): boolean between has() and clear(); the branch's copy had no retirement at all, so keeping the branch's file would have deleted landed behaviour. - src/core/task/__tests__/observationRegistry.spec.ts keeps main's closure and retirement rows and carries the branch's three forget rows as their own describe block, using the blank-line and TAB spelling the file already uses. - src/core/tools/__tests__/applyDiffTool.outsideWorkspace.spec.ts keeps main's expectations: the fs stat double rejects so the guarded publish runs unguarded-by-token, and the open double emulates the real re-read by assigning task.diffViewProvider.originalContent, which is what the branch's no-op open double was not doing. The branch's observationRegistry task double is kept because the merged tool reads and observes through that registry. The other 101 staged paths came from the merge itself with 0 edits by me, including src/utils/safeWriteJson.ts and its test, which a per-commit replay of this branch onto main stops on but a three-way merge against main does not, because main never touched either file. Gate of record at this footing: prettier --check over the tree clean, root check-types 12 of 12 tasks with 0 error TS lines, eslint over src with --prune-suppressions exit 0 and 0 problems, suppression counts 265 files and 2866 entries before and after, the prune writer's indentation rewrite left out of the commit, and the suites covering the three touched paths 16 plus 11 green.
2 parents 5115d62 + efc22ed commit ec3e02e

104 files changed

Lines changed: 3533 additions & 449 deletions

File tree

Some content is hidden

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

‎.github/workflows/visual-regression.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ permissions:
2121
jobs:
2222
webview-visual:
2323
runs-on: ubuntu-latest
24-
timeout-minutes: 10
24+
timeout-minutes: 15
2525
container:
2626
image: mcr.microsoft.com/playwright:v1.64.1-noble@sha256:47b510dd9230bf0b9bfc9b20102241e577ea7a99334ab4c3ca19f19770e9b8ea
2727
options: --ipc=host

‎packages/config-eslint/api-provider-hygiene.js‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,24 @@ export const apiProviderHygieneConfig = {
6363
},
6464
}
6565

66+
// Shared by every spec lane, not only src/api: an abort assertion must not pass on a raw SDK error.
67+
export const abortSubstringSelector = {
68+
selector:
69+
// Strings contain literal punctuation; only regex source can contain anchors, wildcards,
70+
// optional suffix groups, or word boundaries around the generic abort wording.
71+
"CallExpression[callee.property.name=/^toThrow(Error)?$/]:not([callee.object.property.name='not']):matches([arguments.0.value=/^(the )?(request )?(was )?abort(ed)?[.]*$/i], [arguments.0.regex.pattern=/^[$^]?(?:\\.\\*)?(the )?(request )?(was )?abort(ed)?(?:\\(\\?:(?:ed|ion|al)\\)\\??)?(?:\\\\b)?(?:\\.\\*)?[\\\\.$]*$/i])",
72+
message:
73+
"A bare 'abort' substring also matches a raw SDK error. Assert the AbortError name and the full provider message.",
74+
}
75+
6676
// Spec files under src/api. A loop of expects in one test stops at the first failing case and hides the rest.
6777
export const apiProviderTestSelectors = [
6878
{
6979
selector: "ForOfStatement[right.type='ArrayExpression']:has(CallExpression[callee.name='expect'])",
7080
message:
7181
"Use it.each for repeated cases. A loop of expects in one test stops at the first failing case and does not name it.",
7282
},
83+
abortSubstringSelector,
7384
]
7485

7586
// A new @ts-ignore in a provider spec hides a private-member access. Use bracket notation or a typed double.

‎packages/config-eslint/api-provider-hygiene.test.js‎

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import assert from "node:assert/strict"
22
import { describe, it } from "node:test"
3+
import { fileURLToPath, URL } from "node:url"
34

4-
import { Linter } from "eslint"
5+
import { ESLint, Linter } from "eslint"
56
import typescriptEslint from "typescript-eslint"
67

78
import { apiProviderHygieneConfig, apiProviderTestSelectors, apiSpecTsIgnoreConfig } from "./api-provider-hygiene.js"
@@ -173,6 +174,37 @@ describe("api provider hygiene: spec loops", () => {
173174
assert.deepEqual(lintSpec(code), [])
174175
})
175176
}
177+
178+
for (const code of [
179+
'await expect(p).rejects.toThrow("aborted")',
180+
"expect(() => f()).toThrow('abort')",
181+
'await expect(p).rejects.toThrowError("aborted")',
182+
"expect(() => f()).toThrow(/abort/i)",
183+
"await expect(p).rejects.toThrowError(/aborted/i)",
184+
'await expect(p).rejects.toThrow("was aborted")',
185+
'await expect(p).rejects.toThrow("Request was aborted.")',
186+
'await expect(p).rejects.toThrow("The request was aborted")',
187+
"await expect(p).rejects.toThrow(/was aborted/)",
188+
]) {
189+
it(`flags a bare abort substring: ${code}`, () => {
190+
assert.equal(lintSpec(code).length, 1)
191+
})
192+
}
193+
194+
for (const code of [
195+
'await expect(p).rejects.toThrow("The Requesty request was aborted")',
196+
'await expect(p).rejects.toMatchObject({ name: "AbortError" })',
197+
'await expect(p).rejects.toThrow("API Error")',
198+
'expect(() => f()).not.toThrow("aborted")',
199+
'await expect(p).rejects.not.toThrow("aborted")',
200+
"expect(() => f()).not.toThrowError(/abort/i)",
201+
"await expect(p).rejects.not.toThrowError(/aborted/i)",
202+
"await expect(p).rejects.toThrow(/^The Requesty request was aborted$/)",
203+
]) {
204+
it(`allows: ${code}`, () => {
205+
assert.deepEqual(lintSpec(code), [])
206+
})
207+
}
176208
})
177209

178210
describe("api provider hygiene: hand-built AbortError", () => {
@@ -245,3 +277,68 @@ describe("api provider hygiene: @ts-ignore in specs", () => {
245277
assert.deepEqual(lintSpecFile("// @ts-ignore x\nhandler.client", "services/__tests__/example.spec.ts"), [])
246278
})
247279
})
280+
281+
// Resolve each lane through the extension's real config before exercising its syntax rules.
282+
// Isolating syntax rules avoids requiring fixture files in the TypeScript project.
283+
describe("abort assertions in the extension ESLint config", () => {
284+
const srcRoot = fileURLToPath(new URL("../../src/", import.meta.url))
285+
const eslint = new ESLint({ cwd: srcRoot })
286+
287+
for (const filename of [
288+
"core/task/__tests__/example.spec.ts",
289+
"core/config/__tests__/example.spec.ts",
290+
// The workflow tests are exempt from the path-traversal selectors, not from the abort
291+
// rule; listing one proves the lane that re-applies it actually resolves for them.
292+
"services/__tests__/pr-review-state-workflow.test.ts",
293+
]) {
294+
it(`checks rejected and accepted assertions in ${filename}`, async () => {
295+
const resolved = await eslint.calculateConfigForFile(filename)
296+
const syntaxConfig = [
297+
{
298+
files: ["**/*.ts"],
299+
languageOptions: { parser: typescriptEslint.parser },
300+
rules: { "no-restricted-syntax": resolved.rules["no-restricted-syntax"] },
301+
},
302+
]
303+
for (const [code, expectedCount] of [
304+
['await expect(p).rejects.toThrow("aborted")', 1],
305+
["await expect(p).rejects.toThrowError(/abort/i)", 1],
306+
// An escaped dot in the source text is still a bare abort substring: the pattern
307+
// keeps a backslash, so the matcher must allow it before the trailing dot or anchor.
308+
["await expect(p).rejects.toThrow(/^aborted$/)", 1],
309+
["await expect(p).rejects.toThrow(/.*aborted.*/i)", 1],
310+
["await expect(p).rejects.toThrowError(/abort(?:ed)/i)", 1],
311+
["await expect(p).rejects.toThrow(/aborted\\b/i)", 1],
312+
['await expect(p).rejects.toThrow("abort$")', 0],
313+
['await expect(p).rejects.toThrow("^abort")', 0],
314+
["await expect(p).rejects.toThrow(/.*The Requesty request was aborted.*/i)", 0],
315+
["await expect(p).rejects.toThrow(/The Requesty request was aborted\\b/i)", 0],
316+
["await expect(p).rejects.toThrow(/^Request was aborted\\.$/)", 1],
317+
["await expect(p).rejects.toThrowError(/request aborted\\.$/i)", 1],
318+
["await expect(p).rejects.toThrow(/^request aborted\\$/)", 1],
319+
// An inline non-capturing group is regex source, not part of the asserted
320+
// message: an anchored match that stops short of it lets a bare abort through.
321+
["await expect(p).rejects.toThrow(/abort(?:ed)?/i)", 1],
322+
["await expect(p).rejects.toThrowError(/^request aborted(?:ed)?$/i)", 1],
323+
["await expect(p).rejects.toThrow(/cancel(?:ed)?/i)", 0],
324+
[
325+
"await expect(p).rejects.toThrow(/^The Requesty request was aborted(?:ed)? because the peer closed the stream\\.$/)",
326+
0,
327+
],
328+
['await expect(p).rejects.not.toThrow("aborted")', 0],
329+
["await expect(p).rejects.toThrow(/^Request was aborted because the peer closed the stream\\.$/)", 0],
330+
[
331+
'await expect(p).rejects.toMatchObject({ name: "AbortError", message: "The Requesty request was aborted" })',
332+
0,
333+
],
334+
]) {
335+
const messages = linter.verify(code, syntaxConfig, { filename })
336+
assert.equal(messages.length, expectedCount, code)
337+
if (expectedCount) {
338+
assert.equal(messages[0].ruleId, "no-restricted-syntax")
339+
assert.match(messages[0].message, /bare 'abort' substring/)
340+
}
341+
}
342+
})
343+
}
344+
})
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { experimentIds, experimentIdsSchema, experimentsSchema } from "../experiment.js"
2+
3+
describe("dynamicThinkingEffort experiment", () => {
4+
it("is part of the experiment id enum", () => {
5+
expect(experimentIds).toContain("dynamicThinkingEffort")
6+
expect(experimentIdsSchema.safeParse("dynamicThinkingEffort").success).toBe(true)
7+
})
8+
9+
it("parses enabled and disabled states", () => {
10+
expect(experimentsSchema.parse({ dynamicThinkingEffort: true })).toEqual({ dynamicThinkingEffort: true })
11+
expect(experimentsSchema.parse({ dynamicThinkingEffort: false })).toEqual({ dynamicThinkingEffort: false })
12+
expect(experimentsSchema.parse({})).toEqual({})
13+
})
14+
15+
it("rejects non-boolean values", () => {
16+
expect(experimentsSchema.safeParse({ dynamicThinkingEffort: "yes" }).success).toBe(false)
17+
expect(experimentIdsSchema.safeParse("dynamic-thinking-effort").success).toBe(false)
18+
})
19+
})

‎packages/types/src/__tests__/openai-models.test.ts‎

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,71 @@ import { openAiCodexDefaultModelId, openAiCodexModels } from "../providers/opena
22
import { openAiNativeDefaultModelId, openAiNativeModels } from "../providers/openai.js"
33

44
describe("OpenAI native models", () => {
5+
it("describes GPT-6.1 Terra pricing and optional native reasoning without changing defaults", () => {
6+
expect(openAiNativeDefaultModelId).toBe("gpt-5.6-sol")
7+
expect(openAiCodexDefaultModelId).toBe("gpt-5.6-sol")
8+
const model = openAiNativeModels["gpt-6.1-terra"]
9+
expect(model).toMatchObject({
10+
maxTokens: 128_000,
11+
contextWindow: 1_050_000,
12+
includedTools: ["apply_patch"],
13+
excludedTools: ["apply_diff", "write_to_file"],
14+
supportsImages: true,
15+
supportsPromptCache: true,
16+
supportsReasoningEffort: ["none", "low", "medium", "high", "xhigh", "max"],
17+
reasoningEffort: "medium",
18+
supportsTemperature: false,
19+
supportsVerbosity: true,
20+
inputPrice: 1,
21+
outputPrice: 5,
22+
cacheWritesPrice: 1.25,
23+
cacheWritesPrice12h: 2.5,
24+
cacheReadsPrice: 0.05,
25+
longContextPricing: {
26+
thresholdTokens: 272_000,
27+
inputPriceMultiplier: 2,
28+
outputPriceMultiplier: 1.5,
29+
cacheWritesPriceMultiplier: 2,
30+
cacheReadsPriceMultiplier: 2,
31+
appliesToServiceTiers: ["default", "flex", "priority"],
32+
},
33+
})
34+
expect(model).not.toHaveProperty("requiredReasoningEffort", true)
35+
expect(model.tiers).toEqual([
36+
{
37+
name: "flex",
38+
contextWindow: 1_050_000,
39+
inputPrice: 0.5,
40+
outputPrice: 2.5,
41+
cacheWritesPrice: 0.625,
42+
cacheReadsPrice: 0.025,
43+
},
44+
{
45+
name: "priority",
46+
contextWindow: 1_050_000,
47+
inputPrice: 2,
48+
outputPrice: 10,
49+
cacheWritesPrice: 2.5,
50+
cacheReadsPrice: 0.1,
51+
},
52+
])
53+
expect(openAiCodexModels["gpt-6.1-terra"]).toMatchObject({
54+
maxTokens: 128_000,
55+
contextWindow: 872_000,
56+
includedTools: ["apply_patch"],
57+
excludedTools: ["apply_diff", "write_to_file"],
58+
supportsImages: true,
59+
supportsPromptCache: true,
60+
supportsReasoningEffort: ["low", "medium", "high", "xhigh", "max"],
61+
requiredReasoningEffort: true,
62+
reasoningEffort: "low",
63+
supportsTemperature: false,
64+
supportsVerbosity: true,
65+
inputPrice: 0,
66+
outputPrice: 0,
67+
})
68+
})
69+
570
it("describes GPT-6.1 Sol pricing and required reasoning without changing defaults", () => {
671
expect(openAiNativeDefaultModelId).toBe("gpt-5.6-sol")
772
expect(openAiCodexDefaultModelId).toBe("gpt-5.6-sol")

‎packages/types/src/__tests__/provider-identifiers.test.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ describe("provider identifiers", () => {
114114
providerIdentifiers.nanogpt,
115115
providerIdentifiers.ioIntelligence,
116116
providerIdentifiers.kimiCode,
117+
providerIdentifiers.friendli,
117118
])
118119
expect(localProviders).toEqual([providerIdentifiers.ollama, providerIdentifiers.lmstudio])
119120
expect(internalProviders).toEqual([providerIdentifiers.vscodeLm, providerIdentifiers.githubCopilot])

‎packages/types/src/experiment.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export const experimentIds = [
1212
"runSlashCommand",
1313
"customTools",
1414
"parallelToolExecution",
15+
"dynamicThinkingEffort",
1516
] as const
1617

1718
export const experimentIdsSchema = z.enum(experimentIds)
@@ -28,6 +29,7 @@ export const experimentsSchema = z.object({
2829
runSlashCommand: z.boolean().optional(),
2930
customTools: z.boolean().optional(),
3031
parallelToolExecution: z.boolean().optional(),
32+
dynamicThinkingEffort: z.boolean().optional(),
3133
})
3234

3335
export type Experiments = z.infer<typeof experimentsSchema>

‎packages/types/src/provider-settings.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ export const dynamicProviders = [
102102
providerIdentifiers.nanogpt,
103103
providerIdentifiers.ioIntelligence,
104104
providerIdentifiers.kimiCode,
105+
providerIdentifiers.friendli,
105106
] as const
106107

107108
export type DynamicProvider = (typeof dynamicProviders)[number]

‎packages/types/src/providers/openai-codex.ts‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,25 @@ export const openAiCodexDefaultModelId: OpenAiCodexModelId = "gpt-5.6-sol"
3131
* Costs are 0 as they are covered by the subscription.
3232
*/
3333
export const openAiCodexModels = {
34+
// https://github.com/openai/codex/blob/main/codex-rs/models-manager/models.json
35+
"gpt-6.1-terra": {
36+
maxTokens: 128000,
37+
// Use Codex's supported maximum rather than its 500K default compaction budget.
38+
contextWindow: 872000,
39+
includedTools: ["apply_patch"],
40+
excludedTools: ["apply_diff", "write_to_file"],
41+
supportsImages: true,
42+
supportsPromptCache: true,
43+
supportsReasoningEffort: ["low", "medium", "high", "xhigh", "max"],
44+
requiredReasoningEffort: true,
45+
reasoningEffort: "low",
46+
inputPrice: 0,
47+
outputPrice: 0,
48+
supportsVerbosity: true,
49+
supportsTemperature: false,
50+
description:
51+
"GPT-6.1 Terra: Fast, efficient visual reasoning, precise computer use, and natural writing via ChatGPT subscription",
52+
},
3453
// https://github.com/openai/codex/blob/main/codex-rs/models-manager/models.json
3554
"gpt-6.1-sol": {
3655
maxTokens: 128000,

‎packages/types/src/providers/openai.ts‎

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,51 @@ export const OPENAI_API_PROTOCOL = "openai"
2424
export const openAiNativeDefaultModelId: OpenAiNativeModelId = "gpt-5.6-sol"
2525

2626
export const openAiNativeModels = {
27+
// https://developers.openai.com/api/docs/models/gpt-6.1-terra
28+
"gpt-6.1-terra": {
29+
maxTokens: 128000,
30+
contextWindow: 1_050_000,
31+
includedTools: ["apply_patch"],
32+
excludedTools: ["apply_diff", "write_to_file"],
33+
supportsImages: true,
34+
supportsPromptCache: true,
35+
supportsReasoningEffort: ["none", "low", "medium", "high", "xhigh", "max"],
36+
reasoningEffort: "medium",
37+
inputPrice: 1,
38+
outputPrice: 5,
39+
cacheWritesPrice: 1.25,
40+
cacheWritesPrice12h: 2.5,
41+
cacheReadsPrice: 0.05,
42+
longContextPricing: {
43+
thresholdTokens: 272_000,
44+
inputPriceMultiplier: 2,
45+
outputPriceMultiplier: 1.5,
46+
cacheWritesPriceMultiplier: 2,
47+
cacheReadsPriceMultiplier: 2,
48+
appliesToServiceTiers: ["default", "flex", "priority"],
49+
},
50+
supportsVerbosity: true,
51+
supportsTemperature: false,
52+
tiers: [
53+
{
54+
name: "flex",
55+
contextWindow: 1_050_000,
56+
inputPrice: 0.5,
57+
outputPrice: 2.5,
58+
cacheWritesPrice: 0.625,
59+
cacheReadsPrice: 0.025,
60+
},
61+
{
62+
name: "priority",
63+
contextWindow: 1_050_000,
64+
inputPrice: 2,
65+
outputPrice: 10,
66+
cacheWritesPrice: 2.5,
67+
cacheReadsPrice: 0.1,
68+
},
69+
],
70+
description: "GPT-6.1 Terra: Fast, efficient visual reasoning, precise computer use, and natural writing",
71+
},
2772
// https://developers.openai.com/api/docs/models/gpt-6.1-sol
2873
"gpt-6.1-sol": {
2974
maxTokens: 128000,

0 commit comments

Comments
 (0)