@stigg/agent-sdk-js 0.7.8 → 0.7.9
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 +1 -0
- package/dist/bindings/client/core/wire-surface.d.ts +29 -0
- package/dist/bindings/client/core/wire-surface.js +79 -0
- package/dist/bindings/client/gateways/agentgateway/agentgateway-recipe.d.ts +33 -0
- package/dist/bindings/client/gateways/agentgateway/agentgateway-recipe.js +3 -51
- package/dist/bindings/client/gateways/cloudflare/cloudflare-recipe.js +18 -68
- package/dist/bindings/client/gateways/truefoundry/truefoundry-recipe.d.ts +35 -0
- package/dist/bindings/client/gateways/truefoundry/truefoundry-recipe.js +75 -0
- package/dist/bindings/client/gateways/truefoundry/types.d.ts +22 -0
- package/dist/bindings/client/gateways/truefoundry/types.js +2 -0
- package/dist/bindings/shared/token-usage.d.ts +14 -18
- package/dist/bindings/shared/token-usage.js +16 -24
- package/dist/index.d.ts +3 -0
- package/dist/index.js +7 -1
- package/dist/stigg-agent-sdk.js +4 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -155,6 +155,7 @@ closed, and conflating them is how an outage takes an application down.
|
|
|
155
155
|
| <img src="https://raw.githubusercontent.com/stiggio/stigg-agent-sdk-ts/main/docs/logos/bedrock.svg" width="18" alt=""> | Amazon Bedrock | <img src="https://raw.githubusercontent.com/stiggio/stigg-agent-sdk-ts/main/docs/logos/litellm.png" width="18" alt=""> | LiteLLM |
|
|
156
156
|
| <img src="https://raw.githubusercontent.com/stiggio/stigg-agent-sdk-ts/main/docs/logos/cloudflare.svg" width="18" alt=""> | Cloudflare Workers AI | <img src="https://raw.githubusercontent.com/stiggio/stigg-agent-sdk-ts/main/docs/logos/openrouter.svg" width="18" alt=""> | OpenRouter |
|
|
157
157
|
| <img src="https://raw.githubusercontent.com/stiggio/stigg-agent-sdk-ts/main/docs/logos/databricks.svg" width="18" alt=""> | Databricks | | agentgateway (incl. Solo.io Enterprise) |
|
|
158
|
+
| | TrueFoundry AI Gateway | | |
|
|
158
159
|
|
|
159
160
|
A gateway called with plain HTTP rather than through an SDK is governed the same way:
|
|
160
161
|
`stigg.wrapFetch({ baseUrl, recipe })` hands back a `fetch` that is checked and metered per
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { MethodRecipe, MeteredResponse } from './types';
|
|
2
|
+
/** What the response body says about the call, buffered or streamed. */
|
|
3
|
+
export type BodyFacts = {
|
|
4
|
+
model?: string;
|
|
5
|
+
id?: string;
|
|
6
|
+
};
|
|
7
|
+
/** What only the gateway knows, folded onto the wire reader's measurement. */
|
|
8
|
+
export type GatewayFacts = {
|
|
9
|
+
/** The model that actually served, reported under the recipe's model dimension. */
|
|
10
|
+
model?: string;
|
|
11
|
+
/** The upstream's completion id: the idempotency key when the wire reader stated none. */
|
|
12
|
+
id?: string;
|
|
13
|
+
/** The gateway's own request id: the idempotency key ahead of anything the wire reader stated. */
|
|
14
|
+
requestId?: string;
|
|
15
|
+
/** Anything else the gateway states, as dimensions. */
|
|
16
|
+
dimensions?: Record<string, string>;
|
|
17
|
+
};
|
|
18
|
+
export type GatewaySurfaceOptions = {
|
|
19
|
+
estimate: NonNullable<MethodRecipe['estimate']>;
|
|
20
|
+
modelDimension: string;
|
|
21
|
+
/** The gateway's facts, from the body and the response. Defaults to the body's own. */
|
|
22
|
+
facts?: (body: BodyFacts, response: MeteredResponse | undefined) => GatewayFacts;
|
|
23
|
+
/** A call nobody was charged for (a cache hit), which reports nothing at all. */
|
|
24
|
+
skip?: (response: MeteredResponse | undefined) => boolean;
|
|
25
|
+
};
|
|
26
|
+
/** A response header, with an empty value read as absent. */
|
|
27
|
+
export declare function headerOf(response: MeteredResponse | undefined, name: string): string | undefined;
|
|
28
|
+
/** One wire's surface, answered through a gateway. */
|
|
29
|
+
export declare function gatewaySurface(base: MethodRecipe, options: GatewaySurfaceOptions): MethodRecipe;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.headerOf = headerOf;
|
|
4
|
+
exports.gatewaySurface = gatewaySurface;
|
|
5
|
+
/**
|
|
6
|
+
* A provider's wire, answered by a gateway - the wrapper agentgateway, TrueFoundry and Cloudflare
|
|
7
|
+
* AI Gateway share.
|
|
8
|
+
*
|
|
9
|
+
* Each of those gateways answers in the shape of the wire it was called on, so that wire's recipe
|
|
10
|
+
* already has the right reader - and Anthropic's streamed usage in particular accumulates across
|
|
11
|
+
* two event types, which is not logic worth having a second copy of. What the gateway adds is what
|
|
12
|
+
* only it knows: the model that actually served and the id a replay repeats, plus whatever a
|
|
13
|
+
* particular gateway states in its own headers. This file folds those onto the wire reader's
|
|
14
|
+
* measurement; each recipe supplies the facts.
|
|
15
|
+
*/
|
|
16
|
+
const meter_stream_1 = require("../../shared/meter-stream");
|
|
17
|
+
const types_1 = require("../../../types");
|
|
18
|
+
/** A response header, with an empty value read as absent. */
|
|
19
|
+
function headerOf(response, name) {
|
|
20
|
+
const raw = response?.headers?.get(name);
|
|
21
|
+
return raw == null || raw === '' ? undefined : raw;
|
|
22
|
+
}
|
|
23
|
+
function withGatewayFacts(measured, facts, modelDimension) {
|
|
24
|
+
const dimensions = {
|
|
25
|
+
...measured.dimensions,
|
|
26
|
+
...facts.dimensions,
|
|
27
|
+
};
|
|
28
|
+
if (facts.model)
|
|
29
|
+
dimensions[modelDimension] = facts.model;
|
|
30
|
+
return {
|
|
31
|
+
...measured,
|
|
32
|
+
dimensions: Object.keys(dimensions).length > 0 ? dimensions : undefined,
|
|
33
|
+
idempotencyKey: facts.requestId ?? measured.idempotencyKey ?? facts.id,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/** A non-empty string, or absent - an empty id would key every such call as one, the stream tap's rule too. */
|
|
37
|
+
const stringOr = (value) => (typeof value === 'string' && value ? value : undefined);
|
|
38
|
+
/** One wire's surface, answered through a gateway. */
|
|
39
|
+
function gatewaySurface(base, options) {
|
|
40
|
+
const { estimate, modelDimension, skip } = options;
|
|
41
|
+
const facts = options.facts ?? ((body) => body);
|
|
42
|
+
return {
|
|
43
|
+
// Spread first so anything the base declares and this wrapper does not know about - like
|
|
44
|
+
// `contextArg` - survives being wrapped.
|
|
45
|
+
...base,
|
|
46
|
+
estimate,
|
|
47
|
+
extractUsage: (result, response) => {
|
|
48
|
+
// Checked before the body is read, because the body of a cache hit looks like a real call.
|
|
49
|
+
if (skip?.(response))
|
|
50
|
+
return undefined;
|
|
51
|
+
const measured = base.extractUsage?.(result, response);
|
|
52
|
+
if (measured === undefined)
|
|
53
|
+
return undefined;
|
|
54
|
+
const body = { model: stringOr(result?.model), id: stringOr(result?.id) };
|
|
55
|
+
return withGatewayFacts((0, types_1.toMeasurement)(measured), facts(body, response), modelDimension);
|
|
56
|
+
},
|
|
57
|
+
attachStream: base.attachStream
|
|
58
|
+
? (stream, ingest, response) => {
|
|
59
|
+
// Nothing attached at all: the consumer's stream is left untouched and nothing reported.
|
|
60
|
+
if (skip?.(response))
|
|
61
|
+
return;
|
|
62
|
+
// A second tap for the body's model and id - the wire readers carry out counters and
|
|
63
|
+
// nothing else. Each wire states them in its own envelope: Anthropic on
|
|
64
|
+
// `message_start.message`, the Responses API on `response`, Chat Completions at the
|
|
65
|
+
// chunk root. `meterStream` captures what it patches before patching, so two observers
|
|
66
|
+
// each see every chunk exactly once.
|
|
67
|
+
const seen = {};
|
|
68
|
+
(0, meter_stream_1.meterStream)(stream, (chunk) => {
|
|
69
|
+
const envelope = chunk?.message ?? chunk?.response ?? chunk;
|
|
70
|
+
if (typeof envelope?.model === 'string' && envelope.model)
|
|
71
|
+
seen.model = envelope.model;
|
|
72
|
+
if (typeof envelope?.id === 'string' && envelope.id)
|
|
73
|
+
seen.id = envelope.id;
|
|
74
|
+
}, () => { });
|
|
75
|
+
base.attachStream(stream, (measurement) => ingest(withGatewayFacts((0, types_1.toMeasurement)(measurement), facts({ ...seen }, response), modelDimension)), response);
|
|
76
|
+
}
|
|
77
|
+
: undefined,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
@@ -1,3 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The agentgateway recipe - the open-source AI gateway (and the builds on it: Solo.io's enterprise
|
|
3
|
+
* edition, Gloo AI Gateway), a gateway you call that states no price.
|
|
4
|
+
*
|
|
5
|
+
* Its OpenAI routes fit `openAIRecipe` by shape and its Anthropic route fits `anthropicRecipe`, so
|
|
6
|
+
* the recipe has to justify itself, and it does on two counts:
|
|
7
|
+
*
|
|
8
|
+
* - **A vendor name of its own.** Without this, traffic through the gateway meters as `openai.*`
|
|
9
|
+
* or `anthropic.*` - indistinguishable from a direct call, so no meter and no per-provider
|
|
10
|
+
* budget can tell the gateway's traffic apart. Worse on the Anthropic wire: `anthropicRecipe`
|
|
11
|
+
* has no base-URL guard, so an `Anthropic` client pointed here would be claimed by it and
|
|
12
|
+
* reported as direct Anthropic traffic.
|
|
13
|
+
* - **The model that actually served.** The gateway can rewrite the model a request named - an
|
|
14
|
+
* alias, a provider prefix, a failover priority group - and the response body names the one
|
|
15
|
+
* that answered. The bare recipes drop it; this one attributes it.
|
|
16
|
+
*
|
|
17
|
+
* One vendor across every wire, because the gateway is one deployment whichever client reached it,
|
|
18
|
+
* and the wire is already in the event name (`agentgateway.chat.completions.create` against
|
|
19
|
+
* `agentgateway.messages.create`). The gateway translates where it needs to, so each route reports
|
|
20
|
+
* in the vocabulary of the wire it answered on - OpenAI's names on the OpenAI routes, Anthropic's
|
|
21
|
+
* on Messages - and `metrics` is the union, which is what `metrics` is defined to be.
|
|
22
|
+
*
|
|
23
|
+
* No `cost`, and that is the defining fact rather than an oversight. The gateway is self-hosted and
|
|
24
|
+
* states no price on any response; whatever the upstream charged is invoiced by the upstream, out
|
|
25
|
+
* of band. Never declare a metric the platform cannot report.
|
|
26
|
+
*
|
|
27
|
+
* Self-hosted, so there is no public hostname to recognise. The base URL handed to
|
|
28
|
+
* `agentgatewayRecipe({ baseUrl })` is the reliable discriminator; without one the matcher falls
|
|
29
|
+
* back to a guess on the hostname - see {@link isAgentgateway} for what it claims.
|
|
30
|
+
*
|
|
31
|
+
* The same surfaces govern raw HTTP too: `stigg.wrapFetch` routes a request by its path to the
|
|
32
|
+
* method here that names it, so a `fetch` to the gateway is metered by exactly these readers.
|
|
33
|
+
*/
|
|
1
34
|
import { AGENTGATEWAY_METRICS } from '../../../shared/token-usage';
|
|
2
35
|
import type { AgentgatewayRecipeOptions } from './types';
|
|
3
36
|
import type { ClientRecipe, MetricsFor, MetricsOf } from '../../core/types';
|
|
@@ -34,12 +34,11 @@ exports.agentgatewayRecipe = agentgatewayRecipe;
|
|
|
34
34
|
* The same surfaces govern raw HTTP too: `stigg.wrapFetch` routes a request by its path to the
|
|
35
35
|
* method here that names it, so a `fetch` to the gateway is metered by exactly these readers.
|
|
36
36
|
*/
|
|
37
|
-
const meter_stream_1 = require("../../../shared/meter-stream");
|
|
38
37
|
const token_usage_1 = require("../../../shared/token-usage");
|
|
39
38
|
const base_url_1 = require("../../../shared/base-url");
|
|
40
39
|
const openai_recipe_1 = require("../../providers/openai/openai-recipe");
|
|
41
40
|
const anthropic_recipe_1 = require("../../providers/anthropic/anthropic-recipe");
|
|
42
|
-
const
|
|
41
|
+
const wire_surface_1 = require("../../core/wire-surface");
|
|
43
42
|
/**
|
|
44
43
|
* The name that identifies the gateway in an ordinary deployment: the Kubernetes service or
|
|
45
44
|
* namespace (`agentgateway-proxy.agentgateway-system.svc`) or a hostname an operator chose to say
|
|
@@ -54,58 +53,11 @@ const types_1 = require("../../../../types");
|
|
|
54
53
|
* and out of the meters counting them. Solo's and Gloo's builds name the gateway by `baseUrl`.
|
|
55
54
|
*/
|
|
56
55
|
const HOST_LABEL = 'agentgateway';
|
|
57
|
-
/** What only the gateway knows, folded onto whatever the wire's own reader produced. */
|
|
58
|
-
function withGatewayFacts(measured, result, modelDimension) {
|
|
59
|
-
const served = typeof result?.model === 'string' ? result.model : undefined;
|
|
60
|
-
return {
|
|
61
|
-
...measured,
|
|
62
|
-
dimensions: served
|
|
63
|
-
? { ...measured.dimensions, [modelDimension]: served }
|
|
64
|
-
: measured.dimensions,
|
|
65
|
-
// The upstream's completion id, carried back through the gateway. It is what a replayed
|
|
66
|
-
// response repeats, which a local step counter cannot be.
|
|
67
|
-
idempotencyKey: measured.idempotencyKey ?? (typeof result?.id === 'string' ? result.id : undefined),
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
56
|
function agentgatewayRecipe(options = {}) {
|
|
71
57
|
const estimate = options.estimate ?? (() => 1);
|
|
72
58
|
const modelDimension = options.modelDimension ?? 'model';
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
*
|
|
76
|
-
* The wire recipes' own extractors are reused rather than reimplemented: the gateway answers in
|
|
77
|
-
* that wire's shape, so that wire's reader is by definition the right one - and Anthropic's
|
|
78
|
-
* streamed usage in particular accumulates across two event types, which is not logic worth
|
|
79
|
-
* having a second copy of.
|
|
80
|
-
*/
|
|
81
|
-
const surfaceOf = (base) => ({
|
|
82
|
-
// Spread first so anything the base declares and this wrapper does not know about - like
|
|
83
|
-
// `contextArg` - survives being wrapped.
|
|
84
|
-
...base,
|
|
85
|
-
estimate,
|
|
86
|
-
extractUsage: (result, response) => {
|
|
87
|
-
const measured = base.extractUsage?.(result, response);
|
|
88
|
-
return measured === undefined ? undefined : withGatewayFacts((0, types_1.toMeasurement)(measured), result, modelDimension);
|
|
89
|
-
},
|
|
90
|
-
attachStream: base.attachStream
|
|
91
|
-
? (stream, ingest, response) => {
|
|
92
|
-
// A second tap, purely for attribution: the wire readers carry out counters and nothing
|
|
93
|
-
// else. Each wire states the model in its own envelope - Anthropic on
|
|
94
|
-
// `message_start.message`, the Responses API on `response`, Chat Completions at the chunk
|
|
95
|
-
// root. `meterStream` captures what it patches before patching, so two observers each
|
|
96
|
-
// see every chunk exactly once.
|
|
97
|
-
let seen = {};
|
|
98
|
-
(0, meter_stream_1.meterStream)(stream, (chunk) => {
|
|
99
|
-
const envelope = chunk?.message ?? chunk?.response ?? chunk;
|
|
100
|
-
if (envelope?.model)
|
|
101
|
-
seen = { ...seen, model: envelope.model };
|
|
102
|
-
if (envelope?.id)
|
|
103
|
-
seen = { ...seen, id: envelope.id };
|
|
104
|
-
}, () => { });
|
|
105
|
-
base.attachStream(stream, (measurement) => ingest(withGatewayFacts((0, types_1.toMeasurement)(measurement), seen, modelDimension)), response);
|
|
106
|
-
}
|
|
107
|
-
: undefined,
|
|
108
|
-
});
|
|
59
|
+
// The gateway answers in each wire's shape, and the body's model and id are its facts.
|
|
60
|
+
const surfaceOf = (base) => (0, wire_surface_1.gatewaySurface)(base, { estimate, modelDimension });
|
|
109
61
|
return {
|
|
110
62
|
vendor: 'agentgateway',
|
|
111
63
|
metrics: token_usage_1.AGENTGATEWAY_METRICS,
|
|
@@ -32,7 +32,7 @@ const base_url_1 = require("../../../shared/base-url");
|
|
|
32
32
|
const registry_1 = require("../../core/registry");
|
|
33
33
|
const openai_recipe_1 = require("../../providers/openai/openai-recipe");
|
|
34
34
|
const anthropic_recipe_1 = require("../../providers/anthropic/anthropic-recipe");
|
|
35
|
-
const
|
|
35
|
+
const wire_surface_1 = require("../../core/wire-surface");
|
|
36
36
|
/** Workers AI, both of its routes. */
|
|
37
37
|
const CLOUDFLARE_API_HOST = 'api.cloudflare.com';
|
|
38
38
|
/** AI Gateway's public endpoint. A custom domain needs `options.baseUrl` instead. */
|
|
@@ -47,10 +47,6 @@ const ROUTED_MODEL_HEADER = 'cf-aig-routed-model';
|
|
|
47
47
|
const STEP_HEADER = 'cf-aig-step';
|
|
48
48
|
/** The gateway's own request id, stable across its retries - unlike a local step counter. */
|
|
49
49
|
const REQUEST_ID_HEADER = 'cf-aig-request-id';
|
|
50
|
-
const headerOf = (response, name) => {
|
|
51
|
-
const raw = response?.headers?.get(name);
|
|
52
|
-
return raw == null || raw === '' ? undefined : raw;
|
|
53
|
-
};
|
|
54
50
|
/**
|
|
55
51
|
* Whether the gateway served this from its cache.
|
|
56
52
|
*
|
|
@@ -62,28 +58,26 @@ const headerOf = (response, name) => {
|
|
|
62
58
|
* Absent on the Workers AI routes, which have no cache in front of them - so this reads `false`
|
|
63
59
|
* there and the surfaces behave as ordinary metered calls.
|
|
64
60
|
*/
|
|
65
|
-
const isCacheHit = (response) => headerOf(response, CACHE_STATUS_HEADER)?.toUpperCase() === 'HIT';
|
|
66
|
-
/** What only the gateway knows,
|
|
67
|
-
function
|
|
68
|
-
const dimensions = {
|
|
69
|
-
// The model that actually served: the router's own word for it, falling back to whatever the
|
|
70
|
-
// forwarded body said. Anthropic's Messages body carries `model` too, so this works on both wires.
|
|
71
|
-
const served = headerOf(response, ROUTED_MODEL_HEADER) ?? (typeof result?.model === 'string' ? result.model : undefined);
|
|
72
|
-
if (served)
|
|
73
|
-
dimensions[modelDimension] = served;
|
|
61
|
+
const isCacheHit = (response) => (0, wire_surface_1.headerOf)(response, CACHE_STATUS_HEADER)?.toUpperCase() === 'HIT';
|
|
62
|
+
/** What only the gateway knows, from its headers, with the forwarded body as the fallback. */
|
|
63
|
+
function gatewayFacts(body, response) {
|
|
64
|
+
const dimensions = {};
|
|
74
65
|
// The substitute for a cost metric: the key to the gateway's own record of what this call cost.
|
|
75
|
-
const logId = headerOf(response, LOG_ID_HEADER);
|
|
66
|
+
const logId = (0, wire_surface_1.headerOf)(response, LOG_ID_HEADER);
|
|
76
67
|
if (logId)
|
|
77
68
|
dimensions.cf_aig_log_id = logId;
|
|
78
69
|
// Only when a fallback actually happened. Step `0` is the ordinary case and tagging every event
|
|
79
70
|
// with it would add a dimension to all of them for no information.
|
|
80
|
-
const step = headerOf(response, STEP_HEADER);
|
|
71
|
+
const step = (0, wire_surface_1.headerOf)(response, STEP_HEADER);
|
|
81
72
|
if (step !== undefined && step !== '0')
|
|
82
73
|
dimensions.cf_aig_step = step;
|
|
83
74
|
return {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
75
|
+
// The model that actually served: the router's own word for it, falling back to whatever the
|
|
76
|
+
// forwarded body said. Anthropic's Messages body carries `model` too, so this works on both wires.
|
|
77
|
+
model: (0, wire_surface_1.headerOf)(response, ROUTED_MODEL_HEADER) ?? body.model,
|
|
78
|
+
id: body.id,
|
|
79
|
+
requestId: (0, wire_surface_1.headerOf)(response, REQUEST_ID_HEADER),
|
|
80
|
+
dimensions,
|
|
87
81
|
};
|
|
88
82
|
}
|
|
89
83
|
/** The Workers AI reader: the three counters it states, under its own (OpenAI's) names. */
|
|
@@ -106,57 +100,13 @@ function cloudflareRecipe(options = {}) {
|
|
|
106
100
|
const estimate = options.estimate ?? (() => 1);
|
|
107
101
|
const modelDimension = options.modelDimension ?? 'model';
|
|
108
102
|
/**
|
|
109
|
-
* One upstream surface, wrapped.
|
|
103
|
+
* One upstream surface, wrapped. Off the gateway the wrapper is inert: no `cf-aig-*` header is
|
|
104
|
+
* present, so no cache hit is seen and no gateway dimension is added.
|
|
110
105
|
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
* the right one - and Anthropic's streamed usage in particular accumulates across two event
|
|
114
|
-
* types, which is not logic worth having a second copy of. Off the gateway the wrapper is inert:
|
|
115
|
-
* no `cf-aig-*` header is present, so no cache hit is seen and no gateway dimension is added.
|
|
106
|
+
* A cache hit reports nothing, buffered or streamed: nobody was invoiced for it, and the cache
|
|
107
|
+
* header arrives before any token does.
|
|
116
108
|
*/
|
|
117
|
-
const surfaceOf = (base) => ({
|
|
118
|
-
// Spread first so anything the base declares and this wrapper does not know about - like
|
|
119
|
-
// `contextArg`, which says where `stiggContext` rides - survives being wrapped. Rebuilding
|
|
120
|
-
// the object field by field silently dropped it.
|
|
121
|
-
...base,
|
|
122
|
-
path: base.path,
|
|
123
|
-
estimate,
|
|
124
|
-
extractUsage: (result, response) => {
|
|
125
|
-
// Nobody was invoiced for a cache hit, so nothing is reported for one. Checked before the
|
|
126
|
-
// body is read at all, because the body of a hit looks exactly like the body of a real call.
|
|
127
|
-
if (isCacheHit(response))
|
|
128
|
-
return undefined;
|
|
129
|
-
const measured = base.extractUsage?.(result, response);
|
|
130
|
-
return measured === undefined
|
|
131
|
-
? undefined
|
|
132
|
-
: withGatewayFacts((0, types_1.toMeasurement)(measured), result, response, modelDimension);
|
|
133
|
-
},
|
|
134
|
-
attachStream: base.attachStream
|
|
135
|
-
? (stream, ingest, response) => {
|
|
136
|
-
// A cached reply can be streamed back too, and the cache header arrives in time to say so.
|
|
137
|
-
// Nothing is attached at all in that case: the consumer's stream is left untouched and no
|
|
138
|
-
// measurement is produced, which is what a free call should report.
|
|
139
|
-
if (isCacheHit(response))
|
|
140
|
-
return;
|
|
141
|
-
// A second tap, purely for attribution. The upstream recipes' stream readers carry out
|
|
142
|
-
// counters and nothing else, so without this the streamed path had no `result` to fall
|
|
143
|
-
// back on and a turn with no `cf-aig-routed-model` header lost its model entirely -
|
|
144
|
-
// where the buffered path reads it straight off the body. Each wire states it in its own
|
|
145
|
-
// envelope: Anthropic on `message_start.message`, the Responses API on `response`, Chat
|
|
146
|
-
// Completions at the chunk root. `meterStream` captures what it patches before patching,
|
|
147
|
-
// so stacking two observers is safe and each sees every chunk exactly once.
|
|
148
|
-
let seen = {};
|
|
149
|
-
(0, meter_stream_1.meterStream)(stream, (chunk) => {
|
|
150
|
-
const envelope = chunk?.message ?? chunk?.response ?? chunk;
|
|
151
|
-
if (envelope?.model)
|
|
152
|
-
seen = { ...seen, model: envelope.model };
|
|
153
|
-
if (envelope?.id)
|
|
154
|
-
seen = { ...seen, id: envelope.id };
|
|
155
|
-
}, () => { });
|
|
156
|
-
base.attachStream(stream, (measurement) => ingest(withGatewayFacts((0, types_1.toMeasurement)(measurement), seen, response, modelDimension)), response);
|
|
157
|
-
}
|
|
158
|
-
: undefined,
|
|
159
|
-
});
|
|
109
|
+
const surfaceOf = (base) => (0, wire_surface_1.gatewaySurface)(base, { estimate, modelDimension, facts: gatewayFacts, skip: isCacheHit });
|
|
160
110
|
/**
|
|
161
111
|
* Chat Completions, which both products answer on - Workers AI's OpenAI-compatible route and the
|
|
162
112
|
* gateway's `/openai` one.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The TrueFoundry recipe - TrueFoundry's AI Gateway, hosted at `gateway.truefoundry.ai` or
|
|
3
|
+
* self-hosted in your cluster, a gateway you call that states no price.
|
|
4
|
+
*
|
|
5
|
+
* Apps reach it by pointing an existing `OpenAI` or `Anthropic` client at the gateway's base URL
|
|
6
|
+
* with a TrueFoundry key, and naming models as `provider_account/model` (`openai-main/gpt-5-mini`).
|
|
7
|
+
* Both wires fit `openAIRecipe` and `anthropicRecipe` by shape, so the recipe earns its place the
|
|
8
|
+
* way agentgateway's does:
|
|
9
|
+
*
|
|
10
|
+
* - **A vendor name of its own.** Without this, gateway traffic meters as `openai.*` or
|
|
11
|
+
* `anthropic.*`, indistinguishable from a direct call. `anthropicRecipe` has no base-URL guard,
|
|
12
|
+
* so an `Anthropic` client pointed here would be claimed as direct Anthropic traffic.
|
|
13
|
+
* - **The model that actually served.** TrueFoundry load-balances and falls back between provider
|
|
14
|
+
* accounts, and names the one that answered in the `x-tfy-resolved-model` response header.
|
|
15
|
+
* Headers go on the wire before the body, so a streamed call has it too. The body's `model` is
|
|
16
|
+
* the fallback when the header is absent.
|
|
17
|
+
*
|
|
18
|
+
* No `cost`. TrueFoundry tracks spend in its own dashboard and puts no price on any response; the
|
|
19
|
+
* provider accounts behind it are the caller's own keys, so the upstream invoices the tokens.
|
|
20
|
+
*
|
|
21
|
+
* Not covered: TrueFoundry's Gemini (`/gemini/<account>/proxy`), Bedrock (`/bedrock/proxy`) and
|
|
22
|
+
* Azure OpenAI (`/azure-openai`) passthroughs. Each speaks its own provider's SDK and shape.
|
|
23
|
+
*
|
|
24
|
+
* `stigg.wrapFetch` governs raw HTTP to the gateway through these same methods.
|
|
25
|
+
*/
|
|
26
|
+
import { TRUEFOUNDRY_METRICS } from '../../../shared/token-usage';
|
|
27
|
+
import type { TrueFoundryRecipeOptions } from './types';
|
|
28
|
+
import type { ClientRecipe, MetricsFor, MetricsOf } from '../../core/types';
|
|
29
|
+
type TrueFoundryMetric = (typeof TRUEFOUNDRY_METRICS)[number];
|
|
30
|
+
export declare function truefoundryRecipe(options?: TrueFoundryRecipeOptions): ClientRecipe<TrueFoundryMetric>;
|
|
31
|
+
/** One of the metric names TrueFoundry can report, across every wire it serves. */
|
|
32
|
+
export type TrueFoundryMetricName = MetricsOf<ReturnType<typeof truefoundryRecipe>>;
|
|
33
|
+
/** What a mapper reads off a call through TrueFoundry: those names, each possibly absent. */
|
|
34
|
+
export type TrueFoundryMetrics = MetricsFor<ReturnType<typeof truefoundryRecipe>>;
|
|
35
|
+
export {};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.truefoundryRecipe = truefoundryRecipe;
|
|
4
|
+
/**
|
|
5
|
+
* The TrueFoundry recipe - TrueFoundry's AI Gateway, hosted at `gateway.truefoundry.ai` or
|
|
6
|
+
* self-hosted in your cluster, a gateway you call that states no price.
|
|
7
|
+
*
|
|
8
|
+
* Apps reach it by pointing an existing `OpenAI` or `Anthropic` client at the gateway's base URL
|
|
9
|
+
* with a TrueFoundry key, and naming models as `provider_account/model` (`openai-main/gpt-5-mini`).
|
|
10
|
+
* Both wires fit `openAIRecipe` and `anthropicRecipe` by shape, so the recipe earns its place the
|
|
11
|
+
* way agentgateway's does:
|
|
12
|
+
*
|
|
13
|
+
* - **A vendor name of its own.** Without this, gateway traffic meters as `openai.*` or
|
|
14
|
+
* `anthropic.*`, indistinguishable from a direct call. `anthropicRecipe` has no base-URL guard,
|
|
15
|
+
* so an `Anthropic` client pointed here would be claimed as direct Anthropic traffic.
|
|
16
|
+
* - **The model that actually served.** TrueFoundry load-balances and falls back between provider
|
|
17
|
+
* accounts, and names the one that answered in the `x-tfy-resolved-model` response header.
|
|
18
|
+
* Headers go on the wire before the body, so a streamed call has it too. The body's `model` is
|
|
19
|
+
* the fallback when the header is absent.
|
|
20
|
+
*
|
|
21
|
+
* No `cost`. TrueFoundry tracks spend in its own dashboard and puts no price on any response; the
|
|
22
|
+
* provider accounts behind it are the caller's own keys, so the upstream invoices the tokens.
|
|
23
|
+
*
|
|
24
|
+
* Not covered: TrueFoundry's Gemini (`/gemini/<account>/proxy`), Bedrock (`/bedrock/proxy`) and
|
|
25
|
+
* Azure OpenAI (`/azure-openai`) passthroughs. Each speaks its own provider's SDK and shape.
|
|
26
|
+
*
|
|
27
|
+
* `stigg.wrapFetch` governs raw HTTP to the gateway through these same methods.
|
|
28
|
+
*/
|
|
29
|
+
const token_usage_1 = require("../../../shared/token-usage");
|
|
30
|
+
const base_url_1 = require("../../../shared/base-url");
|
|
31
|
+
const openai_recipe_1 = require("../../providers/openai/openai-recipe");
|
|
32
|
+
const anthropic_recipe_1 = require("../../providers/anthropic/anthropic-recipe");
|
|
33
|
+
const wire_surface_1 = require("../../core/wire-surface");
|
|
34
|
+
/** TrueFoundry's own domains: the hosted gateway, and the tenant control planes that serve one. */
|
|
35
|
+
const TRUEFOUNDRY_HOSTS = ['truefoundry.ai', 'truefoundry.cloud', 'truefoundry.com'];
|
|
36
|
+
/** Which model answered, after TrueFoundry's load balancing or fallback. */
|
|
37
|
+
const RESOLVED_MODEL_HEADER = 'x-tfy-resolved-model';
|
|
38
|
+
function truefoundryRecipe(options = {}) {
|
|
39
|
+
const estimate = options.estimate ?? (() => 1);
|
|
40
|
+
const modelDimension = options.modelDimension ?? 'model';
|
|
41
|
+
// The gateway answers in each wire's shape. The header names the model that served, ahead of
|
|
42
|
+
// the body's, and is on the wire before a streamed body is.
|
|
43
|
+
const surfaceOf = (base) => (0, wire_surface_1.gatewaySurface)(base, {
|
|
44
|
+
estimate,
|
|
45
|
+
modelDimension,
|
|
46
|
+
facts: (body, response) => ({ ...body, model: (0, wire_surface_1.headerOf)(response, RESOLVED_MODEL_HEADER) ?? body.model }),
|
|
47
|
+
});
|
|
48
|
+
return {
|
|
49
|
+
vendor: 'truefoundry',
|
|
50
|
+
metrics: token_usage_1.TRUEFOUNDRY_METRICS,
|
|
51
|
+
totalTokensMetric: token_usage_1.TOTAL_TOKENS,
|
|
52
|
+
matches: (client) => isTrueFoundry(client, options.baseUrl) && (openai_recipe_1.openAIRecipe.matches(client) || anthropic_recipe_1.anthropicRecipe.matches(client)),
|
|
53
|
+
// Every wire it serves. This recipe is matched ahead of both bare ones, so a surface left out
|
|
54
|
+
// here would be ungated rather than metered by them.
|
|
55
|
+
methods: [...openai_recipe_1.openAIRecipe.methods.map(surfaceOf), ...anthropic_recipe_1.anthropicRecipe.methods.map(surfaceOf)],
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Given a `baseUrl`, the endpoint is compared one path segment at a time, so `.../v1` cannot claim
|
|
60
|
+
* `.../v10`, and TrueFoundry's own domains are no longer claimed. Without one, only TrueFoundry's own
|
|
61
|
+
* domains and their subdomains are claimed - a self-hosted install under your hostname needs the
|
|
62
|
+
* `baseUrl`.
|
|
63
|
+
*
|
|
64
|
+
* That claims every OpenAI- or Anthropic-shaped client on those domains, not only the gateway - a
|
|
65
|
+
* model TrueFoundry serves there directly reports under `truefoundry.*` too. Name
|
|
66
|
+
* `recipe: openAIRecipe` (or `anthropicRecipe`) at `wrap` to settle it.
|
|
67
|
+
*/
|
|
68
|
+
function isTrueFoundry(client, baseUrl) {
|
|
69
|
+
const clientBase = (0, base_url_1.baseUrlOf)(client);
|
|
70
|
+
if (clientBase === undefined)
|
|
71
|
+
return false;
|
|
72
|
+
if (baseUrl)
|
|
73
|
+
return (0, base_url_1.withinBaseUrl)(baseUrl, clientBase);
|
|
74
|
+
return (0, base_url_1.matchesAnyHost)(clientBase, TRUEFOUNDRY_HOSTS);
|
|
75
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type TrueFoundryRecipeOptions = {
|
|
2
|
+
/**
|
|
3
|
+
* How much to ask permission for before the call, in whatever unit `meter` bills.
|
|
4
|
+
*
|
|
5
|
+
* The default asks for 1 - "is there any budget left at all?" - and lets the ingest afterwards
|
|
6
|
+
* settle the balance. No `unitsPer…` beside it: TrueFoundry puts no price on any response, so a
|
|
7
|
+
* run here bills token counters, which arrive whole.
|
|
8
|
+
*/
|
|
9
|
+
estimate?: (params: any) => number;
|
|
10
|
+
/**
|
|
11
|
+
* Dimension the model that actually served is attributed to, so routing drift is visible in
|
|
12
|
+
* Stigg. TrueFoundry load-balances and falls back between provider accounts, and says which one
|
|
13
|
+
* answered in the `x-tfy-resolved-model` response header.
|
|
14
|
+
*/
|
|
15
|
+
modelDimension?: string;
|
|
16
|
+
/**
|
|
17
|
+
* The gateway this recipe is for, when it is not TrueFoundry's hosted one. A self-hosted install
|
|
18
|
+
* runs under your own hostname, so give it the same base URL the client was built with and
|
|
19
|
+
* `matches` compares against exactly that. Without it only TrueFoundry's own domains are claimed.
|
|
20
|
+
*/
|
|
21
|
+
baseUrl?: string;
|
|
22
|
+
};
|
|
@@ -141,28 +141,24 @@ export declare const OPENAI_METRICS: readonly ["prompt_tokens", "completion_toke
|
|
|
141
141
|
/** A gateway speaks the OpenAI shape and adds the price it charged, under its own field name. */
|
|
142
142
|
export declare const GATEWAY_METRICS: readonly ["prompt_tokens", "completion_tokens", "input_tokens", "output_tokens", "cached_tokens", "total_tokens", "cost"];
|
|
143
143
|
/**
|
|
144
|
-
* A gateway that
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
* this is the union of what its surfaces can report, which is what `metrics` is defined to be.
|
|
150
|
-
*
|
|
151
|
-
* No `cost`, and that is the defining fact rather than an oversight. Cloudflare states no price on
|
|
152
|
-
* any response: its figure lives in the gateway's own log, is explicitly an estimate, and is zero
|
|
153
|
-
* on a cache hit. So the recipe carries the log id as a dimension and lets spend be reconciled out
|
|
154
|
-
* of band, exactly as the gateway reference prescribes for a price that is only available
|
|
155
|
-
* asynchronously.
|
|
144
|
+
* A gateway that answers on the OpenAI routes and on Anthropic Messages, each in its own wire's
|
|
145
|
+
* vocabulary - so the union of the two, which is what `metrics` is defined to be.
|
|
146
|
+
*
|
|
147
|
+
* No `cost` on any of the gateways that use it, and that is the defining fact rather than an
|
|
148
|
+
* oversight: none of them states a price on the response, so spend is reconciled out of band.
|
|
156
149
|
*/
|
|
157
|
-
export declare const
|
|
150
|
+
export declare const OPENAI_ANTHROPIC_METRICS: readonly ["prompt_tokens", "completion_tokens", "input_tokens", "output_tokens", "cached_tokens", "total_tokens", "cache_read_input_tokens", "cache_creation_input_tokens"];
|
|
158
151
|
/**
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
* Whatever the upstream charged is invoiced by the upstream, out of band.
|
|
152
|
+
* Cloudflare AI Gateway's provider-native routes pass the provider's body straight through, unlike
|
|
153
|
+
* LiteLLM, which normalises every upstream to the OpenAI wire. Its price lives in the gateway's own
|
|
154
|
+
* log, is explicitly an estimate, and is zero on a cache hit - so the recipe carries the log id as
|
|
155
|
+
* a dimension instead.
|
|
164
156
|
*/
|
|
157
|
+
export declare const CLOUDFLARE_METRICS: readonly ["prompt_tokens", "completion_tokens", "input_tokens", "output_tokens", "cached_tokens", "total_tokens", "cache_read_input_tokens", "cache_creation_input_tokens"];
|
|
158
|
+
/** agentgateway is self-hosted and states no price; the upstream invoices out of band. */
|
|
165
159
|
export declare const AGENTGATEWAY_METRICS: readonly ["prompt_tokens", "completion_tokens", "input_tokens", "output_tokens", "cached_tokens", "total_tokens", "cache_read_input_tokens", "cache_creation_input_tokens"];
|
|
160
|
+
/** TrueFoundry tracks spend in its own dashboard; the provider accounts behind it are the caller's. */
|
|
161
|
+
export declare const TRUEFOUNDRY_METRICS: readonly ["prompt_tokens", "completion_tokens", "input_tokens", "output_tokens", "cached_tokens", "total_tokens", "cache_read_input_tokens", "cache_creation_input_tokens"];
|
|
166
162
|
/**
|
|
167
163
|
* Bedrock's five, under its own names and with no price among them.
|
|
168
164
|
*
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.normalizedMetrics = exports.addMetrics = exports.METRIC_PATHS = exports.metricNamesMatchPaths = exports.COMPUTED_TOTALS = exports.metricName = exports.WORKERS_AI_METRICS = exports.WORKERS_AI_PATHS = exports.DATABRICKS_QUERY_METRIC_NAME_MAP = exports.DATABRICKS_METRICS = exports.DATABRICKS_RESPONSES_PATHS = exports.DATABRICKS_PATHS = exports.BEDROCK_METRICS = exports.BEDROCK_PATHS = exports.AGENTGATEWAY_METRICS = exports.CLOUDFLARE_METRICS = exports.GATEWAY_METRICS = exports.OPENAI_METRICS = exports.OLLAMA_METRICS = exports.OLLAMA_COMPONENT_METRICS = exports.TYPESAFE_METRICS = exports.TYPESAFE_COMPONENT_METRICS = exports.MISTRAL_METRICS = exports.MISTRAL_METRIC_NAME_MAP = exports.OPENAI_RESPONSES_METRICS = exports.OPENAI_CHAT_METRICS = exports.GEMINI_COMPONENT_METRICS = exports.GEMINI_METRICS = exports.ANTHROPIC_METRICS = exports.ANTHROPIC_COMPONENT_METRICS = exports.TOTAL_TOKENS = exports.addTokenUsage = void 0;
|
|
3
|
+
exports.normalizedMetrics = exports.addMetrics = exports.METRIC_PATHS = exports.metricNamesMatchPaths = exports.COMPUTED_TOTALS = exports.metricName = exports.WORKERS_AI_METRICS = exports.WORKERS_AI_PATHS = exports.DATABRICKS_QUERY_METRIC_NAME_MAP = exports.DATABRICKS_METRICS = exports.DATABRICKS_RESPONSES_PATHS = exports.DATABRICKS_PATHS = exports.BEDROCK_METRICS = exports.BEDROCK_PATHS = exports.TRUEFOUNDRY_METRICS = exports.AGENTGATEWAY_METRICS = exports.CLOUDFLARE_METRICS = exports.OPENAI_ANTHROPIC_METRICS = exports.GATEWAY_METRICS = exports.OPENAI_METRICS = exports.OLLAMA_METRICS = exports.OLLAMA_COMPONENT_METRICS = exports.TYPESAFE_METRICS = exports.TYPESAFE_COMPONENT_METRICS = exports.MISTRAL_METRICS = exports.MISTRAL_METRIC_NAME_MAP = exports.OPENAI_RESPONSES_METRICS = exports.OPENAI_CHAT_METRICS = exports.GEMINI_COMPONENT_METRICS = exports.GEMINI_METRICS = exports.ANTHROPIC_METRICS = exports.ANTHROPIC_COMPONENT_METRICS = exports.TOTAL_TOKENS = exports.addTokenUsage = void 0;
|
|
4
4
|
exports.fromFlatTokenUsage = fromFlatTokenUsage;
|
|
5
5
|
exports.fromOpenAIUsage = fromOpenAIUsage;
|
|
6
6
|
exports.fromResponsesUsage = fromResponsesUsage;
|
|
@@ -407,36 +407,28 @@ exports.OPENAI_METRICS = [
|
|
|
407
407
|
/** A gateway speaks the OpenAI shape and adds the price it charged, under its own field name. */
|
|
408
408
|
exports.GATEWAY_METRICS = [...exports.OPENAI_METRICS, 'cost'];
|
|
409
409
|
/**
|
|
410
|
-
* A gateway that
|
|
411
|
-
*
|
|
412
|
-
*
|
|
413
|
-
*
|
|
414
|
-
*
|
|
415
|
-
* this is the union of what its surfaces can report, which is what `metrics` is defined to be.
|
|
416
|
-
*
|
|
417
|
-
* No `cost`, and that is the defining fact rather than an oversight. Cloudflare states no price on
|
|
418
|
-
* any response: its figure lives in the gateway's own log, is explicitly an estimate, and is zero
|
|
419
|
-
* on a cache hit. So the recipe carries the log id as a dimension and lets spend be reconciled out
|
|
420
|
-
* of band, exactly as the gateway reference prescribes for a price that is only available
|
|
421
|
-
* asynchronously.
|
|
410
|
+
* A gateway that answers on the OpenAI routes and on Anthropic Messages, each in its own wire's
|
|
411
|
+
* vocabulary - so the union of the two, which is what `metrics` is defined to be.
|
|
412
|
+
*
|
|
413
|
+
* No `cost` on any of the gateways that use it, and that is the defining fact rather than an
|
|
414
|
+
* oversight: none of them states a price on the response, so spend is reconciled out of band.
|
|
422
415
|
*/
|
|
423
|
-
exports.
|
|
416
|
+
exports.OPENAI_ANTHROPIC_METRICS = [
|
|
424
417
|
...exports.OPENAI_METRICS,
|
|
425
418
|
'cache_read_input_tokens',
|
|
426
419
|
'cache_creation_input_tokens',
|
|
427
420
|
];
|
|
428
421
|
/**
|
|
429
|
-
*
|
|
430
|
-
*
|
|
431
|
-
*
|
|
432
|
-
*
|
|
433
|
-
* Whatever the upstream charged is invoiced by the upstream, out of band.
|
|
422
|
+
* Cloudflare AI Gateway's provider-native routes pass the provider's body straight through, unlike
|
|
423
|
+
* LiteLLM, which normalises every upstream to the OpenAI wire. Its price lives in the gateway's own
|
|
424
|
+
* log, is explicitly an estimate, and is zero on a cache hit - so the recipe carries the log id as
|
|
425
|
+
* a dimension instead.
|
|
434
426
|
*/
|
|
435
|
-
exports.
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
427
|
+
exports.CLOUDFLARE_METRICS = exports.OPENAI_ANTHROPIC_METRICS;
|
|
428
|
+
/** agentgateway is self-hosted and states no price; the upstream invoices out of band. */
|
|
429
|
+
exports.AGENTGATEWAY_METRICS = exports.OPENAI_ANTHROPIC_METRICS;
|
|
430
|
+
/** TrueFoundry tracks spend in its own dashboard; the provider accounts behind it are the caller's. */
|
|
431
|
+
exports.TRUEFOUNDRY_METRICS = exports.OPENAI_ANTHROPIC_METRICS;
|
|
440
432
|
/**
|
|
441
433
|
* Bedrock's five, under its own names and with no price among them.
|
|
442
434
|
*
|
package/dist/index.d.ts
CHANGED
|
@@ -60,6 +60,9 @@ export type { CloudflareRecipeOptions } from './bindings/client/gateways/cloudfl
|
|
|
60
60
|
export { agentgatewayRecipe } from './bindings/client/gateways/agentgateway/agentgateway-recipe';
|
|
61
61
|
export type { AgentgatewayMetricName, AgentgatewayMetrics, } from './bindings/client/gateways/agentgateway/agentgateway-recipe';
|
|
62
62
|
export type { AgentgatewayRecipeOptions } from './bindings/client/gateways/agentgateway/types';
|
|
63
|
+
export { truefoundryRecipe } from './bindings/client/gateways/truefoundry/truefoundry-recipe';
|
|
64
|
+
export type { TrueFoundryMetricName, TrueFoundryMetrics, } from './bindings/client/gateways/truefoundry/truefoundry-recipe';
|
|
65
|
+
export type { TrueFoundryRecipeOptions } from './bindings/client/gateways/truefoundry/types';
|
|
63
66
|
export type { FetchLike, FetchOptions, GovernedFetch, GovernedRequestInit } from './bindings/fetch/types';
|
|
64
67
|
export { typesafeRecipe, estimateFromState } from './bindings/client/providers/typesafe/typesafe-recipe';
|
|
65
68
|
export type { TypeSafeMetric, TypeSafeMetrics } from './bindings/client/providers/typesafe/typesafe-recipe';
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.normalizeUsage = exports.normalizedMetrics = exports.stiggVercelMiddleware = exports.weightedMetrics = exports.estimateFromState = exports.typesafeRecipe = exports.agentgatewayRecipe = exports.cloudflareRecipe = exports.bedrockRecipe = exports.databricksExternalRecipe = exports.databricksRecipe = exports.ollamaRecipe = exports.mistralRecipe = exports.groqRecipe = exports.litellmRecipe = exports.openrouterRecipe = exports.estimateFromMaxOutputTokens = exports.geminiChatRecipe = exports.geminiRecipe = exports.openAIRecipe = exports.estimateFromMaxTokens = exports.anthropicRecipe = exports.hasMethod = exports.createClientRegistry = exports.wrapClient = exports.toMeasurement = exports.entitlementRef = exports.entitlementKind = exports.entitlementId = exports.emptyTokenUsage = exports.vendorMetrics = exports.unitsPerTokens = exports.looksLikeGovernanceNotEnabled = exports.isGovernanceNotEnabled = exports.isAccessDenied = exports.isEngineError = exports.GovernanceNotEnabledError = exports.AccessDeniedError = exports.EngineError = exports.STIGG_TRACER_NAME = exports.noopLogger = exports.consoleLogger = exports.parseGovernanceHeaders = exports.GOVERNANCE_HEADERS = exports.remedyMessage = exports.describeDecision = exports.flattenRaw = exports.RAW_PREFIX = exports.StiggAgentSDK = void 0;
|
|
3
|
+
exports.normalizeUsage = exports.normalizedMetrics = exports.stiggVercelMiddleware = exports.weightedMetrics = exports.estimateFromState = exports.typesafeRecipe = exports.truefoundryRecipe = exports.agentgatewayRecipe = exports.cloudflareRecipe = exports.bedrockRecipe = exports.databricksExternalRecipe = exports.databricksRecipe = exports.ollamaRecipe = exports.mistralRecipe = exports.groqRecipe = exports.litellmRecipe = exports.openrouterRecipe = exports.estimateFromMaxOutputTokens = exports.geminiChatRecipe = exports.geminiRecipe = exports.openAIRecipe = exports.estimateFromMaxTokens = exports.anthropicRecipe = exports.hasMethod = exports.createClientRegistry = exports.wrapClient = exports.toMeasurement = exports.entitlementRef = exports.entitlementKind = exports.entitlementId = exports.emptyTokenUsage = exports.vendorMetrics = exports.unitsPerTokens = exports.looksLikeGovernanceNotEnabled = exports.isGovernanceNotEnabled = exports.isAccessDenied = exports.isEngineError = exports.GovernanceNotEnabledError = exports.AccessDeniedError = exports.EngineError = exports.STIGG_TRACER_NAME = exports.noopLogger = exports.consoleLogger = exports.parseGovernanceHeaders = exports.GOVERNANCE_HEADERS = exports.remedyMessage = exports.describeDecision = exports.flattenRaw = exports.RAW_PREFIX = exports.StiggAgentSDK = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Public surface.
|
|
6
6
|
*
|
|
@@ -126,6 +126,12 @@ Object.defineProperty(exports, "cloudflareRecipe", { enumerable: true, get: func
|
|
|
126
126
|
// and no `cost`, because the gateway states no price on any response.
|
|
127
127
|
var agentgateway_recipe_1 = require("./bindings/client/gateways/agentgateway/agentgateway-recipe");
|
|
128
128
|
Object.defineProperty(exports, "agentgatewayRecipe", { enumerable: true, get: function () { return agentgateway_recipe_1.agentgatewayRecipe; } });
|
|
129
|
+
// TrueFoundry's AI Gateway, hosted at `gateway.truefoundry.ai` or self-hosted. A default, ahead of both
|
|
130
|
+
// wires it serves, so its traffic reports as `truefoundry.*`. Self-hosted installs pass their base URL at
|
|
131
|
+
// `wrap(client, { recipe: truefoundryRecipe({ baseUrl }) })`. Tokens and the model that served
|
|
132
|
+
// (`x-tfy-resolved-model`), no `cost`.
|
|
133
|
+
var truefoundry_recipe_1 = require("./bindings/client/gateways/truefoundry/truefoundry-recipe");
|
|
134
|
+
Object.defineProperty(exports, "truefoundryRecipe", { enumerable: true, get: function () { return truefoundry_recipe_1.truefoundryRecipe; } });
|
|
129
135
|
var typesafe_recipe_1 = require("./bindings/client/providers/typesafe/typesafe-recipe");
|
|
130
136
|
Object.defineProperty(exports, "typesafeRecipe", { enumerable: true, get: function () { return typesafe_recipe_1.typesafeRecipe; } });
|
|
131
137
|
Object.defineProperty(exports, "estimateFromState", { enumerable: true, get: function () { return typesafe_recipe_1.estimateFromState; } });
|
package/dist/stigg-agent-sdk.js
CHANGED
|
@@ -28,6 +28,7 @@ const litellm_recipe_1 = require("./bindings/client/gateways/litellm/litellm-rec
|
|
|
28
28
|
const databricks_recipe_1 = require("./bindings/client/gateways/databricks/databricks-recipe");
|
|
29
29
|
const cloudflare_recipe_1 = require("./bindings/client/gateways/cloudflare/cloudflare-recipe");
|
|
30
30
|
const agentgateway_recipe_1 = require("./bindings/client/gateways/agentgateway/agentgateway-recipe");
|
|
31
|
+
const truefoundry_recipe_1 = require("./bindings/client/gateways/truefoundry/truefoundry-recipe");
|
|
31
32
|
const groq_recipe_1 = require("./bindings/client/providers/groq/groq-recipe");
|
|
32
33
|
const mistral_recipe_1 = require("./bindings/client/providers/mistral/mistral-recipe");
|
|
33
34
|
const ollama_recipe_1 = require("./bindings/client/providers/ollama/ollama-recipe");
|
|
@@ -71,6 +72,9 @@ exports.DEFAULT_RECIPES = [
|
|
|
71
72
|
// Anthropic Messages wire too, and only the base URL tells its `Anthropic` client from a direct
|
|
72
73
|
// one. Without a configured base URL it matches on a hostname label - see the recipe.
|
|
73
74
|
(0, agentgateway_recipe_1.agentgatewayRecipe)(),
|
|
75
|
+
// Ahead of `anthropicRecipe` for the same reason again: TrueFoundry answers on both wires, and
|
|
76
|
+
// only its hostname (or a configured base URL) tells its clients from direct ones.
|
|
77
|
+
(0, truefoundry_recipe_1.truefoundryRecipe)(),
|
|
74
78
|
anthropic_recipe_1.anthropicRecipe,
|
|
75
79
|
(0, openrouter_recipe_1.openrouterRecipe)(),
|
|
76
80
|
(0, litellm_recipe_1.litellmRecipe)(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stigg/agent-sdk-js",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.9",
|
|
4
4
|
"description": "Stigg agent SDK (JS) - run-lifecycle governance for agentic stacks, with examples per layer",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"typings": "dist/index.d.ts",
|
|
@@ -64,6 +64,7 @@
|
|
|
64
64
|
"example-gateway-openrouter": "tsx examples/gateway/openrouter.ts",
|
|
65
65
|
"example-gateway-litellm": "tsx examples/gateway/litellm.ts",
|
|
66
66
|
"example-gateway-agentgateway": "tsx examples/gateway/agentgateway.ts",
|
|
67
|
+
"example-gateway-truefoundry": "tsx examples/gateway/truefoundry.ts",
|
|
67
68
|
"example-gateway-cloudflare": "tsx examples/gateway/cloudflare.ts",
|
|
68
69
|
"example-gateway-bedrock": "tsx examples/gateway/bedrock.ts",
|
|
69
70
|
"example-gateway-databricks": "tsx examples/gateway/databricks.ts"
|