Skip to content

Commit fe74932

Browse files
Merge branch 'main' into feat/abort-r1-openai-codex
2 parents 13e40f6 + 9af61f8 commit fe74932

43 files changed

Lines changed: 480 additions & 163 deletions

Some content is hidden

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

‎docs/architecture/task-lifecycle-run-state-kernel-tickets.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ Shadow mode must report disagreement. Before any read flips, the kernel must agr
316316
- [ ] Gate the comparison as described in the Rollout gate section. Do not gate the kernel.
317317
- [ ] Do not throw on divergence in production. Log only.
318318

319-
Exit criteria: a forced divergence in a test produces one log line and one metric increment. A forced R-3 window (an abort during the `checkAutoApproval` await, before RSK-20) produces a tagged mismatch, not an untagged one.
319+
Exit criteria: a forced divergence in a test produces one log line and one metric increment. A forced divergence on a known race that still has an open fix ticket produces a tagged mismatch, not an untagged one.
320320

321321
Reversibility: additive observability.
322322

‎docs/architecture/task-lifecycle-target-architecture.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ The bugs come from the runtime layer and from state that escapes the checked red
6161

6262
### Run state is a set of loose booleans
6363

64-
`Task` models mutually exclusive run phases with independent flags. Examples: `abort`, `didFinishAbortingStream`, `isInitialized`, `isPaused`, `isWaitingForFirstChunk`, `isStreaming`, `didRejectTool`, `didAlreadyUseTool`, `didToolFailInCurrentTurn`, `didCompleteReadingStream`. Dispatch re-entrancy uses `presentAssistantMessageLocked` and `presentAssistantMessageHasPendingUpdates`.
64+
`Task` models mutually exclusive run phases with independent flags. Examples: `abort`, `didFinishAbortingStream`, `isInitialized`, `isWaitingForFirstChunk`, `isStreaming`, `didRejectTool`, `didAlreadyUseTool`, `didToolFailInCurrentTurn`, `didCompleteReadingStream`. Dispatch re-entrancy uses `presentAssistantMessageLocked` and `presentAssistantMessageHasPendingUpdates`.
6565

6666
N independent booleans admit 2^N representable states. Only a few are legal. The type system cannot reject the rest. Some combinations are legal phases. For example, `abort` and `isStreaming` are both true during the graceful drain window (`Task.ts:2685, 3484, 3756`). The type system cannot tell a legal combination from an illegal one. The one flag-combination defect with a source is the duplicated render state in #325.
6767

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ export const bedrockModels = {
258258
// 128K max output tokens per the AWS Bedrock model card:
259259
// https://docs.aws.amazon.com/bedrock/latest/userguide/model-card-anthropic-claude-opus-5-5.html
260260
maxTokens: 128_000,
261+
supportsMaxTokens: true,
261262
contextWindow: 1_000_000, // 1M context window native (no beta header required)
262263
supportsImages: true,
263264
supportsPromptCache: true,

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -930,10 +930,11 @@ export const openAiNativeModels = {
930930
},
931931
} as const satisfies Record<string, ModelInfo>
932932

933+
// `maxTokens` is intentionally omitted so that no max output token limit is sent to the
934+
// provider by default.
933935
export const openAiModelInfoSaneDefaults: ModelInfo = {
934-
maxTokens: -1,
935936
contextWindow: 128_000,
936-
supportsImages: true,
937+
supportsImages: false,
937938
supportsPromptCache: false,
938939
inputPrice: 0,
939940
outputPrice: 0,

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,19 @@ export const xaiModels = {
4848
includedTools: ["search_replace"],
4949
excludedTools: ["apply_diff"],
5050
},
51+
"grok-build-0.1": {
52+
maxTokens: 65_536,
53+
contextWindow: 256_000,
54+
supportsImages: true,
55+
supportsPromptCache: true,
56+
inputPrice: 1.0,
57+
outputPrice: 2.0,
58+
cacheWritesPrice: 0.2,
59+
cacheReadsPrice: 0.2,
60+
description: "xAI's Grok Build 0.1 agentic coding model with 256K context window",
61+
includedTools: ["search_replace"],
62+
excludedTools: ["apply_diff"],
63+
},
5164
"grok-4-1-fast-reasoning": {
5265
maxTokens: 65_536,
5366
contextWindow: 2_000_000,

‎pnpm-lock.yaml‎

Lines changed: 14 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎scripts/stryker-diff.mjs‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ export const PACKAGE_CONFIGS = [
3636
sourceRoot: "packages/vscode-shim/src/",
3737
vitestConfig: "vitest.config.ts",
3838
},
39+
{
40+
id: "types",
41+
root: "packages/types",
42+
sourceRoot: "packages/types/src/",
43+
vitestConfig: "vitest.config.ts",
44+
},
3945
{
4046
id: "webview",
4147
root: "webview-ui",

‎scripts/stryker-diff.test.mjs‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,18 +219,21 @@ describe("buildManifest", () => {
219219
"packages/cloud/src/new.ts": "export const enabled = true\n",
220220
"webview-ui/src/utils/changed.ts": "export const changed = (value: boolean) => (value ? 1 : 2)\n",
221221
"src/utils/changed.ts": "export const changed = (value: boolean) => (value ? 1 : 2)\n",
222+
"packages/types/src/changed.ts": "export const changed = (value: boolean) => (value ? 1 : 2)\n",
222223
}
223224
const diffs = {
224225
"packages/core/src/changed.ts": "@@ -1,2 +1,2 @@\n",
225226
"webview-ui/src/utils/changed.ts": "@@ -1 +1 @@\n",
226227
"src/utils/changed.ts": "@@ -1 +1 @@\n",
228+
"packages/types/src/changed.ts": "@@ -1 +1 @@\n",
227229
}
228230
const manifest = buildManifest(
229231
[
230232
{ status: "M", path: "packages/core/src/changed.ts" },
231233
{ status: "A", path: "packages/cloud/src/new.ts" },
232234
{ status: "M", path: "webview-ui/src/utils/changed.ts" },
233235
{ status: "M", path: "src/utils/changed.ts" },
236+
{ status: "M", path: "packages/types/src/changed.ts" },
234237
],
235238
(filePath) => sources[filePath],
236239
(filePath) => diffs[filePath] ?? "",
@@ -243,6 +246,7 @@ describe("buildManifest", () => {
243246
{ id: "cloud", selectors: ["src/new.ts:1-1"] },
244247
{ id: "webview", selectors: ["webview-ui/src/utils/changed.ts:1-1"] },
245248
{ id: "extension", selectors: ["utils/changed.ts:1-1"] },
249+
{ id: "types", selectors: ["src/changed.ts:1-1"] },
246250
],
247251
)
248252
const webview = manifest.packages.find(({ id }) => id === "webview")
@@ -252,6 +256,9 @@ describe("buildManifest", () => {
252256
assert.equal(webview.vitestRelated, false)
253257
assert.equal(extension.discoverRelatedTests, true)
254258
assert.equal(extension.vitestRelated, false)
259+
const types = manifest.packages.find(({ id }) => id === "types")
260+
assert.equal(types.discoverRelatedTests, undefined)
261+
assert.equal(types.vitestRelated, undefined)
255262
})
256263

257264
it("returns no packages for tests, barrels, unsupported packages, and type-only changes", () => {
@@ -303,9 +310,11 @@ describe("buildManifest", () => {
303310
})
304311

305312
describe("packageForPath", () => {
306-
it("routes webview and extension production code while excluding test infrastructure", () => {
313+
it("routes webview, extension, and types production code while excluding test infrastructure", () => {
307314
assert.equal(packageForPath("webview-ui/src/utils/path-mentions.ts").id, "webview")
308315
assert.equal(packageForPath("src/utils/tool-id.ts").id, "extension")
316+
assert.equal(packageForPath("packages/types/src/global-settings.ts").id, "types")
317+
assert.equal(packageForPath("packages/types/src/__tests__/global-settings.test.ts"), undefined)
309318
assert.equal(packageForPath("webview-ui/src/utils/test-utils.ts"), undefined)
310319
assert.equal(packageForPath("src/__mocks__/vscode.js"), undefined)
311320
assert.equal(packageForPath("apps/vscode-e2e/src/example.ts"), undefined)

‎src/__tests__/new-task-delegation.spec.ts‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,7 @@ describe("Task.startSubtask() metadata-driven delegation", () => {
3131
})
3232
expect(child.taskId).toBe("child-1")
3333

34-
// Parent should not be paused and no paused/unpaused events should be emitted
35-
expect((parent as any).isPaused).not.toBe(true)
34+
// No paused/unpaused events should be emitted
3635
expect((parent as any).childTaskId).toBeUndefined()
3736
const emittedEvents = (parent.emit as any).mock.calls.map((c: any[]) => c[0])
3837
expect(emittedEvents).not.toContain(RooCodeEventName.TaskPaused)

‎src/api/providers/__tests__/base-openai-compatible-provider.spec.ts‎

Lines changed: 84 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import { Anthropic } from "@anthropic-ai/sdk"
44
import OpenAI from "openai"
55

6-
import type { ModelInfo } from "@roo-code/types"
6+
import { type ModelInfo, openAiModelInfoSaneDefaults } from "@roo-code/types"
77

88
import { BaseOpenAiCompatibleProvider } from "../base-openai-compatible-provider"
99
import { asyncStreamFrom, collectStream } from "../../../test-utils/stream"
@@ -25,18 +25,20 @@ vi.mock("openai", () => ({
2525
}),
2626
}))
2727

28+
const TEST_MODEL_INFO: ModelInfo = {
29+
maxTokens: 4096,
30+
contextWindow: 128000,
31+
supportsImages: false,
32+
supportsPromptCache: false,
33+
inputPrice: 0.5,
34+
outputPrice: 1.5,
35+
}
36+
2837
// Create a concrete test implementation of the abstract base class
2938
class TestOpenAiCompatibleProvider extends BaseOpenAiCompatibleProvider<"test-model"> {
30-
constructor(apiKey: string) {
39+
constructor(apiKey: string, options?: { apiModelId?: string; modelMaxTokens?: number }) {
3140
const testModels: Record<"test-model", ModelInfo> = {
32-
"test-model": {
33-
maxTokens: 4096,
34-
contextWindow: 128000,
35-
supportsImages: false,
36-
supportsPromptCache: false,
37-
inputPrice: 0.5,
38-
outputPrice: 1.5,
39-
},
41+
"test-model": TEST_MODEL_INFO,
4042
}
4143

4244
super({
@@ -45,6 +47,7 @@ class TestOpenAiCompatibleProvider extends BaseOpenAiCompatibleProvider<"test-mo
4547
defaultProviderModelId: "test-model",
4648
providerModels: testModels,
4749
apiKey,
50+
...options,
4851
})
4952
}
5053
}
@@ -297,6 +300,77 @@ describe("BaseOpenAiCompatibleProvider", () => {
297300
})
298301
})
299302

303+
describe("getModel", () => {
304+
it("returns the provider default when no model id is configured", () => {
305+
const model = handler.getModel()
306+
expect(model.id).toBe("test-model")
307+
expect(model.info).toEqual(TEST_MODEL_INFO)
308+
})
309+
310+
it("returns the predefined metadata for a known model id", () => {
311+
const knownHandler = new TestOpenAiCompatibleProvider("test-api-key", { apiModelId: "test-model" })
312+
const model = knownHandler.getModel()
313+
expect(model.id).toBe("test-model")
314+
expect(model.info).toEqual(TEST_MODEL_INFO)
315+
})
316+
317+
it("honors a custom model id that is not in the predefined list", () => {
318+
// Regression: a user-supplied custom id must not be silently swapped for the
319+
// provider default (which previously caused wrong-model requests / 404s).
320+
const customHandler = new TestOpenAiCompatibleProvider("test-api-key", {
321+
apiModelId: "some/custom-model-not-in-list",
322+
})
323+
const model = customHandler.getModel()
324+
expect(model.id).toBe("some/custom-model-not-in-list")
325+
expect(model.id).not.toBe("test-model")
326+
// Falls back to sane default metadata so the rest of the pipeline works.
327+
expect(model.info).toEqual(openAiModelInfoSaneDefaults)
328+
expect(model.info).not.toHaveProperty("maxTokens")
329+
})
330+
331+
it("sends the custom model id verbatim to the API", async () => {
332+
mockCreate.mockImplementationOnce(() => asyncStreamFrom([]))
333+
334+
const customHandler = new TestOpenAiCompatibleProvider("test-api-key", {
335+
apiModelId: "some/custom-model-not-in-list",
336+
})
337+
338+
const stream = customHandler.createMessage("system prompt", [])
339+
await collectStream(stream)
340+
341+
expect(mockCreate).toHaveBeenCalledWith(
342+
expect.objectContaining({ model: "some/custom-model-not-in-list" }),
343+
undefined,
344+
)
345+
})
346+
347+
it("omits max_tokens for a custom model", async () => {
348+
mockCreate.mockImplementationOnce(() => asyncStreamFrom([]))
349+
350+
const customHandler = new TestOpenAiCompatibleProvider("test-api-key", {
351+
apiModelId: "some/custom-model-not-in-list",
352+
})
353+
354+
await collectStream(customHandler.createMessage("system prompt", []))
355+
356+
expect(mockCreate.mock.calls[0][0].max_tokens).toBeUndefined()
357+
})
358+
359+
it("completePrompt sends the custom model id verbatim to the API", async () => {
360+
mockCreate.mockResolvedValueOnce({ choices: [{ message: { content: "ok" } }] })
361+
362+
const customHandler = new TestOpenAiCompatibleProvider("test-api-key", {
363+
apiModelId: "some/custom-model-not-in-list",
364+
})
365+
366+
await customHandler.completePrompt("hello")
367+
368+
expect(mockCreate).toHaveBeenCalledWith(
369+
expect.objectContaining({ model: "some/custom-model-not-in-list" }),
370+
)
371+
})
372+
})
373+
300374
describe("Tool call handling", () => {
301375
it("should yield tool_call_end events when finish_reason is tool_calls", async () => {
302376
mockCreate.mockImplementationOnce(() =>

0 commit comments

Comments
 (0)