@things-factory/integration-openai 10.1.236 → 10.1.241

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 @@
1
+ import './openai-admin-connector';
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ require("./openai-admin-connector");
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/engine/connector/index.ts"],"names":[],"mappings":";;AAAA,oCAAiC","sourcesContent":["import './openai-admin-connector'\n"]}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * The OpenAI organization Costs API (ADR-0177 개정 6) - what an organization's API usage cost, day by day. An Admin key
3
+ * (`sk-admin-...`) only; a project key is refused by these endpoints. Amounts come in dollars.
4
+ */
5
+ export interface OpenAICostBucket {
6
+ start_time: number;
7
+ results: {
8
+ amount?: {
9
+ value: number;
10
+ currency: string;
11
+ };
12
+ line_item?: string | null;
13
+ project_id?: string | null;
14
+ }[];
15
+ }
16
+ export declare class OpenAIAdminClient {
17
+ private readonly fetcher;
18
+ private readonly origin;
19
+ private readonly key;
20
+ constructor(adminKey: string, origin?: string | null, fetcher?: typeof fetch);
21
+ /** The cost, day by day from `start` to `end` (unix seconds), by line item - every page. */
22
+ costs(start: number, end: number): Promise<OpenAICostBucket[]>;
23
+ }
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ /**
3
+ * The OpenAI organization Costs API (ADR-0177 개정 6) - what an organization's API usage cost, day by day. An Admin key
4
+ * (`sk-admin-...`) only; a project key is refused by these endpoints. Amounts come in dollars.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.OpenAIAdminClient = void 0;
8
+ const DEFAULT_ORIGIN = 'https://api.openai.com';
9
+ class OpenAIAdminClient {
10
+ constructor(adminKey, origin, fetcher = fetch) {
11
+ this.fetcher = fetcher;
12
+ if (!adminKey)
13
+ throw new Error('the OpenAI connection has no Admin key');
14
+ /* An Admin key - never a project key, which these endpoints refuse. The value itself is never said. */
15
+ if (!adminKey.trim().startsWith('sk-admin-'))
16
+ throw new Error('the key is no OpenAI Admin key - create one under Organization settings > Admin keys (sk-admin-...)');
17
+ this.key = adminKey.trim();
18
+ this.origin = (origin || DEFAULT_ORIGIN).replace(/\/+$/, '');
19
+ }
20
+ /** The cost, day by day from `start` to `end` (unix seconds), by line item - every page. */
21
+ async costs(start, end) {
22
+ const buckets = [];
23
+ let page;
24
+ do {
25
+ const url = new URL(`${this.origin}/v1/organization/costs`);
26
+ url.searchParams.set('start_time', String(start));
27
+ url.searchParams.set('end_time', String(end));
28
+ url.searchParams.set('bucket_width', '1d');
29
+ url.searchParams.append('group_by', 'line_item');
30
+ url.searchParams.set('limit', '180');
31
+ if (page)
32
+ url.searchParams.set('page', page);
33
+ const response = await this.fetcher(url.toString(), { headers: { authorization: `Bearer ${this.key}`, 'content-type': 'application/json' } });
34
+ const body = await response.json().catch(() => ({}));
35
+ if (!response.ok)
36
+ throw new Error(`/v1/organization/costs (${response.status}): ${body?.error?.message ?? response.statusText}`);
37
+ buckets.push(...(body.data ?? []));
38
+ page = body.has_more ? body.next_page : undefined;
39
+ } while (page);
40
+ return buckets;
41
+ }
42
+ }
43
+ exports.OpenAIAdminClient = OpenAIAdminClient;
44
+ //# sourceMappingURL=openai-admin-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai-admin-client.js","sourceRoot":"","sources":["../../../server/engine/connector/openai-admin-client.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,MAAM,cAAc,GAAG,wBAAwB,CAAA;AAO/C,MAAa,iBAAiB;IAI5B,YAAY,QAAgB,EAAE,MAAsB,EAAmB,UAAwB,KAAK;QAA7B,YAAO,GAAP,OAAO,CAAsB;QAClG,IAAI,CAAC,QAAQ;YAAE,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAA;QACxE,uGAAuG;QACvG,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,qGAAqG,CAAC,CAAA;QACpK,IAAI,CAAC,GAAG,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAA;QAC1B,IAAI,CAAC,MAAM,GAAG,CAAC,MAAM,IAAI,cAAc,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;IAC9D,CAAC;IAED,4FAA4F;IAC5F,KAAK,CAAC,KAAK,CAAC,KAAa,EAAE,GAAW;QACpC,MAAM,OAAO,GAAuB,EAAE,CAAA;QACtC,IAAI,IAAwB,CAAA;QAC5B,GAAG,CAAC;YACF,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,wBAAwB,CAAC,CAAA;YAC3D,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,YAAY,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;YACjD,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAA;YAC7C,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,cAAc,EAAE,IAAI,CAAC,CAAA;YAC1C,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,UAAU,EAAE,WAAW,CAAC,CAAA;YAChD,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;YACpC,IAAI,IAAI;gBAAE,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;YAC5C,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,IAAI,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,EAAE,CAAC,CAAA;YAC7I,MAAM,IAAI,GAAQ,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;YACzD,IAAI,CAAC,QAAQ,CAAC,EAAE;gBAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,QAAQ,CAAC,MAAM,MAAM,IAAI,EAAE,KAAK,EAAE,OAAO,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAA;YAChI,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAA;YAClC,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAA;QACnD,CAAC,QAAQ,IAAI,EAAC;QACd,OAAO,OAAO,CAAA;IAChB,CAAC;CACF;AAhCD,8CAgCC","sourcesContent":["/**\n * The OpenAI organization Costs API (ADR-0177 개정 6) - what an organization's API usage cost, day by day. An Admin key\n * (`sk-admin-...`) only; a project key is refused by these endpoints. Amounts come in dollars.\n */\n\nconst DEFAULT_ORIGIN = 'https://api.openai.com'\n\nexport interface OpenAICostBucket {\n start_time: number\n results: { amount?: { value: number; currency: string }; line_item?: string | null; project_id?: string | null }[]\n}\n\nexport class OpenAIAdminClient {\n private readonly origin: string\n private readonly key: string\n\n constructor(adminKey: string, origin?: string | null, private readonly fetcher: typeof fetch = fetch) {\n if (!adminKey) throw new Error('the OpenAI connection has no Admin key')\n /* An Admin key - never a project key, which these endpoints refuse. The value itself is never said. */\n if (!adminKey.trim().startsWith('sk-admin-')) throw new Error('the key is no OpenAI Admin key - create one under Organization settings > Admin keys (sk-admin-...)')\n this.key = adminKey.trim()\n this.origin = (origin || DEFAULT_ORIGIN).replace(/\\/+$/, '')\n }\n\n /** The cost, day by day from `start` to `end` (unix seconds), by line item - every page. */\n async costs(start: number, end: number): Promise<OpenAICostBucket[]> {\n const buckets: OpenAICostBucket[] = []\n let page: string | undefined\n do {\n const url = new URL(`${this.origin}/v1/organization/costs`)\n url.searchParams.set('start_time', String(start))\n url.searchParams.set('end_time', String(end))\n url.searchParams.set('bucket_width', '1d')\n url.searchParams.append('group_by', 'line_item')\n url.searchParams.set('limit', '180')\n if (page) url.searchParams.set('page', page)\n const response = await this.fetcher(url.toString(), { headers: { authorization: `Bearer ${this.key}`, 'content-type': 'application/json' } })\n const body: any = await response.json().catch(() => ({}))\n if (!response.ok) throw new Error(`/v1/organization/costs (${response.status}): ${body?.error?.message ?? response.statusText}`)\n buckets.push(...(body.data ?? []))\n page = body.has_more ? body.next_page : undefined\n } while (page)\n return buckets\n }\n}\n"]}
@@ -0,0 +1,19 @@
1
+ import { Connector } from '@things-factory/integration-base';
2
+ /**
3
+ * OpenAI Admin connector (ADR-0177 개정 6) - one connection an organization: its Admin key (a secret) and its id
4
+ * (`org-...`, Organization settings > General), which the subscription account names as its identifier.
5
+ */
6
+ export declare class OpenAIAdminConnector implements Connector {
7
+ ready(connectionConfigs: any): Promise<void>;
8
+ connect(connection: any): Promise<void>;
9
+ disconnect(connection: any): Promise<void>;
10
+ get parameterSpec(): {
11
+ type: string;
12
+ name: string;
13
+ label: string;
14
+ useDomainAttribute: boolean;
15
+ }[];
16
+ get taskPrefixes(): string[];
17
+ get help(): string;
18
+ get description(): string;
19
+ }
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OpenAIAdminConnector = void 0;
4
+ const integration_base_1 = require("@things-factory/integration-base");
5
+ const openai_admin_client_1 = require("./openai-admin-client");
6
+ /**
7
+ * OpenAI Admin connector (ADR-0177 개정 6) - one connection an organization: its Admin key (a secret) and its id
8
+ * (`org-...`, Organization settings > General), which the subscription account names as its identifier.
9
+ */
10
+ class OpenAIAdminConnector {
11
+ async ready(connectionConfigs) {
12
+ await Promise.all(connectionConfigs.map(this.connect.bind(this)));
13
+ integration_base_1.ConnectionManager.logger.info('openai-admin-connector connections are ready');
14
+ }
15
+ async connect(connection) {
16
+ const { endpoint, params } = connection;
17
+ if (!String(params?.organizationId ?? '').trim().startsWith('org-'))
18
+ throw new Error('the connection names no OpenAI organization id (org-...) - Organization settings > General');
19
+ integration_base_1.ConnectionManager.addConnectionInstance(connection, { client: new openai_admin_client_1.OpenAIAdminClient(params?.adminKey, endpoint), organizationId: params.organizationId.trim() });
20
+ integration_base_1.ConnectionManager.logger.info(`openai-admin-connector connection(${connection.name}) is connected`);
21
+ }
22
+ async disconnect(connection) {
23
+ integration_base_1.ConnectionManager.removeConnectionInstance(connection);
24
+ integration_base_1.ConnectionManager.logger.info(`openai-admin-connector connection(${connection.name}) is disconnected`);
25
+ }
26
+ get parameterSpec() {
27
+ return [
28
+ { type: 'secret', name: 'adminKey', label: 'admin-key', useDomainAttribute: true },
29
+ { type: 'string', name: 'organizationId', label: 'organization-id', useDomainAttribute: true }
30
+ ];
31
+ }
32
+ get taskPrefixes() {
33
+ return ['openai-cost'];
34
+ }
35
+ get help() {
36
+ return 'integration/connector/openai-admin-connector';
37
+ }
38
+ get description() {
39
+ return 'OpenAI Admin Connector';
40
+ }
41
+ }
42
+ exports.OpenAIAdminConnector = OpenAIAdminConnector;
43
+ integration_base_1.ConnectionManager.registerConnector('openai-admin-connector', new OpenAIAdminConnector());
44
+ //# sourceMappingURL=openai-admin-connector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai-admin-connector.js","sourceRoot":"","sources":["../../../server/engine/connector/openai-admin-connector.ts"],"names":[],"mappings":";;;AAAA,uEAA+E;AAE/E,+DAAyD;AAEzD;;;GAGG;AACH,MAAa,oBAAoB;IAC/B,KAAK,CAAC,KAAK,CAAC,iBAAiB;QAC3B,MAAM,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QACjE,oCAAiB,CAAC,MAAM,CAAC,IAAI,CAAC,8CAA8C,CAAC,CAAA;IAC/E,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,UAAU;QACtB,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,UAAU,CAAA;QACvC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,cAAc,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,4FAA4F,CAAC,CAAA;QAClL,oCAAiB,CAAC,qBAAqB,CAAC,UAAU,EAAE,EAAE,MAAM,EAAE,IAAI,uCAAiB,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAC,EAAE,cAAc,EAAE,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,EAAE,CAAC,CAAA;QAChK,oCAAiB,CAAC,MAAM,CAAC,IAAI,CAAC,qCAAqC,UAAU,CAAC,IAAI,gBAAgB,CAAC,CAAA;IACrG,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,UAAU;QACzB,oCAAiB,CAAC,wBAAwB,CAAC,UAAU,CAAC,CAAA;QACtD,oCAAiB,CAAC,MAAM,CAAC,IAAI,CAAC,qCAAqC,UAAU,CAAC,IAAI,mBAAmB,CAAC,CAAA;IACxG,CAAC;IAED,IAAI,aAAa;QACf,OAAO;YACL,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,IAAI,EAAE;YAClF,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,gBAAgB,EAAE,KAAK,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,IAAI,EAAE;SAC/F,CAAA;IACH,CAAC;IAED,IAAI,YAAY;QACd,OAAO,CAAC,aAAa,CAAC,CAAA;IACxB,CAAC;IAED,IAAI,IAAI;QACN,OAAO,8CAA8C,CAAA;IACvD,CAAC;IAED,IAAI,WAAW;QACb,OAAO,wBAAwB,CAAA;IACjC,CAAC;CACF;AApCD,oDAoCC;AAED,oCAAiB,CAAC,iBAAiB,CAAC,wBAAwB,EAAE,IAAI,oBAAoB,EAAE,CAAC,CAAA","sourcesContent":["import { ConnectionManager, Connector } from '@things-factory/integration-base'\n\nimport { OpenAIAdminClient } from './openai-admin-client'\n\n/**\n * OpenAI Admin connector (ADR-0177 개정 6) - one connection an organization: its Admin key (a secret) and its id\n * (`org-...`, Organization settings > General), which the subscription account names as its identifier.\n */\nexport class OpenAIAdminConnector implements Connector {\n async ready(connectionConfigs) {\n await Promise.all(connectionConfigs.map(this.connect.bind(this)))\n ConnectionManager.logger.info('openai-admin-connector connections are ready')\n }\n\n async connect(connection) {\n const { endpoint, params } = connection\n if (!String(params?.organizationId ?? '').trim().startsWith('org-')) throw new Error('the connection names no OpenAI organization id (org-...) - Organization settings > General')\n ConnectionManager.addConnectionInstance(connection, { client: new OpenAIAdminClient(params?.adminKey, endpoint), organizationId: params.organizationId.trim() })\n ConnectionManager.logger.info(`openai-admin-connector connection(${connection.name}) is connected`)\n }\n\n async disconnect(connection) {\n ConnectionManager.removeConnectionInstance(connection)\n ConnectionManager.logger.info(`openai-admin-connector connection(${connection.name}) is disconnected`)\n }\n\n get parameterSpec() {\n return [\n { type: 'secret', name: 'adminKey', label: 'admin-key', useDomainAttribute: true },\n { type: 'string', name: 'organizationId', label: 'organization-id', useDomainAttribute: true }\n ]\n }\n\n get taskPrefixes() {\n return ['openai-cost']\n }\n\n get help() {\n return 'integration/connector/openai-admin-connector'\n }\n\n get description() {\n return 'OpenAI Admin Connector'\n }\n}\n\nConnectionManager.registerConnector('openai-admin-connector', new OpenAIAdminConnector())\n"]}
@@ -1 +1,2 @@
1
+ import './connector';
1
2
  import './task';
@@ -1,4 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ require("./connector");
3
4
  require("./task");
4
5
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/engine/index.ts"],"names":[],"mappings":";;AAAA,kBAAe","sourcesContent":["import './task'\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/engine/index.ts"],"names":[],"mappings":";;AAAA,uBAAoB;AACpB,kBAAe","sourcesContent":["import './connector'\nimport './task'\n"]}
@@ -1 +1,2 @@
1
1
  import './openai-completion';
2
+ import './openai-cost-report';
@@ -1,4 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  require("./openai-completion");
4
+ require("./openai-cost-report");
4
5
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/engine/task/index.ts"],"names":[],"mappings":";;AAAA,+BAA4B","sourcesContent":["import './openai-completion'\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/engine/task/index.ts"],"names":[],"mappings":";;AAAA,+BAA4B;AAC5B,gCAA6B","sourcesContent":["import './openai-completion'\nimport './openai-cost-report'\n"]}
@@ -0,0 +1,16 @@
1
+ import type { OpenAICostBucket } from '../connector/openai-admin-client';
2
+ /** The closed months to read, as unix seconds: the first day, and the first day of this month (exclusive). */
3
+ export declare function windowOf(today: Date, months: number): {
4
+ start: number;
5
+ end: number;
6
+ };
7
+ export declare function costsByMonth(buckets: OpenAICostBucket[]): {
8
+ currency: string;
9
+ months: {
10
+ month: string;
11
+ lines: {
12
+ label: string;
13
+ amount: number;
14
+ }[];
15
+ }[];
16
+ };
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.windowOf = windowOf;
4
+ exports.costsByMonth = costsByMonth;
5
+ const integration_base_1 = require("@things-factory/integration-base");
6
+ /*
7
+ * What an OpenAI organization's API usage cost in the closed months before this one, a line a line item (a model and
8
+ * what it was used for) - in the shape an Anthropic organization's come in, so saas `syncSaasCosts` writes its bills
9
+ * (ADR-0177 개정 6).
10
+ */
11
+ /** The closed months to read, as unix seconds: the first day, and the first day of this month (exclusive). */
12
+ function windowOf(today, months) {
13
+ const start = Date.UTC(today.getUTCFullYear(), today.getUTCMonth() - months, 1) / 1000;
14
+ const end = Date.UTC(today.getUTCFullYear(), today.getUTCMonth(), 1) / 1000;
15
+ return { start, end };
16
+ }
17
+ function costsByMonth(buckets) {
18
+ const months = new Map();
19
+ let currency = 'USD';
20
+ for (const bucket of buckets) {
21
+ const month = new Date(bucket.start_time * 1000).toISOString().slice(0, 7);
22
+ const lines = months.get(month) ?? new Map();
23
+ for (const one of bucket.results ?? []) {
24
+ if (one.amount?.currency)
25
+ currency = one.amount.currency.toUpperCase();
26
+ const label = one.line_item || 'usage';
27
+ lines.set(label, (lines.get(label) ?? 0) + Number(one.amount?.value ?? 0));
28
+ }
29
+ months.set(month, lines);
30
+ }
31
+ return {
32
+ currency,
33
+ months: [...months.entries()]
34
+ .sort(([a], [b]) => a.localeCompare(b))
35
+ .map(([month, lines]) => ({
36
+ month,
37
+ lines: [...lines.entries()]
38
+ .map(([label, amount]) => ({ label, amount: Math.round(amount * 100) / 100 }))
39
+ .filter(line => line.amount)
40
+ .sort((a, b) => b.amount - a.amount)
41
+ }))
42
+ .filter(one => one.lines.length)
43
+ };
44
+ }
45
+ async function OpenAICostReport(step, context) {
46
+ const { connection: connectionName, params } = step;
47
+ const instance = await integration_base_1.ConnectionManager.getConnectionInstanceByName(context.domain, connectionName);
48
+ const client = instance?.client;
49
+ if (!client)
50
+ throw new Error(`no connection '${connectionName}'`);
51
+ const months = Math.min(Math.max(Number(params?.months) || 3, 1), 12);
52
+ const { start, end } = windowOf(new Date(), months);
53
+ return { data: { vendorAccount: instance.organizationId, vendorName: null, ...costsByMonth(await client.costs(start, end)) } };
54
+ }
55
+ OpenAICostReport.parameterSpec = [{ type: 'number', name: 'months', label: 'months', placeholder: '3 (1-12)' }];
56
+ OpenAICostReport.help = 'integration/task/openai-cost-report';
57
+ integration_base_1.TaskRegistry.registerTaskHandler('openai-cost-report', OpenAICostReport);
58
+ //# sourceMappingURL=openai-cost-report.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai-cost-report.js","sourceRoot":"","sources":["../../../server/engine/task/openai-cost-report.ts"],"names":[],"mappings":"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","sourcesContent":["import { ConnectionManager, TaskRegistry } from '@things-factory/integration-base'\n\nimport type { OpenAIAdminClient, OpenAICostBucket } from '../connector/openai-admin-client'\n\n/*\n * What an OpenAI organization's API usage cost in the closed months before this one, a line a line item (a model and\n * what it was used for) - in the shape an Anthropic organization's come in, so saas `syncSaasCosts` writes its bills\n * (ADR-0177 개정 6).\n */\n\n/** The closed months to read, as unix seconds: the first day, and the first day of this month (exclusive). */\nexport function windowOf(today: Date, months: number): { start: number; end: number } {\n const start = Date.UTC(today.getUTCFullYear(), today.getUTCMonth() - months, 1) / 1000\n const end = Date.UTC(today.getUTCFullYear(), today.getUTCMonth(), 1) / 1000\n return { start, end }\n}\n\nexport function costsByMonth(buckets: OpenAICostBucket[]) {\n const months = new Map<string, Map<string, number>>()\n let currency = 'USD'\n for (const bucket of buckets) {\n const month = new Date(bucket.start_time * 1000).toISOString().slice(0, 7)\n const lines = months.get(month) ?? new Map<string, number>()\n for (const one of bucket.results ?? []) {\n if (one.amount?.currency) currency = one.amount.currency.toUpperCase()\n const label = one.line_item || 'usage'\n lines.set(label, (lines.get(label) ?? 0) + Number(one.amount?.value ?? 0))\n }\n months.set(month, lines)\n }\n return {\n currency,\n months: [...months.entries()]\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([month, lines]) => ({\n month,\n lines: [...lines.entries()]\n .map(([label, amount]) => ({ label, amount: Math.round(amount * 100) / 100 }))\n .filter(line => line.amount)\n .sort((a, b) => b.amount - a.amount)\n }))\n .filter(one => one.lines.length)\n }\n}\n\nasync function OpenAICostReport(step, context) {\n const { connection: connectionName, params } = step\n const instance = await ConnectionManager.getConnectionInstanceByName(context.domain, connectionName)\n const client: OpenAIAdminClient | undefined = instance?.client\n if (!client) throw new Error(`no connection '${connectionName}'`)\n const months = Math.min(Math.max(Number(params?.months) || 3, 1), 12)\n const { start, end } = windowOf(new Date(), months)\n return { data: { vendorAccount: instance.organizationId, vendorName: null, ...costsByMonth(await client.costs(start, end)) } }\n}\n\nOpenAICostReport.parameterSpec = [{ type: 'number', name: 'months', label: 'months', placeholder: '3 (1-12)' }]\nOpenAICostReport.help = 'integration/task/openai-cost-report'\n\nTaskRegistry.registerTaskHandler('openai-cost-report', OpenAICostReport)\n"]}