OpenTUI 键盘输入处理完全指南:KeyEvent、粘贴事件、文本选择与 Cline CLI 实战解析
本篇基于 Cline 仓库内置的 OpenTUI 键盘参考文档(keyboard/REFERENCE.md)展开,系统讲解终端 UI 框架 OpenTUI 中键盘事件的三种接入方式(Core EventEmitter、React/Solid Hook)、KeyEvent 数据结构、按键名与修饰键规则、粘贴事件与文本选择机制,并结合 Cline CLI 的 TUI 源码(如 use-root-keyboard.ts、clipboard.ts)说明这些 API 在真实产品中的应用方式。读完后你可以为终端应用设计完整的键盘快捷键体系,并理解 Cline CLI 中 Ctrl+C、双击 Esc 回退、Shift+Tab 切换自动批准等交互的底层实现。
三种键盘接入方式
OpenTUI 对同一套底层键盘事件提供了三个层次的封装,分别适配不同技术栈:
- Core(命令式):
renderer.keyInputEventEmitter,监听"keypress"与"paste"事件,性能与可控性最高,也是构建框架/库时的首选; - React:
useKeyboard()Hook(来自@opentui/react),在组件内声明式注册按键回调; - Solid:
useKeyboard()Hook(来自@opentui/solid),API 与 React 版本一致。
Cline CLI 当前实际使用 Core + React 组合,@opentui/core 与 @opentui/react 均锁定在 0.4.3(见 apps/cli/package.json)。其 TUI 入口通过 createCliRenderer 创建渲染器:
// apps/cli/src/tui/index.tsx
const renderer = await createCliRenderer({
exitOnCtrlC: false, // 不交给进程默认行为处理 Ctrl+C,而是走键盘事件流
autoFocus: false,
enableMouseMovement: true, // 开启鼠标以支持文本选择
});
这里的 exitOnCtrlC: false 正是后文“常见陷阱”一节提到的关键配置:它让 Ctrl+C 作为一个普通的 KeyEvent 进入 keyInput 事件流,应用代码才能决定是“清空输入框”还是“退出程序”。
KeyEvent 对象:所有按键回调的统一入参
所有键盘处理器都会收到一个 KeyEvent 对象,它是按键判定的唯一依据:
interface KeyEvent {
name: string // 按键名:"a"、"escape"、"f1" 等
sequence: string // 原始转义序列(escape sequence)
ctrl: boolean // 是否按住 Ctrl
shift: boolean // 是否按住 Shift
meta: boolean // 是否按住 Alt(meta 在多数系统中即 Alt)
option: boolean // 是否按住 Option(macOS 特有)
eventType: "press" | "release" | "repeat"
repeated: boolean // 是否为长按产生的重复事件
}
理解这 8 个字段是掌握 OpenTUI 键盘处理的基础:name 给出逻辑按键,修饰键字段给出组合状态,eventType 与 repeated 则描述按键生命周期阶段。
基础用法
Core 命令式:直接在渲染器的 keyInput 事件发射器上挂监听器:
import { createCliRenderer, type KeyEvent } from "@opentui/core"
const renderer = await createCliRenderer()
renderer.keyInput.on("keypress", (key: KeyEvent) => {
if (key.name === "escape") {
renderer.destroy()
return
}
if (key.ctrl && key.name === "s") {
saveDocument()
}
})
React 声明式:在组件内调用 useKeyboard,并通过 useRenderer() 拿到渲染器实例:
import { useKeyboard, useRenderer } from "@opentui/react"
function App() {
const renderer = useRenderer()
useKeyboard((key) => {
if (key.name === "escape") {
renderer.destroy()
}
})
return <text>Press ESC to exit</text>
}
Solid 声明式:与 React 版本逐行对应,只是从 @opentui/solid 导入:
import { useKeyboard, useRenderer } from "@opentui/solid"
function App() {
const renderer = useRenderer()
useKeyboard((key) => {
if (key.name === "escape") {
renderer.destroy()
}
})
return <text>Press ESC to exit</text>
}
按键名体系:如何判断用户按了什么
OpenTUI 将物理按键归一化为若干类按键名,判定时按类别对照:
字母键
小写字母:a、b、c … z。
需要判断大写时,检查修饰键组合:key.shift && key.name === "a" 即表示按下了 Shift+A。也就是说大写字母不产生独立的 name,而是“小写 name + shift 标志”。
数字键
0、1、2 … 9。
功能键
f1、f2、f3 … f12。
特殊键
| 按键名 | 说明 |
|---|---|
escape |
Esc 键 |
enter |
回车 |
return |
回车(enter 的别名,两者等价) |
tab |
Tab 键 |
backspace |
退格 |
delete |
Delete 键 |
space |
空格 |
enter 与 return 是同一个物理键的两个名字,因此稳健的写法是 key.name === "enter" || key.name === "return"。Cline CLI 的队列提示处理中就是这样同时匹配两个名字的(见 use-root-keyboard.ts 中对 key.name === "enter" || key.name === "return" 的判断)。
方向键
| 按键名 | 说明 |
|---|---|
up |
上箭头 |
down |
下箭头 |
left |
左箭头 |
right |
右箭头 |
导航键
| 按键名 | 说明 |
|---|---|
home |
Home |
end |
End |
pageup |
Page Up |
pagedown |
Page Down |
insert |
Insert |
修饰键与组合键
修饰键不是独立的按键名,而是 KeyEvent 上的布尔标志位。逐个检查即可还原组合:
renderer.keyInput.on("keypress", (key) => {
if (key.ctrl && key.name === "c") {
// Ctrl+C
}
if (key.shift && key.name === "tab") {
// Shift+Tab
}
if (key.meta && key.name === "s") {
// Alt+S(meta 在多数系统中等同 Alt)
}
if (key.option && key.name === "a") {
// Option+A(macOS)
}
})
多修饰键叠加时按位与即可:
// Ctrl+Shift+S
if (key.ctrl && key.shift && key.name === "s") {
saveAs()
}
// Ctrl+Alt+Delete(注意:可能撞上系统级快捷键!)
if (key.ctrl && key.meta && key.name === "delete") {
// ...
}
Cline CLI 的 use-root-keyboard.ts 就利用了 shift 标志区分了同键不同修饰的行为:普通 tab 切换“计划/执行”模式(onToggleMode()),而 key.shift && key.name === "tab" 切换自动批准开关(session.toggleAutoApprove())。这正是原文档 Shift+Tab 示例在产品中的落地。
事件类型:press、repeat 与 release
按下的事件(默认)
"keypress" 事件默认覆盖按下行为,eventType === "press" 表示该键首次按下:
renderer.keyInput.on("keypress", (key) => {
if (key.eventType === "press") {
// 首次按下
}
})
重复事件
长按某键时,终端会持续上报 eventType === "repeat" 的事件,同时 repeated 为 true:
renderer.keyInput.on("keypress", (key) => {
if (key.eventType === "repeat" || key.repeated) {
// 按键正在被长按
}
})
对“切换选中项”这类不希望长按快速连跳的场景,应过滤掉 repeat 事件。
释放事件(需显式开启)
release 事件是选择性启用的:默认收不到按键释放,必须通过第二个参数 { release: true } 打开:
// React
useKeyboard(
(key) => {
if (key.eventType === "release") {
// 按键抬起
}
},
{ release: true } // 启用 release 事件
)
// Solid
useKeyboard(
(key) => {
if (key.eventType === "release") {
// 按键抬起
}
},
{ release: true }
)
实战模式库:菜单、弹窗、Vim 模式与游戏控制
原文档给出了五个经过验证的交互模式,是构建终端应用时可直接套用的参考实现。
导航菜单(vim 式 hjk 快捷键)
function Menu() {
const [selectedIndex, setSelectedIndex] = useState(0)
const items = ["Home", "Settings", "Help", "Quit"]
useKeyboard((key) => {
switch (key.name) {
case "up":
case "k":
setSelectedIndex(i => Math.max(0, i - 1))
break
case "down":
case "j":
setSelectedIndex(i => Math.min(items.length - 1, i + 1))
break
case "enter":
handleSelect(items[selectedIndex])
break
}
})
return (
<box flexDirection="column">
{items.map((item, i) => (
<text
key={item}
fg={i === selectedIndex ? "#00FF00" : "#FFFFFF"}
>
{i === selectedIndex ? "> " : " "}{item}
</text>
))}
</box>
)
}
该模式的核心技巧:用 Math.max/Math.min 钳制索引边界,并用 fg 属性高亮当前选中项。Cline CLI 的 onboarding 流程 useOnboardingKeyboard 实现了同一套逻辑——up/down 在菜单项间循环移动、return 触发当前项动作,甚至额外支持 ctrl+p/ctrl+n 作为上下方向键的别名(终端 Emacs 习惯)。
模态框 Esc 关闭
function Modal({ onClose, children }) {
useKeyboard((key) => {
if (key.name === "escape") {
onClose()
}
})
return (
<box border padding={2}>
{children}
</box>
)
}
Vim 风格双模式编辑器
function Editor() {
const [mode, setMode] = useState<"normal" | "insert">("normal")
const [content, setContent] = useState("")
useKeyboard((key) => {
if (mode === "normal") {
switch (key.name) {
case "i":
setMode("insert")
break
case "escape":
// Already in normal mode
break
case "j":
moveCursorDown()
break
case "k":
moveCursorUp()
break
}
} else if (mode === "insert") {
if (key.name === "escape") {
setMode("normal")
}
// Input component handles text in insert mode
}
})
return (
<box flexDirection="column">
<text>Mode: {mode}</text>
<textarea
value={content}
onChange={setContent}
focused={mode === "insert"}
/>
</box>
)
}
注意最后 <textarea focused={mode === "insert"} />:焦点状态直接由模式驱动,insert 模式下文本输入交给输入组件,normal 模式下 focused 为假、输入组件不再消费字符,全局快捷键因此生效。
游戏控制(基于按键集合)
function Game() {
const [pressed, setPressed] = useState(new Set<string>())
useKeyboard(
(key) => {
setPressed(keys => {
const newKeys = new Set(keys)
if (key.eventType === "release") {
newKeys.delete(key.name)
} else {
newKeys.add(key.name)
}
return newKeys
})
},
{ release: true }
)
// Game logic uses pressed set
useEffect(() => {
if (pressed.has("up") || pressed.has("w")) {
moveUp()
}
if (pressed.has("down") || pressed.has("s")) {
moveDown()
}
}, [pressed])
return <text>WASD or arrows to move</text>
}
这个模式展示了 release: true 的典型用途:维护“当前被按住的所有按键”集合。没有 release 事件,就无法把松开某个键表达出来。
快捷键帮助面板
function ShortcutsHelp() {
const shortcuts = [
{ keys: "Ctrl+S", action: "Save" },
{ keys: "Ctrl+Q", action: "Quit" },
{ keys: "Ctrl+F", action: "Find" },
{ keys: "Tab", action: "Next field" },
{ keys: "Shift+Tab", action: "Previous field" },
]
return (
<box border title="Keyboard Shortcuts" padding={1}>
{shortcuts.map(({ keys, action }) => (
<box key={keys} flexDirection="row">
<text width={15} fg="#00FFFF">{keys}</text>
<text>{action}</text>
</box>
))}
</box>
)
}
粘贴事件:拿到的是一串字节,不是文本
粘贴(Paste)事件与按键事件走同一个 keyInput 发射器,但载荷完全不同:Paste 事件交付的是原始字节(raw bytes),不是解码后的文本。
PasteEvent 对象结构
import { type PasteEvent } from "@opentui/core"
interface PasteEvent {
type: "paste" // 恒为 "paste"
bytes: Uint8Array // 原始粘贴字节
metadata?: PasteMetadata // 可选元数据
preventDefault(): void // 阻止默认粘贴处理
defaultPrevented: boolean // preventDefault 是否已被调用
}
interface PasteMetadata {
mimeType?: string // MIME 类型(如可用)
kind?: PasteKind // 粘贴种类
}
解码粘贴字节
@opentui/core 提供两个工具函数:decodePasteBytes 负责把字节按 UTF-8 解码为字符串;stripAnsiSequences 负责去除剪贴板内容里夹带的 ANSI 转义码(从富文本终端复制内容时常见):
import { decodePasteBytes, stripAnsiSequences } from "@opentui/core"
const text = decodePasteBytes(event.bytes) // 解码 UTF-8
const clean = stripAnsiSequences(decodePasteBytes(event.bytes)) // 解码 + 去除 ANSI
Core 监听方式
import { type PasteEvent, decodePasteBytes } from "@opentui/core"
renderer.keyInput.on("paste", (event: PasteEvent) => {
const text = decodePasteBytes(event.bytes)
console.log("Pasted:", text)
})
Solid 专属 Hook
Solid 提供了专门的 usePaste Hook:
import { usePaste } from "@opentui/solid"
import { decodePasteBytes } from "@opentui/core"
function App() {
usePaste((event) => {
const text = decodePasteBytes(event.bytes)
console.log("Pasted:", text)
})
return <text>Paste something</text>
}
注意:
usePaste仅限 Solid。React 没有对应 Hook——React 应用需要通过 Core 事件发射器(renderer.keyInput.on("paste", ...))或输入组件的onChange处理粘贴。
文本选择:renderer 统一管理的跨组件 Selection
文本选择由渲染器集中管理,而不是各组件各自为政:renderer 内部持有单个 Selection 对象,遍历可渲染树找到所有参与选择的子节点,并在用户完成选择(鼠标抬起)时发出 "selection" 事件。Selection 对象会自动聚合所有选中 renderable 的文本。
让 renderable 参与选择
renderable 必须设置 selectable: true 才能参与选择。文本类 renderable(TextRenderable、TextareaRenderable、ASCIIFontRenderable、TextTableRenderable)支持该属性:
// React / Solid
<text selectable>This text can be selected</text>
// Core
const text = new TextRenderable(renderer, {
id: "label",
content: "This text can be selected",
selectable: true,
})
Cline CLI 在创建渲染器时开启了 enableMouseMovement: true(见 index.tsx),正是为了让这类鼠标驱动的选择交互可用。
选择后复制(Core)
监听 renderer 的 "selection" 事件。Selection 对象的 getSelectedText() 返回按阅读顺序聚合了所有选中 renderable 的文本:
import type { Selection } from "@opentui/core"
renderer.on("selection", (selection: Selection) => {
const text = selection.getSelectedText()
if (text) {
renderer.copyToClipboardOSC52(text)
}
})
重要:务必调用事件回调里
Selection对象的selection.getSelectedText(),而不是renderer.root.getSelectedText()。单个 renderable 只会返回自己内部的选中文本,只有Selection对象才做跨树聚合。
选择后复制(Solid)
import { useSelectionHandler } from "@opentui/solid"
function App() {
useSelectionHandler((selection) => {
const text = selection.getSelectedText()
if (text) {
renderer.copyToClipboardOSC52(text)
}
})
return <text selectable>Select this text</text>
}
注意:
useSelectionHandler仅限 Solid。React 应用应使用 Core 的renderer.on("selection", ...)事件。
Selection 对象 API
事件回调传入的 Selection 对象暴露:
selection.getSelectedText() // 聚合所有选中 renderable 的文本
selection.bounds // { startX, startY, endX, endY } 包围矩形
selection.selectedRenderables // 当前持有选中态的 Renderable[]
selection.isActive // 选择是否仍活跃
单个 renderable 也各自暴露:
renderable.hasSelection() // 该 renderable 是否存在选中文本?
renderable.getSelectedText() // 仅该 renderable 内的选中文本
选择遍历机制
用户拖拽选择时,renderer 的处理流程是:
- 识别选择容器(起点与终点的公共祖先);
- 遍历选择边界内所有
selectable后代; - 对每个后代调用
onSelectionChanged(selection),计算局部选中区域; - 将拥有活跃选择的 renderable 记录进
selection.selectedRenderables。
由此可以推断一个实用结论:选择天然跨组件——从两个相邻的 <text selectable> 上拖过,两个元素的文本都会被选中,且 selection.getSelectedText() 会用换行符把多段文本连接起来。
Cline CLI 的 root.tsx 中就把 copyToClipboardOSC52 包进了统一工具函数,让选择复制、按钮复制等多个调用点复用同一条“OSC 52 优先、系统剪贴板兜底”的链路。
剪贴板 API(OSC 52)与 Cline 的兜底策略
终端程序无法直接访问系统剪贴板,OpenTUI 的解法是 OSC 52 转义序列:它由终端解释并写回用户本地机器的剪贴板,因此在 SSH 远程会话中依然有效,且被大多数现代终端模拟器支持。
// 复制文本到剪贴板
const success = renderer.copyToClipboardOSC52("text to copy")
// 先探测终端是否支持 OSC 52
if (renderer.isOsc52Supported()) {
renderer.copyToClipboardOSC52("Hello!")
}
// 清空剪贴板
renderer.clearClipboardOSC52()
// 指定目标剪贴板(X11 场景)
import { ClipboardTarget } from "@opentui/core"
renderer.copyToClipboardOSC52("text", ClipboardTarget.Primary) // X11 主选择区
renderer.copyToClipboardOSC52("text", ClipboardTarget.Clipboard) // 系统剪贴板(默认)
Cline CLI 在此之上叠加了一层工程化兜底,值得单独拆解(apps/cli/src/tui/utils/clipboard.ts):
- SSH 会话下默认跳过本地命令兜底:OSC 52 写的是用户本地终端的剪贴板;而直接 spawn
pbcopy/xclip会写远程主机的剪贴板。源码通过检测SSH_CONNECTION/SSH_CLIENT/SSH_TTY环境变量判断远程会话,默认直接放弃兜底(返回false),只有显式设置CLINE_CLIPBOARD_FALLBACK_REMOTE=1才写远端剪贴板; - 按平台选择命令链:macOS 用
pbcopy(并强制LANG=en_US.UTF-8环境避免编码问题);Windows 用 PowerShell 的Set-Clipboard(powershell.exe与pwsh.exe各试一次);WSL 走 PowerShell 以写入 Windows 剪贴板;普通 Linux 依次尝试wl-copy(Wayland)与xclip -selection clipboard(X11); - 超时与可观测性:每条命令带 1500ms 超时(
CLIPBOARD_COMMAND_TIMEOUT_MS),设CLINE_DEBUG_CLIPBOARD=1可打印跳过或失败原因,默认关闭以保持 TUI 画布干净。
也就是说,OSC 52 是“跨 SSH 的正确通道”,本地命令是“终端不支持 OSC 52 时的备用通道”,两者按平台与连接方式组合成一条可靠的复制链路。
焦点与输入组件的冲突规避
输入组件(<input>、<textarea>、<select>)在获得焦点时会消费键盘事件,但全局 useKeyboard 依然会收到事件:
<input focused /> // 接收键盘输入
// 全局 useKeyboard still fires, but input consumes characters
推荐的做法是记录输入焦点状态,在焦点位于输入组件时主动让路:
function App() {
const renderer = useRenderer()
const [inputFocused, setInputFocused] = useState(false)
useKeyboard((key) => {
if (inputFocused) return // 让输入组件处理
// Global shortcuts
if (key.name === "escape") {
renderer.destroy()
}
})
return (
<input
focused={inputFocused}
onFocus={() => setInputFocused(true)}
onBlur={() => setInputFocused(false)}
/>
)
}
Cline CLI 的主界面没有用 onFocus 回调,而是采用了“按键特征预判”策略:use-root-keyboard.ts 先计算当前按键是否属于“可能正在编辑 textarea”的按键(单字符、backspace、delete、space 且无 Ctrl/Meta),若是则先从 textarea 同步最新输入文本,再统一判断快捷键;而像 Tab、Esc、Ctrl+ 组合这类结构性按键则直接走全局逻辑。两套思路殊途同归,核心都是让快捷键判断与文本输入解耦。
从源码看 Cline CLI 的完整快捷键体系
Cline CLI 的根级键盘 Hook useRootKeyboard 是原文档各概念的一次集中实战,值得逐条对照:
| 快捷键 | 行为 | 源码位置 |
|---|---|---|
| Ctrl+C | 输入框有内容时清空输入;无内容时退出 | use-root-keyboard.ts |
| Ctrl+D | 空闲且无输入文本时退出 | use-root-keyboard.ts |
| Esc | 分级响应:取消编辑 → 中止运行 → 取消队列选择 → 300ms 内连按两次触发检查点回退 | use-root-keyboard.ts |
| Tab / Shift+Tab | 切换模式 / 切换自动批准 | use-root-keyboard.ts |
| Ctrl+L | 运行中清屏;空闲时清空会话 | use-root-keyboard.ts |
| Ctrl+S | 运行中携带输入时以 steer 方式提交 | use-root-keyboard.ts |
| Ctrl+P | 空闲时打开命令面板 | use-root-keyboard.ts |
| 上/下箭头 | 队列提示选择、输入历史导航(preventDefault 阻止 textarea 默认滚动) |
use-root-keyboard.ts |
几个细节印证了原文档的原则:
- 双击 Esc 判定用一个
useRef记录上次 Esc 时间戳,间隔小于 300ms 视为双击并触发onRestoreCheckpoint()(见 第 243-251 行)——这正是“利用 press 事件做时间序列判定”的范例; key.preventDefault()在需要阻断默认行为的分支上被显式调用(如选中队列提示后的上/下/Tab/Enter),说明 KeyEvent 同样具备事件拦截能力;- 多状态机路由:onboarding 阶段的 useOnboardingKeyboard 把 Esc 定义为“逐级返回”(oauth_pending → 中止 OAuth 回菜单、device_code → 中止设备码流程、byo_apikey → 回 provider 选择……),体现了原文档“Multiple Handlers / 协调处理器防止冲突”的建议——不同视图各自持有键盘逻辑,由
appView与isDialogOpen等状态门控决定哪个处理器生效。
另外,commands/auth.ts 中非 TUI 的交互命令同样使用 exitOnCtrlC: false,说明该配置是 Cline 全产品线的统一约定:进程永远不依赖 shell 默认的 SIGINT 行为,退出意图一律由键盘事件流显式表达。
常见陷阱(Gotchas)
原文档明确列出三类终端环境限制,开发时须提前规避:
终端/操作系统的按键抢占
部分组合键会被终端或 OS 先行截获:
Ctrl+C通常发送 SIGINT——必须设置exitOnCtrlC: false才能在自己的事件流里处理(Cline 即如此);Ctrl+Z会挂起进程(SIGTSTP);- 某些功能键可能同样被终端拦截。
SSH 与远程会话
按键的转义序列经过 SSH 隧道转发后可能发生变化,键位识别在 SSH 下的表现可能不一致,必须在目标环境实测。剪贴板同理:本地命令兜底会写错机器,需按 clipboard.ts 的思路处理。
多个键盘处理器并存
多个 useKeyboard 调用都会收到每个事件。这是框架刻意保留的广播语义(便于不同组件各自响应),但也要求开发者自行协调处理顺序与 preventDefault,防止两个处理器对同一按键做出冲突动作。Cline CLI 通过“视图状态 + 对话框状态 + 编辑特征预判”三层门控来协调,可参考其组织方式。
相关文档
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00