Something went wrong. Try again.
sloproxy
Something went wrong. Try again.
24 kB · 679 lines
Python
at commit 2aeb41ce
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680#!/usr/bin/env python3from __future__ import annotations
import jsonfrom pathlib import Pathfrom typing import NamedTuple
ROOT = Path(__file__).resolve().parents[1]ABI_VERSION = 1SCHEMA_VERSION = 1
class Capability(NamedTuple): slug: str title: str capability_json: str methods: tuple[str, ...] description_cn: str description_en: str
CAPABILITIES = ( Capability("model", "Model", '"model_provider":true', ("model.static", "model.for_auth"), "模型能力示例,只返回静态模型和按认证发现模型。", "Model capability example with static and auth-bound models."), Capability("auth", "Auth", '"auth_provider":true', ("auth.identifier", "auth.parse", "auth.login.start", "auth.login.poll", "auth.refresh"), "认证能力示例,演示解析、登录、轮询和刷新。", "Auth capability example with parse, login, poll, and refresh."), Capability("frontend-auth", "Frontend Auth", '"frontend_auth_provider":true', ("frontend_auth.identifier", "frontend_auth.authenticate"), "前端认证能力示例,演示代理入口前认证。", "Frontend auth capability example."), Capability("executor", "Executor", '"executor":true,"executor_model_scope":"both","executor_input_formats":["chat-completions"],"executor_output_formats":["chat-completions"]', ("executor.identifier", "executor.execute", "executor.execute_stream", "executor.count_tokens", "executor.http_request"), "执行器能力示例,演示普通执行、流式执行、计数和 HTTP 请求。", "Executor capability example."), Capability("protocol-format", "Protocol Format", '"executor":true,"executor_model_scope":"both","executor_input_formats":["chat-completions"],"executor_output_formats":["responses"]', ("executor.identifier", "executor.execute"), "协议格式适配示例,用最小执行器承载格式声明。", "Protocol format example carried by a minimal executor."), Capability("request-translator", "Request Translator", '"request_translator":true', ("request.translate",), "请求转换能力示例。", "Request translator capability example."), Capability("request-normalizer", "Request Normalizer", '"request_normalizer":true', ("request.normalize",), "请求规整能力示例。", "Request normalizer capability example."), Capability("response-translator", "Response Translator", '"response_translator":true', ("response.translate",), "响应转换能力示例。", "Response translator capability example."), Capability("response-normalizer", "Response Normalizer", '"response_before_translator":true,"response_after_translator":true', ("response.normalize_before", "response.normalize_after"), "响应规整能力示例。", "Response normalizer capability example."), Capability("thinking", "Thinking", '"thinking_applier":true', ("thinking.identifier", "thinking.apply"), "Thinking 能力示例。", "Thinking applier capability example."), Capability("usage", "Usage", '"usage_plugin":true', ("usage.handle",), "Usage 能力示例。", "Usage observer capability example."), Capability("cli", "CLI", '"command_line_plugin":true', ("command_line.register", "command_line.execute"), "命令行扩展能力示例。", "Command-line capability example."), Capability("management-api", "Management API", '"management_api":true', ("management.register", "management.handle"), "Management API 扩展能力示例。", "Management API capability example."), Capability("host-callback", "Host Callback", '"management_api":true', ("management.register", "management.handle"), "Host callback 示例,用最小 Management API 入口触发宿主 HTTP 和日志回调。", "Host callback example carried by a minimal Management API route."),)
def plugin_id(cap: Capability, lang: str) -> str: return f"example-{cap.slug}-{lang}"
def write(path: Path, content: str) -> None: path.parent.mkdir(parents=True, exist_ok=True) path.write_text(content, encoding="utf-8")
def json_string(value: str) -> str: return json.dumps(value)
def compact_json(value: object) -> str: return json.dumps(value, separators=(",", ":"))
def c_ident(slug: str) -> str: return slug.replace("-", "_")
def registration_result(cap: Capability, lang: str) -> str: pid = plugin_id(cap, lang) return ( "{" f'"schema_version":{SCHEMA_VERSION},' '"metadata":{' f'"Name":{json.dumps(pid)},' '"Version":"0.1.0",' '"Author":"router-for-me",' '"GitHubRepository":"https://github.com/router-for-me/CLIProxyAPI",' f'"Logo":"https://example.invalid/{pid}.png",' '"ConfigFields":[]' "}," f'"capabilities":{{{cap.capability_json}}}' "}" )
def model_result(cap: Capability, lang: str) -> str: pid = plugin_id(cap, lang) return ( "{" f'"Provider":{json.dumps(pid)},' '"Models":[{' f'"ID":{json.dumps(pid + "-model")},' '"Object":"model",' f'"OwnedBy":{json.dumps(pid)},' f'"DisplayName":{json.dumps(cap.title + " Example Model")},' '"SupportedGenerationMethods":["chat"],' '"ContextLength":8192,' '"MaxCompletionTokens":1024,' '"UserDefined":true' "}]" "}" )
def auth_data_result(cap: Capability, lang: str) -> str: pid = plugin_id(cap, lang) return ( "{" f'"Provider":{json.dumps(pid)},' f'"ID":{json.dumps(pid)},' f'"FileName":{json.dumps(pid + ".json")},' f'"Label":{json.dumps(cap.title + " Example")},' f'"StorageJSON":{json.dumps(base64_json({"type": pid, "token": "example-token"}))},' f'"Metadata":{{"type":{json.dumps(pid)}}}' "}" )
def base64_json(value: object) -> str: import base64
raw = json.dumps(value, separators=(",", ":")).encode() return base64.b64encode(raw).decode()
def result_for_method(cap: Capability, lang: str, method: str) -> str: pid = plugin_id(cap, lang) if method in ("plugin.register", "plugin.reconfigure"): return registration_result(cap, lang) if method == "model.static" or method == "model.for_auth": return model_result(cap, lang) if method.endswith(".identifier"): return f'{{"identifier":{json.dumps(pid)}}}' if method == "auth.parse": return f'{{"Handled":true,"Auth":{auth_data_result(cap, lang)}}}' if method == "auth.login.start": return f'{{"Provider":{json.dumps(pid)},"URL":"https://example.invalid/login","State":"example-state","ExpiresAt":"2030-01-01T00:00:00Z"}}' if method == "auth.login.poll": return f'{{"Status":"success","Message":"example login complete","Auth":{auth_data_result(cap, lang)}}}' if method == "auth.refresh": return f'{{"Auth":{auth_data_result(cap, lang)},"NextRefreshAfter":"2030-01-01T00:00:00Z"}}' if method == "frontend_auth.authenticate": return compact_json({"Authenticated": True, "Principal": pid, "Metadata": {"provider": pid}}) if method == "executor.execute": return compact_json({"Payload": base64_json({"id": pid, "object": "chat.completion"}), "Headers": {"content-type": ["application/json"]}}) if method == "executor.execute_stream": return compact_json({"headers": {"content-type": ["text/event-stream"]}, "chunks": [{"Payload": base64_json("data: " + pid + "\n\n")}]}) if method == "executor.count_tokens": return compact_json({"Payload": base64_json({"total_tokens": 0})}) if method == "executor.http_request": return compact_json({"StatusCode": 200, "Headers": {"content-type": ["application/json"]}, "Body": base64_json({"plugin": pid})}) if method == "request.translate": return compact_json({"Body": base64_json({"translated_by": pid})}) if method == "request.normalize": return compact_json({"Body": base64_json({"normalized_by": pid})}) if method == "response.translate": return compact_json({"Body": base64_json({"response_translated_by": pid})}) if method == "response.normalize_before": return compact_json({"Body": base64_json({"response_normalized_before_by": pid})}) if method == "response.normalize_after": return compact_json({"Body": base64_json({"response_normalized_after_by": pid})}) if method == "thinking.apply": return compact_json({"Body": base64_json({"thinking_applied_by": pid})}) if method == "usage.handle": return "{}" if method == "command_line.register": return f'{{"Flags":[{{"Name":{json.dumps(pid + "-command")},"Usage":"Run the example plugin command","Type":"bool"}}]}}' if method == "command_line.execute": return f'{{"Stdout":{json.dumps(base64_json(pid + " command executed\\n"))},"ExitCode":0}}' if method == "management.register": return f'{{"routes":[{{"Method":"GET","Path":"/plugins/{pid}/status","Menu":{json.dumps(cap.title)},"Description":{json.dumps(cap.description_en)}}}]}}' if method == "management.handle": return compact_json({"StatusCode": 200, "Headers": {"content-type": ["application/json"]}, "Body": base64_json({"plugin": pid})}) raise ValueError(f"unsupported method {method}")
def envelope(result: str) -> str: return f'{{"ok":true,"result":{result}}}'
def error_envelope(code: str, message: str) -> str: return json.dumps({"ok": False, "error": {"code": code, "message": message}}, separators=(",", ":"))
def methods_for(cap: Capability) -> tuple[str, ...]: return ("plugin.register", "plugin.reconfigure", *cap.methods)
def generate_go(cap: Capability) -> None: slug = cap.slug pid = plugin_id(cap, "go") method_cases = [] for method in methods_for(cap): host_callback_call = "" if slug == "host-callback" and method == "management.handle": host_callback_call = f"""\t\tcallHost("host.log", []byte(`{{"level":"info","message":"{pid} host callback log","fields":{{"plugin":"{pid}"}}}}`))\t\tcallHost("host.http.do", []byte(`{{"method":"GET","url":"https://example.com","headers":{{"user-agent":["{pid}"]}}}}`))""" method_cases.append(f'\tcase "{method}":\n{host_callback_call}\t\treturn okEnvelopeJSON({json.dumps(result_for_method(cap, "go", method))})') go_mod = f"""module github.com/router-for-me/CLIProxyAPI/v7/examples/plugin/{slug}/go
go 1.26""" go_main = f"""package main
/*#include <stdint.h>#include <stdlib.h>
typedef struct {{\tvoid* ptr;\tsize_t len;}} cliproxy_buffer;
typedef int (*cliproxy_host_call_fn)(void*, const char*, const uint8_t*, size_t, cliproxy_buffer*);typedef void (*cliproxy_host_free_fn)(void*, size_t);
typedef struct {{\tuint32_t abi_version;\tvoid* host_ctx;\tcliproxy_host_call_fn call;\tcliproxy_host_free_fn free_buffer;}} cliproxy_host_api;
typedef int (*cliproxy_plugin_call_fn)(char*, uint8_t*, size_t, cliproxy_buffer*);typedef void (*cliproxy_plugin_free_fn)(void*, size_t);typedef void (*cliproxy_plugin_shutdown_fn)(void);
typedef struct {{\tuint32_t abi_version;\tcliproxy_plugin_call_fn call;\tcliproxy_plugin_free_fn free_buffer;\tcliproxy_plugin_shutdown_fn shutdown;}} cliproxy_plugin_api;
extern int cliproxyPluginCall(char*, uint8_t*, size_t, cliproxy_buffer*);extern void cliproxyPluginFree(void*, size_t);extern void cliproxyPluginShutdown(void);
static const cliproxy_host_api* stored_host;
static void store_host_api(const cliproxy_host_api* host) {{\tstored_host = host;}}
static int call_host_api(const char* method, const uint8_t* request, size_t request_len, cliproxy_buffer* response) {{\tif (stored_host == NULL || stored_host->call == NULL) {{\t\treturn 1;\t}}\treturn stored_host->call(stored_host->host_ctx, method, request, request_len, response);}}
static void free_host_buffer(void* ptr, size_t len) {{\tif (stored_host != NULL && stored_host->free_buffer != NULL && ptr != NULL) {{\t\tstored_host->free_buffer(ptr, len);\t}}}}*/import "C"
import (\t"encoding/json"\t"net/http"\t"time"\t"unsafe")
const abiVersion uint32 = {ABI_VERSION}
type envelope struct {{\tOK bool `json:"ok"`\tResult json.RawMessage `json:"result,omitempty"`\tError *envelopeError `json:"error,omitempty"`}}
type envelopeError struct {{\tCode string `json:"code"`\tMessage string `json:"message"`}}
func main() {{}}
//export cliproxy_plugin_initfunc cliproxy_plugin_init(host *C.cliproxy_host_api, plugin *C.cliproxy_plugin_api) C.int {{\tif plugin == nil {{\t\treturn 1\t}}\tC.store_host_api(host)\tplugin.abi_version = C.uint32_t(abiVersion)\tplugin.call = C.cliproxy_plugin_call_fn(C.cliproxyPluginCall)\tplugin.free_buffer = C.cliproxy_plugin_free_fn(C.cliproxyPluginFree)\tplugin.shutdown = C.cliproxy_plugin_shutdown_fn(C.cliproxyPluginShutdown)\treturn 0}}
//export cliproxyPluginCallfunc cliproxyPluginCall(method *C.char, request *C.uint8_t, requestLen C.size_t, response *C.cliproxy_buffer) C.int {{\tif response != nil {{\t\tresponse.ptr = nil\t\tresponse.len = 0\t}}\tif method == nil {{\t\twriteResponse(response, errorEnvelope("invalid_method", "method is required"))\t\treturn 1\t}}\traw, errHandle := handleMethod(C.GoString(method))\tif errHandle != nil {{\t\twriteResponse(response, errorEnvelope("plugin_error", errHandle.Error()))\t\treturn 1\t}}\twriteResponse(response, raw)\t_ = request\t_ = requestLen\treturn 0}}
//export cliproxyPluginFreefunc cliproxyPluginFree(ptr unsafe.Pointer, len C.size_t) {{\tif ptr != nil {{\t\tC.free(ptr)\t}}\t_ = len}}
//export cliproxyPluginShutdownfunc cliproxyPluginShutdown() {{}}
func handleMethod(method string) ([]byte, error) {{\t_ = http.StatusOK\t_ = time.Second\tswitch method {{{chr(10).join(method_cases)}\tdefault:\t\treturn errorEnvelope("unknown_method", "unknown method: "+method), nil\t}}}}
func okEnvelopeJSON(result string) ([]byte, error) {{\treturn json.Marshal(envelope{{OK: true, Result: json.RawMessage(result)}})}}
func errorEnvelope(code, message string) []byte {{\traw, _ := json.Marshal(envelope{{OK: false, Error: &envelopeError{{Code: code, Message: message}}}})\treturn raw}}
func writeResponse(response *C.cliproxy_buffer, raw []byte) {{\tif response == nil || len(raw) == 0 {{\t\treturn\t}}\tptr := C.CBytes(raw)\tif ptr == nil {{\t\treturn\t}}\tresponse.ptr = ptr\tresponse.len = C.size_t(len(raw))}}
func callHost(method string, payload []byte) {{\tcMethod := C.CString(method)\tdefer C.free(unsafe.Pointer(cMethod))\tvar response C.cliproxy_buffer\tvar req *C.uint8_t\tif len(payload) > 0 {{\t\treq = (*C.uint8_t)(C.CBytes(payload))\t\tdefer C.free(unsafe.Pointer(req))\t}}\tif C.call_host_api(cMethod, req, C.size_t(len(payload)), &response) == 0 && response.ptr != nil {{\t\tC.free_host_buffer(response.ptr, response.len)\t}}}}""" write(ROOT / slug / "go" / "go.mod", go_mod) write(ROOT / slug / "go" / "main.go", go_main)
def c_string(value: str) -> str: return json.dumps(value)
def generate_c(cap: Capability) -> None: slug = cap.slug ident = c_ident(slug) pid = plugin_id(cap, "c") cases = [] for method in methods_for(cap): result = envelope(result_for_method(cap, "c", method)) host_call = "" if slug == "host-callback" and method == "management.handle": host_call = f"""\t\tcall_host("host.log", "{{\\\"level\\\":\\\"info\\\",\\\"message\\\":\\\"{pid} host callback log\\\",\\\"fields\\\":{{\\\"plugin\\\":\\\"{pid}\\\"}}}}");\t\tcall_host("host.http.do", "{{\\\"method\\\":\\\"GET\\\",\\\"url\\\":\\\"https://example.com\\\",\\\"headers\\\":{{\\\"user-agent\\\":[\\\"{pid}\\\"]}}}}");""" cases.append(f"""\tif (strcmp(method, {c_string(method)}) == 0) {{{host_call}\t\twrite_response(response, {c_string(result)});\t\treturn 0;\t}}""") cmake = f"""cmake_minimum_required(VERSION 3.16)project(cliproxy_{ident}_c C)
add_library(cliproxy_{ident}_c SHARED src/plugin.c)set_target_properties(cliproxy_{ident}_c PROPERTIES OUTPUT_NAME "{slug}-c" PREFIX "")""" source = f"""#include <stdint.h>#include <stdlib.h>#include <string.h>
#if defined(_WIN32)#define CLIPROXY_EXPORT __declspec(dllexport)#else#define CLIPROXY_EXPORT __attribute__((visibility("default")))#endif
#define ABI_VERSION {ABI_VERSION}
typedef struct {{\tvoid* ptr;\tsize_t len;}} cliproxy_buffer;
typedef int (*cliproxy_host_call_fn)(void*, const char*, const uint8_t*, size_t, cliproxy_buffer*);typedef void (*cliproxy_host_free_fn)(void*, size_t);
typedef struct {{\tuint32_t abi_version;\tvoid* host_ctx;\tcliproxy_host_call_fn call;\tcliproxy_host_free_fn free_buffer;}} cliproxy_host_api;
typedef int (*cliproxy_plugin_call_fn)(const char*, const uint8_t*, size_t, cliproxy_buffer*);typedef void (*cliproxy_plugin_free_fn)(void*, size_t);typedef void (*cliproxy_plugin_shutdown_fn)(void);
typedef struct {{\tuint32_t abi_version;\tcliproxy_plugin_call_fn call;\tcliproxy_plugin_free_fn free_buffer;\tcliproxy_plugin_shutdown_fn shutdown;}} cliproxy_plugin_api;
static const cliproxy_host_api* stored_host = NULL;
static void write_response(cliproxy_buffer* response, const char* text) {{\tif (response == NULL || text == NULL) {{\t\treturn;\t}}\tsize_t len = strlen(text);\tvoid* ptr = malloc(len);\tif (ptr == NULL) {{\t\tresponse->ptr = NULL;\t\tresponse->len = 0;\t\treturn;\t}}\tmemcpy(ptr, text, len);\tresponse->ptr = ptr;\tresponse->len = len;}}
static void call_host(const char* method, const char* payload) {{\tif (stored_host == NULL || stored_host->call == NULL || method == NULL) {{\t\treturn;\t}}\tcliproxy_buffer response = {{0}};\tconst uint8_t* request = (const uint8_t*)payload;\tsize_t request_len = payload == NULL ? 0 : strlen(payload);\tif (stored_host->call(stored_host->host_ctx, method, request, request_len, &response) == 0 && response.ptr != NULL && stored_host->free_buffer != NULL) {{\t\tstored_host->free_buffer(response.ptr, response.len);\t}}}}
static int plugin_call(const char* method, const uint8_t* request, size_t request_len, cliproxy_buffer* response) {{\tif (response != NULL) {{\t\tresponse->ptr = NULL;\t\tresponse->len = 0;\t}}\tif (method == NULL) {{\t\twrite_response(response, "{{\\"ok\\":false,\\"error\\":{{\\"code\\":\\"invalid_method\\",\\"message\\":\\"method is required\\"}}}}");\t\treturn 1;\t}}{chr(10).join(cases)}\twrite_response(response, "{{\\"ok\\":false,\\"error\\":{{\\"code\\":\\"unknown_method\\",\\"message\\":\\"unknown method\\"}}}}");\t(void)request;\t(void)request_len;\treturn 0;}}
static void plugin_free(void* ptr, size_t len) {{\t(void)len;\tfree(ptr);}}
static void plugin_shutdown(void) {{}}
CLIPROXY_EXPORT int cliproxy_plugin_init(const cliproxy_host_api* host, cliproxy_plugin_api* plugin) {{\tif (plugin == NULL) {{\t\treturn 1;\t}}\tstored_host = host;\tplugin->abi_version = ABI_VERSION;\tplugin->call = plugin_call;\tplugin->free_buffer = plugin_free;\tplugin->shutdown = plugin_shutdown;\treturn 0;}}""" write(ROOT / slug / "c" / "CMakeLists.txt", cmake) write(ROOT / slug / "c" / "src" / "plugin.c", source)
def generate_rust(cap: Capability) -> None: slug = cap.slug ident = c_ident(slug) pid = plugin_id(cap, "rust") cases = [] for method in methods_for(cap): result = envelope(result_for_method(cap, "rust", method)) host_call = "" if slug == "host-callback" and method == "management.handle": host_call = f""" call_host("host.log", r#"{{"level":"info","message":"{pid} host callback log","fields":{{"plugin":"{pid}"}}}}"#); call_host("host.http.do", r#"{{"method":"GET","url":"https://example.com","headers":{{"user-agent":["{pid}"]}}}}"#);""" cases.append(f'{json.dumps(method)} => {{{host_call} write_response(response, {json.dumps(result)}); 0 }}') cargo = f"""[package]name = "cliproxy-{slug}-rust"version = "0.1.0"edition = "2021"
[lib]crate-type = ["cdylib"]""" cargo_lock = f"""# This file is automatically @generated by Cargo.# It is not intended for manual editing.version = 4
[[package]]name = "cliproxy-{slug}-rust"version = "0.1.0"""" source = f"""use std::ffi::CStr;use std::os::raw::c_char;use std::ptr;
const ABI_VERSION: u32 = {ABI_VERSION};
#[repr(C)]pub struct CliproxyBuffer {{ ptr: *mut u8, len: usize,}}
type HostCall = unsafe extern "C" fn(*mut std::ffi::c_void, *const c_char, *const u8, usize, *mut CliproxyBuffer) -> i32;type HostFree = unsafe extern "C" fn(*mut std::ffi::c_void, usize);type PluginCall = unsafe extern "C" fn(*const c_char, *const u8, usize, *mut CliproxyBuffer) -> i32;type PluginFree = unsafe extern "C" fn(*mut std::ffi::c_void, usize);type PluginShutdown = unsafe extern "C" fn();
#[repr(C)]pub struct CliproxyHostApi {{ abi_version: u32, host_ctx: *mut std::ffi::c_void, call: Option<HostCall>, free_buffer: Option<HostFree>,}}
#[repr(C)]pub struct CliproxyPluginApi {{ abi_version: u32, call: Option<PluginCall>, free_buffer: Option<PluginFree>, shutdown: Option<PluginShutdown>,}}
static mut STORED_HOST: *const CliproxyHostApi = ptr::null();
#[no_mangle]pub extern "C" fn cliproxy_plugin_init(host: *const CliproxyHostApi, plugin: *mut CliproxyPluginApi) -> i32 {{ if plugin.is_null() {{ return 1; }} unsafe {{ STORED_HOST = host; (*plugin).abi_version = ABI_VERSION; (*plugin).call = Some(plugin_call); (*plugin).free_buffer = Some(plugin_free); (*plugin).shutdown = Some(plugin_shutdown); }} 0}}
unsafe extern "C" fn plugin_call(method: *const c_char, request: *const u8, request_len: usize, response: *mut CliproxyBuffer) -> i32 {{ if !response.is_null() {{ (*response).ptr = ptr::null_mut(); (*response).len = 0; }} if method.is_null() {{ write_response(response, r#"{{"ok":false,"error":{{"code":"invalid_method","message":"method is required"}}}}"#); return 1; }} let method = match CStr::from_ptr(method).to_str() {{ Ok(value) => value, Err(_) => {{ write_response(response, r#"{{"ok":false,"error":{{"code":"invalid_method","message":"method is not utf-8"}}}}"#); return 1; }} }}; let _ = request; let _ = request_len; match method {{ {",".join(cases)}, _ => {{ write_response(response, r#"{{"ok":false,"error":{{"code":"unknown_method","message":"unknown method"}}}}"#); 0 }} }}}}
unsafe extern "C" fn plugin_free(ptr: *mut std::ffi::c_void, len: usize) {{ if !ptr.is_null() {{ let _ = Vec::from_raw_parts(ptr as *mut u8, len, len); }}}}
unsafe extern "C" fn plugin_shutdown() {{}}
fn write_response(response: *mut CliproxyBuffer, text: &str) {{ if response.is_null() {{ return; }} let mut bytes = text.as_bytes().to_vec(); let len = bytes.len(); let ptr = bytes.as_mut_ptr(); std::mem::forget(bytes); unsafe {{ (*response).ptr = ptr; (*response).len = len; }}}}
#[allow(dead_code)]fn call_host(method: &str, payload: &str) {{ unsafe {{ if STORED_HOST.is_null() {{ return; }} let host = &*STORED_HOST; let Some(call) = host.call else {{ return; }}; let mut method_bytes = method.as_bytes().to_vec(); method_bytes.push(0); let mut response = CliproxyBuffer {{ ptr: ptr::null_mut(), len: 0 }}; let rc = call( host.host_ctx, method_bytes.as_ptr() as *const c_char, payload.as_ptr(), payload.len(), &mut response, ); if rc == 0 && !response.ptr.is_null() {{ if let Some(free_buffer) = host.free_buffer {{ free_buffer(response.ptr as *mut std::ffi::c_void, response.len); }} }} }}}}""" write(ROOT / slug / "rust" / "Cargo.toml", cargo) write(ROOT / slug / "rust" / "Cargo.lock", cargo_lock) write(ROOT / slug / "rust" / "src" / "lib.rs", source)
def main() -> None: for cap in CAPABILITIES: generate_go(cap) generate_c(cap) generate_rust(cap)
if __name__ == "__main__": main()