源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
Dependency audit / audit (push) Canceled after 0s
CI / plan (push) Canceled after 0s
Gitoxide helper admission / macos-latest (push) Canceled after 0s
Gitoxide helper admission / ubuntu-latest (push) Canceled after 0s
Gitoxide helper admission / windows-latest (push) Canceled after 0s
Runtime Host peer admission / quality (push) Canceled after 0s
Windows recovery / windows_recovery (push) Canceled after 0s
CI / heavy (push) Canceled after 0s
CI / test (push) Canceled after 0s
Dependency audit / audit (push) Canceled after 0s
CI / plan (push) Canceled after 0s
Gitoxide helper admission / macos-latest (push) Canceled after 0s
Gitoxide helper admission / ubuntu-latest (push) Canceled after 0s
Gitoxide helper admission / windows-latest (push) Canceled after 0s
Runtime Host peer admission / quality (push) Canceled after 0s
Windows recovery / windows_recovery (push) Canceled after 0s
CI / heavy (push) Canceled after 0s
CI / test (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,917 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
MODEL_CALL_ATTEMPT_SCHEMA_VERSION,
|
||||
type HistoryCompactRoute,
|
||||
type ModelCallAttempt,
|
||||
type ModelCallKind,
|
||||
type ModelCallUsageBasis,
|
||||
type PreparedRequestObservation,
|
||||
} from '@maka/core/model-call-attempt';
|
||||
import type { PricingConfig } from '@maka/core/usage-stats/types';
|
||||
import { prepareRequestObservation, type PreparedRequestMaterial } from './request-shape.js';
|
||||
import { rawFinishReasonString } from './model-protocol.js';
|
||||
import {
|
||||
providerFailureDiagnostic,
|
||||
type ProviderFailureDiagnostic,
|
||||
} from './provider-error-classification.js';
|
||||
import { latestContextProjectionInput } from './latest-context-snapshot.js';
|
||||
import type { ContextDiagnosticsCompaction } from './context-diagnostics.js';
|
||||
import type { ModelCallCommit } from '@maka/core/agent-run';
|
||||
|
||||
export type ProviderRequestCacheValueSource = 'provider' | 'derived';
|
||||
|
||||
export interface ProviderRequestUsage {
|
||||
inputTokens?: number;
|
||||
cacheReadInputTokens?: number;
|
||||
cacheReadInputSource?: ProviderRequestCacheValueSource;
|
||||
cacheMissInputTokens?: number;
|
||||
cacheMissInputSource?: ProviderRequestCacheValueSource;
|
||||
cacheWriteInputTokens?: number;
|
||||
cacheWriteInputSource?: ProviderRequestCacheValueSource;
|
||||
outputTokens?: number;
|
||||
reasoningTokens?: number;
|
||||
}
|
||||
|
||||
export interface ProviderRequestUsageLike {
|
||||
inputTokens?:
|
||||
| number
|
||||
| {
|
||||
total?: number;
|
||||
noCache?: number;
|
||||
cacheRead?: number;
|
||||
cacheWrite?: number;
|
||||
};
|
||||
outputTokens?: number | { total?: number; text?: number; reasoning?: number };
|
||||
raw?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export type ProviderRequestAttemptStatus = 'completed' | 'failed' | 'interrupted' | 'aborted';
|
||||
|
||||
export interface PreparedRequestArtifactInput extends PreparedRequestMaterial {
|
||||
traceId: string;
|
||||
captureId: string;
|
||||
turnId: string;
|
||||
step: number;
|
||||
providerId: string;
|
||||
modelId: string;
|
||||
}
|
||||
|
||||
export interface PreparedRequestArtifactRef {
|
||||
artifactId: string;
|
||||
}
|
||||
|
||||
interface SettledProviderAttempt extends ProviderRequestUsage {
|
||||
traceId: string;
|
||||
attemptId: string;
|
||||
turnId: string;
|
||||
step: number;
|
||||
attempt: number;
|
||||
captureArtifactId?: string;
|
||||
providerId: string;
|
||||
modelId: string;
|
||||
contextWindow?: number;
|
||||
startedAt: number;
|
||||
completedAt: number;
|
||||
status: ProviderRequestAttemptStatus;
|
||||
finishReason?: string;
|
||||
failure?: ProviderFailureDiagnostic;
|
||||
latencyMs: number;
|
||||
timeToFirstTokenMs?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cost resolved at the moment a call settles, plus the basis it was resolved
|
||||
* against. Recording the basis alongside the amount is what makes a stored
|
||||
* figure auditable later, when rates may have changed.
|
||||
*/
|
||||
export interface ResolvedModelCallCost {
|
||||
costUsd?: number;
|
||||
pricingRevision?: number;
|
||||
pricingRates?: PricingConfig;
|
||||
}
|
||||
|
||||
export interface ProviderRequestTrackerInput {
|
||||
traceId: string;
|
||||
turnId: string;
|
||||
contextWindow?: number;
|
||||
now: () => number;
|
||||
newId: () => string;
|
||||
/**
|
||||
* Optional private artifact sink. Failure leaves the canonical observation
|
||||
* intact and the attempt explicitly has no artifact join.
|
||||
*/
|
||||
persistArtifact?: (input: PreparedRequestArtifactInput) => Promise<PreparedRequestArtifactRef>;
|
||||
/**
|
||||
* Durable run metadata that must exist before any physical provider call.
|
||||
* Kept outside accounting because a dispatch gate is an execution contract,
|
||||
* not a metering concern.
|
||||
*/
|
||||
beforeDispatch?: () => void | Promise<void>;
|
||||
|
||||
/**
|
||||
* Canonical metering. Present as a unit or not at all: a `ModelCallAttempt`
|
||||
* without session, run, and kind is unattributable, so identity and sink are
|
||||
* wired together rather than as independently optional fields. Absent leaves
|
||||
* the tracker purely diagnostic, which is what the capture-only tests use.
|
||||
*/
|
||||
accounting?: ModelCallAccountingInput;
|
||||
}
|
||||
|
||||
export interface ModelCallAccountingInput {
|
||||
sessionId: string;
|
||||
/** Resolved per send: one tracker does not outlive a single run. */
|
||||
resolveRunId: () => string | undefined;
|
||||
/**
|
||||
* The connection the request was dispatched over. Without it a record is
|
||||
* attributable to a provider and model but not to the configured connection,
|
||||
* which is what the Usage surface filters and groups by.
|
||||
*/
|
||||
connectionSlug?: string;
|
||||
/**
|
||||
* The connection's provider type, which is the vocabulary the Usage surface
|
||||
* groups by. The diagnostic record carries the SDK's own provider id instead
|
||||
* (`moonshot.chat` where this says `moonshot`); metering must not split one
|
||||
* provider across two bucket keys, so accounting uses this when given.
|
||||
*/
|
||||
providerId?: string;
|
||||
callKind: ModelCallKind;
|
||||
historyCompactRoute?: ModelCallAttempt['historyCompactRoute'];
|
||||
/**
|
||||
* Commits the attempt, and with it the derived latest-context row when this
|
||||
* request is one that answers "what is the context made of" (#2323). One
|
||||
* call, so the two cannot fail or arrive independently.
|
||||
*/
|
||||
record: (commit: ModelCallCommit<ModelCallAttempt>) => void | Promise<void>;
|
||||
/** Resolves cost at settlement time; absent means the price is unknown. */
|
||||
resolveCost?: (usage: ProviderRequestUsage) => ResolvedModelCallCost | undefined;
|
||||
/**
|
||||
* Pre-dispatch accounting gate. Throws when the canonical record could not be
|
||||
* written for this dispatch. Checked before the provider is called and never
|
||||
* from settlement — a rejection inside `finalize` would surface through the
|
||||
* stream's `pull` handler and error an otherwise-complete model response.
|
||||
*/
|
||||
assertReady?: () => void;
|
||||
}
|
||||
|
||||
export interface TrackProviderStreamInput {
|
||||
providerId: string;
|
||||
modelId: string;
|
||||
params: Record<string, unknown>;
|
||||
abortSignal?: AbortSignal;
|
||||
doStream: () => PromiseLike<ProviderStreamResult>;
|
||||
/**
|
||||
* The compaction boundary THIS request's prompt was built from (#2323).
|
||||
*
|
||||
* Per request rather than per tracker: one tracker spans every physical
|
||||
* request of a send, and mid-turn compaction or overflow recovery can
|
||||
* prepare the next request against a different boundary. Read at settlement
|
||||
* it would be whatever the session holds by then — a boundary this prompt
|
||||
* may never have seen.
|
||||
*/
|
||||
historyCompactBoundary?: ContextDiagnosticsCompaction;
|
||||
/** Physical history-compaction route used by this provider request. */
|
||||
historyCompactRoute?: HistoryCompactRoute;
|
||||
}
|
||||
|
||||
export interface TrackProviderGenerateInput {
|
||||
providerId: string;
|
||||
modelId: string;
|
||||
/** As `TrackProviderStreamInput.historyCompactBoundary`. */
|
||||
historyCompactBoundary?: ContextDiagnosticsCompaction;
|
||||
/** Physical history-compaction route used by this provider request. */
|
||||
historyCompactRoute?: HistoryCompactRoute;
|
||||
params: Record<string, unknown>;
|
||||
abortSignal?: AbortSignal;
|
||||
doGenerate: () => PromiseLike<ProviderGenerateResult>;
|
||||
}
|
||||
|
||||
interface ProviderMiddlewareGenerateInput {
|
||||
doGenerate: () => PromiseLike<ProviderGenerateResult>;
|
||||
params: Record<string, unknown> & { abortSignal?: AbortSignal };
|
||||
model: { provider: string; modelId: string };
|
||||
}
|
||||
|
||||
interface ProviderMiddlewareStreamInput {
|
||||
doStream: () => PromiseLike<ProviderStreamResult>;
|
||||
params: Record<string, unknown> & { abortSignal?: AbortSignal };
|
||||
model: { provider: string; modelId: string };
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps a language model so its single `generate` call is tracked.
|
||||
*
|
||||
* The AI SDK's `wrapLanguageModel` is passed in rather than imported: both
|
||||
* callers load the `ai` package dynamically, and there is no reason for this
|
||||
* module to load it a third time. Non-streaming counterpart of what
|
||||
* `ModelAdapter.startStream` does with `wrapStream`, and the only place either
|
||||
* auxiliary caller attaches a tracker to a model.
|
||||
*/
|
||||
export function withProviderGenerateTracking(input: {
|
||||
model: unknown;
|
||||
wrapLanguageModel: (input: Record<string, unknown>) => unknown;
|
||||
tracker: ProviderRequestTracker;
|
||||
abortSignal?: AbortSignal;
|
||||
historyCompactRoute?: HistoryCompactRoute;
|
||||
}): unknown {
|
||||
return input.wrapLanguageModel({
|
||||
model: input.model,
|
||||
middleware: {
|
||||
wrapGenerate: async ({ doGenerate, params, model }: ProviderMiddlewareGenerateInput) =>
|
||||
await input.tracker.trackGenerate({
|
||||
providerId: model.provider,
|
||||
modelId: model.modelId,
|
||||
params,
|
||||
...(input.abortSignal ? { abortSignal: input.abortSignal } : {}),
|
||||
...(input.historyCompactRoute ? { historyCompactRoute: input.historyCompactRoute } : {}),
|
||||
doGenerate,
|
||||
}),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/** Wraps a language model so its single streaming call is tracked. */
|
||||
export function withProviderStreamTracking(input: {
|
||||
model: unknown;
|
||||
wrapLanguageModel: (input: Record<string, unknown>) => unknown;
|
||||
tracker: ProviderRequestTracker;
|
||||
abortSignal?: AbortSignal;
|
||||
historyCompactRoute?: HistoryCompactRoute;
|
||||
}): unknown {
|
||||
return input.wrapLanguageModel({
|
||||
model: input.model,
|
||||
middleware: {
|
||||
wrapStream: async ({ doStream, params, model }: ProviderMiddlewareStreamInput) =>
|
||||
await input.tracker.trackStream({
|
||||
providerId: model.provider,
|
||||
modelId: model.modelId,
|
||||
params,
|
||||
...(input.abortSignal ? { abortSignal: input.abortSignal } : {}),
|
||||
...(input.historyCompactRoute ? { historyCompactRoute: input.historyCompactRoute } : {}),
|
||||
doStream,
|
||||
}),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export interface ProviderStreamResult {
|
||||
stream: ReadableStream<unknown>;
|
||||
request?: unknown;
|
||||
response?: unknown;
|
||||
}
|
||||
|
||||
export interface ProviderGenerateResult {
|
||||
finishReason?: unknown;
|
||||
usage?: ProviderRequestUsageLike;
|
||||
request?: unknown;
|
||||
response?: unknown;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface StoredCapture {
|
||||
material: PreparedRequestMaterial;
|
||||
/** Absent when artifact persistence is unavailable or failed. */
|
||||
artifactId?: string;
|
||||
}
|
||||
|
||||
const CANONICAL_USAGE_FIELDS = [
|
||||
'inputTokens',
|
||||
'outputTokens',
|
||||
'cacheReadInputTokens',
|
||||
'cacheMissInputTokens',
|
||||
'cacheWriteInputTokens',
|
||||
'reasoningTokens',
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* Distinguishes "the provider never reported usage" from "it reported some of
|
||||
* it". Both differ from an unresolvable price, which `costBasis` carries.
|
||||
*/
|
||||
function resolveUsageBasis(usage: ProviderRequestUsage | undefined): ModelCallUsageBasis {
|
||||
if (!usage) return 'missing';
|
||||
const present = CANONICAL_USAGE_FIELDS.filter((field) => usage[field] !== undefined);
|
||||
if (present.length === 0) return 'missing';
|
||||
return usage.inputTokens !== undefined && usage.outputTokens !== undefined
|
||||
? 'reported'
|
||||
: 'partial';
|
||||
}
|
||||
|
||||
function modelCallUsageFields(
|
||||
usage: ProviderRequestUsage | undefined,
|
||||
): Partial<Record<(typeof CANONICAL_USAGE_FIELDS)[number], number>> {
|
||||
if (!usage) return {};
|
||||
const fields: Partial<Record<(typeof CANONICAL_USAGE_FIELDS)[number], number>> = {};
|
||||
for (const field of CANONICAL_USAGE_FIELDS) {
|
||||
const value = usage[field];
|
||||
if (value !== undefined) fields[field] = value;
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
|
||||
export class ProviderRequestTracker {
|
||||
private step = 0;
|
||||
private readonly attemptsByStep = new Map<number, number>();
|
||||
private readonly captures = new Map<string, StoredCapture>();
|
||||
/**
|
||||
* One logical call per step. Retries of the same step are further attempts of
|
||||
* that call, not new calls, so they share this id.
|
||||
*/
|
||||
private readonly logicalCallIdByStep = new Map<number, string>();
|
||||
|
||||
constructor(private readonly input: ProviderRequestTrackerInput) {}
|
||||
|
||||
get traceId(): string {
|
||||
return this.input.traceId;
|
||||
}
|
||||
|
||||
setStep(step: number): void {
|
||||
this.step = step;
|
||||
}
|
||||
|
||||
async trackStream(input: TrackProviderStreamInput): Promise<ProviderStreamResult> {
|
||||
throwIfAbortedBeforeDispatch(input.abortSignal);
|
||||
await this.input.beforeDispatch?.();
|
||||
throwIfAbortedBeforeDispatch(input.abortSignal);
|
||||
this.input.accounting?.assertReady?.();
|
||||
const step = this.step;
|
||||
const capture = this.capture(step, input);
|
||||
throwIfAbortedBeforeDispatch(input.abortSignal);
|
||||
let sawOutput = false;
|
||||
const attempt = this.beginAttempt(step, capture, input);
|
||||
|
||||
let result: ProviderStreamResult;
|
||||
try {
|
||||
result = await input.doStream();
|
||||
} catch (error) {
|
||||
await attempt.finalize(abortStatus(input.abortSignal, error), { error });
|
||||
throw error;
|
||||
}
|
||||
|
||||
const reader = result.stream.getReader();
|
||||
const stream = new ReadableStream<unknown>({
|
||||
pull: async (controller) => {
|
||||
try {
|
||||
const next = await reader.read();
|
||||
if (next.done) {
|
||||
await attempt.finalize(input.abortSignal?.aborted ? 'aborted' : 'interrupted');
|
||||
controller.close();
|
||||
return;
|
||||
}
|
||||
const part = asRecord(next.value);
|
||||
if (part && isOutputPart(part.type)) {
|
||||
sawOutput = true;
|
||||
attempt.observeOutput();
|
||||
}
|
||||
if (part?.type === 'finish') {
|
||||
await attempt.finalize(input.abortSignal?.aborted ? 'aborted' : 'completed', {
|
||||
reason: rawFinishReasonString(part.finishReason),
|
||||
usage: asUsage(part.usage),
|
||||
});
|
||||
} else if (part?.type === 'error') {
|
||||
await attempt.finalize(
|
||||
input.abortSignal?.aborted ? 'aborted' : sawOutput ? 'interrupted' : 'failed',
|
||||
{ error: part.error },
|
||||
);
|
||||
}
|
||||
controller.enqueue(next.value);
|
||||
} catch (error) {
|
||||
await attempt.finalize(
|
||||
input.abortSignal?.aborted
|
||||
? 'aborted'
|
||||
: sawOutput
|
||||
? 'interrupted'
|
||||
: abortStatus(input.abortSignal, error),
|
||||
{ error },
|
||||
);
|
||||
controller.error(error);
|
||||
}
|
||||
},
|
||||
cancel: async (reason) => {
|
||||
try {
|
||||
await reader.cancel(reason);
|
||||
} finally {
|
||||
await attempt.finalize(input.abortSignal?.aborted ? 'aborted' : 'interrupted');
|
||||
}
|
||||
},
|
||||
});
|
||||
return { ...result, stream };
|
||||
}
|
||||
|
||||
async trackGenerate(input: TrackProviderGenerateInput): Promise<ProviderGenerateResult> {
|
||||
throwIfAbortedBeforeDispatch(input.abortSignal);
|
||||
await this.input.beforeDispatch?.();
|
||||
throwIfAbortedBeforeDispatch(input.abortSignal);
|
||||
this.input.accounting?.assertReady?.();
|
||||
const step = this.step;
|
||||
const capture = this.capture(step, input);
|
||||
throwIfAbortedBeforeDispatch(input.abortSignal);
|
||||
const attempt = this.beginAttempt(step, capture, input);
|
||||
try {
|
||||
const result = await input.doGenerate();
|
||||
await attempt.finalize(input.abortSignal?.aborted ? 'aborted' : 'completed', {
|
||||
reason: rawFinishReasonString(result.finishReason),
|
||||
usage: result.usage,
|
||||
});
|
||||
return result;
|
||||
} catch (error) {
|
||||
await attempt.finalize(abortStatus(input.abortSignal, error), { error });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private beginAttempt(
|
||||
step: number,
|
||||
capture: StoredCapture,
|
||||
input: Pick<
|
||||
TrackProviderStreamInput | TrackProviderGenerateInput,
|
||||
'providerId' | 'modelId' | 'abortSignal' | 'historyCompactBoundary' | 'historyCompactRoute'
|
||||
>,
|
||||
): {
|
||||
observeOutput(): void;
|
||||
finalize(
|
||||
status: ProviderRequestAttemptStatus,
|
||||
finish?: { reason?: string; usage?: ProviderRequestUsageLike; error?: unknown },
|
||||
): Promise<void>;
|
||||
} {
|
||||
const attempt = (this.attemptsByStep.get(step) ?? 0) + 1;
|
||||
this.attemptsByStep.set(step, attempt);
|
||||
let logicalCallId = this.logicalCallIdByStep.get(step);
|
||||
if (logicalCallId === undefined) {
|
||||
logicalCallId = this.input.newId();
|
||||
this.logicalCallIdByStep.set(step, logicalCallId);
|
||||
}
|
||||
const attemptId = this.input.newId();
|
||||
const startedAt = this.input.now();
|
||||
let timeToFirstTokenMs: number | undefined;
|
||||
// Cancellation and provider settlement are different events. An abort that
|
||||
// carries no usage records provisionally but does not close the attempt, so
|
||||
// a `finish` arriving afterwards can still settle it. Both writes share one
|
||||
// `attemptId`, and the canonical record dedupes on that key keeping the last
|
||||
// — otherwise a cancelled call that really did consume tokens would be
|
||||
// frozen as a permanently token-less, cost-less record.
|
||||
let settled = false;
|
||||
let provisionallyRecorded = false;
|
||||
let accountingSettlement = Promise.resolve();
|
||||
let abortListener: (() => void) | undefined;
|
||||
const finalize = async (
|
||||
status: ProviderRequestAttemptStatus,
|
||||
finish?: { reason?: string; usage?: ProviderRequestUsageLike; error?: unknown },
|
||||
): Promise<void> => {
|
||||
if (settled) return;
|
||||
const provisional = status === 'aborted' && finish?.usage === undefined;
|
||||
if (provisional && provisionallyRecorded) return;
|
||||
if (provisional) provisionallyRecorded = true;
|
||||
else settled = true;
|
||||
if (abortListener && !provisional) {
|
||||
input.abortSignal?.removeEventListener('abort', abortListener);
|
||||
}
|
||||
const completedAt = this.input.now();
|
||||
const usage = strictProviderRequestUsage(finish?.usage);
|
||||
const contextWindow = positiveInteger(this.input.contextWindow);
|
||||
const failure =
|
||||
finish?.error !== undefined ? providerFailureDiagnostic(finish.error) : undefined;
|
||||
const record: SettledProviderAttempt = {
|
||||
traceId: this.input.traceId,
|
||||
attemptId,
|
||||
turnId: this.input.turnId,
|
||||
step,
|
||||
attempt,
|
||||
...(capture.artifactId ? { captureArtifactId: capture.artifactId } : {}),
|
||||
providerId: input.providerId,
|
||||
modelId: input.modelId,
|
||||
...(contextWindow !== undefined ? { contextWindow } : {}),
|
||||
startedAt,
|
||||
completedAt,
|
||||
status,
|
||||
...(finish?.reason !== undefined ? { finishReason: finish.reason } : {}),
|
||||
...(failure !== undefined ? { failure } : {}),
|
||||
latencyMs: Math.max(0, completedAt - startedAt),
|
||||
...(timeToFirstTokenMs !== undefined ? { timeToFirstTokenMs } : {}),
|
||||
...(usage ?? {}),
|
||||
};
|
||||
accountingSettlement = accountingSettlement.then(async () => {
|
||||
await this.emitModelCallAttempt(record, {
|
||||
logicalCallId,
|
||||
usage,
|
||||
contextWindow,
|
||||
requestObservation: capture.material.observation,
|
||||
// Frozen when THIS request was prepared, so a checkpoint published
|
||||
// mid-flight by another turn cannot be sealed into a prompt built
|
||||
// before it existed.
|
||||
historyCompactBoundary: input.historyCompactBoundary,
|
||||
historyCompactRoute:
|
||||
input.historyCompactRoute ?? this.input.accounting?.historyCompactRoute,
|
||||
});
|
||||
});
|
||||
await accountingSettlement;
|
||||
};
|
||||
const observeOutput = () => {
|
||||
if (timeToFirstTokenMs === undefined) {
|
||||
timeToFirstTokenMs = Math.max(0, this.input.now() - startedAt);
|
||||
}
|
||||
};
|
||||
if (input.abortSignal) {
|
||||
abortListener = () => {
|
||||
void finalize('aborted');
|
||||
};
|
||||
if (input.abortSignal.aborted) void finalize('aborted');
|
||||
else
|
||||
input.abortSignal.addEventListener('abort', abortListener, {
|
||||
once: true,
|
||||
});
|
||||
}
|
||||
return { observeOutput, finalize };
|
||||
}
|
||||
|
||||
/**
|
||||
* Projects a settled attempt into the canonical accounting record.
|
||||
*
|
||||
* Never throws. This runs from the stream's `pull` handler, where a rejection
|
||||
* would reach `controller.error` and fail an otherwise-complete model
|
||||
* response. The dispatch-time gate is `assertAccountingReady`; a failure here
|
||||
* means the call happened and was billed but went unrecorded, which is
|
||||
* reported, not raised.
|
||||
*/
|
||||
private async emitModelCallAttempt(
|
||||
record: SettledProviderAttempt,
|
||||
context: {
|
||||
logicalCallId: string;
|
||||
usage: ProviderRequestUsage | undefined;
|
||||
contextWindow: number | undefined;
|
||||
requestObservation: PreparedRequestObservation;
|
||||
historyCompactBoundary: ContextDiagnosticsCompaction | undefined;
|
||||
historyCompactRoute: HistoryCompactRoute | undefined;
|
||||
},
|
||||
): Promise<void> {
|
||||
const accounting = this.input.accounting;
|
||||
if (!accounting) return;
|
||||
const runId = accounting.resolveRunId();
|
||||
if (runId === undefined) return;
|
||||
|
||||
const usage = context.usage;
|
||||
const usageBasis = resolveUsageBasis(usage);
|
||||
const cost = usage ? accounting.resolveCost?.(usage) : undefined;
|
||||
const priced = cost?.costUsd !== undefined;
|
||||
|
||||
const attempt: ModelCallAttempt = {
|
||||
schemaVersion: MODEL_CALL_ATTEMPT_SCHEMA_VERSION,
|
||||
logicalCallId: context.logicalCallId,
|
||||
attemptId: record.attemptId,
|
||||
traceId: record.traceId,
|
||||
sessionId: accounting.sessionId,
|
||||
runId,
|
||||
turnId: record.turnId,
|
||||
...(accounting.connectionSlug !== undefined
|
||||
? { connectionSlug: accounting.connectionSlug }
|
||||
: {}),
|
||||
// The physical ordinals are one-based on the diagnostic record; the
|
||||
// canonical record counts retries from zero.
|
||||
step: Math.max(0, record.step),
|
||||
attempt: Math.max(0, record.attempt - 1),
|
||||
callKind: accounting.callKind,
|
||||
...(context.historyCompactRoute !== undefined
|
||||
? { historyCompactRoute: context.historyCompactRoute }
|
||||
: {}),
|
||||
providerId: accounting.providerId ?? record.providerId,
|
||||
modelId: record.modelId,
|
||||
...(context.contextWindow !== undefined ? { contextWindow: context.contextWindow } : {}),
|
||||
...(record.captureArtifactId !== undefined
|
||||
? { captureArtifactId: record.captureArtifactId }
|
||||
: {}),
|
||||
requestObservation: context.requestObservation,
|
||||
startedAt: record.startedAt,
|
||||
completedAt: record.completedAt,
|
||||
latencyMs: record.latencyMs,
|
||||
...(record.timeToFirstTokenMs !== undefined
|
||||
? { timeToFirstTokenMs: record.timeToFirstTokenMs }
|
||||
: {}),
|
||||
status: record.status,
|
||||
...(record.finishReason !== undefined ? { finishReason: record.finishReason } : {}),
|
||||
...(record.failure ?? {}),
|
||||
usageBasis,
|
||||
...(usageBasis === 'missing' ? {} : modelCallUsageFields(usage)),
|
||||
costBasis: priced ? 'priced' : 'unpriced',
|
||||
...(priced
|
||||
? {
|
||||
costUsd: cost?.costUsd,
|
||||
...(cost?.pricingRevision !== undefined
|
||||
? { pricingRevision: cost.pricingRevision }
|
||||
: {}),
|
||||
...(cost?.pricingRates !== undefined ? { pricingRates: cost.pricingRates } : {}),
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
|
||||
// Only a completed MAIN call describes the conversation's own context, so
|
||||
// only that one carries the derived row. A failed, aborted or compaction
|
||||
// call commits its metering alone and leaves the last answer standing.
|
||||
const latestContext =
|
||||
attempt.callKind === 'main' && attempt.status === 'completed'
|
||||
? latestContextProjectionInput(
|
||||
attempt,
|
||||
attempt.requestObservation?.segments,
|
||||
context.historyCompactBoundary,
|
||||
)
|
||||
: undefined;
|
||||
|
||||
try {
|
||||
await accounting.record({ attempt, ...(latestContext ? { latestContext } : {}) });
|
||||
} catch {
|
||||
// Reported through the run's accounting-incomplete signal by the sink
|
||||
// itself. Settlement must not fail the turn the call already completed.
|
||||
}
|
||||
}
|
||||
|
||||
private capture(
|
||||
step: number,
|
||||
input: TrackProviderStreamInput | TrackProviderGenerateInput,
|
||||
): StoredCapture {
|
||||
const material = preparedRequestMaterial(input.params);
|
||||
const key = `${step}:${input.providerId}:${input.modelId}:${material.observation.digest}`;
|
||||
const existing = this.captures.get(key);
|
||||
if (existing) return existing;
|
||||
|
||||
const persistArtifact = this.input.persistArtifact;
|
||||
const capture: StoredCapture = { material };
|
||||
this.captures.set(key, capture);
|
||||
if (persistArtifact) {
|
||||
const artifactInput: PreparedRequestArtifactInput = {
|
||||
...material,
|
||||
traceId: this.input.traceId,
|
||||
captureId: this.input.newId(),
|
||||
turnId: this.input.turnId,
|
||||
step,
|
||||
providerId: input.providerId,
|
||||
modelId: input.modelId,
|
||||
};
|
||||
// Persist the private body in parallel. Dispatch and canonical accounting
|
||||
// are both allowed to finish without it; the bounded observation already
|
||||
// lives on the canonical attempt. If persistence wins the race, the
|
||||
// attempt also carries the optional artifact join.
|
||||
try {
|
||||
void persistArtifact(artifactInput)
|
||||
.then((ref) => {
|
||||
capture.artifactId = ref.artifactId;
|
||||
})
|
||||
.catch(() => undefined);
|
||||
} catch {
|
||||
// A synchronous adapter failure is the same optional-artifact miss.
|
||||
}
|
||||
}
|
||||
return capture;
|
||||
}
|
||||
}
|
||||
|
||||
function throwIfAbortedBeforeDispatch(signal: AbortSignal | undefined): void {
|
||||
if (signal?.aborted) {
|
||||
throw new DOMException('The provider request was cancelled before dispatch', 'AbortError');
|
||||
}
|
||||
}
|
||||
|
||||
function preparedRequestMaterial(params: Record<string, unknown>): PreparedRequestMaterial {
|
||||
const safeParams = secretFreeParams(params);
|
||||
return prepareRequestObservation(safeParams);
|
||||
}
|
||||
|
||||
function secretFreeParams(params: Record<string, unknown>): Record<string, unknown> {
|
||||
const { abortSignal: _abortSignal, headers: _headers, ...safe } = params;
|
||||
if (!Array.isArray(safe.prompt)) return safe;
|
||||
return { ...safe, prompt: safe.prompt.map(redactPromptCompactionState) };
|
||||
}
|
||||
|
||||
function redactPromptCompactionState(value: unknown): unknown {
|
||||
if (!isPlainRecord(value) || !Array.isArray(value.content)) return value;
|
||||
return { ...value, content: value.content.map(redactCompactionContentPart) };
|
||||
}
|
||||
|
||||
function redactCompactionContentPart(value: unknown): unknown {
|
||||
if (!isPlainRecord(value) || value.type !== 'custom' || value.kind !== 'openai.compaction') {
|
||||
return value;
|
||||
}
|
||||
const providerOptions = isPlainRecord(value.providerOptions) ? value.providerOptions : undefined;
|
||||
const openai = isPlainRecord(providerOptions?.openai) ? providerOptions.openai : undefined;
|
||||
const { itemId: _itemId, encryptedContent: _encryptedContent, ...safeOpenai } = openai ?? {};
|
||||
return {
|
||||
...value,
|
||||
providerOptions: {
|
||||
...(providerOptions ?? {}),
|
||||
openai: { ...safeOpenai, redacted: true },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function isPlainRecord(value: unknown): value is Record<string, unknown> {
|
||||
if (!value || typeof value !== 'object') return false;
|
||||
const prototype = Object.getPrototypeOf(value);
|
||||
return prototype === Object.prototype || prototype === null;
|
||||
}
|
||||
|
||||
function abortStatus(signal: AbortSignal | undefined, error: unknown): 'failed' | 'aborted' {
|
||||
if (signal?.aborted) return 'aborted';
|
||||
return error instanceof Error && error.name === 'AbortError' ? 'aborted' : 'failed';
|
||||
}
|
||||
|
||||
function isOutputPart(type: unknown): boolean {
|
||||
return (
|
||||
typeof type === 'string' &&
|
||||
![
|
||||
'stream-start',
|
||||
'response-metadata',
|
||||
'raw',
|
||||
'finish',
|
||||
'error',
|
||||
'text-start',
|
||||
'text-end',
|
||||
'reasoning-start',
|
||||
'reasoning-end',
|
||||
'tool-input-start',
|
||||
'tool-input-end',
|
||||
].includes(type)
|
||||
);
|
||||
}
|
||||
|
||||
function asUsage(value: unknown): ProviderRequestUsageLike | undefined {
|
||||
return asRecord(value) as ProviderRequestUsageLike | undefined;
|
||||
}
|
||||
|
||||
function asRecord(value: unknown): Record<string, unknown> | undefined {
|
||||
return value !== null && typeof value === 'object'
|
||||
? (value as Record<string, unknown>)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract provider-request usage without inheriting adapter-filled zeroes.
|
||||
* Cache evidence is read from the raw provider payload; only cache miss may be
|
||||
* derived, and only when the total plus every cache component needed for the
|
||||
* subtraction was explicitly reported.
|
||||
*/
|
||||
export function strictProviderRequestUsage(
|
||||
usage: ProviderRequestUsageLike | undefined,
|
||||
): ProviderRequestUsage | undefined {
|
||||
if (!usage) return undefined;
|
||||
const raw = usage.raw;
|
||||
const normalizedInputTokens = tokenTotal(usage.inputTokens);
|
||||
const normalizedOutputTokens = tokenTotal(usage.outputTokens);
|
||||
const inputTokens = canUseNormalizedTotal(raw, [
|
||||
'prompt_tokens',
|
||||
'input_tokens',
|
||||
'promptTokenCount',
|
||||
])
|
||||
? normalizedInputTokens
|
||||
: undefined;
|
||||
const outputTokens = canUseNormalizedTotal(raw, [
|
||||
'completion_tokens',
|
||||
'output_tokens',
|
||||
'candidatesTokenCount',
|
||||
])
|
||||
? normalizedOutputTokens
|
||||
: undefined;
|
||||
const result: ProviderRequestUsage = {
|
||||
...(inputTokens !== undefined ? { inputTokens } : {}),
|
||||
...(outputTokens !== undefined ? { outputTokens } : {}),
|
||||
};
|
||||
|
||||
if (raw) {
|
||||
const normalizedCacheMiss =
|
||||
typeof usage.inputTokens === 'object' && usage.inputTokens !== null
|
||||
? finiteToken(usage.inputTokens.noCache)
|
||||
: undefined;
|
||||
applyAnthropicCacheUsage(result, raw, normalizedCacheMiss);
|
||||
applyOpenAiCacheUsage(result, raw);
|
||||
applyGoogleCacheUsage(result, raw);
|
||||
const reasoningTokens = firstToken(
|
||||
nestedToken(raw, 'completion_tokens_details', 'reasoning_tokens'),
|
||||
nestedToken(raw, 'output_tokens_details', 'reasoning_tokens'),
|
||||
ownToken(raw, 'thoughtsTokenCount'),
|
||||
);
|
||||
if (reasoningTokens !== undefined) result.reasoningTokens = reasoningTokens;
|
||||
}
|
||||
|
||||
return Object.keys(result).length > 0 ? result : undefined;
|
||||
}
|
||||
|
||||
function applyGoogleCacheUsage(result: ProviderRequestUsage, raw: Record<string, unknown>): void {
|
||||
const totalInput = ownToken(raw, 'promptTokenCount');
|
||||
const cacheRead = ownToken(raw, 'cachedContentTokenCount');
|
||||
if (cacheRead === undefined) return;
|
||||
result.cacheReadInputTokens = cacheRead;
|
||||
result.cacheReadInputSource = 'provider';
|
||||
if (totalInput === undefined || cacheRead > totalInput) return;
|
||||
result.cacheMissInputTokens = totalInput - cacheRead;
|
||||
result.cacheMissInputSource = 'derived';
|
||||
}
|
||||
|
||||
function applyAnthropicCacheUsage(
|
||||
result: ProviderRequestUsage,
|
||||
raw: Record<string, unknown>,
|
||||
normalizedCacheMiss: number | undefined,
|
||||
): void {
|
||||
const cacheRead = ownToken(raw, 'cache_read_input_tokens');
|
||||
const cacheWrite = ownToken(raw, 'cache_creation_input_tokens');
|
||||
if (cacheRead !== undefined) {
|
||||
result.cacheReadInputTokens = cacheRead;
|
||||
result.cacheReadInputSource = 'provider';
|
||||
}
|
||||
if (cacheWrite !== undefined) {
|
||||
result.cacheWriteInputTokens = cacheWrite;
|
||||
result.cacheWriteInputSource = 'provider';
|
||||
}
|
||||
// Anthropic defines input_tokens as the non-cached input component. Treat it
|
||||
// as cache-miss evidence only when this is recognizably an Anthropic cache
|
||||
// usage object, rather than an OpenAI Responses usage object with the same
|
||||
// top-level input_tokens spelling.
|
||||
if (cacheRead !== undefined || cacheWrite !== undefined) {
|
||||
const rawCacheMiss = ownToken(raw, 'input_tokens');
|
||||
if (rawCacheMiss !== undefined) {
|
||||
result.cacheMissInputTokens = normalizedCacheMiss ?? rawCacheMiss;
|
||||
result.cacheMissInputSource = 'provider';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function applyOpenAiCacheUsage(result: ProviderRequestUsage, raw: Record<string, unknown>): void {
|
||||
const promptInput = ownToken(raw, 'prompt_tokens');
|
||||
const responsesInput = ownToken(raw, 'input_tokens');
|
||||
const cacheRead = firstToken(
|
||||
nestedToken(raw, 'prompt_tokens_details', 'cached_tokens'),
|
||||
nestedToken(raw, 'input_tokens_details', 'cached_tokens'),
|
||||
);
|
||||
const cacheWrite = firstToken(
|
||||
nestedToken(raw, 'prompt_tokens_details', 'cache_write_tokens'),
|
||||
nestedToken(raw, 'input_tokens_details', 'cache_write_tokens'),
|
||||
);
|
||||
if (cacheRead === undefined && cacheWrite === undefined) return;
|
||||
|
||||
if (cacheRead !== undefined) {
|
||||
result.cacheReadInputTokens = cacheRead;
|
||||
result.cacheReadInputSource = 'provider';
|
||||
}
|
||||
if (cacheWrite !== undefined) {
|
||||
result.cacheWriteInputTokens = cacheWrite;
|
||||
result.cacheWriteInputSource = 'provider';
|
||||
}
|
||||
const totalInput = promptInput ?? responsesInput;
|
||||
if (totalInput === undefined || cacheRead === undefined) return;
|
||||
const accountedInput = cacheRead + (cacheWrite ?? 0);
|
||||
if (accountedInput > totalInput) return;
|
||||
result.cacheMissInputTokens = totalInput - accountedInput;
|
||||
result.cacheMissInputSource = 'derived';
|
||||
}
|
||||
|
||||
function canUseNormalizedTotal(
|
||||
raw: Record<string, unknown> | undefined,
|
||||
keys: readonly string[],
|
||||
): boolean {
|
||||
return raw === undefined || keys.some((key) => ownToken(raw, key) !== undefined);
|
||||
}
|
||||
|
||||
function tokenTotal(
|
||||
value: ProviderRequestUsageLike['inputTokens'] | ProviderRequestUsageLike['outputTokens'],
|
||||
): number | undefined {
|
||||
return finiteToken(typeof value === 'object' && value !== null ? value.total : value);
|
||||
}
|
||||
|
||||
function ownToken(value: Record<string, unknown>, key: string): number | undefined {
|
||||
return Object.hasOwn(value, key) ? finiteToken(value[key]) : undefined;
|
||||
}
|
||||
|
||||
function nestedToken(
|
||||
value: Record<string, unknown>,
|
||||
key: string,
|
||||
nestedKey: string,
|
||||
): number | undefined {
|
||||
if (!Object.hasOwn(value, key)) return undefined;
|
||||
const nested = value[key];
|
||||
if (!nested || typeof nested !== 'object' || !Object.hasOwn(nested, nestedKey)) return undefined;
|
||||
return finiteToken((nested as Record<string, unknown>)[nestedKey]);
|
||||
}
|
||||
|
||||
function firstToken(...values: Array<number | undefined>): number | undefined {
|
||||
return values.find((value) => value !== undefined);
|
||||
}
|
||||
|
||||
function finiteToken(value: unknown): number | undefined {
|
||||
return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : undefined;
|
||||
}
|
||||
|
||||
function positiveInteger(value: unknown): number | undefined {
|
||||
return typeof value === 'number' && Number.isInteger(value) && value > 0 ? value : undefined;
|
||||
}
|
||||
Reference in New Issue
Block a user