agentic-server 0.21.1 → 0.22.0
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/README.md +41 -48
- package/esm/index.js +17 -17
- package/esm/providers.js +77 -0
- package/esm/router.js +236 -459
- package/esm/server.js +31 -0
- package/esm/transforms.js +111 -0
- package/esm/types.js +1 -0
- package/index.d.ts +19 -19
- package/index.js +22 -21
- package/package.json +9 -16
- package/providers.d.ts +8 -0
- package/providers.js +82 -0
- package/router.d.ts +10 -18
- package/router.js +238 -493
- package/server.d.ts +10 -0
- package/server.js +38 -0
- package/transforms.d.ts +11 -0
- package/transforms.js +117 -0
- package/types.d.ts +64 -0
- package/types.js +2 -0
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;
|
package/transforms.d.ts
ADDED
|
@@ -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