@compaction/cli 0.1.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/LICENSE +663 -0
- package/README.md +319 -0
- package/dist/cli/commands/adapter.d.ts +2 -0
- package/dist/cli/commands/adapter.js +49 -0
- package/dist/cli/commands/aggregate.d.ts +2 -0
- package/dist/cli/commands/aggregate.js +60 -0
- package/dist/cli/commands/analyze.d.ts +2 -0
- package/dist/cli/commands/analyze.js +55 -0
- package/dist/cli/commands/apply-context.d.ts +12 -0
- package/dist/cli/commands/apply-context.js +80 -0
- package/dist/cli/commands/apply.d.ts +2 -0
- package/dist/cli/commands/apply.js +65 -0
- package/dist/cli/commands/approve.d.ts +2 -0
- package/dist/cli/commands/approve.js +55 -0
- package/dist/cli/commands/audit.d.ts +2 -0
- package/dist/cli/commands/audit.js +63 -0
- package/dist/cli/commands/billing-delta.d.ts +2 -0
- package/dist/cli/commands/billing-delta.js +246 -0
- package/dist/cli/commands/capture-claude-code.d.ts +19 -0
- package/dist/cli/commands/capture-claude-code.js +198 -0
- package/dist/cli/commands/capture-provider-usage.d.ts +35 -0
- package/dist/cli/commands/capture-provider-usage.js +113 -0
- package/dist/cli/commands/capture.d.ts +2 -0
- package/dist/cli/commands/capture.js +76 -0
- package/dist/cli/commands/compact.d.ts +2 -0
- package/dist/cli/commands/compact.js +206 -0
- package/dist/cli/commands/eval.d.ts +2 -0
- package/dist/cli/commands/eval.js +66 -0
- package/dist/cli/commands/export-session-seed.d.ts +11 -0
- package/dist/cli/commands/export-session-seed.js +42 -0
- package/dist/cli/commands/feedback.d.ts +2 -0
- package/dist/cli/commands/feedback.js +233 -0
- package/dist/cli/commands/import.d.ts +2 -0
- package/dist/cli/commands/import.js +60 -0
- package/dist/cli/commands/init.d.ts +2 -0
- package/dist/cli/commands/init.js +216 -0
- package/dist/cli/commands/integrations.d.ts +2 -0
- package/dist/cli/commands/integrations.js +29 -0
- package/dist/cli/commands/launch-treatment-session.d.ts +14 -0
- package/dist/cli/commands/launch-treatment-session.js +50 -0
- package/dist/cli/commands/optimize.d.ts +2 -0
- package/dist/cli/commands/optimize.js +33 -0
- package/dist/cli/commands/recommend.d.ts +4 -0
- package/dist/cli/commands/recommend.js +66 -0
- package/dist/cli/commands/review.d.ts +2 -0
- package/dist/cli/commands/review.js +60 -0
- package/dist/cli/commands/run.d.ts +2 -0
- package/dist/cli/commands/run.js +49 -0
- package/dist/cli/commands/spend.d.ts +2 -0
- package/dist/cli/commands/spend.js +24 -0
- package/dist/cli/commands/summary.d.ts +2 -0
- package/dist/cli/commands/summary.js +19 -0
- package/dist/cli/engine-degrade.d.ts +40 -0
- package/dist/cli/engine-degrade.js +59 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +59 -0
- package/dist/core/adapter-validation.d.ts +32 -0
- package/dist/core/adapter-validation.js +185 -0
- package/dist/core/adapters/claude-code-adapter.d.ts +51 -0
- package/dist/core/adapters/claude-code-adapter.js +539 -0
- package/dist/core/adapters/claude-code-discovery.d.ts +40 -0
- package/dist/core/adapters/claude-code-discovery.js +100 -0
- package/dist/core/aggregate-format.d.ts +62 -0
- package/dist/core/aggregate-format.js +349 -0
- package/dist/core/api-client/client.d.ts +43 -0
- package/dist/core/api-client/client.js +143 -0
- package/dist/core/api-client/config.d.ts +36 -0
- package/dist/core/api-client/config.js +44 -0
- package/dist/core/api-client/index.d.ts +13 -0
- package/dist/core/api-client/index.js +14 -0
- package/dist/core/api-client/payload.d.ts +93 -0
- package/dist/core/api-client/payload.js +180 -0
- package/dist/core/api-client/types.d.ts +95 -0
- package/dist/core/api-client/types.js +21 -0
- package/dist/core/artifact-writer.d.ts +2 -0
- package/dist/core/artifact-writer.js +14 -0
- package/dist/core/audit-report.d.ts +91 -0
- package/dist/core/audit-report.js +422 -0
- package/dist/core/billing-delta/billing-delta-record.d.ts +290 -0
- package/dist/core/billing-delta/billing-delta-record.js +394 -0
- package/dist/core/capture-adapter.d.ts +74 -0
- package/dist/core/capture-adapter.js +2 -0
- package/dist/core/command-runner.d.ts +29 -0
- package/dist/core/command-runner.js +96 -0
- package/dist/core/content-hash.d.ts +1 -0
- package/dist/core/content-hash.js +5 -0
- package/dist/core/cost-calculator.d.ts +6 -0
- package/dist/core/cost-calculator.js +20 -0
- package/dist/core/feedback-bundle.d.ts +159 -0
- package/dist/core/feedback-bundle.js +406 -0
- package/dist/core/openai-agents-capture.d.ts +66 -0
- package/dist/core/openai-agents-capture.js +475 -0
- package/dist/core/policy-types.d.ts +85 -0
- package/dist/core/policy-types.js +29 -0
- package/dist/core/pricing.d.ts +23 -0
- package/dist/core/pricing.js +55 -0
- package/dist/core/provider-usage/credential-redaction.d.ts +23 -0
- package/dist/core/provider-usage/credential-redaction.js +60 -0
- package/dist/core/provider-usage/provider-usage-adapter.d.ts +65 -0
- package/dist/core/provider-usage/provider-usage-adapter.js +147 -0
- package/dist/core/provider-usage/provider-usage-client.d.ts +89 -0
- package/dist/core/provider-usage/provider-usage-client.js +96 -0
- package/dist/core/report-generator.d.ts +7 -0
- package/dist/core/report-generator.js +275 -0
- package/dist/core/run-aggregator.d.ts +50 -0
- package/dist/core/run-aggregator.js +251 -0
- package/dist/core/run-labels.d.ts +52 -0
- package/dist/core/run-labels.js +80 -0
- package/dist/core/run-trace-converter.d.ts +3 -0
- package/dist/core/run-trace-converter.js +94 -0
- package/dist/core/safety-report.d.ts +52 -0
- package/dist/core/safety-report.js +440 -0
- package/dist/core/savings.d.ts +22 -0
- package/dist/core/savings.js +36 -0
- package/dist/core/session-aggregate.d.ts +211 -0
- package/dist/core/session-aggregate.js +490 -0
- package/dist/core/skill-injection-detector.d.ts +51 -0
- package/dist/core/skill-injection-detector.js +146 -0
- package/dist/core/spend-attribution.d.ts +169 -0
- package/dist/core/spend-attribution.js +611 -0
- package/dist/core/standalone-capsule-provenance.d.ts +4 -0
- package/dist/core/standalone-capsule-provenance.js +62 -0
- package/dist/core/state-capsule.d.ts +3 -0
- package/dist/core/state-capsule.js +147 -0
- package/dist/core/token-accounting.d.ts +62 -0
- package/dist/core/token-accounting.js +81 -0
- package/dist/core/token-estimator.d.ts +3 -0
- package/dist/core/token-estimator.js +18 -0
- package/dist/core/trace-adapters.d.ts +53 -0
- package/dist/core/trace-adapters.js +714 -0
- package/dist/core/trace-fingerprint.d.ts +79 -0
- package/dist/core/trace-fingerprint.js +102 -0
- package/dist/core/trace-intake.d.ts +41 -0
- package/dist/core/trace-intake.js +164 -0
- package/dist/core/trace-parser.d.ts +203 -0
- package/dist/core/trace-parser.js +143 -0
- package/dist/core/types.d.ts +238 -0
- package/dist/core/types.js +2 -0
- package/dist/core/usage-metadata.d.ts +51 -0
- package/dist/core/usage-metadata.js +122 -0
- package/dist/core/waste-detector.d.ts +8 -0
- package/dist/core/waste-detector.js +58 -0
- package/package.json +90 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential-shaped redaction for the provider-usage capture path.
|
|
3
|
+
*
|
|
4
|
+
* Per docs/design/provider-api-secrets-handling.md §4: a single redaction pass
|
|
5
|
+
* applies to EVERY output path (logs, errors, artifacts, reports, debug dumps).
|
|
6
|
+
*
|
|
7
|
+
* The PRIMARY control is that the credential is never placed into any structure
|
|
8
|
+
* that gets logged or serialized (see provider-usage-adapter.ts). This redaction
|
|
9
|
+
* pass is the defense-in-depth backstop: it masks the configured secret value (if
|
|
10
|
+
* present) AND anything matching known credential shapes, before any string is
|
|
11
|
+
* written to stdout/stderr/log/error/artifact.
|
|
12
|
+
*
|
|
13
|
+
* NO real secret ever appears here — tests exercise this with fake sentinels only.
|
|
14
|
+
*/
|
|
15
|
+
export declare const REDACTION_MASK = "***REDACTED***";
|
|
16
|
+
/**
|
|
17
|
+
* Redact a string for safe output. If `configuredSecret` is provided and present
|
|
18
|
+
* in the text, every occurrence is masked first (exact match — provider-agnostic),
|
|
19
|
+
* then known credential shapes are masked as a backstop.
|
|
20
|
+
*/
|
|
21
|
+
export declare function redactSecrets(text: string, configuredSecret?: string): string;
|
|
22
|
+
/** Redact then write to stderr. Use for ALL error/diagnostic output on this path. */
|
|
23
|
+
export declare function redactedError(message: string, configuredSecret?: string): string;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential-shaped redaction for the provider-usage capture path.
|
|
3
|
+
*
|
|
4
|
+
* Per docs/design/provider-api-secrets-handling.md §4: a single redaction pass
|
|
5
|
+
* applies to EVERY output path (logs, errors, artifacts, reports, debug dumps).
|
|
6
|
+
*
|
|
7
|
+
* The PRIMARY control is that the credential is never placed into any structure
|
|
8
|
+
* that gets logged or serialized (see provider-usage-adapter.ts). This redaction
|
|
9
|
+
* pass is the defense-in-depth backstop: it masks the configured secret value (if
|
|
10
|
+
* present) AND anything matching known credential shapes, before any string is
|
|
11
|
+
* written to stdout/stderr/log/error/artifact.
|
|
12
|
+
*
|
|
13
|
+
* NO real secret ever appears here — tests exercise this with fake sentinels only.
|
|
14
|
+
*/
|
|
15
|
+
export const REDACTION_MASK = "***REDACTED***";
|
|
16
|
+
/**
|
|
17
|
+
* Known credential-shaped patterns (defense in depth). These are deliberately
|
|
18
|
+
* conservative shape matchers, NOT a DLP/ML classifier:
|
|
19
|
+
* - `Authorization: Bearer <token>` header values
|
|
20
|
+
* - `x-api-key: <token>` header values
|
|
21
|
+
* - common provider key prefixes followed by a long token body
|
|
22
|
+
* - long high-entropy bearer-style tokens
|
|
23
|
+
*/
|
|
24
|
+
const KNOWN_CREDENTIAL_SHAPE_PATTERNS = [
|
|
25
|
+
// Authorization: Bearer <token> (mask the token, keep the header name)
|
|
26
|
+
/(authorization\s*:\s*bearer\s+)[A-Za-z0-9._\-+/=]{8,}/gi,
|
|
27
|
+
// x-api-key: <token> (mask the value, keep the header name)
|
|
28
|
+
/(x-api-key\s*:\s*)[A-Za-z0-9._\-+/=]{8,}/gi,
|
|
29
|
+
// Common provider key prefixes (sk-, sk-ant-, sk-proj-, anthropic-, etc.) + body
|
|
30
|
+
/\b(?:sk-(?:ant-|proj-)?|api[-_]?key[-_]|anthropic-|openai-)[A-Za-z0-9._\-]{12,}\b/gi,
|
|
31
|
+
// Long high-entropy token bodies (>= 32 chars of base64url-ish content)
|
|
32
|
+
/\b[A-Za-z0-9._\-+/=]{40,}\b/g
|
|
33
|
+
];
|
|
34
|
+
/**
|
|
35
|
+
* Redact a string for safe output. If `configuredSecret` is provided and present
|
|
36
|
+
* in the text, every occurrence is masked first (exact match — provider-agnostic),
|
|
37
|
+
* then known credential shapes are masked as a backstop.
|
|
38
|
+
*/
|
|
39
|
+
export function redactSecrets(text, configuredSecret) {
|
|
40
|
+
let redacted = text;
|
|
41
|
+
if (configuredSecret && configuredSecret.length > 0) {
|
|
42
|
+
// Exact-match mask of the known secret value (provider-agnostic, highest-precision).
|
|
43
|
+
redacted = redacted.split(configuredSecret).join(REDACTION_MASK);
|
|
44
|
+
}
|
|
45
|
+
for (const pattern of KNOWN_CREDENTIAL_SHAPE_PATTERNS) {
|
|
46
|
+
redacted = redacted.replace(pattern, (match, prefix) => {
|
|
47
|
+
// Header patterns capture a non-secret prefix we preserve; keep it, mask the value.
|
|
48
|
+
if (typeof prefix === "string" && prefix.length > 0) {
|
|
49
|
+
return `${prefix}${REDACTION_MASK}`;
|
|
50
|
+
}
|
|
51
|
+
return REDACTION_MASK;
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return redacted;
|
|
55
|
+
}
|
|
56
|
+
/** Redact then write to stderr. Use for ALL error/diagnostic output on this path. */
|
|
57
|
+
export function redactedError(message, configuredSecret) {
|
|
58
|
+
return redactSecrets(message, configuredSecret);
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=credential-redaction.js.map
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider-usage capture adapter (Option A, read-only, aggregate-only).
|
|
3
|
+
*
|
|
4
|
+
* Normalizes a read-only provider usage/cost API read into a CapturedRun carrying
|
|
5
|
+
* usage/cost UsageMetadata ONLY (source = "provider_usage"), with NO message
|
|
6
|
+
* content. See:
|
|
7
|
+
* - docs/design/provider-api-capture.md (Option A)
|
|
8
|
+
* - docs/design/provider-api-secrets-handling.md (credential: env-only, in-memory,
|
|
9
|
+
* never stored/logged/serialized)
|
|
10
|
+
* - docs/design/provider-api-privacy-redaction.md (aggregate-only, no content)
|
|
11
|
+
* - docs/design/provider-api-tracesource-classification.md (Option 2: provider_usage
|
|
12
|
+
* -> provider_reported_usage; cost/spend evidence, caps approval-readiness at
|
|
13
|
+
* "conditional", never "ready")
|
|
14
|
+
*
|
|
15
|
+
* HARD INVARIANTS enforced here:
|
|
16
|
+
* - The credential is read from an env var, held in a local variable, passed ONLY
|
|
17
|
+
* to the client's auth header, and NEVER copied into the trace/usage/provenance
|
|
18
|
+
* or any returned/serialized structure.
|
|
19
|
+
* - Missing/empty credential => clean refusal (clear message naming the env var),
|
|
20
|
+
* no artifact, no network read. Never a silent fallback.
|
|
21
|
+
* - The produced trace has NO messages (a usage-only read has nothing to compact)
|
|
22
|
+
* and NO content fields. UsageMetadata carries aggregate usage/cost only.
|
|
23
|
+
* - cost_source: "provider_reported" ONLY when the endpoint genuinely exposed cost;
|
|
24
|
+
* otherwise price-table estimate / missing.
|
|
25
|
+
*/
|
|
26
|
+
import type { CapturedRun } from "../capture-adapter.js";
|
|
27
|
+
import { type ProviderUsageClient } from "./provider-usage-client.js";
|
|
28
|
+
export declare const PROVIDER_USAGE_ADAPTER_ID = "provider-usage-read-v1";
|
|
29
|
+
/** Default env var name the credential is read from. A flag may only NAME the var. */
|
|
30
|
+
export declare const DEFAULT_CREDENTIAL_ENV_VAR = "COMPACTION_PROVIDER_USAGE_TOKEN";
|
|
31
|
+
export declare const PROVIDER_USAGE_PRIVACY_NOTE: string;
|
|
32
|
+
/** Thrown when the credential env var is absent/empty. Carries NO secret value. */
|
|
33
|
+
export declare class MissingProviderCredentialError extends Error {
|
|
34
|
+
readonly envVar: string;
|
|
35
|
+
constructor(envVar: string);
|
|
36
|
+
}
|
|
37
|
+
export interface ProviderUsageCaptureInput {
|
|
38
|
+
/** Configurable provider usage/cost endpoint (non-secret descriptor). */
|
|
39
|
+
endpoint: string;
|
|
40
|
+
/** Env var name to read the credential from (NAME only — never the value). */
|
|
41
|
+
credentialEnvVar?: string;
|
|
42
|
+
/** Optional reporting window start (ISO 8601). */
|
|
43
|
+
windowStart?: string;
|
|
44
|
+
/** Optional reporting window end (ISO 8601). */
|
|
45
|
+
windowEnd?: string;
|
|
46
|
+
/** Human-readable label for the run/window, if any. */
|
|
47
|
+
label?: string;
|
|
48
|
+
/** Injectable client. Tests pass a mock; defaults to the real fetch client. */
|
|
49
|
+
client?: ProviderUsageClient;
|
|
50
|
+
/** Override env reader (tests). Defaults to process.env. */
|
|
51
|
+
env?: Record<string, string | undefined>;
|
|
52
|
+
/** Fixed timestamp for deterministic artifacts/tests. */
|
|
53
|
+
capturedAt?: string;
|
|
54
|
+
/** Fixed run id for deterministic artifacts/tests. */
|
|
55
|
+
runId?: string;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Capture aggregate provider usage/cost into a CapturedRun.
|
|
59
|
+
*
|
|
60
|
+
* Reads the credential from the named env var (refusing cleanly if absent/empty),
|
|
61
|
+
* performs the single read via the injectable client, and normalizes the result
|
|
62
|
+
* into a usage-only CapturedRun. The credential never leaves this function except
|
|
63
|
+
* as the client's auth header value.
|
|
64
|
+
*/
|
|
65
|
+
export declare function captureProviderUsage(input: ProviderUsageCaptureInput): Promise<CapturedRun>;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider-usage capture adapter (Option A, read-only, aggregate-only).
|
|
3
|
+
*
|
|
4
|
+
* Normalizes a read-only provider usage/cost API read into a CapturedRun carrying
|
|
5
|
+
* usage/cost UsageMetadata ONLY (source = "provider_usage"), with NO message
|
|
6
|
+
* content. See:
|
|
7
|
+
* - docs/design/provider-api-capture.md (Option A)
|
|
8
|
+
* - docs/design/provider-api-secrets-handling.md (credential: env-only, in-memory,
|
|
9
|
+
* never stored/logged/serialized)
|
|
10
|
+
* - docs/design/provider-api-privacy-redaction.md (aggregate-only, no content)
|
|
11
|
+
* - docs/design/provider-api-tracesource-classification.md (Option 2: provider_usage
|
|
12
|
+
* -> provider_reported_usage; cost/spend evidence, caps approval-readiness at
|
|
13
|
+
* "conditional", never "ready")
|
|
14
|
+
*
|
|
15
|
+
* HARD INVARIANTS enforced here:
|
|
16
|
+
* - The credential is read from an env var, held in a local variable, passed ONLY
|
|
17
|
+
* to the client's auth header, and NEVER copied into the trace/usage/provenance
|
|
18
|
+
* or any returned/serialized structure.
|
|
19
|
+
* - Missing/empty credential => clean refusal (clear message naming the env var),
|
|
20
|
+
* no artifact, no network read. Never a silent fallback.
|
|
21
|
+
* - The produced trace has NO messages (a usage-only read has nothing to compact)
|
|
22
|
+
* and NO content fields. UsageMetadata carries aggregate usage/cost only.
|
|
23
|
+
* - cost_source: "provider_reported" ONLY when the endpoint genuinely exposed cost;
|
|
24
|
+
* otherwise price-table estimate / missing.
|
|
25
|
+
*/
|
|
26
|
+
import { createUsageMetadata } from "../usage-metadata.js";
|
|
27
|
+
import { FetchProviderUsageClient } from "./provider-usage-client.js";
|
|
28
|
+
export const PROVIDER_USAGE_ADAPTER_ID = "provider-usage-read-v1";
|
|
29
|
+
/** Default env var name the credential is read from. A flag may only NAME the var. */
|
|
30
|
+
export const DEFAULT_CREDENTIAL_ENV_VAR = "COMPACTION_PROVIDER_USAGE_TOKEN";
|
|
31
|
+
export const PROVIDER_USAGE_PRIVACY_NOTE = "NOTE: This is a read-only, aggregate-only provider usage/cost read. It captures token/cost\n" +
|
|
32
|
+
"aggregates ONLY — NO prompt/completion content is read or stored. The captured artifact is\n" +
|
|
33
|
+
"written locally to the output directory only. The credential is read from an environment\n" +
|
|
34
|
+
"variable, used only for the single read-only request, and is never logged or written to any\n" +
|
|
35
|
+
"artifact.";
|
|
36
|
+
/** Thrown when the credential env var is absent/empty. Carries NO secret value. */
|
|
37
|
+
export class MissingProviderCredentialError extends Error {
|
|
38
|
+
envVar;
|
|
39
|
+
constructor(envVar) {
|
|
40
|
+
super(`No provider usage credential found. Set the ${envVar} environment variable ` +
|
|
41
|
+
`(a read-only, usage/cost-scoped token) and re-run. Refusing; no fallback, no artifact written.`);
|
|
42
|
+
this.name = "MissingProviderCredentialError";
|
|
43
|
+
this.envVar = envVar;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Build the UsageMetadata for an aggregate read. cost_source is "provider_reported"
|
|
48
|
+
* only when the endpoint genuinely exposed a cost; otherwise the existing estimate
|
|
49
|
+
* path (price-table when a known model is present, else missing) is used.
|
|
50
|
+
*/
|
|
51
|
+
function buildUsageMetadata(usage) {
|
|
52
|
+
const endpointExposedCost = typeof usage.cost === "number";
|
|
53
|
+
const limitations = [
|
|
54
|
+
"Aggregate usage/cost read from the provider usage/cost reporting API for account-level reporting; this is a usage/cost report, not a per-run invoice reconciliation.",
|
|
55
|
+
"Aggregate usage only — NO prompt/completion content was captured by this read.",
|
|
56
|
+
"Not billing-confirmed savings (Tier 3): this is the usage/cost substrate only, not a measured applied-optimization billing delta."
|
|
57
|
+
];
|
|
58
|
+
if (!endpointExposedCost) {
|
|
59
|
+
limitations.push("Cost not exposed by the endpoint; cost remains a price-table estimate or missing.");
|
|
60
|
+
}
|
|
61
|
+
return createUsageMetadata({
|
|
62
|
+
inputTokens: usage.input_tokens,
|
|
63
|
+
outputTokens: usage.output_tokens,
|
|
64
|
+
totalTokens: usage.total_tokens,
|
|
65
|
+
providerReportedTokens: true,
|
|
66
|
+
estimatedTokens: false,
|
|
67
|
+
providerReportedCost: endpointExposedCost,
|
|
68
|
+
currency: usage.currency,
|
|
69
|
+
model: usage.model,
|
|
70
|
+
provider: usage.provider,
|
|
71
|
+
limitations
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/** A usage-only read produces a trace with NO messages and NO content. */
|
|
75
|
+
function buildUsageOnlyTrace(usage, input, capturedAt, runId) {
|
|
76
|
+
return {
|
|
77
|
+
id: runId,
|
|
78
|
+
title: input.label ?? `Provider usage/cost read (${input.endpoint})`,
|
|
79
|
+
artifactVersion: "agent-trace-v1",
|
|
80
|
+
source: "provider_usage",
|
|
81
|
+
createdAt: capturedAt,
|
|
82
|
+
generatedAt: capturedAt,
|
|
83
|
+
model: usage.model ?? "unknown",
|
|
84
|
+
// Aggregate-only: NO messages. A usage-only trace has nothing to compact.
|
|
85
|
+
messages: []
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Build a non-secret provenance descriptor of the read. Records the endpoint and
|
|
90
|
+
* window and the explicit "aggregate usage only, no content" note. NEVER records
|
|
91
|
+
* the credential or any credential-derived value.
|
|
92
|
+
*/
|
|
93
|
+
function buildProvenance(input, usage, capturedAt) {
|
|
94
|
+
const windowStart = input.windowStart ?? usage.window_start ?? null;
|
|
95
|
+
const windowEnd = input.windowEnd ?? usage.window_end ?? null;
|
|
96
|
+
const windowDescriptor = windowStart || windowEnd ? `window ${windowStart ?? "unspecified"} .. ${windowEnd ?? "unspecified"}` : "window unspecified";
|
|
97
|
+
return {
|
|
98
|
+
captureAdapter: PROVIDER_USAGE_ADAPTER_ID,
|
|
99
|
+
// Non-secret descriptor ONLY: endpoint + window. Never the credential.
|
|
100
|
+
sourcePath: `${input.endpoint} (${windowDescriptor})`,
|
|
101
|
+
capturedAt,
|
|
102
|
+
sessionId: null,
|
|
103
|
+
warnings: [PROVIDER_USAGE_PRIVACY_NOTE],
|
|
104
|
+
limitations: [
|
|
105
|
+
"Read-only, aggregate-only provider usage/cost read (Option A).",
|
|
106
|
+
"NO prompt/completion content was captured by this read.",
|
|
107
|
+
"Credential was read from an environment variable, used only for the single read-only request, and is not recorded here.",
|
|
108
|
+
`Provider-reported usage; cost is ${typeof usage.cost === "number" ? "provider-reported" : "price-table estimated or missing"}.`,
|
|
109
|
+
"Billing-confirmed savings (Tier 3) are NOT claimed."
|
|
110
|
+
]
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Capture aggregate provider usage/cost into a CapturedRun.
|
|
115
|
+
*
|
|
116
|
+
* Reads the credential from the named env var (refusing cleanly if absent/empty),
|
|
117
|
+
* performs the single read via the injectable client, and normalizes the result
|
|
118
|
+
* into a usage-only CapturedRun. The credential never leaves this function except
|
|
119
|
+
* as the client's auth header value.
|
|
120
|
+
*/
|
|
121
|
+
export async function captureProviderUsage(input) {
|
|
122
|
+
const envVar = input.credentialEnvVar ?? DEFAULT_CREDENTIAL_ENV_VAR;
|
|
123
|
+
const env = input.env ?? process.env;
|
|
124
|
+
// Credential acquisition: env var ONLY. Clean refusal on absence — never a fallback.
|
|
125
|
+
const credential = env[envVar];
|
|
126
|
+
if (typeof credential !== "string" || credential.length === 0) {
|
|
127
|
+
throw new MissingProviderCredentialError(envVar);
|
|
128
|
+
}
|
|
129
|
+
const capturedAt = input.capturedAt ?? new Date().toISOString();
|
|
130
|
+
const runId = input.runId ?? `provider-usage-${Date.parse(capturedAt) || Date.now()}`;
|
|
131
|
+
const client = input.client ?? new FetchProviderUsageClient();
|
|
132
|
+
// The single read-only request. The credential is passed in-memory only and is
|
|
133
|
+
// NOT retained after this call (no copy into any returned structure below).
|
|
134
|
+
const usage = await client.fetchUsage({
|
|
135
|
+
endpoint: input.endpoint,
|
|
136
|
+
credential,
|
|
137
|
+
windowStart: input.windowStart,
|
|
138
|
+
windowEnd: input.windowEnd
|
|
139
|
+
});
|
|
140
|
+
const trace = buildUsageOnlyTrace(usage, input, capturedAt, runId);
|
|
141
|
+
const usageMetadata = buildUsageMetadata(usage);
|
|
142
|
+
const provenance = buildProvenance(input, usage, capturedAt);
|
|
143
|
+
// The returned CapturedRun carries usage/cost + non-secret descriptors ONLY.
|
|
144
|
+
// The credential is never part of trace, usage, or provenance.
|
|
145
|
+
return { trace, usage: usageMetadata, provenance };
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=provider-usage-adapter.js.map
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Injectable provider-usage read client (Option A, read-only, aggregate-only).
|
|
3
|
+
*
|
|
4
|
+
* Per docs/design/provider-api-capture.md and the secrets/privacy designs:
|
|
5
|
+
* - The provider HTTP read is abstracted behind this small interface so tests
|
|
6
|
+
* inject a MOCK returning a fixture aggregate-usage response (NO real network,
|
|
7
|
+
* NO real secret).
|
|
8
|
+
* - The real implementation uses Node's global `fetch` (built-in — NO new npm
|
|
9
|
+
* dependency) to a configurable endpoint with the credential in the auth header.
|
|
10
|
+
* - The credential is passed in-memory only and is NEVER stored, logged, or
|
|
11
|
+
* placed into any serialized structure (see provider-usage-adapter.ts).
|
|
12
|
+
*
|
|
13
|
+
* Aggregate-only invariant: the response carries usage/cost AGGREGATES ONLY.
|
|
14
|
+
* There is NO field for prompt/completion/message content anywhere in this contract.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Expected aggregate-usage input contract — the JSON shape the read expects from a
|
|
18
|
+
* provider usage/cost reporting endpoint. Parsed defensively (parseAggregateUsage).
|
|
19
|
+
*
|
|
20
|
+
* NOTE: the exact per-provider response schema mapping is only validated when an
|
|
21
|
+
* operator runs this against a real provider; here it is the documented contract
|
|
22
|
+
* the read normalizes into, exercised with mock fixtures only. There is NO content
|
|
23
|
+
* field in this contract by construction.
|
|
24
|
+
*/
|
|
25
|
+
export interface ProviderAggregateUsage {
|
|
26
|
+
/** Model identifier the aggregate is reported for (e.g. "claude-3-5-sonnet"). */
|
|
27
|
+
model?: string;
|
|
28
|
+
/** Provider identifier (e.g. "anthropic", "openai"). */
|
|
29
|
+
provider?: string;
|
|
30
|
+
/** Aggregate input token count for the window. */
|
|
31
|
+
input_tokens?: number;
|
|
32
|
+
/** Aggregate output token count for the window. */
|
|
33
|
+
output_tokens?: number;
|
|
34
|
+
/** Aggregate total token count for the window (derived if absent). */
|
|
35
|
+
total_tokens?: number;
|
|
36
|
+
/** Aggregate request count for the window, if the endpoint reports it. */
|
|
37
|
+
request_count?: number;
|
|
38
|
+
/**
|
|
39
|
+
* Aggregate cost for the window, ONLY when the endpoint genuinely exposes cost.
|
|
40
|
+
* When present, cost is labeled provider-reported; when absent, cost stays a
|
|
41
|
+
* price-table estimate. Never guessed.
|
|
42
|
+
*/
|
|
43
|
+
cost?: number;
|
|
44
|
+
/** Currency code for `cost` (e.g. "USD"), when cost is exposed. */
|
|
45
|
+
currency?: string;
|
|
46
|
+
/** Start of the reporting window (ISO 8601), if reported. */
|
|
47
|
+
window_start?: string;
|
|
48
|
+
/** End of the reporting window (ISO 8601), if reported. */
|
|
49
|
+
window_end?: string;
|
|
50
|
+
}
|
|
51
|
+
export interface FetchUsageInput {
|
|
52
|
+
/** Configurable provider usage/cost endpoint (non-secret). */
|
|
53
|
+
endpoint: string;
|
|
54
|
+
/**
|
|
55
|
+
* The read-only credential, held in memory only for the single request.
|
|
56
|
+
* Supplied as the auth header value; NEVER logged, stored, or serialized.
|
|
57
|
+
*/
|
|
58
|
+
credential: string;
|
|
59
|
+
/** Optional reporting window start (ISO 8601). */
|
|
60
|
+
windowStart?: string;
|
|
61
|
+
/** Optional reporting window end (ISO 8601). */
|
|
62
|
+
windowEnd?: string;
|
|
63
|
+
}
|
|
64
|
+
export interface ProviderUsageClient {
|
|
65
|
+
/**
|
|
66
|
+
* Read aggregate usage/cost from the provider's own usage/cost reporting API.
|
|
67
|
+
* Returns aggregate usage/cost ONLY — never prompt/completion content.
|
|
68
|
+
*/
|
|
69
|
+
fetchUsage(input: FetchUsageInput): Promise<ProviderAggregateUsage>;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Defensive parser for the aggregate-usage contract. Accepts an unknown JSON value
|
|
73
|
+
* (e.g. a provider response body) and extracts ONLY the aggregate usage/cost fields.
|
|
74
|
+
* Any content-shaped or unexpected fields are dropped — they never reach the trace.
|
|
75
|
+
*/
|
|
76
|
+
export declare function parseAggregateUsage(raw: unknown): ProviderAggregateUsage;
|
|
77
|
+
/**
|
|
78
|
+
* Real provider-usage client using Node's built-in global `fetch` (NO new npm
|
|
79
|
+
* dependency). Performs exactly ONE read-only GET to the configured endpoint with
|
|
80
|
+
* the credential in the auth header. The credential is used only for this request
|
|
81
|
+
* and is never logged or returned. The response is parsed defensively to aggregate
|
|
82
|
+
* usage/cost only.
|
|
83
|
+
*
|
|
84
|
+
* This live path is unit-tested with a MOCK client; it is NOT dogfooded against a
|
|
85
|
+
* real provider in this environment (no credential/endpoint available).
|
|
86
|
+
*/
|
|
87
|
+
export declare class FetchProviderUsageClient implements ProviderUsageClient {
|
|
88
|
+
fetchUsage(input: FetchUsageInput): Promise<ProviderAggregateUsage>;
|
|
89
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Injectable provider-usage read client (Option A, read-only, aggregate-only).
|
|
3
|
+
*
|
|
4
|
+
* Per docs/design/provider-api-capture.md and the secrets/privacy designs:
|
|
5
|
+
* - The provider HTTP read is abstracted behind this small interface so tests
|
|
6
|
+
* inject a MOCK returning a fixture aggregate-usage response (NO real network,
|
|
7
|
+
* NO real secret).
|
|
8
|
+
* - The real implementation uses Node's global `fetch` (built-in — NO new npm
|
|
9
|
+
* dependency) to a configurable endpoint with the credential in the auth header.
|
|
10
|
+
* - The credential is passed in-memory only and is NEVER stored, logged, or
|
|
11
|
+
* placed into any serialized structure (see provider-usage-adapter.ts).
|
|
12
|
+
*
|
|
13
|
+
* Aggregate-only invariant: the response carries usage/cost AGGREGATES ONLY.
|
|
14
|
+
* There is NO field for prompt/completion/message content anywhere in this contract.
|
|
15
|
+
*/
|
|
16
|
+
function coerceNonNegativeNumber(value) {
|
|
17
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : undefined;
|
|
18
|
+
}
|
|
19
|
+
function coerceString(value) {
|
|
20
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Defensive parser for the aggregate-usage contract. Accepts an unknown JSON value
|
|
24
|
+
* (e.g. a provider response body) and extracts ONLY the aggregate usage/cost fields.
|
|
25
|
+
* Any content-shaped or unexpected fields are dropped — they never reach the trace.
|
|
26
|
+
*/
|
|
27
|
+
export function parseAggregateUsage(raw) {
|
|
28
|
+
const obj = (raw && typeof raw === "object" ? raw : {});
|
|
29
|
+
const result = {};
|
|
30
|
+
const model = coerceString(obj.model);
|
|
31
|
+
if (model !== undefined)
|
|
32
|
+
result.model = model;
|
|
33
|
+
const provider = coerceString(obj.provider);
|
|
34
|
+
if (provider !== undefined)
|
|
35
|
+
result.provider = provider;
|
|
36
|
+
const inputTokens = coerceNonNegativeNumber(obj.input_tokens);
|
|
37
|
+
if (inputTokens !== undefined)
|
|
38
|
+
result.input_tokens = inputTokens;
|
|
39
|
+
const outputTokens = coerceNonNegativeNumber(obj.output_tokens);
|
|
40
|
+
if (outputTokens !== undefined)
|
|
41
|
+
result.output_tokens = outputTokens;
|
|
42
|
+
const totalTokens = coerceNonNegativeNumber(obj.total_tokens);
|
|
43
|
+
if (totalTokens !== undefined)
|
|
44
|
+
result.total_tokens = totalTokens;
|
|
45
|
+
const requestCount = coerceNonNegativeNumber(obj.request_count);
|
|
46
|
+
if (requestCount !== undefined)
|
|
47
|
+
result.request_count = requestCount;
|
|
48
|
+
const cost = coerceNonNegativeNumber(obj.cost);
|
|
49
|
+
if (cost !== undefined)
|
|
50
|
+
result.cost = cost;
|
|
51
|
+
const currency = coerceString(obj.currency);
|
|
52
|
+
if (currency !== undefined)
|
|
53
|
+
result.currency = currency;
|
|
54
|
+
const windowStart = coerceString(obj.window_start);
|
|
55
|
+
if (windowStart !== undefined)
|
|
56
|
+
result.window_start = windowStart;
|
|
57
|
+
const windowEnd = coerceString(obj.window_end);
|
|
58
|
+
if (windowEnd !== undefined)
|
|
59
|
+
result.window_end = windowEnd;
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Real provider-usage client using Node's built-in global `fetch` (NO new npm
|
|
64
|
+
* dependency). Performs exactly ONE read-only GET to the configured endpoint with
|
|
65
|
+
* the credential in the auth header. The credential is used only for this request
|
|
66
|
+
* and is never logged or returned. The response is parsed defensively to aggregate
|
|
67
|
+
* usage/cost only.
|
|
68
|
+
*
|
|
69
|
+
* This live path is unit-tested with a MOCK client; it is NOT dogfooded against a
|
|
70
|
+
* real provider in this environment (no credential/endpoint available).
|
|
71
|
+
*/
|
|
72
|
+
export class FetchProviderUsageClient {
|
|
73
|
+
async fetchUsage(input) {
|
|
74
|
+
const url = new URL(input.endpoint);
|
|
75
|
+
if (input.windowStart)
|
|
76
|
+
url.searchParams.set("window_start", input.windowStart);
|
|
77
|
+
if (input.windowEnd)
|
|
78
|
+
url.searchParams.set("window_end", input.windowEnd);
|
|
79
|
+
const response = await fetch(url, {
|
|
80
|
+
method: "GET",
|
|
81
|
+
headers: {
|
|
82
|
+
// Auth header carries the credential for this single read only.
|
|
83
|
+
Authorization: `Bearer ${input.credential}`,
|
|
84
|
+
Accept: "application/json"
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
if (!response.ok) {
|
|
88
|
+
// Surface status WITHOUT echoing the request auth header or the credential.
|
|
89
|
+
// The caller redacts before printing as a backstop.
|
|
90
|
+
throw new Error(`Provider usage endpoint returned HTTP ${response.status} ${response.statusText}.`);
|
|
91
|
+
}
|
|
92
|
+
const body = await response.json();
|
|
93
|
+
return parseAggregateUsage(body);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=provider-usage-client.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { SkillInjectionAdvisory } from "./skill-injection-detector.js";
|
|
2
|
+
import type { AgentTrace, CompactionPolicy, CompactionReport, CostEstimate, TokenEstimate, WasteFinding } from "./types.js";
|
|
3
|
+
import type { UsageMetadata } from "./usage-metadata.js";
|
|
4
|
+
export declare function formatAnalyzeReport(trace: AgentTrace, tokens: TokenEstimate, cost: CostEstimate, findings?: WasteFinding[], usage?: UsageMetadata, skillInjectionAdvisory?: SkillInjectionAdvisory): string;
|
|
5
|
+
export declare function formatCompactionReport(report: CompactionReport): string;
|
|
6
|
+
export declare function formatCompactionMarkdownReport(report: CompactionReport, policy: CompactionPolicy): string;
|
|
7
|
+
export declare function formatPrCommentReport(report: CompactionReport, policy: CompactionPolicy, artifactNames: string[]): string;
|