@lobu/core 3.0.5 → 3.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/src/__tests__/encryption.test.ts +103 -0
- package/src/__tests__/fixtures/factories.ts +76 -0
- package/src/__tests__/fixtures/index.ts +9 -0
- package/src/__tests__/fixtures/mock-fetch.ts +32 -0
- package/src/__tests__/fixtures/mock-queue.ts +50 -0
- package/src/__tests__/fixtures/mock-redis.ts +300 -0
- package/src/__tests__/retry.test.ts +134 -0
- package/src/__tests__/sanitize.test.ts +158 -0
- package/src/agent-policy.ts +207 -0
- package/src/agent-store.ts +220 -0
- package/src/api-types.ts +256 -0
- package/src/command-registry.ts +73 -0
- package/src/constants.ts +60 -0
- package/src/errors.ts +220 -0
- package/src/index.ts +131 -0
- package/src/integration-types.ts +26 -0
- package/src/logger.ts +248 -0
- package/src/modules.ts +184 -0
- package/src/otel.ts +306 -0
- package/src/plugin-types.ts +46 -0
- package/src/provider-config-types.ts +54 -0
- package/src/redis/base-store.ts +200 -0
- package/src/sentry.ts +54 -0
- package/src/trace.ts +32 -0
- package/src/types.ts +430 -0
- package/src/utils/encryption.ts +78 -0
- package/src/utils/env.ts +50 -0
- package/src/utils/json.ts +37 -0
- package/src/utils/lock.ts +75 -0
- package/src/utils/mcp-tool-instructions.ts +5 -0
- package/src/utils/retry.ts +91 -0
- package/src/utils/sanitize.ts +127 -0
- package/src/worker/auth.ts +100 -0
- package/src/worker/transport.ts +107 -0
- package/tsconfig.json +20 -0
package/src/modules.ts
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { createLogger } from "./logger";
|
|
2
|
+
|
|
3
|
+
const logger = createLogger("modules");
|
|
4
|
+
|
|
5
|
+
// ============================================================================
|
|
6
|
+
// Module Type Definitions
|
|
7
|
+
// ============================================================================
|
|
8
|
+
|
|
9
|
+
export interface ModuleInterface<_TModuleData = unknown> {
|
|
10
|
+
/** Module identifier */
|
|
11
|
+
name: string;
|
|
12
|
+
|
|
13
|
+
/** Check if module should be enabled based on environment */
|
|
14
|
+
isEnabled(): boolean;
|
|
15
|
+
|
|
16
|
+
/** Initialize module - called once at startup */
|
|
17
|
+
init(): Promise<void>;
|
|
18
|
+
|
|
19
|
+
/** Register HTTP endpoints with Express app */
|
|
20
|
+
registerEndpoints(app: any): void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface WorkerContext {
|
|
24
|
+
workspaceDir: string;
|
|
25
|
+
userId: string;
|
|
26
|
+
conversationId: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface WorkerModule<TModuleData = unknown>
|
|
30
|
+
extends ModuleInterface<TModuleData> {
|
|
31
|
+
/** Initialize workspace - called when worker starts session */
|
|
32
|
+
initWorkspace(config: any): Promise<void>;
|
|
33
|
+
|
|
34
|
+
/** Called at session start - can modify system prompt */
|
|
35
|
+
onSessionStart(context: ModuleSessionContext): Promise<ModuleSessionContext>;
|
|
36
|
+
|
|
37
|
+
/** Called at session end - can add action buttons */
|
|
38
|
+
onSessionEnd(context: ModuleSessionContext): Promise<ActionButton[]>;
|
|
39
|
+
|
|
40
|
+
/** Collect module-specific data before sending response. Return null if no data. */
|
|
41
|
+
onBeforeResponse(context: WorkerContext): Promise<TModuleData | null>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface ModuleSessionContext {
|
|
45
|
+
userId: string;
|
|
46
|
+
conversationId: string;
|
|
47
|
+
systemPrompt: string;
|
|
48
|
+
workspace?: any;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface ActionButton {
|
|
52
|
+
text: string;
|
|
53
|
+
action_id: string;
|
|
54
|
+
style?: "primary" | "danger";
|
|
55
|
+
value?: string;
|
|
56
|
+
url?: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ============================================================================
|
|
60
|
+
// Module Registry
|
|
61
|
+
// ============================================================================
|
|
62
|
+
|
|
63
|
+
export interface IModuleRegistry {
|
|
64
|
+
register(module: ModuleInterface): void;
|
|
65
|
+
getWorkerModules(): WorkerModule[];
|
|
66
|
+
registerAvailableModules(modulePackages?: string[]): Promise<void>;
|
|
67
|
+
initAll(): Promise<void>;
|
|
68
|
+
registerEndpoints(app: any): void;
|
|
69
|
+
/** Return all registered modules as base ModuleInterface array. */
|
|
70
|
+
getModules(): ModuleInterface[];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Module registry for managing plugin modules across the application.
|
|
75
|
+
*
|
|
76
|
+
* Modules must be explicitly registered by calling `register()` before use.
|
|
77
|
+
* This allows each package (dispatcher, worker) to load only the modules it needs.
|
|
78
|
+
*
|
|
79
|
+
* For production: use the global `moduleRegistry` instance
|
|
80
|
+
* For testing: create a new instance to avoid shared state
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* // In gateway/worker
|
|
84
|
+
* import { MyModule } from './my-module';
|
|
85
|
+
* moduleRegistry.register(new MyModule());
|
|
86
|
+
* await moduleRegistry.initAll();
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* // In tests
|
|
90
|
+
* const testRegistry = new ModuleRegistry();
|
|
91
|
+
* testRegistry.register(mockModule);
|
|
92
|
+
*/
|
|
93
|
+
export class ModuleRegistry implements IModuleRegistry {
|
|
94
|
+
private modules: Map<string, ModuleInterface> = new Map();
|
|
95
|
+
|
|
96
|
+
register(module: ModuleInterface): void {
|
|
97
|
+
if (module.isEnabled()) {
|
|
98
|
+
this.modules.set(module.name, module);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Automatically discover and register available modules.
|
|
104
|
+
* Tries to import module packages and registers them if available.
|
|
105
|
+
*
|
|
106
|
+
* @param modulePackages - List of module package names to try loading.
|
|
107
|
+
* Users can provide custom modules to register.
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* // Register custom modules
|
|
111
|
+
* await moduleRegistry.registerAvailableModules([
|
|
112
|
+
* '@mycompany/slack-module',
|
|
113
|
+
* '@mycompany/jira-module'
|
|
114
|
+
* ]);
|
|
115
|
+
*/
|
|
116
|
+
async registerAvailableModules(modulePackages: string[] = []): Promise<void> {
|
|
117
|
+
for (const packageName of modulePackages) {
|
|
118
|
+
try {
|
|
119
|
+
// Dynamic import to avoid build-time dependencies
|
|
120
|
+
const moduleExports = await import(packageName);
|
|
121
|
+
|
|
122
|
+
// Try common export patterns
|
|
123
|
+
const ModuleClass =
|
|
124
|
+
moduleExports.default ||
|
|
125
|
+
Object.values(moduleExports).find(
|
|
126
|
+
(exp) => typeof exp === "function" && exp.name.endsWith("Module")
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
if (ModuleClass && typeof ModuleClass === "function") {
|
|
130
|
+
const moduleInstance = new (ModuleClass as any)();
|
|
131
|
+
if (!this.modules.has(moduleInstance.name)) {
|
|
132
|
+
this.register(moduleInstance);
|
|
133
|
+
logger.debug(`${packageName} registered`);
|
|
134
|
+
}
|
|
135
|
+
} else {
|
|
136
|
+
logger.debug(`${packageName}: No module class found in exports`);
|
|
137
|
+
}
|
|
138
|
+
} catch {
|
|
139
|
+
logger.debug(`${packageName} not available`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async initAll(): Promise<void> {
|
|
145
|
+
for (const module of this.modules.values()) {
|
|
146
|
+
if (module.init) {
|
|
147
|
+
logger.debug(`Initializing module: ${module.name}`);
|
|
148
|
+
await module.init();
|
|
149
|
+
logger.debug(`Module ${module.name} initialized`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
registerEndpoints(app: any): void {
|
|
155
|
+
for (const module of this.modules.values()) {
|
|
156
|
+
if (module.registerEndpoints) {
|
|
157
|
+
try {
|
|
158
|
+
module.registerEndpoints(app);
|
|
159
|
+
} catch (error) {
|
|
160
|
+
logger.error(
|
|
161
|
+
`Failed to register endpoints for module ${module.name}:`,
|
|
162
|
+
error
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
getWorkerModules(): WorkerModule[] {
|
|
170
|
+
return Array.from(this.modules.values()).filter(
|
|
171
|
+
(m): m is WorkerModule => "onBeforeResponse" in m
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
getModules(): ModuleInterface[] {
|
|
176
|
+
return Array.from(this.modules.values());
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Global registry instance for production use.
|
|
182
|
+
* For testing, create separate instances: `new ModuleRegistry()`
|
|
183
|
+
*/
|
|
184
|
+
export const moduleRegistry = new ModuleRegistry();
|
package/src/otel.ts
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenTelemetry tracing setup for distributed tracing with Grafana Tempo.
|
|
3
|
+
* Provides Chrome DevTools-style waterfall visualization in Grafana.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Span, Tracer } from "@opentelemetry/api";
|
|
7
|
+
import { context, SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
|
|
8
|
+
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
|
|
9
|
+
import { Resource } from "@opentelemetry/resources";
|
|
10
|
+
import {
|
|
11
|
+
NodeTracerProvider,
|
|
12
|
+
SimpleSpanProcessor,
|
|
13
|
+
} from "@opentelemetry/sdk-trace-node";
|
|
14
|
+
import {
|
|
15
|
+
ATTR_SERVICE_NAME,
|
|
16
|
+
ATTR_SERVICE_VERSION,
|
|
17
|
+
} from "@opentelemetry/semantic-conventions";
|
|
18
|
+
import { createLogger } from "./logger";
|
|
19
|
+
|
|
20
|
+
const logger = createLogger("otel");
|
|
21
|
+
|
|
22
|
+
let provider: NodeTracerProvider | null = null;
|
|
23
|
+
let tracer: Tracer | null = null;
|
|
24
|
+
|
|
25
|
+
export interface OtelConfig {
|
|
26
|
+
serviceName: string;
|
|
27
|
+
serviceVersion?: string;
|
|
28
|
+
tempoEndpoint?: string; // e.g., "http://tempo:4318/v1/traces"
|
|
29
|
+
enabled?: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Initialize OpenTelemetry tracing.
|
|
34
|
+
* Call this once at application startup.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* initTracing({
|
|
38
|
+
* serviceName: "lobu-gateway",
|
|
39
|
+
* tempoEndpoint: "http://lobu-tempo:4318/v1/traces",
|
|
40
|
+
* });
|
|
41
|
+
*/
|
|
42
|
+
export function initTracing(config: OtelConfig): void {
|
|
43
|
+
if (provider) {
|
|
44
|
+
return; // Already initialized
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const enabled = config.enabled ?? !!config.tempoEndpoint;
|
|
48
|
+
if (!enabled) {
|
|
49
|
+
logger.debug("Tracing disabled (no TEMPO_ENDPOINT configured)");
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const resource = new Resource({
|
|
54
|
+
[ATTR_SERVICE_NAME]: config.serviceName,
|
|
55
|
+
[ATTR_SERVICE_VERSION]: config.serviceVersion || "1.0.0",
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
provider = new NodeTracerProvider({ resource });
|
|
59
|
+
|
|
60
|
+
// Configure OTLP exporter to send traces to Tempo
|
|
61
|
+
const exporter = new OTLPTraceExporter({
|
|
62
|
+
url: config.tempoEndpoint,
|
|
63
|
+
timeoutMillis: 30000, // 30 second timeout for reliability
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Use SimpleSpanProcessor for immediate export (better for short-lived workers)
|
|
67
|
+
provider.addSpanProcessor(new SimpleSpanProcessor(exporter));
|
|
68
|
+
provider.register();
|
|
69
|
+
|
|
70
|
+
tracer = trace.getTracer(config.serviceName, config.serviceVersion);
|
|
71
|
+
|
|
72
|
+
logger.info(
|
|
73
|
+
`Tracing initialized: ${config.serviceName} -> ${config.tempoEndpoint}`
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Get the configured tracer. Returns null if not initialized.
|
|
79
|
+
*/
|
|
80
|
+
export function getTracer(): Tracer | null {
|
|
81
|
+
return tracer;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Shutdown tracing gracefully.
|
|
86
|
+
*/
|
|
87
|
+
export async function shutdownTracing(): Promise<void> {
|
|
88
|
+
if (provider) {
|
|
89
|
+
await provider.shutdown();
|
|
90
|
+
provider = null;
|
|
91
|
+
tracer = null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Force flush all pending spans to the exporter.
|
|
97
|
+
* Call this after processing a message to ensure spans are exported promptly.
|
|
98
|
+
*/
|
|
99
|
+
export async function flushTracing(): Promise<void> {
|
|
100
|
+
if (provider) {
|
|
101
|
+
await provider.forceFlush();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Create a new span for tracing.
|
|
107
|
+
* If tracing is not initialized, returns a no-op span.
|
|
108
|
+
*
|
|
109
|
+
* @param name Span name (e.g., "queue_processing", "agent_execution")
|
|
110
|
+
* @param attributes Optional attributes to add to the span
|
|
111
|
+
* @param parentContext Optional parent context for trace correlation
|
|
112
|
+
*/
|
|
113
|
+
export function createSpan(
|
|
114
|
+
name: string,
|
|
115
|
+
attributes?: Record<string, string | number | boolean>,
|
|
116
|
+
kind: SpanKind = SpanKind.INTERNAL
|
|
117
|
+
): Span | null {
|
|
118
|
+
if (!tracer) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const span = tracer.startSpan(name, {
|
|
123
|
+
kind,
|
|
124
|
+
attributes,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
return span;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Execute a function within a span context.
|
|
132
|
+
* Automatically handles span lifecycle (start, end, error recording).
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* const result = await withSpan("process_message", async (span) => {
|
|
136
|
+
* span?.setAttribute("messageId", messageId);
|
|
137
|
+
* return await processMessage();
|
|
138
|
+
* });
|
|
139
|
+
*/
|
|
140
|
+
export async function withSpan<T>(
|
|
141
|
+
name: string,
|
|
142
|
+
fn: (span: Span | null) => Promise<T>,
|
|
143
|
+
attributes?: Record<string, string | number | boolean>
|
|
144
|
+
): Promise<T> {
|
|
145
|
+
const span = createSpan(name, attributes);
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
const result = await fn(span);
|
|
149
|
+
span?.setStatus({ code: SpanStatusCode.OK });
|
|
150
|
+
return result;
|
|
151
|
+
} catch (error) {
|
|
152
|
+
if (span) {
|
|
153
|
+
span.setStatus({
|
|
154
|
+
code: SpanStatusCode.ERROR,
|
|
155
|
+
message: error instanceof Error ? error.message : String(error),
|
|
156
|
+
});
|
|
157
|
+
span.recordException(error as Error);
|
|
158
|
+
}
|
|
159
|
+
throw error;
|
|
160
|
+
} finally {
|
|
161
|
+
span?.end();
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Get current active span from context.
|
|
167
|
+
*/
|
|
168
|
+
export function getCurrentSpan(): Span | undefined {
|
|
169
|
+
return trace.getActiveSpan();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Run a function within a span context, propagating the span.
|
|
174
|
+
*/
|
|
175
|
+
export function runInSpanContext<T>(span: Span, fn: () => T): T {
|
|
176
|
+
const ctx = trace.setSpan(context.active(), span);
|
|
177
|
+
return context.with(ctx, fn);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Create a root span and return traceparent header for propagation.
|
|
182
|
+
* Use this at the entry point (message ingestion) to start a trace.
|
|
183
|
+
*
|
|
184
|
+
* @example
|
|
185
|
+
* const { span, traceparent } = createRootSpan("message_received", { messageId });
|
|
186
|
+
* // Store traceparent in message metadata for downstream propagation
|
|
187
|
+
* await queueProducer.enqueueMessage({ ...data, platformMetadata: { traceparent } });
|
|
188
|
+
* span.end();
|
|
189
|
+
*/
|
|
190
|
+
export function createRootSpan(
|
|
191
|
+
name: string,
|
|
192
|
+
attributes?: Record<string, string | number | boolean>
|
|
193
|
+
): { span: Span | null; traceparent: string | null } {
|
|
194
|
+
if (!tracer) {
|
|
195
|
+
return { span: null, traceparent: null };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const span = tracer.startSpan(name, {
|
|
199
|
+
kind: SpanKind.SERVER,
|
|
200
|
+
attributes,
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// Extract W3C traceparent header from span context
|
|
204
|
+
const spanContext = span.spanContext();
|
|
205
|
+
const traceparent = `00-${spanContext.traceId}-${spanContext.spanId}-01`;
|
|
206
|
+
|
|
207
|
+
return { span, traceparent };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Create a child span from a traceparent header.
|
|
212
|
+
* Use this to continue a trace in downstream services (queue consumer, worker).
|
|
213
|
+
*
|
|
214
|
+
* @example
|
|
215
|
+
* const traceparent = data.platformMetadata?.traceparent;
|
|
216
|
+
* const span = createChildSpan("queue_processing", traceparent, { jobId });
|
|
217
|
+
* // ... do work ...
|
|
218
|
+
* span?.end();
|
|
219
|
+
*/
|
|
220
|
+
export function createChildSpan(
|
|
221
|
+
name: string,
|
|
222
|
+
traceparent: string | null | undefined,
|
|
223
|
+
attributes?: Record<string, string | number | boolean>
|
|
224
|
+
): Span | null {
|
|
225
|
+
if (!tracer) {
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (!traceparent) {
|
|
230
|
+
// No parent context - create independent span
|
|
231
|
+
return createSpan(name, attributes);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Parse W3C traceparent: 00-traceId-parentSpanId-flags
|
|
235
|
+
const parts = traceparent.split("-");
|
|
236
|
+
if (parts.length !== 4) {
|
|
237
|
+
return createSpan(name, attributes);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const traceId = parts[1]!;
|
|
241
|
+
const parentSpanId = parts[2]!;
|
|
242
|
+
|
|
243
|
+
// Create span context from traceparent
|
|
244
|
+
const parentContext = trace.setSpanContext(context.active(), {
|
|
245
|
+
traceId,
|
|
246
|
+
spanId: parentSpanId,
|
|
247
|
+
traceFlags: 1, // sampled
|
|
248
|
+
isRemote: true,
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// Start span as child of the propagated context
|
|
252
|
+
return tracer.startSpan(
|
|
253
|
+
name,
|
|
254
|
+
{ kind: SpanKind.INTERNAL, attributes },
|
|
255
|
+
parentContext
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Run a function within a child span context.
|
|
261
|
+
* Automatically handles span lifecycle and error recording.
|
|
262
|
+
*
|
|
263
|
+
* @example
|
|
264
|
+
* const result = await withChildSpan("process_job", traceparent, async (span) => {
|
|
265
|
+
* span?.setAttribute("jobId", jobId);
|
|
266
|
+
* return await processJob();
|
|
267
|
+
* });
|
|
268
|
+
*/
|
|
269
|
+
export async function withChildSpan<T>(
|
|
270
|
+
name: string,
|
|
271
|
+
traceparent: string | null | undefined,
|
|
272
|
+
fn: (span: Span | null) => Promise<T>,
|
|
273
|
+
attributes?: Record<string, string | number | boolean>
|
|
274
|
+
): Promise<T> {
|
|
275
|
+
const span = createChildSpan(name, traceparent, attributes);
|
|
276
|
+
|
|
277
|
+
try {
|
|
278
|
+
const result = await fn(span);
|
|
279
|
+
span?.setStatus({ code: SpanStatusCode.OK });
|
|
280
|
+
return result;
|
|
281
|
+
} catch (error) {
|
|
282
|
+
if (span) {
|
|
283
|
+
span.setStatus({
|
|
284
|
+
code: SpanStatusCode.ERROR,
|
|
285
|
+
message: error instanceof Error ? error.message : String(error),
|
|
286
|
+
});
|
|
287
|
+
span.recordException(error as Error);
|
|
288
|
+
}
|
|
289
|
+
throw error;
|
|
290
|
+
} finally {
|
|
291
|
+
span?.end();
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Extract traceparent from span for propagation to downstream services.
|
|
297
|
+
*/
|
|
298
|
+
export function getTraceparent(span: Span | null): string | null {
|
|
299
|
+
if (!span) return null;
|
|
300
|
+
const ctx = span.spanContext();
|
|
301
|
+
return `00-${ctx.traceId}-${ctx.spanId}-01`;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Re-export OpenTelemetry types for convenience
|
|
305
|
+
export { SpanKind, SpanStatusCode };
|
|
306
|
+
export type { Span, Tracer };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenClaw plugin types for Lobu.
|
|
3
|
+
*
|
|
4
|
+
* Supports loading existing OpenClaw community plugins.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** Supported plugin slots */
|
|
8
|
+
export type PluginSlot = "tool" | "provider" | "memory";
|
|
9
|
+
|
|
10
|
+
/** Configuration for a single plugin */
|
|
11
|
+
export interface PluginConfig {
|
|
12
|
+
/** npm package name or local path (e.g., "@openclaw/voice-call", "./my-plugin") */
|
|
13
|
+
source: string;
|
|
14
|
+
/** Which slot this plugin fills */
|
|
15
|
+
slot: PluginSlot;
|
|
16
|
+
/** Whether this plugin is enabled (default: true) */
|
|
17
|
+
enabled?: boolean;
|
|
18
|
+
/** Plugin-specific configuration passed through to the plugin runtime */
|
|
19
|
+
config?: Record<string, unknown>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Top-level plugins configuration stored in agent settings */
|
|
23
|
+
export interface PluginsConfig {
|
|
24
|
+
plugins: PluginConfig[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Metadata about a loaded plugin */
|
|
28
|
+
export interface PluginManifest {
|
|
29
|
+
/** Source identifier (package name or path) */
|
|
30
|
+
source: string;
|
|
31
|
+
/** Plugin slot */
|
|
32
|
+
slot: PluginSlot;
|
|
33
|
+
/** Display name (from package or source) */
|
|
34
|
+
name: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* A provider registration captured from pi.registerProvider().
|
|
39
|
+
* The config is opaque here — it's passed directly to ModelRegistry.registerProvider().
|
|
40
|
+
*/
|
|
41
|
+
export interface ProviderRegistration {
|
|
42
|
+
/** Provider name (e.g., "corporate-ai", "my-proxy") */
|
|
43
|
+
name: string;
|
|
44
|
+
/** Provider config (ProviderConfigInput from pi-coding-agent) */
|
|
45
|
+
config: Record<string, unknown>;
|
|
46
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for config-driven LLM providers.
|
|
3
|
+
* Loaded from the `providers` section of system skills config.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface ProviderConfigEntry {
|
|
7
|
+
/** Display name in settings page (e.g. "Groq") */
|
|
8
|
+
displayName: string;
|
|
9
|
+
/** Provider icon URL */
|
|
10
|
+
iconUrl: string;
|
|
11
|
+
/** Env var name for API key (e.g. "GROQ_API_KEY") */
|
|
12
|
+
envVarName: string;
|
|
13
|
+
/** Provider's API base URL (e.g. "https://api.groq.com/openai") */
|
|
14
|
+
upstreamBaseUrl: string;
|
|
15
|
+
/** HTML help text for the settings page API key input */
|
|
16
|
+
apiKeyInstructions: string;
|
|
17
|
+
/** Placeholder text for the API key input */
|
|
18
|
+
apiKeyPlaceholder: string;
|
|
19
|
+
/** SDK compatibility hint — "openai" means OpenAI-compatible API format */
|
|
20
|
+
sdkCompat?: "openai";
|
|
21
|
+
/** Default model ID when none is configured */
|
|
22
|
+
defaultModel?: string;
|
|
23
|
+
/** Relative path to fetch model list (e.g. "/v1/models") */
|
|
24
|
+
modelsEndpoint?: string;
|
|
25
|
+
/** Override provider name for model registry lookup */
|
|
26
|
+
registryAlias?: string;
|
|
27
|
+
/** Whether to show in "Add Provider" catalog (default: true) */
|
|
28
|
+
catalogVisible?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Optional speech-to-text configuration.
|
|
31
|
+
* If omitted and sdkCompat is "openai", STT is enabled with default endpoint/model.
|
|
32
|
+
* Use this block to override endpoint/model or disable STT for a provider.
|
|
33
|
+
*/
|
|
34
|
+
stt?: {
|
|
35
|
+
/** Set false to disable STT even when this block exists. */
|
|
36
|
+
enabled?: boolean;
|
|
37
|
+
/** STT protocol compatibility; currently only OpenAI-compatible is supported here. */
|
|
38
|
+
sdkCompat?: "openai";
|
|
39
|
+
/** Optional upstream base URL override for STT requests. */
|
|
40
|
+
baseUrl?: string;
|
|
41
|
+
/** Relative or absolute transcription endpoint path/URL. */
|
|
42
|
+
transcriptionPath?: string;
|
|
43
|
+
/** STT model ID (for OpenAI-compatible endpoints). */
|
|
44
|
+
model?: string;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Metadata passed from gateway to worker for config-driven providers. */
|
|
49
|
+
export interface ConfigProviderMeta {
|
|
50
|
+
sdkCompat?: "openai";
|
|
51
|
+
defaultModel?: string;
|
|
52
|
+
registryAlias?: string;
|
|
53
|
+
baseUrlEnvVar: string;
|
|
54
|
+
}
|