@agentic-kit/metering 0.3.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 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` — not a `/v1` path. */
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
- /** Normalize the gateway root: absolute http(s), no trailing slash, no `/v1`. */
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
- /** Normalize the gateway root: absolute http(s), no trailing slash, no `/v1`. */
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: normalizeGatewayUrl(options.gatewayUrl),
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` — not a `/v1` path. */
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
- /** Normalize the gateway root: absolute http(s), no trailing slash, no `/v1`. */
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
- /** Normalize the gateway root: absolute http(s), no trailing slash, no `/v1`. */
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: normalizeGatewayUrl(options.gatewayUrl),
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.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": "b5b6b9f05276b7e9cbd3d1f4acd31dd315c353ea"
38
+ "gitHead": "224b9030a0314a070f3054bc551a986949993b5d"
39
39
  }