agentic-server 0.21.2 → 0.22.1

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/server.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ import express from 'express';
2
+ import type { AgenticServerOptions } from './types';
3
+ export interface AgenticServerStartOptions extends AgenticServerOptions {
4
+ port?: number;
5
+ }
6
+ /**
7
+ * Create a standalone Express app for the agentic server.
8
+ * Used by both standalone mode and integration tests.
9
+ */
10
+ export declare const createAgenticServer: (options: AgenticServerStartOptions) => express.Express;
package/server.js ADDED
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createAgenticServer = void 0;
7
+ const express_1 = __importDefault(require("express"));
8
+ const router_1 = require("./router");
9
+ /** Identity headers trusted only when isPublic === false (private network). */
10
+ const IDENTITY_HEADERS = [
11
+ 'x-database-id',
12
+ 'x-entity-id',
13
+ 'x-actor-id'
14
+ ];
15
+ /**
16
+ * Create a standalone Express app for the agentic server.
17
+ * Used by both standalone mode and integration tests.
18
+ */
19
+ const createAgenticServer = (options) => {
20
+ const app = (0, express_1.default)();
21
+ app.use(express_1.default.json());
22
+ // When isPublic === true, strip identity headers from all incoming requests.
23
+ // Public deployments cannot trust client-supplied tenant context.
24
+ // When isPublic === false (default), the server is on a private network
25
+ // and trusts identity headers directly — same pattern as the GraphQL server
26
+ // with API_IS_PUBLIC=false.
27
+ if (options.isPublic) {
28
+ app.use((req, _res, next) => {
29
+ for (const header of IDENTITY_HEADERS) {
30
+ req.headers[header] = undefined;
31
+ }
32
+ next();
33
+ });
34
+ }
35
+ app.use((0, router_1.createRouter)(options));
36
+ return app;
37
+ };
38
+ exports.createAgenticServer = createAgenticServer;
@@ -0,0 +1,11 @@
1
+ import type { ResolvedProvider, UsageResult } from './types';
2
+ export declare function transformChatRequest(provider: ResolvedProvider, body: Record<string, unknown>): Record<string, unknown>;
3
+ export declare function transformEmbedRequest(provider: ResolvedProvider, body: Record<string, unknown>): Record<string, unknown>;
4
+ export declare function transformChatResponse(data: Record<string, unknown>, provider: ResolvedProvider): {
5
+ body: Record<string, unknown>;
6
+ usage: UsageResult;
7
+ };
8
+ export declare function transformEmbedResponse(data: Record<string, unknown>, provider: ResolvedProvider): {
9
+ body: Record<string, unknown>;
10
+ usage: UsageResult;
11
+ };
package/transforms.js ADDED
@@ -0,0 +1,117 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.transformChatRequest = transformChatRequest;
4
+ exports.transformEmbedRequest = transformEmbedRequest;
5
+ exports.transformChatResponse = transformChatResponse;
6
+ exports.transformEmbedResponse = transformEmbedResponse;
7
+ function transformChatRequest(provider, body) {
8
+ let model = body.model || provider.defaultModel;
9
+ if (typeof model === 'string' && model.includes('/')) {
10
+ const prefix = model.split('/')[0];
11
+ if (prefix === provider.type) {
12
+ model = model.split('/').slice(1).join('/');
13
+ }
14
+ }
15
+ if (provider.type === 'ollama') {
16
+ return {
17
+ model: model || 'llama3',
18
+ messages: body.messages,
19
+ stream: false,
20
+ ...(body.temperature !== undefined && {
21
+ options: { temperature: body.temperature }
22
+ })
23
+ };
24
+ }
25
+ if (provider.type === 'anthropic') {
26
+ const messages = body.messages;
27
+ const systemMsg = messages?.find((m) => m.role === 'system');
28
+ const nonSystem = messages?.filter((m) => m.role !== 'system') || [];
29
+ return {
30
+ model: model || 'claude-sonnet-4-20250514',
31
+ messages: nonSystem,
32
+ max_tokens: body.max_tokens || 4096,
33
+ ...(systemMsg && { system: systemMsg.content }),
34
+ ...(body.temperature !== undefined && { temperature: body.temperature })
35
+ };
36
+ }
37
+ // OpenAI-compatible (default)
38
+ return { ...body, model: model || 'gpt-4o' };
39
+ }
40
+ function transformEmbedRequest(provider, body) {
41
+ const model = body.model || provider.defaultModel;
42
+ if (provider.type === 'ollama') {
43
+ return {
44
+ model: model || 'nomic-embed-text',
45
+ input: body.input
46
+ };
47
+ }
48
+ return { ...body, model: model || 'text-embedding-3-small' };
49
+ }
50
+ function transformChatResponse(data, provider) {
51
+ if (provider.type === 'ollama') {
52
+ const msg = data.message;
53
+ const promptTokens = data.prompt_eval_count || 0;
54
+ const completionTokens = data.eval_count || 0;
55
+ const usage = {
56
+ prompt_tokens: promptTokens,
57
+ completion_tokens: completionTokens,
58
+ total_tokens: promptTokens + completionTokens
59
+ };
60
+ return {
61
+ body: {
62
+ id: `chatcmpl-${Date.now()}`,
63
+ object: 'chat.completion',
64
+ choices: [{
65
+ message: msg || { role: 'assistant', content: '' },
66
+ finish_reason: 'stop',
67
+ index: 0
68
+ }],
69
+ usage
70
+ },
71
+ usage
72
+ };
73
+ }
74
+ if (provider.type === 'anthropic') {
75
+ const content = data.content;
76
+ const text = content?.find((c) => c.type === 'text')?.text || '';
77
+ const inputUsage = data.usage || {};
78
+ const usage = {
79
+ prompt_tokens: inputUsage.input_tokens || 0,
80
+ completion_tokens: inputUsage.output_tokens || 0,
81
+ total_tokens: (inputUsage.input_tokens || 0) + (inputUsage.output_tokens || 0)
82
+ };
83
+ return {
84
+ body: {
85
+ id: data.id || `chatcmpl-${Date.now()}`,
86
+ object: 'chat.completion',
87
+ choices: [{
88
+ message: { role: 'assistant', content: text },
89
+ finish_reason: 'stop',
90
+ index: 0
91
+ }],
92
+ usage
93
+ },
94
+ usage
95
+ };
96
+ }
97
+ // OpenAI — pass through
98
+ const usage = (data.usage || { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 });
99
+ return { body: data, usage };
100
+ }
101
+ function transformEmbedResponse(data, provider) {
102
+ if (provider.type === 'ollama') {
103
+ const raw = (data.embeddings || data.embedding || []);
104
+ const embeddings = Array.isArray(raw[0]) ? raw : [raw];
105
+ const usage = { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 };
106
+ return {
107
+ body: {
108
+ object: 'list',
109
+ data: embeddings.map((emb, i) => ({ object: 'embedding', embedding: emb, index: i })),
110
+ usage
111
+ },
112
+ usage
113
+ };
114
+ }
115
+ const usage = (data.usage || { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 });
116
+ return { body: data, usage };
117
+ }
package/types.d.ts ADDED
@@ -0,0 +1,64 @@
1
+ export interface ProviderConfig {
2
+ type: string;
3
+ baseUrl: string;
4
+ apiKey?: string;
5
+ defaultModel?: string;
6
+ }
7
+ export interface AgenticServerOptions {
8
+ providers?: ProviderConfig[];
9
+ /** Legacy single-provider base URL */
10
+ providerBaseUrl?: string;
11
+ /** Legacy API key */
12
+ providerApiKey?: string;
13
+ /** Legacy default model */
14
+ defaultModel?: string;
15
+ /** Legacy provider type: 'openai' | 'ollama' | 'anthropic' */
16
+ providerType?: string;
17
+ /** Fire-and-forget sink for inference metering. Inject this to record usage
18
+ * against whatever telemetry/billing backend the consumer owns. When
19
+ * omitted, no metering occurs. The gateway itself is backend-agnostic and
20
+ * never imports a concrete metering implementation. */
21
+ inferenceSink?: InferenceSink;
22
+ /** Whether this server instance is publicly accessible.
23
+ * When false (default), the server is deployed behind a private network
24
+ * (Docker network, K8s service mesh) and trusts identity headers directly:
25
+ * X-Database-Id, X-Entity-Id, X-Actor-Id
26
+ * When true, identity headers are stripped from incoming requests
27
+ * (external clients cannot set tenant context). */
28
+ isPublic?: boolean;
29
+ }
30
+ export interface ResolvedProvider {
31
+ type: string;
32
+ baseUrl: string;
33
+ apiKey?: string;
34
+ defaultModel?: string;
35
+ }
36
+ export interface UsageResult {
37
+ prompt_tokens: number;
38
+ completion_tokens: number;
39
+ total_tokens: number;
40
+ }
41
+ /**
42
+ * Fire-and-forget sink for inference metering. Implementations record the entry
43
+ * to whatever telemetry/billing backend they own (e.g. compute_log) and MUST
44
+ * NOT throw — metering must never affect inference latency or response delivery.
45
+ */
46
+ export interface InferenceSink {
47
+ logInference(entry: InferenceEntry): void;
48
+ }
49
+ export interface InferenceEntry {
50
+ databaseId: string;
51
+ entityId?: string;
52
+ actorId?: string;
53
+ model: string;
54
+ provider: string;
55
+ service: 'chat' | 'embed';
56
+ operation: string;
57
+ inputTokens: number;
58
+ outputTokens: number;
59
+ totalTokens: number;
60
+ latencyMs: number;
61
+ status: 'ok' | 'error';
62
+ errorType?: string;
63
+ rawUsage?: unknown;
64
+ }
package/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });