Skip to content

Commit 26245e0

Browse files
authored
Merge branch 'main' into main
2 parents 1f8f90e + 522a1a3 commit 26245e0

28 files changed

Lines changed: 2276 additions & 41 deletions

packages/@ant/computer-use-input/src/index.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,18 @@ export interface InputBackend {
2222
}
2323

2424
function loadBackend(): InputBackend | null {
25-
if (process.platform !== 'darwin') return null
2625
try {
27-
return require('./backends/darwin.js') as InputBackend
26+
if (process.platform === 'darwin') {
27+
return require('./backends/darwin.js') as InputBackend
28+
} else if (process.platform === 'win32') {
29+
return require('./backends/win32.js') as InputBackend
30+
} else if (process.platform === 'linux') {
31+
return require('./backends/linux.js') as InputBackend
32+
}
2833
} catch {
2934
return null
3035
}
36+
return null
3137
}
3238

3339
const backend = loadBackend()

packages/@ant/computer-use-swift/src/index.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,25 @@ export type {
1818

1919
import type { ResolvePrepareCaptureResult } from './backends/darwin.js'
2020

21-
function loadDarwin() {
22-
if (process.platform !== 'darwin') return null
21+
function loadBackend() {
2322
try {
24-
return require('./backends/darwin.js')
23+
if (process.platform === 'darwin') {
24+
return require('./backends/darwin.js')
25+
} else if (process.platform === 'win32') {
26+
return require('./backends/win32.js')
27+
} else if (process.platform === 'linux') {
28+
return require('./backends/linux.js')
29+
}
2530
} catch {
2631
return null
2732
}
33+
return null
2834
}
2935

30-
const darwin = loadDarwin()
36+
const backend = loadBackend()
3137

3238
export class ComputerUseAPI {
33-
apps = darwin?.apps ?? {
39+
apps = backend?.apps ?? {
3440
async prepareDisplay() { return { activated: '', hidden: [] } },
3541
async previewHideSet() { return [] },
3642
async findWindowDisplays(ids: string[]) { return ids.map((b: string) => ({ bundleId: b, displayIds: [] as number[] })) },
@@ -42,12 +48,12 @@ export class ComputerUseAPI {
4248
async unhide() {},
4349
}
4450

45-
display = darwin?.display ?? {
51+
display = backend?.display ?? {
4652
getSize() { throw new Error('@ant/computer-use-swift: macOS only') },
4753
listAll() { throw new Error('@ant/computer-use-swift: macOS only') },
4854
}
4955

50-
screenshot = darwin?.screenshot ?? {
56+
screenshot = backend?.screenshot ?? {
5157
async captureExcluding() { throw new Error('@ant/computer-use-swift: macOS only') },
5258
async captureRegion() { throw new Error('@ant/computer-use-swift: macOS only') },
5359
}

src/components/ConsoleOAuthFlow.tsx

Lines changed: 262 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,15 @@ type OAuthStatus =
4949
opusModel: string
5050
activeField: 'base_url' | 'api_key' | 'haiku_model' | 'sonnet_model' | 'opus_model'
5151
} // OpenAI Chat Completions API platform
52+
| {
53+
state: 'gemini_api'
54+
baseUrl: string
55+
apiKey: string
56+
haikuModel: string
57+
sonnetModel: string
58+
opusModel: string
59+
activeField: 'base_url' | 'api_key' | 'haiku_model' | 'sonnet_model' | 'opus_model'
60+
} // Gemini Generate Content API platform
5261
| { state: 'ready_to_start' } // Flow started, waiting for browser to open
5362
| { state: 'waiting_for_login'; url: string } // Browser opened, waiting for user to login
5463
| { state: 'creating_api_key' } // Got access token, creating API key
@@ -61,7 +70,6 @@ type OAuthStatus =
6170
}
6271

6372
const PASTE_HERE_MSG = 'Paste code here if prompted > '
64-
6573
export function ConsoleOAuthFlow({
6674
onDone,
6775
startingMessage,
@@ -470,6 +478,16 @@ function OAuthStatusMessage({
470478
),
471479
value: 'openai_chat_api',
472480
},
481+
{
482+
label: (
483+
<Text>
484+
Gemini API ·{' '}
485+
<Text dimColor>Google Gemini native REST/SSE</Text>
486+
{'\n'}
487+
</Text>
488+
),
489+
value: 'gemini_api',
490+
},
473491
{
474492
label: (
475493
<Text>
@@ -537,6 +555,17 @@ function OAuthStatusMessage({
537555
opusModel: process.env.OPENAI_DEFAULT_OPUS_MODEL ?? '',
538556
activeField: 'base_url',
539557
})
558+
} else if (value === 'gemini_api') {
559+
logEvent('tengu_gemini_api_selected', {})
560+
setOAuthStatus({
561+
state: 'gemini_api',
562+
baseUrl: process.env.GEMINI_BASE_URL ?? '',
563+
apiKey: process.env.GEMINI_API_KEY ?? '',
564+
haikuModel: process.env.ANTHROPIC_DEFAULT_HAIKU_MODEL ?? '',
565+
sonnetModel: process.env.ANTHROPIC_DEFAULT_SONNET_MODEL ?? '',
566+
opusModel: process.env.ANTHROPIC_DEFAULT_OPUS_MODEL ?? '',
567+
activeField: 'base_url',
568+
})
540569
} else if (value === 'platform') {
541570
logEvent('tengu_oauth_platform_selected', {})
542571
setOAuthStatus({ state: 'platform_setup' })
@@ -1014,6 +1043,238 @@ function OAuthStatusMessage({
10141043
)
10151044
}
10161045

1046+
case 'gemini_api':
1047+
{
1048+
type GeminiField = 'base_url' | 'api_key' | 'haiku_model' | 'sonnet_model' | 'opus_model'
1049+
const GEMINI_FIELDS: GeminiField[] = [
1050+
'base_url',
1051+
'api_key',
1052+
'haiku_model',
1053+
'sonnet_model',
1054+
'opus_model',
1055+
]
1056+
const gp = oauthStatus as {
1057+
state: 'gemini_api'
1058+
activeField: GeminiField
1059+
baseUrl: string
1060+
apiKey: string
1061+
haikuModel: string
1062+
sonnetModel: string
1063+
opusModel: string
1064+
}
1065+
const { activeField, baseUrl, apiKey, haikuModel, sonnetModel, opusModel } = gp
1066+
const geminiDisplayValues: Record<GeminiField, string> = {
1067+
base_url: baseUrl,
1068+
api_key: apiKey,
1069+
haiku_model: haikuModel,
1070+
sonnet_model: sonnetModel,
1071+
opus_model: opusModel,
1072+
}
1073+
1074+
const [geminiInputValue, setGeminiInputValue] = useState(
1075+
() => geminiDisplayValues[activeField],
1076+
)
1077+
const [geminiInputCursorOffset, setGeminiInputCursorOffset] = useState(
1078+
() => geminiDisplayValues[activeField].length,
1079+
)
1080+
1081+
const buildGeminiState = useCallback(
1082+
(field: GeminiField, value: string, newActive?: GeminiField) => {
1083+
const s = {
1084+
state: 'gemini_api' as const,
1085+
activeField: newActive ?? activeField,
1086+
baseUrl,
1087+
apiKey,
1088+
haikuModel,
1089+
sonnetModel,
1090+
opusModel,
1091+
}
1092+
switch (field) {
1093+
case 'base_url':
1094+
return { ...s, baseUrl: value }
1095+
case 'api_key':
1096+
return { ...s, apiKey: value }
1097+
case 'haiku_model':
1098+
return { ...s, haikuModel: value }
1099+
case 'sonnet_model':
1100+
return { ...s, sonnetModel: value }
1101+
case 'opus_model':
1102+
return { ...s, opusModel: value }
1103+
}
1104+
},
1105+
[activeField, baseUrl, apiKey, haikuModel, sonnetModel, opusModel],
1106+
)
1107+
1108+
const doGeminiSave = useCallback(() => {
1109+
const finalVals = { ...geminiDisplayValues, [activeField]: geminiInputValue }
1110+
if (!finalVals.haiku_model || !finalVals.sonnet_model || !finalVals.opus_model) {
1111+
setOAuthStatus({
1112+
state: 'error',
1113+
message: 'Gemini setup requires Haiku, Sonnet, and Opus model names.',
1114+
toRetry: {
1115+
state: 'gemini_api',
1116+
baseUrl: finalVals.base_url,
1117+
apiKey: finalVals.api_key,
1118+
haikuModel: finalVals.haiku_model,
1119+
sonnetModel: finalVals.sonnet_model,
1120+
opusModel: finalVals.opus_model,
1121+
activeField,
1122+
},
1123+
})
1124+
return
1125+
}
1126+
1127+
const env: Record<string, string> = {}
1128+
if (finalVals.base_url) env.GEMINI_BASE_URL = finalVals.base_url
1129+
if (finalVals.api_key) env.GEMINI_API_KEY = finalVals.api_key
1130+
if (finalVals.haiku_model) env.ANTHROPIC_DEFAULT_HAIKU_MODEL = finalVals.haiku_model
1131+
if (finalVals.sonnet_model) env.ANTHROPIC_DEFAULT_SONNET_MODEL = finalVals.sonnet_model
1132+
if (finalVals.opus_model) env.ANTHROPIC_DEFAULT_OPUS_MODEL = finalVals.opus_model
1133+
const { error } = updateSettingsForSource('userSettings', {
1134+
modelType: 'gemini' as any,
1135+
env,
1136+
} as any)
1137+
if (error) {
1138+
setOAuthStatus({
1139+
state: 'error',
1140+
message: `Failed to save: ${error.message}`,
1141+
toRetry: {
1142+
state: 'gemini_api',
1143+
baseUrl: '',
1144+
apiKey: '',
1145+
haikuModel: '',
1146+
sonnetModel: '',
1147+
opusModel: '',
1148+
activeField: 'base_url',
1149+
},
1150+
})
1151+
} else {
1152+
for (const [k, v] of Object.entries(env)) process.env[k] = v
1153+
setOAuthStatus({ state: 'success' })
1154+
void onDone()
1155+
}
1156+
}, [activeField, geminiInputValue, geminiDisplayValues, onDone, setOAuthStatus])
1157+
1158+
const handleGeminiEnter = useCallback(() => {
1159+
const idx = GEMINI_FIELDS.indexOf(activeField)
1160+
setOAuthStatus(buildGeminiState(activeField, geminiInputValue))
1161+
if (idx === GEMINI_FIELDS.length - 1) {
1162+
doGeminiSave()
1163+
} else {
1164+
const next = GEMINI_FIELDS[idx + 1]!
1165+
setGeminiInputValue(geminiDisplayValues[next] ?? '')
1166+
setGeminiInputCursorOffset((geminiDisplayValues[next] ?? '').length)
1167+
}
1168+
}, [
1169+
activeField,
1170+
buildGeminiState,
1171+
doGeminiSave,
1172+
geminiDisplayValues,
1173+
geminiInputValue,
1174+
setOAuthStatus,
1175+
])
1176+
1177+
useKeybinding(
1178+
'tabs:next',
1179+
() => {
1180+
const idx = GEMINI_FIELDS.indexOf(activeField)
1181+
if (idx < GEMINI_FIELDS.length - 1) {
1182+
setOAuthStatus(
1183+
buildGeminiState(activeField, geminiInputValue, GEMINI_FIELDS[idx + 1]),
1184+
)
1185+
setGeminiInputValue(geminiDisplayValues[GEMINI_FIELDS[idx + 1]!] ?? '')
1186+
setGeminiInputCursorOffset(
1187+
(geminiDisplayValues[GEMINI_FIELDS[idx + 1]!] ?? '').length,
1188+
)
1189+
}
1190+
},
1191+
{ context: 'Tabs' },
1192+
)
1193+
useKeybinding(
1194+
'tabs:previous',
1195+
() => {
1196+
const idx = GEMINI_FIELDS.indexOf(activeField)
1197+
if (idx > 0) {
1198+
setOAuthStatus(
1199+
buildGeminiState(activeField, geminiInputValue, GEMINI_FIELDS[idx - 1]),
1200+
)
1201+
setGeminiInputValue(geminiDisplayValues[GEMINI_FIELDS[idx - 1]!] ?? '')
1202+
setGeminiInputCursorOffset(
1203+
(geminiDisplayValues[GEMINI_FIELDS[idx - 1]!] ?? '').length,
1204+
)
1205+
}
1206+
},
1207+
{ context: 'Tabs' },
1208+
)
1209+
useKeybinding(
1210+
'confirm:no',
1211+
() => {
1212+
setOAuthStatus({ state: 'idle' })
1213+
},
1214+
{ context: 'Confirmation' },
1215+
)
1216+
1217+
const geminiColumns = useTerminalSize().columns - 20
1218+
1219+
const renderGeminiRow = (
1220+
field: GeminiField,
1221+
label: string,
1222+
opts?: { mask?: boolean },
1223+
) => {
1224+
const active = activeField === field
1225+
const val = geminiDisplayValues[field]
1226+
return (
1227+
<Box>
1228+
<Text
1229+
backgroundColor={active ? 'suggestion' : undefined}
1230+
color={active ? 'inverseText' : undefined}
1231+
>
1232+
{` ${label} `}
1233+
</Text>
1234+
<Text> </Text>
1235+
{active ? (
1236+
<TextInput
1237+
value={geminiInputValue}
1238+
onChange={setGeminiInputValue}
1239+
onSubmit={handleGeminiEnter}
1240+
cursorOffset={geminiInputCursorOffset}
1241+
onChangeCursorOffset={setGeminiInputCursorOffset}
1242+
columns={geminiColumns}
1243+
mask={opts?.mask ? '*' : undefined}
1244+
focus={true}
1245+
/>
1246+
) : val ? (
1247+
<Text color="success">
1248+
{opts?.mask
1249+
? val.slice(0, 8) + '\u00b7'.repeat(Math.max(0, val.length - 8))
1250+
: val}
1251+
</Text>
1252+
) : null}
1253+
</Box>
1254+
)
1255+
}
1256+
1257+
return (
1258+
<Box flexDirection="column" gap={1}>
1259+
<Text bold>Gemini API Setup</Text>
1260+
<Text dimColor>
1261+
Configure a Gemini Generate Content compatible endpoint. Base URL is
1262+
optional and defaults to Google&apos;s v1beta API.
1263+
</Text>
1264+
<Box flexDirection="column" gap={1}>
1265+
{renderGeminiRow('base_url', 'Base URL ')}
1266+
{renderGeminiRow('api_key', 'API Key ', { mask: true })}
1267+
{renderGeminiRow('haiku_model', 'Haiku ')}
1268+
{renderGeminiRow('sonnet_model', 'Sonnet ')}
1269+
{renderGeminiRow('opus_model', 'Opus ')}
1270+
</Box>
1271+
<Text dimColor>
1272+
Tab to switch · Enter on last field to save · Esc to go back
1273+
</Text>
1274+
</Box>
1275+
)
1276+
}
1277+
10171278
case 'platform_setup':
10181279
return (
10191280
<Box flexDirection="column" gap={1} marginTop={1}>

src/main.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2434,7 +2434,7 @@ async function run(): Promise<CommanderCommand> {
24342434
// shipped without incident; chicago places itself correctly.
24352435
if (
24362436
feature('CHICAGO_MCP') &&
2437-
getPlatform() === 'macos' &&
2437+
getPlatform() !== 'unknown' &&
24382438
!getIsNonInteractiveSession()
24392439
) {
24402440
try {

0 commit comments

Comments
 (0)