348 lines · 12.7 KB · Go
RawDownload
package main /*#include <stdint.h>#include <stdlib.h> typedef struct {	void* ptr;	size_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 {	uint32_t abi_version;	void* host_ctx;	cliproxy_host_call_fn call;	cliproxy_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 {	uint32_t abi_version;	cliproxy_plugin_call_fn call;	cliproxy_plugin_free_fn free_buffer;	cliproxy_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);*/import "C" import (	"encoding/json"	"net/http"	"sync/atomic"	"time"	"unsafe" 	"github.com/router-for-me/CLIProxyAPI/v7/sdk/pluginabi"	"github.com/router-for-me/CLIProxyAPI/v7/sdk/pluginapi") var usageCount atomic.Int64 type envelope struct {	OK     bool            `json:"ok"`	Result json.RawMessage `json:"result,omitempty"`	Error  *envelopeError  `json:"error,omitempty"`} type envelopeError struct {	Code    string `json:"code"`	Message string `json:"message"`} type lifecycleRequest struct {	ConfigYAML []byte `json:"config_yaml"`} type registration struct {	SchemaVersion uint32                 `json:"schema_version"`	Metadata      pluginapi.Metadata     `json:"metadata"`	Capabilities  registrationCapability `json:"capabilities"`} type registrationCapability struct {	ModelRegistrar           bool                         `json:"model_registrar"`	ModelProvider            bool                         `json:"model_provider"`	AuthProvider             bool                         `json:"auth_provider"`	FrontendAuthProvider     bool                         `json:"frontend_auth_provider"`	Executor                 bool                         `json:"executor"`	ExecutorModelScope       pluginapi.ExecutorModelScope `json:"executor_model_scope"`	ExecutorInputFormats     []string                     `json:"executor_input_formats,omitempty"`	ExecutorOutputFormats    []string                     `json:"executor_output_formats,omitempty"`	RequestTranslator        bool                         `json:"request_translator"`	RequestNormalizer        bool                         `json:"request_normalizer"`	ResponseTranslator       bool                         `json:"response_translator"`	ResponseBeforeTranslator bool                         `json:"response_before_translator"`	ResponseAfterTranslator  bool                         `json:"response_after_translator"`	ThinkingApplier          bool                         `json:"thinking_applier"`	UsagePlugin              bool                         `json:"usage_plugin"`	CommandLinePlugin        bool                         `json:"command_line_plugin"`	ManagementAPI            bool                         `json:"management_api"`} type identifierResponse struct {	Identifier string `json:"identifier"`} type streamResponse struct {	Headers http.Header                     `json:"headers,omitempty"`	Chunks  []pluginapi.ExecutorStreamChunk `json:"chunks,omitempty"`} type managementRegistrationResponse struct {	Routes    []pluginapi.ManagementRoute `json:"routes,omitempty"`	Resources []pluginapi.ResourceRoute   `json:"resources,omitempty"`} func main() {} //export cliproxy_plugin_initfunc cliproxy_plugin_init(host *C.cliproxy_host_api, plugin *C.cliproxy_plugin_api) C.int {	if plugin == nil {		return 1	}	plugin.abi_version = C.uint32_t(pluginabi.ABIVersion)	plugin.call = C.cliproxy_plugin_call_fn(C.cliproxyPluginCall)	plugin.free_buffer = C.cliproxy_plugin_free_fn(C.cliproxyPluginFree)	plugin.shutdown = C.cliproxy_plugin_shutdown_fn(C.cliproxyPluginShutdown)	return 0} //export cliproxyPluginCallfunc cliproxyPluginCall(method *C.char, request *C.uint8_t, requestLen C.size_t, response *C.cliproxy_buffer) C.int {	if response != nil {		response.ptr = nil		response.len = 0	}	if method == nil {		writeResponse(response, errorEnvelope("invalid_method", "method is required"))		return 1	}	var requestBytes []byte	if request != nil && requestLen > 0 {		requestBytes = C.GoBytes(unsafe.Pointer(request), C.int(requestLen))	}	raw, errHandle := handleMethod(C.GoString(method), requestBytes)	if errHandle != nil {		writeResponse(response, errorEnvelope("plugin_error", errHandle.Error()))		return 1	}	writeResponse(response, raw)	return 0} //export cliproxyPluginFreefunc cliproxyPluginFree(ptr unsafe.Pointer, len C.size_t) {	if ptr != nil {		C.free(ptr)	}} //export cliproxyPluginShutdownfunc cliproxyPluginShutdown() {} func handleMethod(method string, request []byte) ([]byte, error) {	switch method {	case pluginabi.MethodPluginRegister, pluginabi.MethodPluginReconfigure:		return okEnvelope(exampleRegistration())	case pluginabi.MethodModelRegister:		return okEnvelope(pluginapi.ModelRegistrationResponse{Provider: "plugin-example", Models: exampleModels()})	case pluginabi.MethodModelStatic, pluginabi.MethodModelForAuth:		return okEnvelope(pluginapi.ModelResponse{Provider: "plugin-example", Models: exampleModels()})	case pluginabi.MethodAuthIdentifier:		return okEnvelope(identifierResponse{Identifier: "plugin-example"})	case pluginabi.MethodAuthParse:		return okEnvelope(pluginapi.AuthParseResponse{Handled: true, Auth: exampleAuthData(request)})	case pluginabi.MethodAuthLoginStart:		return okEnvelope(pluginapi.AuthLoginStartResponse{			Provider:  "plugin-example",			URL:       "https://example.invalid/plugin-login",			State:     "example-state",			ExpiresAt: time.Now().Add(5 * time.Minute).UTC(),		})	case pluginabi.MethodAuthLoginPoll:		return okEnvelope(pluginapi.AuthLoginPollResponse{Status: pluginapi.AuthLoginStatusError, Message: "example plugin has no interactive login"})	case pluginabi.MethodAuthRefresh:		return okEnvelope(pluginapi.AuthRefreshResponse{Auth: exampleAuthData(request)})	case pluginabi.MethodFrontendAuthIdentifier:		return okEnvelope(identifierResponse{Identifier: "plugin-example"})	case pluginabi.MethodFrontendAuthAuthenticate:		return okEnvelope(pluginapi.FrontendAuthResponse{Authenticated: true, Principal: "plugin-example"})	case pluginabi.MethodExecutorIdentifier:		return okEnvelope(identifierResponse{Identifier: "plugin-example"})	case pluginabi.MethodExecutorExecute:		return okEnvelope(pluginapi.ExecutorResponse{Payload: []byte(`{"id":"plugin-example","object":"chat.completion"}`)})	case pluginabi.MethodExecutorExecuteStream:		return okEnvelope(streamResponse{Chunks: []pluginapi.ExecutorStreamChunk{{Payload: []byte("plugin-example")}}})	case pluginabi.MethodExecutorCountTokens:		return okEnvelope(pluginapi.ExecutorResponse{Payload: []byte(`{"total_tokens":0}`)})	case pluginabi.MethodExecutorHTTPRequest:		return okEnvelope(pluginapi.ExecutorHTTPResponse{StatusCode: http.StatusOK, Body: []byte(`{"plugin":"example"}`)})	case pluginabi.MethodRequestTranslate, pluginabi.MethodRequestNormalize:		return payloadEcho(request)	case pluginabi.MethodResponseTranslate, pluginabi.MethodResponseNormalizeBefore, pluginabi.MethodResponseNormalizeAfter:		return responsePayloadEcho(request)	case pluginabi.MethodThinkingIdentifier:		return okEnvelope(identifierResponse{Identifier: "plugin-example"})	case pluginabi.MethodThinkingApply:		return applyThinking(request)	case pluginabi.MethodUsageHandle:		usageCount.Add(1)		return okEnvelope(map[string]any{})	case pluginabi.MethodCommandLineRegister:		return okEnvelope(pluginapi.CommandLineRegistrationResponse{Flags: []pluginapi.CommandLineFlag{{			Name:  "plugin-example-command",			Usage: "Run the example C ABI plugin command",			Type:  "bool",		}}})	case pluginabi.MethodCommandLineExecute:		return okEnvelope(pluginapi.CommandLineExecutionResponse{Stdout: []byte("plugin example command\n")})	case pluginabi.MethodManagementRegister:		// CPA exposes menu resources under /v0/resource/plugins/<plugin-id>/.		return okEnvelope(managementRegistrationResponse{Resources: []pluginapi.ResourceRoute{{			Path:        "/status",			Menu:        "Example Plugin",			Description: "Shows example plugin status as a browser-navigable resource.",		}}})	case pluginabi.MethodManagementHandle:		return okEnvelope(pluginapi.ManagementResponse{			StatusCode: http.StatusOK,			Headers:    http.Header{"Content-Type": []string{"text/html; charset=utf-8"}},			Body:       []byte(`<!doctype html><title>Example Plugin</title><main>Example Plugin</main>`),		})	default:		return errorEnvelope("unknown_method", "unknown method: "+method), nil	}} func exampleRegistration() registration {	return registration{		SchemaVersion: pluginabi.SchemaVersion,		Metadata: pluginapi.Metadata{			Name:             "example",			Version:          "0.1.0",			Author:           "router-for-me",			GitHubRepository: "https://github.com/router-for-me/CLIProxyAPI",			Logo:             "https://raw.githubusercontent.com/router-for-me/CLIProxyAPI/main/docs/logo.png",			ConfigFields: []pluginapi.ConfigField{				{Name: "config1", Type: pluginapi.ConfigFieldTypeBoolean, Description: "Enables the example boolean option."},				{Name: "config2", Type: pluginapi.ConfigFieldTypeString, Description: "Stores the example string option."},				{Name: "config3", Type: pluginapi.ConfigFieldTypeInteger, Description: "Stores the example integer option."},				{Name: "mode", Type: pluginapi.ConfigFieldTypeEnum, EnumValues: []string{"safe", "fast"}, Description: "Selects the example execution mode."},			},		},		Capabilities: registrationCapability{			ModelRegistrar:           true,			ModelProvider:            true,			AuthProvider:             true,			FrontendAuthProvider:     true,			Executor:                 true,			ExecutorModelScope:       pluginapi.ExecutorModelScopeBoth,			ExecutorInputFormats:     []string{"chat-completions"},			ExecutorOutputFormats:    []string{"chat-completions"},			RequestTranslator:        true,			RequestNormalizer:        true,			ResponseTranslator:       true,			ResponseBeforeTranslator: true,			ResponseAfterTranslator:  true,			ThinkingApplier:          true,			UsagePlugin:              true,			CommandLinePlugin:        true,			ManagementAPI:            true,		},	}} func exampleModels() []pluginapi.ModelInfo {	return []pluginapi.ModelInfo{{		ID:                         "plugin-example-model",		Object:                     "model",		OwnedBy:                    "plugin-example",		DisplayName:                "Plugin Example Model",		SupportedGenerationMethods: []string{"chat"},		ContextLength:              8192,		MaxCompletionTokens:        1024,		UserDefined:                true,	}}} func exampleAuthData(raw []byte) pluginapi.AuthData {	return pluginapi.AuthData{		Provider:    "plugin-example",		ID:          "plugin-example",		FileName:    "plugin-example.json",		Label:       "Plugin Example",		StorageJSON: append([]byte(nil), raw...),		Metadata:    map[string]any{"type": "plugin-example"},	}} func payloadEcho(raw []byte) ([]byte, error) {	var req pluginapi.RequestTransformRequest	if errUnmarshal := json.Unmarshal(raw, &req); errUnmarshal != nil {		return nil, errUnmarshal	}	return okEnvelope(pluginapi.PayloadResponse{Body: req.Body})} func responsePayloadEcho(raw []byte) ([]byte, error) {	var req pluginapi.ResponseTransformRequest	if errUnmarshal := json.Unmarshal(raw, &req); errUnmarshal != nil {		return nil, errUnmarshal	}	return okEnvelope(pluginapi.PayloadResponse{Body: req.Body})} func applyThinking(raw []byte) ([]byte, error) {	var req pluginapi.ThinkingApplyRequest	if errUnmarshal := json.Unmarshal(raw, &req); errUnmarshal != nil {		return nil, errUnmarshal	}	body := map[string]any{}	_ = json.Unmarshal(req.Body, &body)	body["plugin_example_thinking"] = map[string]any{		"mode":   req.Config.Mode,		"budget": req.Config.Budget,		"level":  req.Config.Level,	}	out, errMarshal := json.Marshal(body)	if errMarshal != nil {		return nil, errMarshal	}	return okEnvelope(pluginapi.PayloadResponse{Body: out})} func okEnvelope(v any) ([]byte, error) {	raw, errMarshal := json.Marshal(v)	if errMarshal != nil {		return nil, errMarshal	}	return json.Marshal(envelope{OK: true, Result: raw})} func errorEnvelope(code, message string) []byte {	raw, _ := json.Marshal(envelope{OK: false, Error: &envelopeError{Code: code, Message: message}})	return raw} func writeResponse(response *C.cliproxy_buffer, raw []byte) {	if response == nil || len(raw) == 0 {		return	}	ptr := C.CBytes(raw)	if ptr == nil {		return	}	response.ptr = ptr	response.len = C.size_t(len(raw))}