`vendor/MetasequoiaImeEngine` submodule 改为 `engine/` 目录,导入的是 product-lock.json 原本钉住的提交 c810d201,逐文件核对过 blob,因此这是一次 搬迁而不是引擎升级。引擎自己的嵌套 submodule(googlepinyinime-rev、utfcpp、 miniaudio)按各自 gitlink 一并展开,来源与提交见 engine/UPSTREAM.md。 Windows 专门化裁掉了不属于本产品的部分:whisper.cpp 与本地推理 provider、 macOS 示例宿主、整个 dictionary/ 建库流水线、上游 CI 与 release-please 配置。 词库仍按 product-lock.json 在构建时取发布产物。 引擎不再进 product-lock.json:没有 gitlink 就没有可比对的东西,锁里写一个上游 commit 等于宣称这棵树仍等于上游,而第一个 Windows 专门化改动落地时这句话就假了。 verify-contracts / verify-checkout 随之移除,verify-published 保留。 CI 与工具链:引擎源码并入根 scripts/format.sh(删掉引擎自己的那份,避免两套排除 列表分叉);引擎 voice.yml 里的 silero_vad.onnx 摘要校验移到本仓 lock job; engine-bump-triage 工作流与其测试删除;gitsubmodule 的 dependabot 频率由 daily 改为 monthly。 README、README.en、AGENTS.md、CONTRIBUTING、docs/product-release.md 及各子目录 文档同步说明新的位置与边界。
77 lines
3.5 KiB
Python
77 lines
3.5 KiB
Python
"""Real libcurl transport against loopback only; never uses microphone or credentials."""
|
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
from email.parser import BytesParser
|
|
from email.policy import default
|
|
import json
|
|
import subprocess
|
|
import sys
|
|
from socketserver import TCPServer
|
|
import threading
|
|
import time
|
|
|
|
errors = []
|
|
class Handler(BaseHTTPRequestHandler):
|
|
def log_message(self, *_):
|
|
pass
|
|
def do_POST(self):
|
|
try:
|
|
body = self.rfile.read(int(self.headers['Content-Length']))
|
|
assert self.headers.get('Authorization') == 'Bearer fixture-token'
|
|
if self.path == '/asr':
|
|
assert self.headers['Content-Type'].startswith('multipart/form-data; boundary=')
|
|
assert b'name="file"' in body and b'RIFF' in body and b'WAVE' in body
|
|
assert b'name="model"' in body and b'fixture-model' in body
|
|
multipart = BytesParser(policy=default).parsebytes(
|
|
('Content-Type: ' + self.headers['Content-Type'] + '\r\nMIME-Version: 1.0\r\n\r\n').encode() + body)
|
|
parts = {part.get_param('name', header='content-disposition'): part for part in multipart.iter_parts()}
|
|
assert set(parts) == {'model', 'file'}
|
|
assert parts['model'].get_payload(decode=True) == b'fixture-model'
|
|
audio = parts['file'].get_payload(decode=True)
|
|
assert len(audio) == 364 and audio[:4] == b'RIFF' and audio[8:12] == b'WAVE'
|
|
assert parts['file'].get_filename() == 'audio.wav'
|
|
|
|
if self.path == '/polish':
|
|
request = json.loads(body)
|
|
assert request['model'] == 'fixture-model'
|
|
assert request['messages'][1]['content'].startswith('<asr_text>\n')
|
|
if self.path == '/slow':
|
|
time.sleep(2)
|
|
status = 401 if self.path == '/denied' else 302 if self.path == '/redirect' else 200
|
|
self.send_response(status)
|
|
if status == 302: self.send_header('Location', '/asr')
|
|
self.end_headers()
|
|
response = {'text': '水杉 voice'}
|
|
if self.path == '/missing': response = {'other': 'not a transcript'}
|
|
if self.path == '/polish': response = {'choices': [{'message': {'content': '水杉'}}]}
|
|
data = json.dumps(response, ensure_ascii=False).encode()
|
|
if self.path == '/invalid': data = b'invalid json'
|
|
if self.path == '/oversize': data = b'x' * (1024 * 1024 + 1)
|
|
self.wfile.write(data)
|
|
except (BrokenPipeError, ConnectionResetError):
|
|
pass
|
|
except Exception as error:
|
|
errors.append(repr(error))
|
|
self.send_error(500)
|
|
|
|
class LoopbackServer(ThreadingHTTPServer):
|
|
def server_bind(self):
|
|
# HTTPServer.server_bind calls getfqdn(), which may wait on external DNS.
|
|
# This fixture is loopback-only and has no need for reverse DNS.
|
|
TCPServer.server_bind(self)
|
|
self.server_name = 'localhost'
|
|
self.server_port = self.server_address[1]
|
|
|
|
print('Starting loopback HTTP fixture', flush=True)
|
|
server = LoopbackServer(('127.0.0.1', 0), Handler)
|
|
thread = threading.Thread(target=server.serve_forever, daemon=True)
|
|
thread.start()
|
|
try:
|
|
print('Running HTTP consumer', flush=True)
|
|
result = subprocess.run([sys.argv[1], f'http://127.0.0.1:{server.server_port}'], timeout=20)
|
|
assert not errors, errors
|
|
raise SystemExit(result.returncode)
|
|
finally:
|
|
print('Stopping loopback HTTP fixture', flush=True)
|
|
server.shutdown()
|
|
server.server_close()
|