yams/docs/spec/plugin_spec.md
2025-10-18 00:39:19 -04:00

233 lines
No EOL
8.1 KiB
Markdown

# YAMS Plugin ABI Specification
## Core C-ABI Plugin Interface
The YAMS plugin system is built around a stable C ABI that enables plugins to expose services to the host and optionally call back into host services.
### Plugin Entry Points
Every plugin MUST implement the following symbols:
```c
// Return the plugin ABI version (always 1)
int yams_plugin_get_abi_version(void);
// Return the plugin name (static string, lifetime of plugin)
const char* yams_plugin_get_name(void);
// Return the plugin version (static string, lifetime of plugin)
const char* yams_plugin_get_version(void);
// Return the plugin manifest as JSON (static string, lifetime of plugin)
const char* yams_plugin_get_manifest_json(void);
// Get an interface vtable by ID and version
// Returns 0 on success, non-zero on error/interface not found
int yams_plugin_get_interface(const char* iface_id, uint32_t version, void** out_iface);
// Initialize the plugin with config JSON and optional host context
// Returns 0 on success, non-zero error code on failure
int yams_plugin_init(const char* config_json, const void* host_context);
// Shutdown the plugin (cleanup resources)
void yams_plugin_shutdown(void);
// Optional: Get health status as JSON (allocated with malloc, host frees)
// Returns 0 on success, non-zero if not implemented
int yams_plugin_get_health_json(char** out_json);
```
### Memory Management
- **Plugin-allocated memory**: For any function that returns allocated memory (like `get_health_json`), the plugin MUST use `malloc()`/`calloc()`. The host is responsible for calling `free()`.
- **Host-allocated memory**: When the host provides data to plugins via callbacks, the host allocates and frees the memory.
- **Interface vtables**: Interface pointers returned by `get_interface` have static lifetime for the duration of the plugin load.
### Error Codes
Plugins SHOULD use standard error codes:
```c
#define YAMS_PLUGIN_OK 0
#define YAMS_PLUGIN_ERR_INVALID 1
#define YAMS_PLUGIN_ERR_INCOMPATIBLE 2
#define YAMS_PLUGIN_ERR_NOT_FOUND 3
#define YAMS_PLUGIN_ERR_IO 4
#define YAMS_PLUGIN_ERR_INTERNAL 5
#define YAMS_PLUGIN_ERR_UNSUPPORTED 6
```
## Interface VTables
### Standard Interface Pattern
All interfaces follow this pattern:
```c
typedef struct iface_name_v1 {
void* handle; // Plugin-specific context
// Interface methods...
} iface_name_v1_t;
```
### Required Interfaces
#### model_provider_v1
Header: `include/yams/plugins/model_provider_v1.h`
```c
typedef struct model_provider_v1 {
void* handle;
// Set progress callback for long operations (optional)
int (*set_progress_callback)(void* handle,
void (*callback)(void* user_data, const char* phase, int progress, int total),
void* user_data);
// Load a model (non-blocking)
int (*load_model)(void* handle, const char* model_id, const char* model_path, const char* options_json);
// Unload a model
int (*unload_model)(void* handle, const char* model_id);
// Check if model is loaded
int (*is_model_loaded)(void* handle, const char* model_id, bool* out_loaded);
// Get list of loaded models (plugin allocates arrays, host frees)
int (*get_loaded_models)(void* handle, char*** out_ids, uint32_t* out_count);
void (*free_model_list)(void* handle, char** ids, uint32_t count);
// Generate single embedding
int (*generate_embedding)(void* handle, const char* model_id,
const uint8_t* input, size_t input_len,
float** out_vec, uint32_t* out_dim);
void (*free_embedding)(void* handle, float* vec, uint32_t dim);
// Generate batch embeddings
int (*generate_embedding_batch)(void* handle, const char* model_id,
const uint8_t** inputs, const size_t* lens, uint32_t batch,
float*** out_vecs, uint32_t* out_batch, uint32_t* out_dim);
void (*free_embedding_batch)(void* handle, float** vecs, uint32_t batch, uint32_t dim);
// v1.2 extensions (optional, may return UNSUPPORTED)
int (*get_embedding_dim)(void* handle, const char* model_id, uint32_t* out_dim);
int (*get_runtime_info_json)(void* handle, const char* model_id, char** out_json);
void (*free_string)(void* handle, char* str);
int (*set_threading)(void* handle, const char* model_id, uint32_t intra_threads, uint32_t inter_threads);
} model_provider_v1_t;
```
#### content_extractor_v1
Header: `include/yams/plugins/content_extractor_v1.h`
```c
typedef struct content_extractor_v1 {
void* handle;
// Extract content from a document
int (*extract_content)(void* handle, const char* file_path,
char** out_text, char** out_metadata_json);
void (*free_extracted_content)(void* handle, char* text, char* metadata);
// Get supported file extensions (plugin allocates, host frees)
int (*get_supported_extensions)(void* handle, char*** out_extensions, uint32_t* out_count);
void (*free_extension_list)(void* handle, char** extensions, uint32_t count);
} content_extractor_v1_t;
```
## Host Services API (Optional)
Plugins that need to call back into YAMS can use the Host Services API.
### Host Context
During `yams_plugin_init`, the host MAY pass a `yams_plugin_host_context_v1` struct:
```c
typedef struct yams_plugin_host_context_v1 {
uint32_t version; // YAMS_PLUGIN_HOST_SERVICES_API_VERSION
const document_service_v1* document_service;
const search_service_v1* search_service;
// Additional services may be added in future versions
} yams_plugin_host_context_v1_t;
```
### Document Service v1
```c
typedef struct document_service_v1 {
void* handle;
int (*list_documents)(void* handle, const list_documents_request_t* req,
list_documents_response_t** out_res);
void (*free_list_documents_response)(void* handle, list_documents_response_t* res);
int (*get_document)(void* handle, const get_document_request_t* req,
get_document_response_t** out_res);
void (*free_get_document_response)(void* handle, get_document_response_t* res);
// Additional methods...
} document_service_v1_t;
```
## Plugin Manifest
Each plugin MUST provide a JSON manifest via `yams_plugin_get_manifest_json()`:
```json
{
"name": "plugin_name",
"version": "1.0.0",
"description": "Brief description",
"interfaces": [
{
"id": "model_provider_v1",
"version": 2
}
],
"abi_version": 1,
"capabilities": [],
"dependencies": []
}
```
## Lifecycle
1. **Discovery**: Host scans for plugin files and reads symbols without side effects
2. **Validation**: Host verifies ABI version, manifest format, and required interfaces
3. **Initialization**: Host calls `yams_plugin_init()` with config and optional host context
4. **Interface Registration**: Host retrieves interface vtables via `yams_plugin_get_interface()`
5. **Operation**: Host calls into plugin interfaces as needed
6. **Shutdown**: Host calls `yams_plugin_shutdown()` before unloading
## Security Considerations
- Plugins run in the same process space as the host
- Host should validate plugin signatures and maintain a trust list
- Plugins should validate all input parameters and handle errors gracefully
- Memory safety is critical - use proper bounds checking and null checks
## Threading
- Plugin initialization and shutdown are single-threaded
- Interface methods MAY be called from multiple threads concurrently
- Plugins MUST implement their own synchronization if needed
- Callbacks MAY be invoked from background threads
## Versioning
- Plugin ABI version is incremented for breaking changes
- Interface versions are incremented independently
- Host should support multiple interface versions in parallel
- Plugins should advertise the highest interface version they support
## Best Practices
- Use static allocation for fixed-size data structures
- Validate all pointers before dereferencing
- Provide meaningful error messages
- Implement graceful degradation for optional features
- Use consistent naming conventions across interfaces
- Document thread safety guarantees for each method