Skip to content

Commit 7449df1

Browse files
author
u5a-2359
committed
Merge main 18861b2 into 1321 head (folds 8 commits of drift; hooks live, no --no-verify per campaign rules)
2 parents 0637006 + 18861b2 commit 7449df1

84 files changed

Lines changed: 3922 additions & 553 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/code-qa.yml‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,11 @@ jobs:
247247
- name: Verify coverage cache inputs
248248
if: matrix.collect-coverage
249249
run: pnpm --dir src run verify:coverage-cache-inputs
250+
# Validate build/static cache inputs (issue #2290 contract) so a
251+
# stale webview or extension bundle is never restored as a hit.
252+
- name: Verify build cache inputs
253+
if: matrix.collect-coverage
254+
run: pnpm --dir src run verify:build-cache-inputs
250255
- name: Save Turbo cache
251256
if: steps.turbo-cache.outputs.cache-hit != 'true'
252257
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0

‎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)