@agent-finops/core 0.9.0 → 0.9.2

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.
@@ -0,0 +1,185 @@
1
+ import { z } from "zod";
2
+ import { type ResultCard } from "./resultCard.js";
3
+ /**
4
+ * Post-launch transport groundwork only.
5
+ *
6
+ * This module does not upload, send, persist, authenticate, render markup, or
7
+ * expose a CLI command. It defines the bounded aggregate payload that a future
8
+ * opt-in receipt route may accept after its own waitlist, durable-rate-limit,
9
+ * retention, and mail-provider controls exist.
10
+ */
11
+ export declare const RECEIPT_SHARE_V0_VERSION: "0.1.0";
12
+ export declare const RECEIPT_SHARE_CARD_V0_KIND: "aibill.receipt_share_card";
13
+ export declare const RECEIPT_EMAIL_REQUEST_V0_KIND: "aibill.receipt_email_request";
14
+ export declare const receiptShareCutV0Schema: z.ZodObject<{
15
+ template: z.ZodEnum<{
16
+ route_lower_cost_model: "route_lower_cost_model";
17
+ narrow_context: "narrow_context";
18
+ cache_repeated_work: "cache_repeated_work";
19
+ use_batch_api: "use_batch_api";
20
+ }>;
21
+ modeledOpportunityUsd: z.ZodNumber;
22
+ evidence: z.ZodLiteral<"modeled_not_verified">;
23
+ }, z.core.$strict>;
24
+ export declare const receiptShareCardV0Schema: z.ZodObject<{
25
+ kind: z.ZodLiteral<"aibill.receipt_share_card">;
26
+ schemaVersion: z.ZodLiteral<"0.1.0">;
27
+ currency: z.ZodLiteral<"USD">;
28
+ windowDays: z.ZodNumber;
29
+ mode: z.ZodEnum<{
30
+ "local-logs": "local-logs";
31
+ connected: "connected";
32
+ mixed: "mixed";
33
+ }>;
34
+ financials: z.ZodObject<{
35
+ subscriptionCommitted: z.ZodObject<{
36
+ amountUsd: z.ZodNullable<z.ZodNumber>;
37
+ pricedSubs: z.ZodNumber;
38
+ totalSubs: z.ZodNumber;
39
+ }, z.core.$strict>;
40
+ apiEquivalent: z.ZodObject<{
41
+ amountUsd: z.ZodNullable<z.ZodNumber>;
42
+ financialEvidence: z.ZodEnum<{
43
+ estimated: "estimated";
44
+ missing: "missing";
45
+ }>;
46
+ }, z.core.$strict>;
47
+ providerBilled: z.ZodObject<{
48
+ amountUsd: z.ZodNullable<z.ZodNumber>;
49
+ financialEvidence: z.ZodEnum<{
50
+ missing: "missing";
51
+ verified: "verified";
52
+ }>;
53
+ }, z.core.$strict>;
54
+ blended: z.ZodNull;
55
+ blendPolicy: z.ZodLiteral<"never_blended">;
56
+ }, z.core.$strict>;
57
+ providerCount: z.ZodNumber;
58
+ recordCount: z.ZodNumber;
59
+ confidence: z.ZodEnum<{
60
+ estimated: "estimated";
61
+ missing: "missing";
62
+ verified: "verified";
63
+ detected_unverified: "detected_unverified";
64
+ }>;
65
+ cuts: z.ZodArray<z.ZodObject<{
66
+ template: z.ZodEnum<{
67
+ route_lower_cost_model: "route_lower_cost_model";
68
+ narrow_context: "narrow_context";
69
+ cache_repeated_work: "cache_repeated_work";
70
+ use_batch_api: "use_batch_api";
71
+ }>;
72
+ modeledOpportunityUsd: z.ZodNumber;
73
+ evidence: z.ZodLiteral<"modeled_not_verified">;
74
+ }, z.core.$strict>>;
75
+ contentBoundary: z.ZodObject<{
76
+ rawHistoryIncluded: z.ZodLiteral<false>;
77
+ localIdentifiersIncluded: z.ZodLiteral<false>;
78
+ clientMarkupIncluded: z.ZodLiteral<false>;
79
+ }, z.core.$strict>;
80
+ }, z.core.$strict>;
81
+ export declare const receiptEmailRequestV0Schema: z.ZodObject<{
82
+ kind: z.ZodLiteral<"aibill.receipt_email_request">;
83
+ schemaVersion: z.ZodLiteral<"0.1.0">;
84
+ recipientEmail: z.ZodString;
85
+ consent: z.ZodLiteral<"email_and_aggregate_card_via_mail_provider">;
86
+ card: z.ZodObject<{
87
+ kind: z.ZodLiteral<"aibill.receipt_share_card">;
88
+ schemaVersion: z.ZodLiteral<"0.1.0">;
89
+ currency: z.ZodLiteral<"USD">;
90
+ windowDays: z.ZodNumber;
91
+ mode: z.ZodEnum<{
92
+ "local-logs": "local-logs";
93
+ connected: "connected";
94
+ mixed: "mixed";
95
+ }>;
96
+ financials: z.ZodObject<{
97
+ subscriptionCommitted: z.ZodObject<{
98
+ amountUsd: z.ZodNullable<z.ZodNumber>;
99
+ pricedSubs: z.ZodNumber;
100
+ totalSubs: z.ZodNumber;
101
+ }, z.core.$strict>;
102
+ apiEquivalent: z.ZodObject<{
103
+ amountUsd: z.ZodNullable<z.ZodNumber>;
104
+ financialEvidence: z.ZodEnum<{
105
+ estimated: "estimated";
106
+ missing: "missing";
107
+ }>;
108
+ }, z.core.$strict>;
109
+ providerBilled: z.ZodObject<{
110
+ amountUsd: z.ZodNullable<z.ZodNumber>;
111
+ financialEvidence: z.ZodEnum<{
112
+ missing: "missing";
113
+ verified: "verified";
114
+ }>;
115
+ }, z.core.$strict>;
116
+ blended: z.ZodNull;
117
+ blendPolicy: z.ZodLiteral<"never_blended">;
118
+ }, z.core.$strict>;
119
+ providerCount: z.ZodNumber;
120
+ recordCount: z.ZodNumber;
121
+ confidence: z.ZodEnum<{
122
+ estimated: "estimated";
123
+ missing: "missing";
124
+ verified: "verified";
125
+ detected_unverified: "detected_unverified";
126
+ }>;
127
+ cuts: z.ZodArray<z.ZodObject<{
128
+ template: z.ZodEnum<{
129
+ route_lower_cost_model: "route_lower_cost_model";
130
+ narrow_context: "narrow_context";
131
+ cache_repeated_work: "cache_repeated_work";
132
+ use_batch_api: "use_batch_api";
133
+ }>;
134
+ modeledOpportunityUsd: z.ZodNumber;
135
+ evidence: z.ZodLiteral<"modeled_not_verified">;
136
+ }, z.core.$strict>>;
137
+ contentBoundary: z.ZodObject<{
138
+ rawHistoryIncluded: z.ZodLiteral<false>;
139
+ localIdentifiersIncluded: z.ZodLiteral<false>;
140
+ clientMarkupIncluded: z.ZodLiteral<false>;
141
+ }, z.core.$strict>;
142
+ }, z.core.$strict>;
143
+ }, z.core.$strict>;
144
+ export type ReceiptShareCutV0 = z.infer<typeof receiptShareCutV0Schema>;
145
+ export type ReceiptShareCardV0 = z.infer<typeof receiptShareCardV0Schema>;
146
+ export type ReceiptEmailRequestV0 = z.infer<typeof receiptEmailRequestV0Schema>;
147
+ export type BuildReceiptShareCardV0Input = {
148
+ resultCard: ResultCard;
149
+ providerCount: number;
150
+ recordCount: number;
151
+ confidence: ReceiptShareCardV0["confidence"];
152
+ cuts: ReceiptShareCutV0[];
153
+ };
154
+ /**
155
+ * Projects the canonical local result card into a smaller aggregate-only
156
+ * transport card. Subscription labels, project rows, runways, record IDs,
157
+ * source metadata, prompts, paths, and raw history are intentionally omitted.
158
+ */
159
+ export declare function buildReceiptShareCardV0(input: BuildReceiptShareCardV0Input): ReceiptShareCardV0;
160
+ export declare const RECEIPT_EMAIL_MAX_SENDS_PER_EMAIL_24H: 1;
161
+ export declare const RECEIPT_EMAIL_MAX_SENDS_PER_IP_24H: 10;
162
+ declare const receiptEmailDeliveryStateV0Schema: z.ZodObject<{
163
+ waitlistMember: z.ZodBoolean;
164
+ emailSendsLast24Hours: z.ZodNumber;
165
+ ipSendsLast24Hours: z.ZodNumber;
166
+ }, z.core.$strict>;
167
+ export type ReceiptEmailDeliveryDecisionV0 = {
168
+ status: "join_first";
169
+ httpStatus: 403;
170
+ } | {
171
+ status: "rate_limited";
172
+ httpStatus: 429;
173
+ scope: "email" | "ip";
174
+ } | {
175
+ status: "accepted";
176
+ httpStatus: 202;
177
+ };
178
+ /**
179
+ * Pure authorization policy for a future route. Counters must come from a
180
+ * durable shared store; this function creates no in-memory limiter and has no
181
+ * persistence or network side effects.
182
+ */
183
+ export declare function decideReceiptEmailDeliveryV0(state: z.input<typeof receiptEmailDeliveryStateV0Schema>): ReceiptEmailDeliveryDecisionV0;
184
+ export {};
185
+ //# sourceMappingURL=receiptShare.d.ts.map
@@ -0,0 +1,118 @@
1
+ import { z } from "zod";
2
+ import { resultCardSchema, resultCardTotalsSchema } from "./resultCard.js";
3
+ /**
4
+ * Post-launch transport groundwork only.
5
+ *
6
+ * This module does not upload, send, persist, authenticate, render markup, or
7
+ * expose a CLI command. It defines the bounded aggregate payload that a future
8
+ * opt-in receipt route may accept after its own waitlist, durable-rate-limit,
9
+ * retention, and mail-provider controls exist.
10
+ */
11
+ export const RECEIPT_SHARE_V0_VERSION = "0.1.0";
12
+ export const RECEIPT_SHARE_CARD_V0_KIND = "aibill.receipt_share_card";
13
+ export const RECEIPT_EMAIL_REQUEST_V0_KIND = "aibill.receipt_email_request";
14
+ const boundedUsdSchema = z.number().finite().nonnegative().max(1_000_000_000);
15
+ export const receiptShareCutV0Schema = z.object({
16
+ template: z.enum([
17
+ "route_lower_cost_model",
18
+ "narrow_context",
19
+ "cache_repeated_work",
20
+ "use_batch_api"
21
+ ]),
22
+ modeledOpportunityUsd: boundedUsdSchema.positive(),
23
+ evidence: z.literal("modeled_not_verified")
24
+ }).strict();
25
+ export const receiptShareCardV0Schema = z.object({
26
+ kind: z.literal(RECEIPT_SHARE_CARD_V0_KIND),
27
+ schemaVersion: z.literal(RECEIPT_SHARE_V0_VERSION),
28
+ currency: z.literal("USD"),
29
+ windowDays: z.number().int().min(1).max(365),
30
+ // Demo/sample payloads are intentionally ineligible for real delivery.
31
+ mode: z.enum(["local-logs", "connected", "mixed"]),
32
+ /** Canonical three-basis stack, including blended:null/never_blended. */
33
+ financials: resultCardTotalsSchema,
34
+ providerCount: z.number().int().positive().max(64),
35
+ recordCount: z.number().int().positive().max(1_000_000),
36
+ confidence: z.enum(["verified", "estimated", "detected_unverified", "missing"]),
37
+ /** Fixed templates only: no client-provided title, model, operation, or markup. */
38
+ cuts: z.array(receiptShareCutV0Schema).max(3),
39
+ contentBoundary: z.object({
40
+ rawHistoryIncluded: z.literal(false),
41
+ localIdentifiersIncluded: z.literal(false),
42
+ clientMarkupIncluded: z.literal(false)
43
+ }).strict()
44
+ }).strict().superRefine((card, context) => {
45
+ if (card.providerCount > card.recordCount) {
46
+ context.addIssue({
47
+ code: "custom",
48
+ path: ["providerCount"],
49
+ message: "Provider count cannot exceed aggregate record count."
50
+ });
51
+ }
52
+ });
53
+ // Deliberately ASCII and single-line. The future route must additionally use
54
+ // the already-waitlisted normalized address as its authorization identity.
55
+ const receiptRecipientEmailV0Schema = z.string()
56
+ .trim()
57
+ .toLowerCase()
58
+ .min(3)
59
+ .max(254)
60
+ .regex(/^[A-Za-z0-9.!#$%&'*+/=?^_`{|}~-]+@[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?(?:\.[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)+$/u);
61
+ export const receiptEmailRequestV0Schema = z.object({
62
+ kind: z.literal(RECEIPT_EMAIL_REQUEST_V0_KIND),
63
+ schemaVersion: z.literal(RECEIPT_SHARE_V0_VERSION),
64
+ recipientEmail: receiptRecipientEmailV0Schema,
65
+ /** Names both pieces of data and the third-party transit boundary. */
66
+ consent: z.literal("email_and_aggregate_card_via_mail_provider"),
67
+ card: receiptShareCardV0Schema
68
+ }).strict();
69
+ /**
70
+ * Projects the canonical local result card into a smaller aggregate-only
71
+ * transport card. Subscription labels, project rows, runways, record IDs,
72
+ * source metadata, prompts, paths, and raw history are intentionally omitted.
73
+ */
74
+ export function buildReceiptShareCardV0(input) {
75
+ const resultCard = resultCardSchema.parse(input.resultCard);
76
+ return receiptShareCardV0Schema.parse({
77
+ kind: RECEIPT_SHARE_CARD_V0_KIND,
78
+ schemaVersion: RECEIPT_SHARE_V0_VERSION,
79
+ currency: "USD",
80
+ windowDays: resultCard.windowDays,
81
+ mode: resultCard.mode,
82
+ financials: resultCard.totals,
83
+ providerCount: input.providerCount,
84
+ recordCount: input.recordCount,
85
+ confidence: input.confidence,
86
+ cuts: input.cuts,
87
+ contentBoundary: {
88
+ rawHistoryIncluded: false,
89
+ localIdentifiersIncluded: false,
90
+ clientMarkupIncluded: false
91
+ }
92
+ });
93
+ }
94
+ export const RECEIPT_EMAIL_MAX_SENDS_PER_EMAIL_24H = 1;
95
+ export const RECEIPT_EMAIL_MAX_SENDS_PER_IP_24H = 10;
96
+ const receiptEmailDeliveryStateV0Schema = z.object({
97
+ waitlistMember: z.boolean(),
98
+ emailSendsLast24Hours: z.number().int().nonnegative().max(1_000_000),
99
+ ipSendsLast24Hours: z.number().int().nonnegative().max(1_000_000)
100
+ }).strict();
101
+ /**
102
+ * Pure authorization policy for a future route. Counters must come from a
103
+ * durable shared store; this function creates no in-memory limiter and has no
104
+ * persistence or network side effects.
105
+ */
106
+ export function decideReceiptEmailDeliveryV0(state) {
107
+ const parsed = receiptEmailDeliveryStateV0Schema.parse(state);
108
+ if (!parsed.waitlistMember)
109
+ return { status: "join_first", httpStatus: 403 };
110
+ if (parsed.emailSendsLast24Hours >= RECEIPT_EMAIL_MAX_SENDS_PER_EMAIL_24H) {
111
+ return { status: "rate_limited", httpStatus: 429, scope: "email" };
112
+ }
113
+ if (parsed.ipSendsLast24Hours >= RECEIPT_EMAIL_MAX_SENDS_PER_IP_24H) {
114
+ return { status: "rate_limited", httpStatus: 429, scope: "ip" };
115
+ }
116
+ return { status: "accepted", httpStatus: 202 };
117
+ }
118
+ //# sourceMappingURL=receiptShare.js.map
@@ -18,4 +18,19 @@ export declare const AIBILL_IMPROVE_DELIVERY_V0: AibillImproveDeliveryV0;
18
18
  export declare function aibillCommandV0(args: string, delivery?: AibillImproveDeliveryV0): string;
19
19
  /** One privacy-safe command shared by terminal, MCP, and Glance. */
20
20
  export declare function aibillImproveCommandV0(delivery?: AibillImproveDeliveryV0): string;
21
+ /**
22
+ * Version-pinned command for machine-composed lines (M4c): a command an AI
23
+ * client relays to a human must be reproducible and must not silently
24
+ * resolve to a different release, so `draft_improve_command` pins to the
25
+ * composing package's own version (`npx aibill@<version> …`). A version
26
+ * that is not a plain semver falls back to the unpinned published command
27
+ * rather than composing an unrunnable line. In source-preview builds the
28
+ * checkout command needs no pin.
29
+ *
30
+ * Release gate (n2): the coordinated release must also prove the pinned
31
+ * version EXISTS on the public registry and supports the composed flags —
32
+ * the packed-install gate described on AIBILL_IMPROVE_DELIVERY_V0 is the
33
+ * natural home for that check; QA 24 asserts only that the pin is present.
34
+ */
35
+ export declare function aibillPinnedCommandV0(args: string, version: string, delivery?: AibillImproveDeliveryV0): string;
21
36
  //# sourceMappingURL=runtimeCommands.d.ts.map
@@ -24,4 +24,27 @@ export function aibillCommandV0(args, delivery = AIBILL_IMPROVE_DELIVERY_V0) {
24
24
  export function aibillImproveCommandV0(delivery = AIBILL_IMPROVE_DELIVERY_V0) {
25
25
  return aibillCommandV0(delivery === "source_preview" ? "improve --path ." : "improve", delivery);
26
26
  }
27
+ /** Published semver shape a composed pin must have (charset-safe by regex). */
28
+ const pinnableVersionPattern = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
29
+ /**
30
+ * Version-pinned command for machine-composed lines (M4c): a command an AI
31
+ * client relays to a human must be reproducible and must not silently
32
+ * resolve to a different release, so `draft_improve_command` pins to the
33
+ * composing package's own version (`npx aibill@<version> …`). A version
34
+ * that is not a plain semver falls back to the unpinned published command
35
+ * rather than composing an unrunnable line. In source-preview builds the
36
+ * checkout command needs no pin.
37
+ *
38
+ * Release gate (n2): the coordinated release must also prove the pinned
39
+ * version EXISTS on the public registry and supports the composed flags —
40
+ * the packed-install gate described on AIBILL_IMPROVE_DELIVERY_V0 is the
41
+ * natural home for that check; QA 24 asserts only that the pin is present.
42
+ */
43
+ export function aibillPinnedCommandV0(args, version, delivery = AIBILL_IMPROVE_DELIVERY_V0) {
44
+ const commandArgs = args.trim();
45
+ if (delivery === "source_preview" || !pinnableVersionPattern.test(version)) {
46
+ return aibillCommandV0(commandArgs, delivery);
47
+ }
48
+ return `npx aibill@${version}${commandArgs.length > 0 ? ` ${commandArgs}` : ""}`;
49
+ }
27
50
  //# sourceMappingURL=runtimeCommands.js.map
package/dist/schema.d.ts CHANGED
@@ -18,6 +18,7 @@ export declare const spendSourceSchema: z.ZodObject<{
18
18
  missing: "missing";
19
19
  }>;
20
20
  observedFrom: z.ZodString;
21
+ account: z.ZodOptional<z.ZodString>;
21
22
  }, z.core.$strip>;
22
23
  export type SpendSource = z.infer<typeof spendSourceSchema>;
23
24
  /**
@@ -65,6 +66,7 @@ export declare const usageRecordSchema: z.ZodObject<{
65
66
  missing: "missing";
66
67
  }>;
67
68
  observedFrom: z.ZodString;
69
+ account: z.ZodOptional<z.ZodString>;
68
70
  }, z.core.$strip>;
69
71
  model: z.ZodString;
70
72
  inputTokens: z.ZodNumber;
package/dist/schema.js CHANGED
@@ -11,7 +11,15 @@ export const spendSourceSchema = z.object({
11
11
  name: z.string().min(1),
12
12
  provider: z.string().min(1),
13
13
  confidence: costConfidenceSchema,
14
- observedFrom: z.string().min(1)
14
+ observedFrom: z.string().min(1),
15
+ /**
16
+ * Stable per-account identity within one provider (an organization, team,
17
+ * or enterprise slice). Derived from the user-chosen credential reference
18
+ * or an explicit --org/--enterprise/--account-id flag — never from secret
19
+ * material. Absent on local-agent records and on provider records synced
20
+ * before multi-account support (treated as one unnamed legacy slice).
21
+ */
22
+ account: z.string().min(1).optional()
15
23
  });
16
24
  /**
17
25
  * What one normalized usage record represents.
@@ -62,10 +62,17 @@ export const providerCatalog = [
62
62
  label: "GitHub Copilot",
63
63
  preferredSourceType: "provider_api",
64
64
  preferredAccessMethod: "api",
65
- verifiedFields: ["Copilot usage metrics", "seat assignments and reported plan types"],
65
+ // AI-credit billing (gross/discount/net) is implemented and shipped;
66
+ // billed dollars stay estimated until an AI_SPEND_COPILOT_RECONCILE_*
67
+ // reconciliation matches the billing page figure. Legacy premium-request
68
+ // billing is deliberately never fetched.
69
+ verifiedFields: [
70
+ "Copilot usage metrics",
71
+ "seat assignments and reported plan types",
72
+ "AI-credit gross, discount, and net billing usage report"
73
+ ],
66
74
  missingFields: [
67
75
  "GitHub admin token reference and organization or enterprise slug",
68
- "AI-credit gross, discount, and net billing",
69
76
  "license invoice settlement"
70
77
  ]
71
78
  },
@@ -177,7 +184,7 @@ export const providerConnectorCatalog = [
177
184
  fallbackAuthModes: [],
178
185
  scopes: ["fine-grained Administration: read", "organization or enterprise billing access"],
179
186
  tokenStorage: "local_reference_only",
180
- setupHint: "Use a local env reference to a GitHub token with read-only organization or enterprise Copilot metrics and seat access; AI-credit billing is not implemented."
187
+ setupHint: "Use a local env reference to a GitHub token with read-only organization or enterprise Copilot metrics, seat, and AI-credit billing usage access; billed AI-credit dollars stay estimated until an AI_SPEND_COPILOT_RECONCILE_* reconciliation matches the billing page figure."
181
188
  },
182
189
  {
183
190
  provider: "cursor",
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@agent-finops/core",
3
- "version": "0.9.0",
3
+ "version": "0.9.2",
4
+ "funding": "https://asktilden.com",
4
5
  "type": "module",
5
6
  "main": "./dist/index.js",
6
7
  "types": "./dist/index.d.ts",