@agentic-kit/metering 0.2.0 → 0.3.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/esm/gateway.d.ts +17 -3
- package/esm/gateway.js +20 -9
- package/esm/index.d.ts +1 -1
- package/esm/index.js +1 -1
- package/gateway.d.ts +17 -3
- package/gateway.js +21 -9
- package/index.d.ts +1 -1
- package/index.js +2 -1
- package/package.json +2 -2
package/esm/gateway.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* `agentic-server` speaks OpenAI's `/v1/chat/completions`, so routing a harness
|
|
5
5
|
* through it needs no custom streaming code — only an endpoint whose `baseUrl` is
|
|
6
|
-
* the gateway and whose headers carry the run's identity. Every harness worth
|
|
6
|
+
* the gateway's OpenAI api root and whose headers carry the run's identity. Every harness worth
|
|
7
7
|
* adapting can already talk to an OpenAI-compatible endpoint, which is why this
|
|
8
8
|
* resolution is neutral and only the registration is vendor-specific: an adapter
|
|
9
9
|
* turns `MeteredGateway` into whatever its harness calls a provider.
|
|
@@ -35,7 +35,7 @@ export interface MeteredModelSpec {
|
|
|
35
35
|
cost?: MeteredModelCost;
|
|
36
36
|
}
|
|
37
37
|
export interface MeteredGatewayOptions {
|
|
38
|
-
/** Gateway root, e.g. `https://agentic.example.com
|
|
38
|
+
/** Gateway root, e.g. `https://agentic.example.com`; a trailing `/v1` is tolerated. */
|
|
39
39
|
gatewayUrl: string;
|
|
40
40
|
identity: MeteredIdentity;
|
|
41
41
|
models: readonly MeteredModelSpec[];
|
|
@@ -72,8 +72,22 @@ export interface MeteredModel {
|
|
|
72
72
|
contextWindow: number;
|
|
73
73
|
maxTokens: number;
|
|
74
74
|
}
|
|
75
|
-
/**
|
|
75
|
+
/**
|
|
76
|
+
* Normalize the gateway root: absolute http(s), no trailing slash, no `/v1`.
|
|
77
|
+
*
|
|
78
|
+
* A caller who names the api root instead is answered the root it hangs under,
|
|
79
|
+
* so a deployment that spells its gateway either way reaches the same routes
|
|
80
|
+
* rather than one of them 404ing on the first model turn.
|
|
81
|
+
*/
|
|
76
82
|
export declare function normalizeGatewayUrl(gatewayUrl: string): string;
|
|
83
|
+
/**
|
|
84
|
+
* The OpenAI api root under a gateway root.
|
|
85
|
+
*
|
|
86
|
+
* An `openai-completions` client (pi-ai, the OpenAI SDKs, any harness built on
|
|
87
|
+
* either) appends `/chat/completions` to its `baseUrl` and nothing else, so the
|
|
88
|
+
* `/v1` belongs here rather than in the caller's environment.
|
|
89
|
+
*/
|
|
90
|
+
export declare function completionsBaseUrl(gatewayUrl: string): string;
|
|
77
91
|
export declare function resolveMeteredModel(spec: MeteredModelSpec): MeteredModel;
|
|
78
92
|
/**
|
|
79
93
|
* Resolve the gateway endpoint for a run.
|
package/esm/gateway.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* `agentic-server` speaks OpenAI's `/v1/chat/completions`, so routing a harness
|
|
5
5
|
* through it needs no custom streaming code — only an endpoint whose `baseUrl` is
|
|
6
|
-
* the gateway and whose headers carry the run's identity. Every harness worth
|
|
6
|
+
* the gateway's OpenAI api root and whose headers carry the run's identity. Every harness worth
|
|
7
7
|
* adapting can already talk to an OpenAI-compatible endpoint, which is why this
|
|
8
8
|
* resolution is neutral and only the registration is vendor-specific: an adapter
|
|
9
9
|
* turns `MeteredGateway` into whatever its harness calls a provider.
|
|
@@ -14,7 +14,13 @@ export const GATEWAY_API = 'openai-completions';
|
|
|
14
14
|
/** Default provider name; also the prefix a model picker shows. */
|
|
15
15
|
export const DEFAULT_PROVIDER_NAME = 'constructive-gateway';
|
|
16
16
|
const ZERO_COST = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
17
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* Normalize the gateway root: absolute http(s), no trailing slash, no `/v1`.
|
|
19
|
+
*
|
|
20
|
+
* A caller who names the api root instead is answered the root it hangs under,
|
|
21
|
+
* so a deployment that spells its gateway either way reaches the same routes
|
|
22
|
+
* rather than one of them 404ing on the first model turn.
|
|
23
|
+
*/
|
|
18
24
|
export function normalizeGatewayUrl(gatewayUrl) {
|
|
19
25
|
const raw = gatewayUrl?.trim();
|
|
20
26
|
if (!raw)
|
|
@@ -29,14 +35,19 @@ export function normalizeGatewayUrl(gatewayUrl) {
|
|
|
29
35
|
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
30
36
|
throw new Error(`metered model: gatewayUrl must be http(s), got ${parsed.protocol}`);
|
|
31
37
|
}
|
|
32
|
-
const path = parsed.pathname.replace(/\/+$/, '');
|
|
33
|
-
// An OpenAI-compatible client appends `/v1/chat/completions`, so a baseUrl that
|
|
34
|
-
// already ends in `/v1` would request `/v1/v1/chat/completions` and 404 at the
|
|
35
|
-
// first model turn.
|
|
36
|
-
if (/\/v1$/.test(path))
|
|
37
|
-
throw new Error(`metered model: gatewayUrl must be the gateway root, not ${raw} (drop the /v1)`);
|
|
38
|
+
const path = parsed.pathname.replace(/\/+$/, '').replace(/\/v1$/, '');
|
|
38
39
|
return `${parsed.origin}${path}`;
|
|
39
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* The OpenAI api root under a gateway root.
|
|
43
|
+
*
|
|
44
|
+
* An `openai-completions` client (pi-ai, the OpenAI SDKs, any harness built on
|
|
45
|
+
* either) appends `/chat/completions` to its `baseUrl` and nothing else, so the
|
|
46
|
+
* `/v1` belongs here rather than in the caller's environment.
|
|
47
|
+
*/
|
|
48
|
+
export function completionsBaseUrl(gatewayUrl) {
|
|
49
|
+
return `${normalizeGatewayUrl(gatewayUrl)}/v1`;
|
|
50
|
+
}
|
|
40
51
|
export function resolveMeteredModel(spec) {
|
|
41
52
|
if (!spec.id?.trim())
|
|
42
53
|
throw new Error('metered model: model id is required');
|
|
@@ -64,7 +75,7 @@ export function resolveMeteredGateway(options) {
|
|
|
64
75
|
return {
|
|
65
76
|
providerName: options.providerName ?? DEFAULT_PROVIDER_NAME,
|
|
66
77
|
displayName: options.displayName ?? 'Constructive (metered)',
|
|
67
|
-
baseUrl:
|
|
78
|
+
baseUrl: completionsBaseUrl(options.gatewayUrl),
|
|
68
79
|
api: GATEWAY_API,
|
|
69
80
|
headers,
|
|
70
81
|
// Harnesses typically require an apiKey once models are declared; the gateway
|
package/esm/index.d.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* No harness SDK is imported here. An adapter registers `MeteredGateway` with
|
|
8
8
|
* its own harness and feeds `toUsageReport()` from its own message events.
|
|
9
9
|
*/
|
|
10
|
-
export { DEFAULT_PROVIDER_NAME, GATEWAY_API, type MeteredGateway, type MeteredGatewayOptions, type MeteredModel, type MeteredModelCost, type MeteredModelSpec, normalizeGatewayUrl, resolveMeteredGateway, resolveMeteredModel } from './gateway';
|
|
10
|
+
export { completionsBaseUrl, DEFAULT_PROVIDER_NAME, GATEWAY_API, type MeteredGateway, type MeteredGatewayOptions, type MeteredModel, type MeteredModelCost, type MeteredModelSpec, normalizeGatewayUrl, resolveMeteredGateway, resolveMeteredModel } from './gateway';
|
|
11
11
|
export { ACTOR_ID_HEADER, buildIdentityHeaders, DATABASE_ID_HEADER, ENTITY_ID_HEADER, type MeteredIdentity } from './identity';
|
|
12
12
|
export { httpUsageSink, type HttpUsageSinkOptions, UsageReporter, type UsageReporterOptions, type UsageSink } from './reporter';
|
|
13
13
|
export { type AssistantUsageMessage, isAssistantMessage, toUsageReport, type ToUsageReportOptions, type UsageReport } from './usage-report';
|
package/esm/index.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* No harness SDK is imported here. An adapter registers `MeteredGateway` with
|
|
8
8
|
* its own harness and feeds `toUsageReport()` from its own message events.
|
|
9
9
|
*/
|
|
10
|
-
export { DEFAULT_PROVIDER_NAME, GATEWAY_API, normalizeGatewayUrl, resolveMeteredGateway, resolveMeteredModel } from './gateway';
|
|
10
|
+
export { completionsBaseUrl, DEFAULT_PROVIDER_NAME, GATEWAY_API, normalizeGatewayUrl, resolveMeteredGateway, resolveMeteredModel } from './gateway';
|
|
11
11
|
export { ACTOR_ID_HEADER, buildIdentityHeaders, DATABASE_ID_HEADER, ENTITY_ID_HEADER } from './identity';
|
|
12
12
|
export { httpUsageSink, UsageReporter } from './reporter';
|
|
13
13
|
export { isAssistantMessage, toUsageReport } from './usage-report';
|
package/gateway.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* `agentic-server` speaks OpenAI's `/v1/chat/completions`, so routing a harness
|
|
5
5
|
* through it needs no custom streaming code — only an endpoint whose `baseUrl` is
|
|
6
|
-
* the gateway and whose headers carry the run's identity. Every harness worth
|
|
6
|
+
* the gateway's OpenAI api root and whose headers carry the run's identity. Every harness worth
|
|
7
7
|
* adapting can already talk to an OpenAI-compatible endpoint, which is why this
|
|
8
8
|
* resolution is neutral and only the registration is vendor-specific: an adapter
|
|
9
9
|
* turns `MeteredGateway` into whatever its harness calls a provider.
|
|
@@ -35,7 +35,7 @@ export interface MeteredModelSpec {
|
|
|
35
35
|
cost?: MeteredModelCost;
|
|
36
36
|
}
|
|
37
37
|
export interface MeteredGatewayOptions {
|
|
38
|
-
/** Gateway root, e.g. `https://agentic.example.com
|
|
38
|
+
/** Gateway root, e.g. `https://agentic.example.com`; a trailing `/v1` is tolerated. */
|
|
39
39
|
gatewayUrl: string;
|
|
40
40
|
identity: MeteredIdentity;
|
|
41
41
|
models: readonly MeteredModelSpec[];
|
|
@@ -72,8 +72,22 @@ export interface MeteredModel {
|
|
|
72
72
|
contextWindow: number;
|
|
73
73
|
maxTokens: number;
|
|
74
74
|
}
|
|
75
|
-
/**
|
|
75
|
+
/**
|
|
76
|
+
* Normalize the gateway root: absolute http(s), no trailing slash, no `/v1`.
|
|
77
|
+
*
|
|
78
|
+
* A caller who names the api root instead is answered the root it hangs under,
|
|
79
|
+
* so a deployment that spells its gateway either way reaches the same routes
|
|
80
|
+
* rather than one of them 404ing on the first model turn.
|
|
81
|
+
*/
|
|
76
82
|
export declare function normalizeGatewayUrl(gatewayUrl: string): string;
|
|
83
|
+
/**
|
|
84
|
+
* The OpenAI api root under a gateway root.
|
|
85
|
+
*
|
|
86
|
+
* An `openai-completions` client (pi-ai, the OpenAI SDKs, any harness built on
|
|
87
|
+
* either) appends `/chat/completions` to its `baseUrl` and nothing else, so the
|
|
88
|
+
* `/v1` belongs here rather than in the caller's environment.
|
|
89
|
+
*/
|
|
90
|
+
export declare function completionsBaseUrl(gatewayUrl: string): string;
|
|
77
91
|
export declare function resolveMeteredModel(spec: MeteredModelSpec): MeteredModel;
|
|
78
92
|
/**
|
|
79
93
|
* Resolve the gateway endpoint for a run.
|
package/gateway.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* `agentic-server` speaks OpenAI's `/v1/chat/completions`, so routing a harness
|
|
6
6
|
* through it needs no custom streaming code — only an endpoint whose `baseUrl` is
|
|
7
|
-
* the gateway and whose headers carry the run's identity. Every harness worth
|
|
7
|
+
* the gateway's OpenAI api root and whose headers carry the run's identity. Every harness worth
|
|
8
8
|
* adapting can already talk to an OpenAI-compatible endpoint, which is why this
|
|
9
9
|
* resolution is neutral and only the registration is vendor-specific: an adapter
|
|
10
10
|
* turns `MeteredGateway` into whatever its harness calls a provider.
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
13
|
exports.DEFAULT_PROVIDER_NAME = exports.GATEWAY_API = void 0;
|
|
14
14
|
exports.normalizeGatewayUrl = normalizeGatewayUrl;
|
|
15
|
+
exports.completionsBaseUrl = completionsBaseUrl;
|
|
15
16
|
exports.resolveMeteredModel = resolveMeteredModel;
|
|
16
17
|
exports.resolveMeteredGateway = resolveMeteredGateway;
|
|
17
18
|
const identity_1 = require("./identity");
|
|
@@ -20,7 +21,13 @@ exports.GATEWAY_API = 'openai-completions';
|
|
|
20
21
|
/** Default provider name; also the prefix a model picker shows. */
|
|
21
22
|
exports.DEFAULT_PROVIDER_NAME = 'constructive-gateway';
|
|
22
23
|
const ZERO_COST = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
23
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* Normalize the gateway root: absolute http(s), no trailing slash, no `/v1`.
|
|
26
|
+
*
|
|
27
|
+
* A caller who names the api root instead is answered the root it hangs under,
|
|
28
|
+
* so a deployment that spells its gateway either way reaches the same routes
|
|
29
|
+
* rather than one of them 404ing on the first model turn.
|
|
30
|
+
*/
|
|
24
31
|
function normalizeGatewayUrl(gatewayUrl) {
|
|
25
32
|
const raw = gatewayUrl?.trim();
|
|
26
33
|
if (!raw)
|
|
@@ -35,14 +42,19 @@ function normalizeGatewayUrl(gatewayUrl) {
|
|
|
35
42
|
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
36
43
|
throw new Error(`metered model: gatewayUrl must be http(s), got ${parsed.protocol}`);
|
|
37
44
|
}
|
|
38
|
-
const path = parsed.pathname.replace(/\/+$/, '');
|
|
39
|
-
// An OpenAI-compatible client appends `/v1/chat/completions`, so a baseUrl that
|
|
40
|
-
// already ends in `/v1` would request `/v1/v1/chat/completions` and 404 at the
|
|
41
|
-
// first model turn.
|
|
42
|
-
if (/\/v1$/.test(path))
|
|
43
|
-
throw new Error(`metered model: gatewayUrl must be the gateway root, not ${raw} (drop the /v1)`);
|
|
45
|
+
const path = parsed.pathname.replace(/\/+$/, '').replace(/\/v1$/, '');
|
|
44
46
|
return `${parsed.origin}${path}`;
|
|
45
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* The OpenAI api root under a gateway root.
|
|
50
|
+
*
|
|
51
|
+
* An `openai-completions` client (pi-ai, the OpenAI SDKs, any harness built on
|
|
52
|
+
* either) appends `/chat/completions` to its `baseUrl` and nothing else, so the
|
|
53
|
+
* `/v1` belongs here rather than in the caller's environment.
|
|
54
|
+
*/
|
|
55
|
+
function completionsBaseUrl(gatewayUrl) {
|
|
56
|
+
return `${normalizeGatewayUrl(gatewayUrl)}/v1`;
|
|
57
|
+
}
|
|
46
58
|
function resolveMeteredModel(spec) {
|
|
47
59
|
if (!spec.id?.trim())
|
|
48
60
|
throw new Error('metered model: model id is required');
|
|
@@ -70,7 +82,7 @@ function resolveMeteredGateway(options) {
|
|
|
70
82
|
return {
|
|
71
83
|
providerName: options.providerName ?? exports.DEFAULT_PROVIDER_NAME,
|
|
72
84
|
displayName: options.displayName ?? 'Constructive (metered)',
|
|
73
|
-
baseUrl:
|
|
85
|
+
baseUrl: completionsBaseUrl(options.gatewayUrl),
|
|
74
86
|
api: exports.GATEWAY_API,
|
|
75
87
|
headers,
|
|
76
88
|
// Harnesses typically require an apiKey once models are declared; the gateway
|
package/index.d.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* No harness SDK is imported here. An adapter registers `MeteredGateway` with
|
|
8
8
|
* its own harness and feeds `toUsageReport()` from its own message events.
|
|
9
9
|
*/
|
|
10
|
-
export { DEFAULT_PROVIDER_NAME, GATEWAY_API, type MeteredGateway, type MeteredGatewayOptions, type MeteredModel, type MeteredModelCost, type MeteredModelSpec, normalizeGatewayUrl, resolveMeteredGateway, resolveMeteredModel } from './gateway';
|
|
10
|
+
export { completionsBaseUrl, DEFAULT_PROVIDER_NAME, GATEWAY_API, type MeteredGateway, type MeteredGatewayOptions, type MeteredModel, type MeteredModelCost, type MeteredModelSpec, normalizeGatewayUrl, resolveMeteredGateway, resolveMeteredModel } from './gateway';
|
|
11
11
|
export { ACTOR_ID_HEADER, buildIdentityHeaders, DATABASE_ID_HEADER, ENTITY_ID_HEADER, type MeteredIdentity } from './identity';
|
|
12
12
|
export { httpUsageSink, type HttpUsageSinkOptions, UsageReporter, type UsageReporterOptions, type UsageSink } from './reporter';
|
|
13
13
|
export { type AssistantUsageMessage, isAssistantMessage, toUsageReport, type ToUsageReportOptions, type UsageReport } from './usage-report';
|
package/index.js
CHANGED
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
* its own harness and feeds `toUsageReport()` from its own message events.
|
|
10
10
|
*/
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.toUsageReport = exports.isAssistantMessage = exports.UsageReporter = exports.httpUsageSink = exports.ENTITY_ID_HEADER = exports.DATABASE_ID_HEADER = exports.buildIdentityHeaders = exports.ACTOR_ID_HEADER = exports.resolveMeteredModel = exports.resolveMeteredGateway = exports.normalizeGatewayUrl = exports.GATEWAY_API = exports.DEFAULT_PROVIDER_NAME = void 0;
|
|
12
|
+
exports.toUsageReport = exports.isAssistantMessage = exports.UsageReporter = exports.httpUsageSink = exports.ENTITY_ID_HEADER = exports.DATABASE_ID_HEADER = exports.buildIdentityHeaders = exports.ACTOR_ID_HEADER = exports.resolveMeteredModel = exports.resolveMeteredGateway = exports.normalizeGatewayUrl = exports.GATEWAY_API = exports.DEFAULT_PROVIDER_NAME = exports.completionsBaseUrl = void 0;
|
|
13
13
|
var gateway_1 = require("./gateway");
|
|
14
|
+
Object.defineProperty(exports, "completionsBaseUrl", { enumerable: true, get: function () { return gateway_1.completionsBaseUrl; } });
|
|
14
15
|
Object.defineProperty(exports, "DEFAULT_PROVIDER_NAME", { enumerable: true, get: function () { return gateway_1.DEFAULT_PROVIDER_NAME; } });
|
|
15
16
|
Object.defineProperty(exports, "GATEWAY_API", { enumerable: true, get: function () { return gateway_1.GATEWAY_API; } });
|
|
16
17
|
Object.defineProperty(exports, "normalizeGatewayUrl", { enumerable: true, get: function () { return gateway_1.normalizeGatewayUrl; } });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentic-kit/metering",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"author": "Constructive <developers@constructive.io>",
|
|
5
5
|
"description": "harness-neutral metering for an agent run — the Constructive gateway as a model endpoint, run identity headers, and usage reporting",
|
|
6
6
|
"main": "index.js",
|
|
@@ -35,5 +35,5 @@
|
|
|
35
35
|
"gateway",
|
|
36
36
|
"constructive"
|
|
37
37
|
],
|
|
38
|
-
"gitHead": "
|
|
38
|
+
"gitHead": "224b9030a0314a070f3054bc551a986949993b5d"
|
|
39
39
|
}
|