Add projects
This commit is contained in:
parent
2d3a9ad623
commit
8b607dd700
1802 changed files with 503346 additions and 2 deletions
7
backend/examples/plugin/simple/go/go.mod
Normal file
7
backend/examples/plugin/simple/go/go.mod
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
module github.com/router-for-me/CLIProxyAPI/v7/examples/plugin/simple/go
|
||||
|
||||
go 1.26.0
|
||||
|
||||
require github.com/router-for-me/CLIProxyAPI/v7 v7.0.0
|
||||
|
||||
replace github.com/router-for-me/CLIProxyAPI/v7 => ../../../..
|
||||
348
backend/examples/plugin/simple/go/main.go
Normal file
348
backend/examples/plugin/simple/go/main.go
Normal file
|
|
@ -0,0 +1,348 @@
|
|||
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_init
|
||||
func 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 cliproxyPluginCall
|
||||
func 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 cliproxyPluginFree
|
||||
func cliproxyPluginFree(ptr unsafe.Pointer, len C.size_t) {
|
||||
if ptr != nil {
|
||||
C.free(ptr)
|
||||
}
|
||||
}
|
||||
|
||||
//export cliproxyPluginShutdown
|
||||
func 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))
|
||||
}
|
||||
Loading…
Reference in a new issue