@akiojin/gwt 2.3.0 → 2.4.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/README.ja.md +5 -3
- package/README.md +5 -3
- package/dist/claude.d.ts +1 -0
- package/dist/claude.d.ts.map +1 -1
- package/dist/claude.js +6 -3
- package/dist/claude.js.map +1 -1
- package/dist/cli/ui/components/App.d.ts +3 -1
- package/dist/cli/ui/components/App.d.ts.map +1 -1
- package/dist/cli/ui/components/App.js +40 -2
- package/dist/cli/ui/components/App.js.map +1 -1
- package/dist/cli/ui/components/screens/AIToolSelectorScreen.d.ts +1 -1
- package/dist/cli/ui/components/screens/AIToolSelectorScreen.d.ts.map +1 -1
- package/dist/cli/ui/components/screens/AIToolSelectorScreen.js.map +1 -1
- package/dist/cli/ui/components/screens/ModelSelectorScreen.d.ts +18 -0
- package/dist/cli/ui/components/screens/ModelSelectorScreen.d.ts.map +1 -0
- package/dist/cli/ui/components/screens/ModelSelectorScreen.js +201 -0
- package/dist/cli/ui/components/screens/ModelSelectorScreen.js.map +1 -0
- package/dist/cli/ui/types.d.ts +11 -1
- package/dist/cli/ui/types.d.ts.map +1 -1
- package/dist/cli/ui/utils/modelOptions.d.ts +6 -0
- package/dist/cli/ui/utils/modelOptions.d.ts.map +1 -0
- package/dist/cli/ui/utils/modelOptions.js +111 -0
- package/dist/cli/ui/utils/modelOptions.js.map +1 -0
- package/dist/codex.d.ts +6 -0
- package/dist/codex.d.ts.map +1 -1
- package/dist/codex.js +11 -4
- package/dist/codex.js.map +1 -1
- package/dist/gemini.d.ts +1 -0
- package/dist/gemini.d.ts.map +1 -1
- package/dist/gemini.js +6 -3
- package/dist/gemini.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +33 -11
- package/dist/index.js.map +1 -1
- package/dist/qwen.d.ts +1 -0
- package/dist/qwen.d.ts.map +1 -1
- package/dist/qwen.js +6 -3
- package/dist/qwen.js.map +1 -1
- package/package.json +1 -1
- package/src/claude.ts +8 -3
- package/src/cli/ui/__tests__/components/ModelSelectorScreen.initial.test.tsx +81 -0
- package/src/cli/ui/__tests__/components/common/LoadingIndicator.test.tsx +28 -14
- package/src/cli/ui/components/App.tsx +74 -4
- package/src/cli/ui/components/screens/AIToolSelectorScreen.tsx +1 -2
- package/src/cli/ui/components/screens/ModelSelectorScreen.tsx +320 -0
- package/src/cli/ui/types.ts +13 -0
- package/src/cli/ui/utils/modelOptions.test.ts +36 -0
- package/src/cli/ui/utils/modelOptions.ts +122 -0
- package/src/codex.ts +23 -4
- package/src/gemini.ts +8 -3
- package/src/index.ts +60 -10
- package/src/qwen.ts +8 -3
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import React, { useEffect, useMemo, useState } from "react";
|
|
2
|
+
import { Box, Text, useInput } from "ink";
|
|
3
|
+
import { Header } from "../parts/Header.js";
|
|
4
|
+
import { Footer } from "../parts/Footer.js";
|
|
5
|
+
import { Select, type SelectItem } from "../common/Select.js";
|
|
6
|
+
import { useTerminalSize } from "../../hooks/useTerminalSize.js";
|
|
7
|
+
import type { AITool, InferenceLevel, ModelOption } from "../../types.js";
|
|
8
|
+
import {
|
|
9
|
+
getDefaultInferenceForModel,
|
|
10
|
+
getDefaultModelOption,
|
|
11
|
+
getInferenceLevelsForModel,
|
|
12
|
+
getModelOptions,
|
|
13
|
+
} from "../../utils/modelOptions.js";
|
|
14
|
+
|
|
15
|
+
export interface ModelSelectionResult {
|
|
16
|
+
model: string | null;
|
|
17
|
+
inferenceLevel?: InferenceLevel;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface ModelSelectItem extends SelectItem {
|
|
21
|
+
description?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface InferenceSelectItem extends SelectItem {
|
|
25
|
+
hint?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ModelSelectorScreenProps {
|
|
29
|
+
tool: AITool;
|
|
30
|
+
onBack: () => void;
|
|
31
|
+
onSelect: (selection: ModelSelectionResult) => void;
|
|
32
|
+
version?: string | null;
|
|
33
|
+
initialSelection?: ModelSelectionResult | null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const TOOL_LABELS: Record<string, string> = {
|
|
37
|
+
"claude-code": "Claude Code",
|
|
38
|
+
"codex-cli": "Codex",
|
|
39
|
+
"gemini-cli": "Gemini",
|
|
40
|
+
"qwen-cli": "Qwen",
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const INFERENCE_LABELS: Record<InferenceLevel, string> = {
|
|
44
|
+
low: "Low (lighter reasoning)",
|
|
45
|
+
medium: "Medium (balanced reasoning)",
|
|
46
|
+
high: "High (deeper reasoning)",
|
|
47
|
+
xhigh: "Extra high (maximum reasoning)",
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* モデル選択 → (必要なら) 推論レベル選択を行う画面
|
|
52
|
+
*/
|
|
53
|
+
export function ModelSelectorScreen({
|
|
54
|
+
tool,
|
|
55
|
+
onBack,
|
|
56
|
+
onSelect,
|
|
57
|
+
version,
|
|
58
|
+
initialSelection,
|
|
59
|
+
}: ModelSelectorScreenProps) {
|
|
60
|
+
const { rows } = useTerminalSize();
|
|
61
|
+
|
|
62
|
+
const [step, setStep] = useState<"model" | "inference">("model");
|
|
63
|
+
const [modelOptions, setModelOptions] = useState<ModelOption[]>([]);
|
|
64
|
+
const [selectedModel, setSelectedModel] = useState<ModelOption | null>(null);
|
|
65
|
+
|
|
66
|
+
// モデル候補をツールに応じてロード
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
const options = getModelOptions(tool);
|
|
69
|
+
setModelOptions(options);
|
|
70
|
+
// 初期選択が有効なら保持
|
|
71
|
+
if (initialSelection?.model) {
|
|
72
|
+
const found = options.find((opt) => opt.id === initialSelection.model);
|
|
73
|
+
if (found) {
|
|
74
|
+
setSelectedModel(found);
|
|
75
|
+
setStep("model");
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
setSelectedModel(null);
|
|
80
|
+
setStep("model");
|
|
81
|
+
}, [tool, initialSelection?.model]);
|
|
82
|
+
|
|
83
|
+
const modelItems: ModelSelectItem[] = useMemo(
|
|
84
|
+
() =>
|
|
85
|
+
modelOptions.map((option) => ({
|
|
86
|
+
label: option.label,
|
|
87
|
+
value: option.id,
|
|
88
|
+
...(option.description ? { description: option.description } : {}),
|
|
89
|
+
})),
|
|
90
|
+
[modelOptions],
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const defaultModelIndex = useMemo(() => {
|
|
94
|
+
const initial = initialSelection?.model
|
|
95
|
+
? modelOptions.findIndex((opt) => opt.id === initialSelection.model)
|
|
96
|
+
: -1;
|
|
97
|
+
if (initial !== -1) return initial;
|
|
98
|
+
const defaultOption = getDefaultModelOption(tool);
|
|
99
|
+
if (!defaultOption) return 0;
|
|
100
|
+
const index = modelOptions.findIndex((opt) => opt.id === defaultOption.id);
|
|
101
|
+
return index >= 0 ? index : 0;
|
|
102
|
+
}, [initialSelection?.model, modelOptions, tool]);
|
|
103
|
+
|
|
104
|
+
const inferenceOptions = useMemo(
|
|
105
|
+
() => getInferenceLevelsForModel(selectedModel ?? undefined),
|
|
106
|
+
[selectedModel],
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
const inferenceItems: InferenceSelectItem[] = useMemo(
|
|
110
|
+
() => {
|
|
111
|
+
return inferenceOptions.map((level) => {
|
|
112
|
+
if (selectedModel?.id === "gpt-5.1-codex-max") {
|
|
113
|
+
if (level === "low") {
|
|
114
|
+
return {
|
|
115
|
+
label: "Low",
|
|
116
|
+
value: level,
|
|
117
|
+
hint: "Fast responses with lighter reasoning",
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
if (level === "medium") {
|
|
121
|
+
return {
|
|
122
|
+
label: "Medium (default)",
|
|
123
|
+
value: level,
|
|
124
|
+
hint: "Balances speed and reasoning depth for everyday tasks",
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
if (level === "high") {
|
|
128
|
+
return {
|
|
129
|
+
label: "High",
|
|
130
|
+
value: level,
|
|
131
|
+
hint: "Maximizes reasoning depth for complex problems",
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
if (level === "xhigh") {
|
|
135
|
+
return {
|
|
136
|
+
label: "Extra high",
|
|
137
|
+
value: level,
|
|
138
|
+
hint:
|
|
139
|
+
"Extra high reasoning depth; may quickly consume Plus plan rate limits.",
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
label: INFERENCE_LABELS[level],
|
|
146
|
+
value: level,
|
|
147
|
+
};
|
|
148
|
+
});
|
|
149
|
+
},
|
|
150
|
+
[inferenceOptions, selectedModel?.id],
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
const defaultInferenceIndex = useMemo(() => {
|
|
154
|
+
const initialLevel = initialSelection?.inferenceLevel;
|
|
155
|
+
if (initialLevel && inferenceOptions.includes(initialLevel)) {
|
|
156
|
+
return inferenceOptions.findIndex((lvl) => lvl === initialLevel);
|
|
157
|
+
}
|
|
158
|
+
const defaultLevel = getDefaultInferenceForModel(selectedModel ?? undefined);
|
|
159
|
+
if (!defaultLevel) return 0;
|
|
160
|
+
const index = inferenceOptions.findIndex((lvl) => lvl === defaultLevel);
|
|
161
|
+
return index >= 0 ? index : 0;
|
|
162
|
+
}, [initialSelection?.inferenceLevel, inferenceOptions, selectedModel]);
|
|
163
|
+
|
|
164
|
+
useInput((_input, key) => {
|
|
165
|
+
if (key.escape) {
|
|
166
|
+
if (step === "inference") {
|
|
167
|
+
setStep("model");
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
onBack();
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const handleModelSelect = (item: ModelSelectItem) => {
|
|
175
|
+
const option =
|
|
176
|
+
modelOptions.find((opt) => opt.id === item.value) ?? modelOptions[0];
|
|
177
|
+
|
|
178
|
+
if (!option) {
|
|
179
|
+
onSelect({ model: null });
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
setSelectedModel(option);
|
|
184
|
+
|
|
185
|
+
const levels = getInferenceLevelsForModel(option);
|
|
186
|
+
if (levels.length > 0) {
|
|
187
|
+
setStep("inference");
|
|
188
|
+
} else {
|
|
189
|
+
onSelect({ model: option.id });
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const handleInferenceSelect = (item: InferenceSelectItem) => {
|
|
194
|
+
if (!selectedModel) {
|
|
195
|
+
setStep("model");
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
onSelect({
|
|
200
|
+
model: selectedModel.id,
|
|
201
|
+
inferenceLevel: item.value as InferenceLevel,
|
|
202
|
+
});
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const footerActions =
|
|
206
|
+
step === "model"
|
|
207
|
+
? [
|
|
208
|
+
{ key: "enter", description: "Select" },
|
|
209
|
+
{ key: "esc", description: "Back" },
|
|
210
|
+
]
|
|
211
|
+
: [
|
|
212
|
+
{ key: "enter", description: "Select" },
|
|
213
|
+
{ key: "esc", description: "Back to model" },
|
|
214
|
+
];
|
|
215
|
+
|
|
216
|
+
const toolLabel = TOOL_LABELS[tool] ?? tool;
|
|
217
|
+
|
|
218
|
+
const renderModelItem = (
|
|
219
|
+
item: ModelSelectItem,
|
|
220
|
+
isSelected: boolean,
|
|
221
|
+
): React.ReactNode => (
|
|
222
|
+
<Box flexDirection="column">
|
|
223
|
+
{isSelected ? (
|
|
224
|
+
<Text color="cyan">➤ {item.label}</Text>
|
|
225
|
+
) : (
|
|
226
|
+
<Text> {item.label}</Text>
|
|
227
|
+
)}
|
|
228
|
+
{item.description ? (
|
|
229
|
+
<Text color="gray"> {item.description}</Text>
|
|
230
|
+
) : null}
|
|
231
|
+
</Box>
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
return (
|
|
235
|
+
<Box flexDirection="column" height={rows}>
|
|
236
|
+
<Header
|
|
237
|
+
title={step === "model" ? "Model Selection" : "Inference Level"}
|
|
238
|
+
titleColor="blue"
|
|
239
|
+
version={version}
|
|
240
|
+
/>
|
|
241
|
+
|
|
242
|
+
<Box flexDirection="column" flexGrow={1} marginTop={1}>
|
|
243
|
+
{step === "model" ? (
|
|
244
|
+
<>
|
|
245
|
+
{tool === "gemini-cli" ? (
|
|
246
|
+
<Box marginBottom={1} flexDirection="column">
|
|
247
|
+
<Text>Gemini 3 preview is enabled.</Text>
|
|
248
|
+
<Text>
|
|
249
|
+
Selecting Pro uses gemini-3-pro-preview and falls back to
|
|
250
|
+
gemini-2.5-pro if unavailable.
|
|
251
|
+
</Text>
|
|
252
|
+
<Text>Use --model to pin a specific Gemini model.</Text>
|
|
253
|
+
</Box>
|
|
254
|
+
) : null}
|
|
255
|
+
|
|
256
|
+
<Box marginBottom={1}>
|
|
257
|
+
<Text>
|
|
258
|
+
Select a model for {toolLabel}
|
|
259
|
+
{modelOptions.length === 0 ? " (no options)" : ""}
|
|
260
|
+
</Text>
|
|
261
|
+
</Box>
|
|
262
|
+
{tool === "qwen-cli" ? (
|
|
263
|
+
<Box marginBottom={1} flexDirection="column">
|
|
264
|
+
<Text>Latest Qwen models from Alibaba Cloud ModelStudio:</Text>
|
|
265
|
+
<Text>• coder-model (qwen3-coder-plus-2025-09-23)</Text>
|
|
266
|
+
<Text>• vision-model (qwen3-vl-plus-2025-09-23)</Text>
|
|
267
|
+
</Box>
|
|
268
|
+
) : null}
|
|
269
|
+
|
|
270
|
+
{modelItems.length === 0 ? (
|
|
271
|
+
<Select
|
|
272
|
+
items={[
|
|
273
|
+
{
|
|
274
|
+
label: "No model selection required. Press Enter to continue.",
|
|
275
|
+
value: "__continue__",
|
|
276
|
+
},
|
|
277
|
+
]}
|
|
278
|
+
onSelect={() => onSelect({ model: null })}
|
|
279
|
+
/>
|
|
280
|
+
) : (
|
|
281
|
+
<Select
|
|
282
|
+
items={modelItems}
|
|
283
|
+
onSelect={handleModelSelect}
|
|
284
|
+
initialIndex={defaultModelIndex}
|
|
285
|
+
renderItem={renderModelItem}
|
|
286
|
+
/>
|
|
287
|
+
)}
|
|
288
|
+
</>
|
|
289
|
+
) : (
|
|
290
|
+
<>
|
|
291
|
+
<Box marginBottom={1}>
|
|
292
|
+
<Text>
|
|
293
|
+
Select reasoning level for {selectedModel?.label ?? "model"}
|
|
294
|
+
</Text>
|
|
295
|
+
</Box>
|
|
296
|
+
<Select
|
|
297
|
+
items={inferenceItems}
|
|
298
|
+
onSelect={handleInferenceSelect}
|
|
299
|
+
initialIndex={defaultInferenceIndex}
|
|
300
|
+
renderItem={(item, isSelected) => (
|
|
301
|
+
<Box flexDirection="column">
|
|
302
|
+
{isSelected ? (
|
|
303
|
+
<Text color="cyan">➤ {item.label}</Text>
|
|
304
|
+
) : (
|
|
305
|
+
<Text> {item.label}</Text>
|
|
306
|
+
)}
|
|
307
|
+
{"hint" in item && item.hint ? (
|
|
308
|
+
<Text color="gray"> {item.hint}</Text>
|
|
309
|
+
) : null}
|
|
310
|
+
</Box>
|
|
311
|
+
)}
|
|
312
|
+
/>
|
|
313
|
+
</>
|
|
314
|
+
)}
|
|
315
|
+
</Box>
|
|
316
|
+
|
|
317
|
+
<Footer actions={footerActions} />
|
|
318
|
+
</Box>
|
|
319
|
+
);
|
|
320
|
+
}
|
package/src/cli/ui/types.ts
CHANGED
|
@@ -5,6 +5,18 @@ export interface WorktreeInfo {
|
|
|
5
5
|
isAccessible?: boolean;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
+
export type AITool = string;
|
|
9
|
+
export type InferenceLevel = "low" | "medium" | "high" | "xhigh";
|
|
10
|
+
|
|
11
|
+
export interface ModelOption {
|
|
12
|
+
id: string;
|
|
13
|
+
label: string;
|
|
14
|
+
description?: string;
|
|
15
|
+
inferenceLevels?: InferenceLevel[];
|
|
16
|
+
defaultInference?: InferenceLevel;
|
|
17
|
+
isDefault?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
8
20
|
export interface BranchInfo {
|
|
9
21
|
name: string;
|
|
10
22
|
type: "local" | "remote";
|
|
@@ -161,6 +173,7 @@ export type ScreenType =
|
|
|
161
173
|
| "branch-creator"
|
|
162
174
|
| "branch-action-selector"
|
|
163
175
|
| "ai-tool-selector"
|
|
176
|
+
| "model-selector"
|
|
164
177
|
| "session-selector"
|
|
165
178
|
| "execution-mode-selector"
|
|
166
179
|
| "batch-merge-progress"
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { getModelOptions, getDefaultInferenceForModel } from "./modelOptions.js";
|
|
3
|
+
|
|
4
|
+
const byId = (tool: string) => getModelOptions(tool).map((m) => m.id);
|
|
5
|
+
|
|
6
|
+
describe("modelOptions", () => {
|
|
7
|
+
it("has unique Codex models", () => {
|
|
8
|
+
const ids = byId("codex-cli");
|
|
9
|
+
const unique = new Set(ids);
|
|
10
|
+
expect(unique.size).toBe(ids.length);
|
|
11
|
+
expect(ids).toEqual([
|
|
12
|
+
"gpt-5.1-codex",
|
|
13
|
+
"gpt-5.1-codex-max",
|
|
14
|
+
"gpt-5.1-codex-mini",
|
|
15
|
+
"gpt-5.1",
|
|
16
|
+
]);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("uses medium as default reasoning for codex-max", () => {
|
|
20
|
+
const codexMax = getModelOptions("codex-cli").find((m) => m.id === "gpt-5.1-codex-max");
|
|
21
|
+
expect(getDefaultInferenceForModel(codexMax)).toBe("medium");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("lists expected Gemini models", () => {
|
|
25
|
+
expect(byId("gemini-cli")).toEqual([
|
|
26
|
+
"gemini-3-pro-preview",
|
|
27
|
+
"gemini-2.5-pro",
|
|
28
|
+
"gemini-2.5-flash",
|
|
29
|
+
"gemini-2.5-flash-lite",
|
|
30
|
+
]);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("lists expected Qwen models", () => {
|
|
34
|
+
expect(byId("qwen-cli")).toEqual(["coder-model", "vision-model"]);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import type { AITool, InferenceLevel, ModelOption } from "../types.js";
|
|
2
|
+
|
|
3
|
+
const CODEX_BASE_LEVELS: InferenceLevel[] = ["high", "medium", "low"];
|
|
4
|
+
const CODEX_MAX_LEVELS: InferenceLevel[] = ["xhigh", "high", "medium", "low"];
|
|
5
|
+
|
|
6
|
+
const MODEL_OPTIONS: Record<string, ModelOption[]> = {
|
|
7
|
+
"claude-code": [
|
|
8
|
+
{
|
|
9
|
+
id: "claude-sonnet-4.5",
|
|
10
|
+
label: "Default (recommended) — Sonnet 4.5",
|
|
11
|
+
description: "Smartest model for daily use (released Sep 29, 2025)",
|
|
12
|
+
isDefault: true,
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
id: "claude-opus-4.1",
|
|
16
|
+
label: "Opus 4.1",
|
|
17
|
+
description: "Legacy: Opus 4.1 · reaches usage limits faster",
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
id: "claude-haiku-4.5",
|
|
21
|
+
label: "Haiku 4.5",
|
|
22
|
+
description: "Fastest model for simple tasks (released Oct 15, 2025)",
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
"codex-cli": [
|
|
26
|
+
{
|
|
27
|
+
id: "gpt-5.1-codex",
|
|
28
|
+
label: "gpt-5.1-codex",
|
|
29
|
+
description: "Standard Codex model",
|
|
30
|
+
inferenceLevels: CODEX_BASE_LEVELS,
|
|
31
|
+
defaultInference: "high",
|
|
32
|
+
isDefault: true,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
id: "gpt-5.1-codex-max",
|
|
36
|
+
label: "gpt-5.1-codex-max",
|
|
37
|
+
description: "Max performance (xhigh available)",
|
|
38
|
+
inferenceLevels: CODEX_MAX_LEVELS,
|
|
39
|
+
defaultInference: "medium",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: "gpt-5.1-codex-mini",
|
|
43
|
+
label: "gpt-5.1-codex-mini",
|
|
44
|
+
description: "Lightweight / cost-saving",
|
|
45
|
+
inferenceLevels: CODEX_BASE_LEVELS,
|
|
46
|
+
defaultInference: "medium",
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: "gpt-5.1",
|
|
50
|
+
label: "gpt-5.1",
|
|
51
|
+
description: "General-purpose GPT-5.1",
|
|
52
|
+
inferenceLevels: CODEX_BASE_LEVELS,
|
|
53
|
+
defaultInference: "high",
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
"gemini-cli": [
|
|
57
|
+
{
|
|
58
|
+
id: "gemini-3-pro-preview",
|
|
59
|
+
label: "Pro (gemini-3-pro-preview)",
|
|
60
|
+
description:
|
|
61
|
+
"Default Pro. Falls back to gemini-2.5-pro when preview is unavailable.",
|
|
62
|
+
isDefault: true,
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: "gemini-2.5-pro",
|
|
66
|
+
label: "Pro (gemini-2.5-pro)",
|
|
67
|
+
description: "Stable Pro model for deep reasoning and creativity",
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
id: "gemini-2.5-flash",
|
|
71
|
+
label: "Flash (gemini-2.5-flash)",
|
|
72
|
+
description: "Balance of speed and reasoning",
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: "gemini-2.5-flash-lite",
|
|
76
|
+
label: "Flash-Lite (gemini-2.5-flash-lite)",
|
|
77
|
+
description: "Fastest for simple tasks",
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
"qwen-cli": [
|
|
81
|
+
{
|
|
82
|
+
id: "coder-model",
|
|
83
|
+
label: "Coder Model",
|
|
84
|
+
description:
|
|
85
|
+
"Latest Qwen Coder model (qwen3-coder-plus-2025-09-23) from Alibaba Cloud ModelStudio",
|
|
86
|
+
isDefault: true,
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
id: "vision-model",
|
|
90
|
+
label: "Vision Model",
|
|
91
|
+
description:
|
|
92
|
+
"Latest Qwen Vision model (qwen3-vl-plus-2025-09-23) from Alibaba Cloud ModelStudio",
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export function getModelOptions(tool: AITool): ModelOption[] {
|
|
98
|
+
return MODEL_OPTIONS[tool] ?? [];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function getDefaultModelOption(tool: AITool): ModelOption | undefined {
|
|
102
|
+
const options = getModelOptions(tool);
|
|
103
|
+
return options.find((opt) => opt.isDefault) ?? options[0];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function getInferenceLevelsForModel(
|
|
107
|
+
model?: ModelOption,
|
|
108
|
+
): InferenceLevel[] {
|
|
109
|
+
if (!model?.inferenceLevels || model.inferenceLevels.length === 0) {
|
|
110
|
+
return [];
|
|
111
|
+
}
|
|
112
|
+
return model.inferenceLevels;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function getDefaultInferenceForModel(
|
|
116
|
+
model?: ModelOption,
|
|
117
|
+
): InferenceLevel | undefined {
|
|
118
|
+
if (!model) return undefined;
|
|
119
|
+
if (model.defaultInference) return model.defaultInference;
|
|
120
|
+
const levels = getInferenceLevelsForModel(model);
|
|
121
|
+
return levels[0];
|
|
122
|
+
}
|
package/src/codex.ts
CHANGED
|
@@ -5,14 +5,23 @@ import { existsSync } from "fs";
|
|
|
5
5
|
import { createChildStdio, getTerminalStreams } from "./utils/terminal.js";
|
|
6
6
|
|
|
7
7
|
const CODEX_CLI_PACKAGE = "@openai/codex@latest";
|
|
8
|
-
|
|
8
|
+
|
|
9
|
+
export type CodexReasoningEffort = "low" | "medium" | "high" | "xhigh";
|
|
10
|
+
|
|
11
|
+
export const DEFAULT_CODEX_MODEL = "gpt-5.1-codex";
|
|
12
|
+
export const DEFAULT_CODEX_REASONING_EFFORT: CodexReasoningEffort = "high";
|
|
13
|
+
|
|
14
|
+
export const buildDefaultCodexArgs = (
|
|
15
|
+
model: string = DEFAULT_CODEX_MODEL,
|
|
16
|
+
reasoningEffort: CodexReasoningEffort = DEFAULT_CODEX_REASONING_EFFORT,
|
|
17
|
+
): string[] => [
|
|
9
18
|
"--enable",
|
|
10
19
|
"web_search_request",
|
|
11
|
-
|
|
20
|
+
`--model=${model}`,
|
|
12
21
|
"--sandbox",
|
|
13
22
|
"workspace-write",
|
|
14
23
|
"-c",
|
|
15
|
-
|
|
24
|
+
`model_reasoning_effort=${reasoningEffort}`,
|
|
16
25
|
"-c",
|
|
17
26
|
"model_reasoning_summaries=detailed",
|
|
18
27
|
"-c",
|
|
@@ -42,6 +51,8 @@ export async function launchCodexCLI(
|
|
|
42
51
|
extraArgs?: string[];
|
|
43
52
|
bypassApprovals?: boolean;
|
|
44
53
|
envOverrides?: Record<string, string>;
|
|
54
|
+
model?: string;
|
|
55
|
+
reasoningEffort?: CodexReasoningEffort;
|
|
45
56
|
} = {},
|
|
46
57
|
): Promise<void> {
|
|
47
58
|
const terminal = getTerminalStreams();
|
|
@@ -55,6 +66,12 @@ export async function launchCodexCLI(
|
|
|
55
66
|
console.log(chalk.gray(` Working directory: ${worktreePath}`));
|
|
56
67
|
|
|
57
68
|
const args: string[] = [];
|
|
69
|
+
const model = options.model ?? DEFAULT_CODEX_MODEL;
|
|
70
|
+
const reasoningEffort =
|
|
71
|
+
options.reasoningEffort ?? DEFAULT_CODEX_REASONING_EFFORT;
|
|
72
|
+
|
|
73
|
+
console.log(chalk.green(` 🎯 Model: ${model}`));
|
|
74
|
+
console.log(chalk.green(` 🧠 Reasoning: ${reasoningEffort}`));
|
|
58
75
|
|
|
59
76
|
switch (options.mode) {
|
|
60
77
|
case "continue":
|
|
@@ -80,7 +97,9 @@ export async function launchCodexCLI(
|
|
|
80
97
|
args.push(...options.extraArgs);
|
|
81
98
|
}
|
|
82
99
|
|
|
83
|
-
|
|
100
|
+
const codexArgs = buildDefaultCodexArgs(model, reasoningEffort);
|
|
101
|
+
|
|
102
|
+
args.push(...codexArgs);
|
|
84
103
|
|
|
85
104
|
terminal.exitRawMode();
|
|
86
105
|
|
package/src/gemini.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { execa } from "execa";
|
|
2
2
|
import chalk from "chalk";
|
|
3
|
-
import { platform } from "os";
|
|
4
3
|
import { existsSync } from "fs";
|
|
5
4
|
import { createChildStdio, getTerminalStreams } from "./utils/terminal.js";
|
|
6
5
|
|
|
@@ -23,6 +22,7 @@ export async function launchGeminiCLI(
|
|
|
23
22
|
mode?: "normal" | "continue" | "resume";
|
|
24
23
|
extraArgs?: string[];
|
|
25
24
|
envOverrides?: Record<string, string>;
|
|
25
|
+
model?: string;
|
|
26
26
|
} = {},
|
|
27
27
|
): Promise<void> {
|
|
28
28
|
const terminal = getTerminalStreams();
|
|
@@ -38,6 +38,11 @@ export async function launchGeminiCLI(
|
|
|
38
38
|
|
|
39
39
|
const args: string[] = [];
|
|
40
40
|
|
|
41
|
+
if (options.model) {
|
|
42
|
+
args.push("--model", options.model);
|
|
43
|
+
console.log(chalk.green(` 🎯 Model: ${options.model}`));
|
|
44
|
+
}
|
|
45
|
+
|
|
41
46
|
// Handle execution mode
|
|
42
47
|
switch (options.mode) {
|
|
43
48
|
case "continue":
|
|
@@ -135,7 +140,7 @@ export async function launchGeminiCLI(
|
|
|
135
140
|
errorMessage = `Failed to launch Gemini CLI: ${error.message || "Unknown error"}`;
|
|
136
141
|
}
|
|
137
142
|
|
|
138
|
-
if (platform
|
|
143
|
+
if (process.platform === "win32") {
|
|
139
144
|
console.error(chalk.red("\n💡 Windows troubleshooting tips:"));
|
|
140
145
|
if (hasLocalGemini) {
|
|
141
146
|
console.error(
|
|
@@ -175,7 +180,7 @@ export async function launchGeminiCLI(
|
|
|
175
180
|
*/
|
|
176
181
|
async function isGeminiCommandAvailable(): Promise<boolean> {
|
|
177
182
|
try {
|
|
178
|
-
const command = platform
|
|
183
|
+
const command = process.platform === "win32" ? "where" : "which";
|
|
179
184
|
await execa(command, ["gemini"], { shell: true });
|
|
180
185
|
return true;
|
|
181
186
|
} catch {
|