731 lines · 21.5 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); static const cliproxy_host_api* stored_host; static void store_host_api(const cliproxy_host_api* host) {	stored_host = host;} static int call_host_api(const char* method, const uint8_t* request, size_t request_len, cliproxy_buffer* response) {	if (stored_host == NULL || stored_host->call == NULL) {		return 1;	}	return stored_host->call(stored_host->host_ctx, method, request, request_len, response);} static void free_host_buffer(void* ptr, size_t len) {	if (stored_host != NULL && stored_host->free_buffer != NULL && ptr != NULL) {		stored_host->free_buffer(ptr, len);	}}*/import "C" import (	"bytes"	"encoding/json"	"fmt"	"html"	"net/http"	"net/url"	"strconv"	"strings"	"unsafe" 	"github.com/router-for-me/CLIProxyAPI/v7/sdk/pluginabi"	"github.com/router-for-me/CLIProxyAPI/v7/sdk/pluginapi") const (	defaultModel        = "gpt-5.5"	defaultPrompt       = "Summarize host model callbacks in one short sentence."	pluginName          = "host-model-callback"	resourcePath        = "/status"	resourceContentType = "text/html; charset=utf-8") 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 registration struct {	SchemaVersion uint32                   `json:"schema_version"`	Metadata      pluginapi.Metadata       `json:"metadata"`	Capabilities  registrationCapabilities `json:"capabilities"`} type registrationCapabilities struct {	ManagementAPI bool `json:"management_api"`} type managementRegistration struct {	Resources []managementResource `json:"resources,omitempty"`} type managementResource struct {	Path        string `json:"Path"`	Menu        string `json:"Menu"`	Description string `json:"Description"`} type managementRequest struct {	Method         string	Path           string	Headers        http.Header	Query          url.Values	Body           []byte	HostCallbackID string `json:"host_callback_id,omitempty"`} type managementResponse struct {	StatusCode int         `json:"StatusCode"`	Headers    http.Header `json:"Headers"`	Body       []byte      `json:"Body"`} type managementBodyOptions struct {	Model         string          `json:"model"`	Mode          string          `json:"mode"`	EntryProtocol string          `json:"entry_protocol"`	ExitProtocol  string          `json:"exit_protocol"`	Prompt        string          `json:"prompt"`	Stream        *bool           `json:"stream"`	Body          json.RawMessage `json:"body"`	Headers       http.Header     `json:"headers"`	Query         url.Values      `json:"query"`	Alt           string          `json:"alt"`	ImplicitClose *bool           `json:"implicit_close"`} type hostModelExecutionRequest struct {	pluginapi.HostModelExecutionRequest	HostCallbackID string `json:"host_callback_id,omitempty"`} type runOptions struct {	Model          string	Mode           string	EntryProtocol  string	ExitProtocol   string	Prompt         string	Stream         bool	Body           []byte	Headers        http.Header	Query          url.Values	Alt            string	ImplicitClose  bool	HostCallbackID string} type chatCompletionRequest struct {	Model    string        `json:"model"`	Stream   bool          `json:"stream"`	Messages []chatMessage `json:"messages"`} type chatMessage struct {	Role    string `json:"role"`	Content string `json:"content"`} type streamPageData struct {	StatusCode int	Headers    http.Header	StreamID   string	Chunks     []string	Error      string	CloseMode  string	CloseError string} 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	}	C.store_host_api(host)	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)	}	_ = len} //export cliproxyPluginShutdownfunc cliproxyPluginShutdown() {} func handleMethod(method string, request []byte) ([]byte, error) {	switch method {	case pluginabi.MethodPluginRegister, pluginabi.MethodPluginReconfigure:		return okEnvelope(pluginRegistration())	case pluginabi.MethodManagementRegister:		return okEnvelope(managementRegistration{			Resources: []managementResource{{				Path:        resourcePath,				Menu:        "Host Model Callback",				Description: "Runs a model request through host.model callbacks and displays the result.",			}},		})	case pluginabi.MethodManagementHandle:		return handleManagement(request)	default:		return errorEnvelope("unknown_method", "unknown method: "+method), nil	}} func pluginRegistration() registration {	return registration{		SchemaVersion: pluginabi.SchemaVersion,		Metadata: pluginapi.Metadata{			Name:             pluginName,			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{},		},		Capabilities: registrationCapabilities{			ManagementAPI: true,		},	}} func handleManagement(raw []byte) ([]byte, error) {	var req managementRequest	if len(raw) > 0 {		if errUnmarshal := json.Unmarshal(raw, &req); errUnmarshal != nil {			return nil, fmt.Errorf("decode management request: %w", errUnmarshal)		}	}	opts, errOptions := optionsFromManagementRequest(req)	if errOptions != nil {		page := renderPage(opts, 0, nil, nil, nil, errOptions.Error(), "", "")		return okEnvelope(htmlResponse(http.StatusBadRequest, page))	}	if opts.Stream {		data := executeStream(opts)		page := renderPage(opts, data.StatusCode, data.Headers, nil, data.Chunks, data.Error, data.CloseMode, data.CloseError)		return okEnvelope(htmlResponse(http.StatusOK, page))	}	resp, errExecute := executeOnce(opts)	if errExecute != nil {		page := renderPage(opts, 0, nil, nil, nil, errExecute.Error(), "", "")		return okEnvelope(htmlResponse(http.StatusOK, page))	}	page := renderPage(opts, resp.StatusCode, resp.Headers, resp.Body, nil, "", "", "")	return okEnvelope(htmlResponse(http.StatusOK, page))} func optionsFromManagementRequest(req managementRequest) (runOptions, error) {	opts := runOptions{		Model:         defaultModel,		Mode:          "non-stream",		EntryProtocol: "openai",		ExitProtocol:  "openai",		Prompt:        defaultPrompt,		Headers:       http.Header{},		Query:         url.Values{},	}	opts.HostCallbackID = strings.TrimSpace(req.HostCallbackID)	if len(req.Body) > 0 {		if errApplyBody := applyBodyOptions(&opts, req.Body); errApplyBody != nil {			return opts, errApplyBody		}	}	if errApplyQuery := applyQueryOptions(&opts, req.Query); errApplyQuery != nil {		return opts, errApplyQuery	}	if opts.Stream {		opts.Mode = "stream"	} else {		opts.Mode = "non-stream"	}	return opts, nil} func applyBodyOptions(opts *runOptions, raw []byte) error {	var bodyOpts managementBodyOptions	if errUnmarshal := json.Unmarshal(raw, &bodyOpts); errUnmarshal != nil {		return fmt.Errorf("decode JSON request body: %w", errUnmarshal)	}	if strings.TrimSpace(bodyOpts.Model) != "" {		opts.Model = strings.TrimSpace(bodyOpts.Model)	}	if strings.TrimSpace(bodyOpts.Mode) != "" {		applyMode(opts, bodyOpts.Mode)	}	if strings.TrimSpace(bodyOpts.EntryProtocol) != "" {		opts.EntryProtocol = strings.TrimSpace(bodyOpts.EntryProtocol)	}	if strings.TrimSpace(bodyOpts.ExitProtocol) != "" {		opts.ExitProtocol = strings.TrimSpace(bodyOpts.ExitProtocol)	}	if bodyOpts.Prompt != "" {		opts.Prompt = bodyOpts.Prompt	}	if bodyOpts.Stream != nil {		opts.Stream = *bodyOpts.Stream	}	if len(bodyOpts.Body) > 0 && string(bodyOpts.Body) != "null" {		if !json.Valid(bodyOpts.Body) {			return fmt.Errorf("body must be valid JSON")		}		opts.Body = append([]byte(nil), bodyOpts.Body...)	}	if bodyOpts.Headers != nil {		opts.Headers = cloneHeader(bodyOpts.Headers)	}	if bodyOpts.Query != nil {		opts.Query = cloneValues(bodyOpts.Query)	}	if bodyOpts.Alt != "" {		opts.Alt = bodyOpts.Alt	}	if bodyOpts.ImplicitClose != nil {		opts.ImplicitClose = *bodyOpts.ImplicitClose	}	return nil} func applyQueryOptions(opts *runOptions, query url.Values) error {	if query == nil {		return nil	}	if raw := strings.TrimSpace(query.Get("model")); raw != "" {		opts.Model = raw	}	if raw := strings.TrimSpace(query.Get("mode")); raw != "" {		applyMode(opts, raw)	}	if raw := strings.TrimSpace(query.Get("entry_protocol")); raw != "" {		opts.EntryProtocol = raw	}	if raw := strings.TrimSpace(query.Get("exit_protocol")); raw != "" {		opts.ExitProtocol = raw	}	if raw := query.Get("prompt"); raw != "" {		opts.Prompt = raw	}	if raw := strings.TrimSpace(query.Get("body")); raw != "" {		body := []byte(raw)		if !json.Valid(body) {			return fmt.Errorf("query body must be valid JSON")		}		opts.Body = append([]byte(nil), body...)	}	if raw := strings.TrimSpace(query.Get("alt")); raw != "" {		opts.Alt = raw	}	if errStream := applyBoolQuery(query, "stream", &opts.Stream); errStream != nil {		return errStream	}	if errImplicitClose := applyBoolQuery(query, "implicit_close", &opts.ImplicitClose); errImplicitClose != nil {		return errImplicitClose	}	return nil} func applyMode(opts *runOptions, mode string) {	normalized := strings.ToLower(strings.TrimSpace(mode))	switch normalized {	case "stream", "streaming":		opts.Stream = true	case "non-stream", "non_stream", "nonstream", "sync":		opts.Stream = false	}} func applyBoolQuery(query url.Values, key string, target *bool) error {	raw := strings.TrimSpace(query.Get(key))	if raw == "" {		return nil	}	parsed, errParse := strconv.ParseBool(raw)	if errParse != nil {		return fmt.Errorf("%s must be a boolean: %w", key, errParse)	}	*target = parsed	return nil} func executeOnce(opts runOptions) (pluginapi.HostModelExecutionResponse, error) {	body, errBody := modelRequestBody(opts)	if errBody != nil {		return pluginapi.HostModelExecutionResponse{}, errBody	}	// Forward HostCallbackID so the host skips this plugin's interceptors on the	// nested model execution. Host model callbacks do not recursively call the	// originating plugin's interceptor chain.	result, errCall := callHost(pluginabi.MethodHostModelExecute, hostModelExecutionRequest{		HostModelExecutionRequest: pluginapi.HostModelExecutionRequest{			EntryProtocol: opts.EntryProtocol,			ExitProtocol:  opts.ExitProtocol,			Model:         opts.Model,			Stream:        false,			Body:          body,			Headers:       cloneHeader(opts.Headers),			Query:         cloneValues(opts.Query),			Alt:           opts.Alt,		},		HostCallbackID: opts.HostCallbackID,	})	if errCall != nil {		return pluginapi.HostModelExecutionResponse{}, errCall	}	var resp pluginapi.HostModelExecutionResponse	if errUnmarshal := json.Unmarshal(result, &resp); errUnmarshal != nil {		return pluginapi.HostModelExecutionResponse{}, fmt.Errorf("decode host.model.execute result: %w", errUnmarshal)	}	return resp, nil} func executeStream(opts runOptions) (data streamPageData) {	body, errBody := modelRequestBody(opts)	if errBody != nil {		data.Error = errBody.Error()		return data	}	// Forward HostCallbackID so the host skips this plugin's interceptors on the	// nested model execution. Host model callbacks do not recursively call the	// originating plugin's interceptor chain.	result, errCall := callHost(pluginabi.MethodHostModelExecuteStream, hostModelExecutionRequest{		HostModelExecutionRequest: pluginapi.HostModelExecutionRequest{			EntryProtocol: opts.EntryProtocol,			ExitProtocol:  opts.ExitProtocol,			Model:         opts.Model,			Stream:        true,			Body:          body,			Headers:       cloneHeader(opts.Headers),			Query:         cloneValues(opts.Query),			Alt:           opts.Alt,		},		HostCallbackID: opts.HostCallbackID,	})	if errCall != nil {		data.Error = errCall.Error()		return data	}	var resp pluginapi.HostModelStreamResponse	if errUnmarshal := json.Unmarshal(result, &resp); errUnmarshal != nil {		data.Error = fmt.Sprintf("decode host.model.execute_stream result: %v", errUnmarshal)		return data	}	data.StatusCode = resp.StatusCode	data.Headers = cloneHeader(resp.Headers)	data.StreamID = resp.StreamID	if resp.StreamID == "" {		data.Error = "host.model.execute_stream returned an empty stream_id"		return data	}	if opts.ImplicitClose {		// When implicit_close=true, the host closes this stream when the management.handle RPC callback scope returns.		data.CloseMode = "implicit close at management.handle return"	} else {		data.CloseMode = "explicit close through host.model.stream_close"		defer func() {			if errClose := closeHostModelStream(resp.StreamID); errClose != nil {

Lines 1–500 of 731.