@lionhummer/constellation 0.4.0 → 0.6.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 +28 -0
- package/dist/index.js +31 -3
- package/dist/types/client-core/src/index.d.ts +83 -3
- package/dist/types/sdk/src/index.d.ts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -201,3 +201,31 @@ const body = await client.telemetry.requests.getHttpBody(requestId, attemptId, {
|
|
|
201
201
|
Request lists accept `RequestListQuery`, including exact `externalId` and requested-model filters, exclusions and substring matches. Record lists accept `RequestRecordsQuery` (`id`, `q`, log-only `level`, `offset`, `limit`). `PageQuery.filters` belongs only to registry list rows. Every other method has its endpoint's exact query type. See the [HTTP filter matrix](../../docs/api.md#filter-audit-and-exact-query-support) for search semantics, caps, cursor behavior and capabilities not currently provided.
|
|
202
202
|
|
|
203
203
|
Unscoped telemetry also exposes `observability.getBilling`, `getTraffic`, `getTiming`, dashboard and measurement series. Use `providerId` for local series and `upstream` for gateway series. `system.getComponent(component, { status, statusNot, q, offset })` and `jobs.listHistory` page matching operational records; notifications use fixed 25-item pages and status/offset, not generic filters.
|
|
204
|
+
|
|
205
|
+
### Supplier cost, customer basis and margin
|
|
206
|
+
|
|
207
|
+
Each provider attempt records immutable `attributes.billing` on its Constellation `provider.call` span. The `step.usage_ready` event carries the same `billing` facts. Both are included in default telemetry metadata. `supplierPrice`/`supplierCost` record the selected supplier rates and usage cost. `customerPricing` identifies the model or offering basis, plan, scoped rule and adjustment; `customerBasisPrice`/`customerBasisCost` record the customer calculation before adjustment. `customerPrice`/`customerCost` record the final rates and charge. `grossMarginAmount` is charge minus supplier cost; `grossMarginPercent` divides it by charge. Unknown costs leave margins null, and a zero charge has a null margin percentage. These costs use provider-reported usage and captured schedules, not reconciled supplier invoices. Prices are USD per million tokens; costs are decimal USD strings.
|
|
208
|
+
|
|
209
|
+
Provider cost capture is optional: `billing.amountPointer` locates a provider-reported amount in response JSON and `unitsPerUsd` converts it to USD. `reportedSupplierCost` is retained only for analysis; it never changes supplier cost, customer basis, charges or margin. Supplier cost is calculated from reported token usage and captured supplier rates. `supplierCostDifference` is reported minus calculated cost when both are known. The offering selects `customerPriceBasis:model|offering`; plans apply the customer adjustment.
|
|
210
|
+
|
|
211
|
+
### Standard settings and headers
|
|
212
|
+
|
|
213
|
+
Use `parameters.reasoningEffort` for reasoning effort across providers. The model discovery schema defines its allowed values and other supported settings. Constellation translates fields and values for the selected offering. Required unsupported settings reject; `optionalParameters` permits declared omissions with warnings.
|
|
214
|
+
|
|
215
|
+
The standard Reasoning Effort vocabulary is `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, and `max`; Verbosity is `low`, `medium`, or `high`; Service Tier is `auto`, `default`, `flex`, or `priority`. Discovery lists the values accessible to your key and eligible routes. A model’s standard definitions cannot be renamed or given additional values; custom parameters have separately authored definitions. Provider wire names and values are translated internally.
|
|
216
|
+
|
|
217
|
+
For cache diagnostics, send `cache: { mode: "automatic", diagnostics: true }` on the first step. `providerResponseId` appears on attempts and in the `afterModel` checkpoint payload. Use that upstream ID as a later step’s `cache.comparisonId`, which also enables diagnostics. A successful cache hit may return no diagnostic payload. Telemetry exposes the response ID by default; raw `cacheDiagnostics` requires `include: "content"`. Diagnostics and prewarm are offering capabilities: discover support before requesting them. Invalid TTLs or unsupported required cache settings fail rather than being silently dropped.
|
|
218
|
+
|
|
219
|
+
Send beta tokens as `headers: { "anthropic-beta": "your-beta-token" }`, not inside `parameters`. The provider connection must allow the header name. Unapproved, protected or malformed headers are dropped with warnings. A permitted header can still contain an upstream-unsupported beta token; the provider's own error remains visible. Cache diagnostics can add their required beta separately. Obvious may choose provider-specific prompt placement and cache boundaries while using Constellation's standard cache request.
|
|
220
|
+
|
|
221
|
+
## Reasoning history and model handoffs
|
|
222
|
+
|
|
223
|
+
Retain the complete `nativeContent` artifact from assistant output alongside canonical text, tool calls and results. Reasoning summaries alone cannot reconstruct signed/encrypted state. Use `nativeContentPolicy: "omitIncompatible"` on submitted or prepared steps to permit cross-family handoffs, including active tool continuations. Constellation omits only incompatible native state in the outbound attempt and records a warning; the original history remains intact. `require` excludes incompatible routes; `omitCompleted` permits omission only from completed turns.
|
|
224
|
+
|
|
225
|
+
Same-protocol Claude/Responses model switches let providers verify native readability. Responses account scopes must be verified; Gemini also requires the model replay group. Preserve the exact prompt prefix for signed thinking. Provider-reported thinking drops and allowed prefix mismatches appear in provider span `attributes.reasoningTransformations`; a successful response does not prove retention. See [the HTTP contract](../../docs/api.md#provider-protocols).
|
|
226
|
+
|
|
227
|
+
### Tool timing
|
|
228
|
+
|
|
229
|
+
`inputs.handle` and `inputs.poll` record tool-input inspection, claim and response POST phases. A tool handler receives `context.parentSpanId` and `context.recordSpan(span)`; use the latter to attach closed preparation/execution spans without an HTTP call per phase. Child spans are batched with the tool-span close. Keep spans linked to the current input and omit arguments, results and secrets from attributes. The result POST observation is best effort: a request that has already ended rejects late telemetry, without replaying the committed response.
|
|
230
|
+
|
|
231
|
+
Callback round trips include your handler's processing time. Compare the input wait, tool span and child execution phases rather than adding their overlapping durations. Monotonic `elapsedMs` values are retained in metadata-only telemetry; timestamps across hosts assume aligned clocks.
|
package/dist/index.js
CHANGED
|
@@ -202,7 +202,7 @@ class RequestClient {
|
|
|
202
202
|
status: options2.status ?? closed?.status ?? "ok",
|
|
203
203
|
attributes: options2.attributes ?? closed?.attributes ?? span.attributes
|
|
204
204
|
};
|
|
205
|
-
return this.#addSpans(id, [closed], signal2);
|
|
205
|
+
return this.#addSpans(id, [closed, ...options2.spans ?? []], signal2);
|
|
206
206
|
}
|
|
207
207
|
};
|
|
208
208
|
}
|
|
@@ -285,7 +285,11 @@ class RequestClient {
|
|
|
285
285
|
async#processInputs(id, options = {}) {
|
|
286
286
|
const consumerId = options.consumerId ?? uuid7();
|
|
287
287
|
while (true) {
|
|
288
|
+
const inspectStarted = new Date().toISOString();
|
|
289
|
+
const inspectClock = performance.now();
|
|
288
290
|
const state = await this.#inspect(id, options.signal);
|
|
291
|
+
const inspectEnded = new Date().toISOString();
|
|
292
|
+
const inspectMs = performance.now() - inspectClock;
|
|
289
293
|
options.onUpdate?.(state);
|
|
290
294
|
if (terminal(state.request.status))
|
|
291
295
|
return state;
|
|
@@ -351,6 +355,8 @@ class RequestClient {
|
|
|
351
355
|
}
|
|
352
356
|
if (pending.kind !== "tool")
|
|
353
357
|
continue;
|
|
358
|
+
const claimStarted = new Date().toISOString();
|
|
359
|
+
const claimClock = performance.now();
|
|
354
360
|
const tool = options.tools?.[pending.payload.name ?? ""];
|
|
355
361
|
if (!tool && !pending.payload.validationError)
|
|
356
362
|
continue;
|
|
@@ -361,8 +367,14 @@ class RequestClient {
|
|
|
361
367
|
continue;
|
|
362
368
|
throw error;
|
|
363
369
|
}
|
|
370
|
+
const claimEnded = new Date().toISOString();
|
|
371
|
+
const claimMs = performance.now() - claimClock;
|
|
364
372
|
await this.#whileClaimed(id, pending.id, consumerId, true, async (activeSignal) => {
|
|
365
373
|
const span = await this.#startSpan(id, `tool.${pending.payload.name}`, { stepId: pending.stepId ?? undefined, inputId: pending.id }, activeSignal);
|
|
374
|
+
const phases = [
|
|
375
|
+
{ id: uuid7(), inputId: pending.id, name: "caller.input.inspect", startedAt: inspectStarted, endedAt: inspectEnded, status: "ok", attributes: { elapsedMs: inspectMs } },
|
|
376
|
+
{ id: uuid7(), inputId: pending.id, name: "caller.input.claim", startedAt: claimStarted, endedAt: claimEnded, status: "ok", attributes: { elapsedMs: claimMs } }
|
|
377
|
+
];
|
|
366
378
|
let result;
|
|
367
379
|
let isError = false;
|
|
368
380
|
let errorDetails;
|
|
@@ -375,6 +387,8 @@ class RequestClient {
|
|
|
375
387
|
result = await bounded(tool(pending.payload.arguments ?? {}, {
|
|
376
388
|
inputId: pending.id,
|
|
377
389
|
requestId: id,
|
|
390
|
+
parentSpanId: span.id,
|
|
391
|
+
recordSpan: (phase) => phases.push({ ...phase, inputId: pending.id, parentSpanId: span.id }),
|
|
378
392
|
signal: toolSignal
|
|
379
393
|
}), toolSignal);
|
|
380
394
|
} catch (error) {
|
|
@@ -383,9 +397,23 @@ class RequestClient {
|
|
|
383
397
|
errorDetails = callerErrorDetails(error);
|
|
384
398
|
result = { error: error instanceof Error ? error.message : "Tool failed" };
|
|
385
399
|
}
|
|
386
|
-
await span.end({ status: isError ? "error" : "ok", ...errorDetails ? { attributes: { error: errorDetails } } : {} }, activeSignal);
|
|
400
|
+
await span.end({ status: isError ? "error" : "ok", spans: phases, ...errorDetails ? { attributes: { error: errorDetails } } : {} }, activeSignal);
|
|
387
401
|
await this.#claim(id, pending.id, consumerId, activeSignal);
|
|
388
|
-
|
|
402
|
+
const responseStarted = new Date().toISOString();
|
|
403
|
+
const responseClock = performance.now();
|
|
404
|
+
let responseStatus = "ok";
|
|
405
|
+
try {
|
|
406
|
+
await this.#respond(id, pending.id, { result, isError, claimId: consumerId }, activeSignal);
|
|
407
|
+
} catch (error) {
|
|
408
|
+
responseStatus = "error";
|
|
409
|
+
throw error;
|
|
410
|
+
} finally {
|
|
411
|
+
const responseSpan = { id: uuid7(), inputId: pending.id, name: "caller.input.respond", startedAt: responseStarted, endedAt: new Date().toISOString(), status: responseStatus, attributes: { elapsedMs: performance.now() - responseClock } };
|
|
412
|
+
await this.#addSpans(id, [responseSpan], activeSignal ? AbortSignal.any([activeSignal, AbortSignal.timeout(2000)]) : AbortSignal.timeout(2000)).catch((error) => {
|
|
413
|
+
if (!(error instanceof ConstellationError && (error.status === 409 || error.status === 404)))
|
|
414
|
+
console.warn("Constellation input-response timing could not be recorded");
|
|
415
|
+
});
|
|
416
|
+
}
|
|
389
417
|
}, options.signal);
|
|
390
418
|
handled = true;
|
|
391
419
|
}
|
|
@@ -29,6 +29,20 @@ export type Message = {
|
|
|
29
29
|
isError?: boolean;
|
|
30
30
|
}[];
|
|
31
31
|
};
|
|
32
|
+
/** Standard settings. The model discovery schema describes supported values and extensions. */
|
|
33
|
+
export type InferenceParameters = Record<string, Json> & {
|
|
34
|
+
reasoningEffort?: "none" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
|
|
35
|
+
verbosity?: "low" | "medium" | "high";
|
|
36
|
+
temperature?: number;
|
|
37
|
+
topP?: number;
|
|
38
|
+
thinking?: Json;
|
|
39
|
+
toolChoice?: Json;
|
|
40
|
+
parallelToolCalls?: boolean;
|
|
41
|
+
toolStrict?: boolean;
|
|
42
|
+
responseFormat?: Json;
|
|
43
|
+
stopSequences?: string[];
|
|
44
|
+
serviceTier?: "auto" | "default" | "flex" | "priority";
|
|
45
|
+
};
|
|
32
46
|
type RequestFields = {
|
|
33
47
|
idempotencyKey: string;
|
|
34
48
|
externalId?: string;
|
|
@@ -39,12 +53,21 @@ type RequestFields = {
|
|
|
39
53
|
instructions?: string;
|
|
40
54
|
messages: Message[];
|
|
41
55
|
tools?: Tool[];
|
|
42
|
-
parameters?:
|
|
56
|
+
parameters?: InferenceParameters;
|
|
57
|
+
optionalParameters?: string[];
|
|
58
|
+
/** Only headers approved by the selected provider are forwarded; others produce warnings. */
|
|
59
|
+
headers?: Record<string, string>;
|
|
60
|
+
nativeContentPolicy?: "require" | "omitCompleted" | "omitIncompatible";
|
|
43
61
|
maxSteps?: number;
|
|
44
62
|
timeoutMs?: number;
|
|
45
63
|
maxOutputTokens?: number;
|
|
46
64
|
cache?: {
|
|
47
65
|
mode: "automatic" | "explicit";
|
|
66
|
+
ttl?: string;
|
|
67
|
+
prewarm?: boolean;
|
|
68
|
+
comparisonId?: string;
|
|
69
|
+
/** Request cache diagnostics, including a first-turn baseline without a comparison ID. */
|
|
70
|
+
diagnostics?: boolean;
|
|
48
71
|
points?: ({
|
|
49
72
|
target: "tools" | "instructions";
|
|
50
73
|
} | {
|
|
@@ -126,6 +149,8 @@ export type Attempt = {
|
|
|
126
149
|
providerId: string;
|
|
127
150
|
offeringId: string;
|
|
128
151
|
providerModel: string;
|
|
152
|
+
providerResponseId?: string | null;
|
|
153
|
+
cacheDiagnostics?: Json | null;
|
|
129
154
|
connectionKind: string;
|
|
130
155
|
endpoint: string;
|
|
131
156
|
fallback: boolean;
|
|
@@ -154,7 +179,11 @@ export type PreparedStep = {
|
|
|
154
179
|
instructions: string;
|
|
155
180
|
messages: Message[];
|
|
156
181
|
tools: Tool[];
|
|
157
|
-
parameters:
|
|
182
|
+
parameters: InferenceParameters;
|
|
183
|
+
optionalParameters?: string[];
|
|
184
|
+
/** Only headers approved by the selected provider are forwarded; others produce warnings. */
|
|
185
|
+
headers?: Record<string, string>;
|
|
186
|
+
nativeContentPolicy?: "require" | "omitCompleted" | "omitIncompatible";
|
|
158
187
|
maxOutputTokens: number;
|
|
159
188
|
cache: RequestInput["cache"] | null;
|
|
160
189
|
};
|
|
@@ -174,6 +203,8 @@ export type PendingInput = {
|
|
|
174
203
|
customerCost?: string | null;
|
|
175
204
|
supplierCost?: string | null;
|
|
176
205
|
customerPrice?: Json;
|
|
206
|
+
customerPricing?: CustomerPricing;
|
|
207
|
+
billing?: BillingFacts;
|
|
177
208
|
output?: Json;
|
|
178
209
|
};
|
|
179
210
|
claimId: string | null;
|
|
@@ -269,6 +300,8 @@ export type InputHandlers = {
|
|
|
269
300
|
tools?: Record<string, (args: Json, context: {
|
|
270
301
|
inputId: string;
|
|
271
302
|
requestId: string;
|
|
303
|
+
parentSpanId: string;
|
|
304
|
+
recordSpan: (span: Span) => void;
|
|
272
305
|
signal?: AbortSignal;
|
|
273
306
|
}) => Promise<Json>>;
|
|
274
307
|
userInput?: (state: Inspection, context: {
|
|
@@ -307,7 +340,11 @@ export type ModelResolution = ModelScope & {
|
|
|
307
340
|
targetId: string;
|
|
308
341
|
inputTokens?: number;
|
|
309
342
|
outputTokens?: number;
|
|
310
|
-
parameters?:
|
|
343
|
+
parameters?: InferenceParameters;
|
|
344
|
+
optionalParameters?: string[];
|
|
345
|
+
/** Only headers approved by the selected provider are forwarded; others produce warnings. */
|
|
346
|
+
headers?: Record<string, string>;
|
|
347
|
+
nativeContentPolicy?: "require" | "omitCompleted" | "omitIncompatible";
|
|
311
348
|
};
|
|
312
349
|
export type WebhookEvent = {
|
|
313
350
|
requestId: string;
|
|
@@ -336,9 +373,51 @@ export type TokenUsage = {
|
|
|
336
373
|
outputTokens?: number;
|
|
337
374
|
cacheReadTokens?: number;
|
|
338
375
|
cacheWriteTokens?: number;
|
|
376
|
+
cacheWriteOneHourTokens?: number;
|
|
339
377
|
reasoningTokens?: number;
|
|
340
378
|
partial?: boolean;
|
|
341
379
|
};
|
|
380
|
+
/** USD per million tokens. The selected rates apply to the whole call. */
|
|
381
|
+
export type TokenPrice = {
|
|
382
|
+
input: string;
|
|
383
|
+
output: string;
|
|
384
|
+
cacheRead?: string | null;
|
|
385
|
+
cacheWrite?: string | null;
|
|
386
|
+
cacheWriteOneHour?: string | null;
|
|
387
|
+
tiers?: {
|
|
388
|
+
aboveInputTokens: number;
|
|
389
|
+
price: TokenPrice;
|
|
390
|
+
}[];
|
|
391
|
+
};
|
|
392
|
+
export type CustomerPricing = {
|
|
393
|
+
basis: "model" | "offering";
|
|
394
|
+
basisTargetId: string;
|
|
395
|
+
basisPrice?: TokenPrice;
|
|
396
|
+
planId: string | null;
|
|
397
|
+
pricingRuleId: string | null;
|
|
398
|
+
adjustment: {
|
|
399
|
+
mode: "margin" | "markup" | "discount";
|
|
400
|
+
percent: string;
|
|
401
|
+
};
|
|
402
|
+
};
|
|
403
|
+
/** Costs use captured rates; provider-reported amounts are analysis-only. */
|
|
404
|
+
export type BillingFacts = {
|
|
405
|
+
totalInputTokens: number | null;
|
|
406
|
+
supplierPrice: TokenPrice | null;
|
|
407
|
+
supplierCost: string | null;
|
|
408
|
+
calculatedSupplierCost: string | null;
|
|
409
|
+
reportedSupplierCost: string | null;
|
|
410
|
+
supplierCostSource: "calculated" | "unknown";
|
|
411
|
+
supplierCostDifference: string | null;
|
|
412
|
+
customerPricing: CustomerPricing;
|
|
413
|
+
customerBasisPrice: TokenPrice | null;
|
|
414
|
+
customerBasisCost: string | null;
|
|
415
|
+
customerBasisSource: "calculated" | "unknown" | "model_schedule";
|
|
416
|
+
customerPrice: TokenPrice | null;
|
|
417
|
+
customerCost: string | null;
|
|
418
|
+
grossMarginAmount: string | null;
|
|
419
|
+
grossMarginPercent: string | null;
|
|
420
|
+
};
|
|
342
421
|
export type TimingInterval = {
|
|
343
422
|
id: string;
|
|
344
423
|
requestId: string;
|
|
@@ -494,6 +573,7 @@ export declare class RequestClient {
|
|
|
494
573
|
endedAt?: string;
|
|
495
574
|
status?: "ok" | "error" | "cancelled";
|
|
496
575
|
attributes?: Record<string, Json>;
|
|
576
|
+
spans?: Span[];
|
|
497
577
|
}, signal?: AbortSignal) => Promise<void>;
|
|
498
578
|
}>;
|
|
499
579
|
add: (requestId: string, spans: Span[], signal?: AbortSignal) => Promise<void>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** External server SDK. Operator APIs and cookie sessions are internal-only clients. */
|
|
2
2
|
import { RequestClient, type WebhookEvent, type Json, type Page, type PageQuery, type ModelResolution, type RequestRecord, type RequestEvent, type RecordTypes, type Timeline, type TimelineRow, type RequestListQuery, type RequestRecordsQuery, type RequestTimings, type TokenUsage } from "../../client-core/src/index";
|
|
3
3
|
export { ConstellationError, uuid7 } from "../../client-core/src/index";
|
|
4
|
-
export type { Json, Tool, ToolCall, Message, RequestEventModes, RequestInput, RequestTimings, TimingInterval, TokenUsage, Status, RequestRecord, Step, Attempt, PreparedStep, PendingInput, Span, TimelineRow, Timeline, Log, Inspection, RecordTypes, InputResponse, InputHandlers, RequestEvent, Page, PageQuery, ModelScope, ModelResolution, WebhookEvent, RequestRecordsQuery, RequestListQuery, StartSpanOptions } from "../../client-core/src/index";
|
|
4
|
+
export type { InferenceParameters, Json, Tool, ToolCall, Message, RequestEventModes, RequestInput, RequestTimings, TimingInterval, TokenUsage, TokenPrice, CustomerPricing, BillingFacts, Status, RequestRecord, Step, Attempt, PreparedStep, PendingInput, Span, TimelineRow, Timeline, Log, Inspection, RecordTypes, InputResponse, InputHandlers, RequestEvent, Page, PageQuery, ModelScope, ModelResolution, WebhookEvent, RequestRecordsQuery, RequestListQuery, StartSpanOptions } from "../../client-core/src/index";
|
|
5
5
|
/** OpenAPI contract filtered to this key's permissions and scope. */
|
|
6
6
|
export type DiscoveryDocument = {
|
|
7
7
|
openapi: string;
|