@stackfactor/agent-utils 1.1.0 → 1.1.1

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.
@@ -1 +1 @@
1
- {"version":3,"file":"langChain.d.ts","sourceRoot":"","sources":["../../src/langChain.ts"],"names":[],"mappings":";0CAiD6C,GAAG,KAAG,IAAI;wBAke/C,MAAM,aACD,MAAM,gBACH,MAAM,SACb,GAAG,EAAE,kBACI,GAAG,UACX,GAAG,KACV,GAAG;sBAyBG,GAAG,UACF,MAAM,UACN,GAAG,eACC,QAAQ,GAAG,IAAI,KAC1B,OAAO,CAAC,GAAG,CAAC;oCAuXF,MAAM,UACT,GAAG,UACH,GAAG,oBACO,GAAG,eACT,MAAM,eACN,MAAM,wBACG,OAAO,WACpB,GAAG,cACA,MAAM,UACV,GAAG,EAAE,KACX,OAAO,CAAC,GAAG,CAAC;sDAwsBF,MAAM,UACT,GAAG,UACH,MAAM,YACL,GAAG,KACX,OAAO,CAAC,GAAG,CAAC;0CAv+B8B,GAAG,KAAG,MAAM;mCAoBxC,MAAM,mBACJ,MAAM,EAAE,KACxB,MAAM;;AA4gCT,wBAQE"}
1
+ {"version":3,"file":"langChain.d.ts","sourceRoot":"","sources":["../../src/langChain.ts"],"names":[],"mappings":";0CAiD6C,GAAG,KAAG,IAAI;wBAqgB/C,MAAM,aACD,MAAM,gBACH,MAAM,SACb,GAAG,EAAE,kBACI,GAAG,UACX,GAAG,KACV,GAAG;sBAyBG,GAAG,UACF,MAAM,UACN,GAAG,eACC,QAAQ,GAAG,IAAI,KAC1B,OAAO,CAAC,GAAG,CAAC;oCAqXF,MAAM,UACT,GAAG,UACH,GAAG,oBACO,GAAG,eACT,MAAM,eACN,MAAM,wBACG,OAAO,WACpB,GAAG,cACA,MAAM,UACV,GAAG,EAAE,KACX,OAAO,CAAC,GAAG,CAAC;sDAosBF,MAAM,UACT,GAAG,UACH,MAAM,YACL,GAAG,KACX,OAAO,CAAC,GAAG,CAAC;0CAn+B8B,GAAG,KAAG,MAAM;mCAoBxC,MAAM,mBACJ,MAAM,EAAE,KACxB,MAAM;;AAwgCT,wBAQE"}
@@ -68,40 +68,68 @@ const recordCost = (cost) => {
68
68
  }
69
69
  };
70
70
  /**
71
- * Looks up the per-model pricing entry from `config.modelPricing`. Returns `null`
72
- * when pricing is not configured for the model, in which case cost recording
73
- * becomes a no-op (the call still succeeds pricing data is the host's
74
- * responsibility, not the agent's).
71
+ * Reads a per-model rate from the flat cost constants the integration defines
72
+ * in its `config.yaml` (exposed on the config object), e.g.
73
+ * `claude-opus-4-7-input-token-costs`. Returns `null` when the constant is
74
+ * absent or not numeric, in which case cost recording becomes a no-op (the
75
+ * call still succeeds — pricing is the integration's configuration concern).
75
76
  */
76
- const getModelPrice = (modelName, config) => {
77
- const pricing = config?.modelPricing;
78
- if (!pricing)
77
+ const getModelRate = (modelName, config, kind) => {
78
+ if (!config || !modelName)
79
79
  return null;
80
- return pricing[modelName] || null;
80
+ const rate = Number(config[`${modelName}-${kind}-costs`]);
81
+ return Number.isFinite(rate) ? rate : null;
81
82
  };
82
83
  /**
83
- * Computes USD cost for a text LLM call. `config.modelPricing[modelName]` is
84
- * expected to provide `input` and `output` rates in dollars per million tokens.
84
+ * Accumulates the per-model cost breakdown on `global.quota.byModel` so the
85
+ * agent can report input vs output spend per model to the UI. No-ops when the
86
+ * quota global is absent.
85
87
  */
86
- const calculateTextCost = (modelName, usage, config) => {
88
+ const recordModelBreakdown = (modelName, inputCost, outputCost, imageCost) => {
89
+ const quota = globalThis.quota;
90
+ if (!quota || !modelName)
91
+ return;
92
+ if (!quota.byModel || typeof quota.byModel !== "object")
93
+ quota.byModel = {};
94
+ const entry = (quota.byModel[modelName] ||= {
95
+ inputCost: 0,
96
+ outputCost: 0,
97
+ imageCost: 0,
98
+ });
99
+ if (Number.isFinite(inputCost) && inputCost > 0)
100
+ entry.inputCost += inputCost;
101
+ if (Number.isFinite(outputCost) && outputCost > 0)
102
+ entry.outputCost += outputCost;
103
+ if (Number.isFinite(imageCost) && imageCost > 0)
104
+ entry.imageCost += imageCost;
105
+ };
106
+ /**
107
+ * Computes and records the USD cost of a text LLM call from the
108
+ * `<model>-input-token-costs` / `<model>-output-token-costs` constants
109
+ * (dollars per million tokens). Updates both the session total
110
+ * (`quota.usedThisSession` / `quota.remaining`) and the per-model breakdown
111
+ * (`quota.byModel`).
112
+ */
113
+ const recordTextCost = (modelName, usage, config) => {
87
114
  if (!usage)
88
- return 0;
89
- const price = getModelPrice(modelName, config);
90
- if (!price)
91
- return 0;
92
- const inputCost = ((usage.input_tokens || 0) / 1_000_000) * (price.input || 0);
93
- const outputCost = ((usage.output_tokens || 0) / 1_000_000) * (price.output || 0);
94
- return inputCost + outputCost;
115
+ return;
116
+ const inputRate = getModelRate(modelName, config, "input-token") || 0;
117
+ const outputRate = getModelRate(modelName, config, "output-token") || 0;
118
+ const inputCost = ((usage.input_tokens || 0) / 1_000_000) * inputRate;
119
+ const outputCost = ((usage.output_tokens || 0) / 1_000_000) * outputRate;
120
+ recordCost(inputCost + outputCost);
121
+ recordModelBreakdown(modelName, inputCost, outputCost, 0);
95
122
  };
96
123
  /**
97
- * Computes USD cost for an image generation call. `config.modelPricing[modelName]`
98
- * is expected to provide a `perImage` rate in dollars.
124
+ * Computes and records the USD cost of an image generation call from the
125
+ * `<model>-image-costs` constant (dollars per generated image). Updates both
126
+ * the session total and the per-model breakdown.
99
127
  */
100
- const calculateImageCost = (modelName, numImages, config) => {
101
- const price = getModelPrice(modelName, config);
102
- if (!price)
103
- return 0;
104
- return (numImages || 0) * (price.perImage || 0);
128
+ const recordImageCost = (modelName, numImages, config) => {
129
+ const rate = getModelRate(modelName, config, "image") || 0;
130
+ const imageCost = (numImages || 0) * rate;
131
+ recordCost(imageCost);
132
+ recordModelBreakdown(modelName, 0, 0, imageCost);
105
133
  };
106
134
  /**
107
135
  * Extracts a normalized token-usage object from a single LangChain `invoke()`
@@ -569,7 +597,7 @@ const runAgent = async (agent, prompt, config, onProgress = null) => {
569
597
  throw err;
570
598
  }
571
599
  }
572
- recordCost(calculateTextCost(modelName, sumAgentResponseUsage(response), config));
600
+ recordTextCost(modelName, sumAgentResponseUsage(response), config);
573
601
  const endTime = Date.now();
574
602
  const duration = endTime - startTime;
575
603
  logger_js_1.default.log(null, logger_js_1.default.levels.info, `Agent "${agent.options?.name}" completed in ${Math.round(duration / 1000)} seconds.`);
@@ -1044,7 +1072,7 @@ const runPromptWithModel = async (modelName, config, prompt, onProgressReport, m
1044
1072
  throw err;
1045
1073
  }
1046
1074
  }
1047
- recordCost(calculateTextCost(modelName, streamUsage, config));
1075
+ recordTextCost(modelName, streamUsage, config);
1048
1076
  if (!rawContent) {
1049
1077
  throw errorHandling_js_1.default.create(const_js_1.default.HTTP_CODES.INTERNAL_SERVER_ERROR, "LLM returned no content");
1050
1078
  }
@@ -1144,7 +1172,7 @@ const runPromptWithModel = async (modelName, config, prompt, onProgressReport, m
1144
1172
  throw err;
1145
1173
  }
1146
1174
  }
1147
- recordCost(calculateTextCost(modelName, extractUsageFromInvoke(response), config));
1175
+ recordTextCost(modelName, extractUsageFromInvoke(response), config);
1148
1176
  const rawContent = response?.content || response;
1149
1177
  // If not expecting JSON, return raw content directly
1150
1178
  if (!expectsJsonResponse) {
@@ -1252,7 +1280,7 @@ const generateImageWithOpenAI = async (modelName, config, prompt, options) => {
1252
1280
  }
1253
1281
  assertQuotaAvailable();
1254
1282
  const response = await openai.images.generate(requestParams);
1255
- recordCost(calculateImageCost(modelName, response.data?.length || n, config));
1283
+ recordImageCost(modelName, response.data?.length || n, config);
1256
1284
  // Format response based on number of images
1257
1285
  if (n === 1) {
1258
1286
  const imageData = response.data[0];
@@ -1369,7 +1397,7 @@ const generateImageWithGoogle = async (modelName, config, prompt, options) => {
1369
1397
  if (images.length === 0) {
1370
1398
  throw errorHandling_js_1.default.create(const_js_1.default.HTTP_CODES.INTERNAL_SERVER_ERROR, `No images were generated by ${modelName}`);
1371
1399
  }
1372
- recordCost(calculateImageCost(modelName, images.length, config));
1400
+ recordImageCost(modelName, images.length, config);
1373
1401
  if (numberOfImages === 1 || images.length === 1) {
1374
1402
  return images[0];
1375
1403
  }
@@ -1 +1 @@
1
- {"version":3,"file":"langChain.d.ts","sourceRoot":"","sources":["../../src/langChain.ts"],"names":[],"mappings":";0CAiD6C,GAAG,KAAG,IAAI;wBAke/C,MAAM,aACD,MAAM,gBACH,MAAM,SACb,GAAG,EAAE,kBACI,GAAG,UACX,GAAG,KACV,GAAG;sBAyBG,GAAG,UACF,MAAM,UACN,GAAG,eACC,QAAQ,GAAG,IAAI,KAC1B,OAAO,CAAC,GAAG,CAAC;oCAuXF,MAAM,UACT,GAAG,UACH,GAAG,oBACO,GAAG,eACT,MAAM,eACN,MAAM,wBACG,OAAO,WACpB,GAAG,cACA,MAAM,UACV,GAAG,EAAE,KACX,OAAO,CAAC,GAAG,CAAC;sDAwsBF,MAAM,UACT,GAAG,UACH,MAAM,YACL,GAAG,KACX,OAAO,CAAC,GAAG,CAAC;0CAv+B8B,GAAG,KAAG,MAAM;mCAoBxC,MAAM,mBACJ,MAAM,EAAE,KACxB,MAAM;;AA4gCT,wBAQE"}
1
+ {"version":3,"file":"langChain.d.ts","sourceRoot":"","sources":["../../src/langChain.ts"],"names":[],"mappings":";0CAiD6C,GAAG,KAAG,IAAI;wBAqgB/C,MAAM,aACD,MAAM,gBACH,MAAM,SACb,GAAG,EAAE,kBACI,GAAG,UACX,GAAG,KACV,GAAG;sBAyBG,GAAG,UACF,MAAM,UACN,GAAG,eACC,QAAQ,GAAG,IAAI,KAC1B,OAAO,CAAC,GAAG,CAAC;oCAqXF,MAAM,UACT,GAAG,UACH,GAAG,oBACO,GAAG,eACT,MAAM,eACN,MAAM,wBACG,OAAO,WACpB,GAAG,cACA,MAAM,UACV,GAAG,EAAE,KACX,OAAO,CAAC,GAAG,CAAC;sDAosBF,MAAM,UACT,GAAG,UACH,MAAM,YACL,GAAG,KACX,OAAO,CAAC,GAAG,CAAC;0CAn+B8B,GAAG,KAAG,MAAM;mCAoBxC,MAAM,mBACJ,MAAM,EAAE,KACxB,MAAM;;AAwgCT,wBAQE"}
@@ -63,40 +63,68 @@ const recordCost = (cost) => {
63
63
  }
64
64
  };
65
65
  /**
66
- * Looks up the per-model pricing entry from `config.modelPricing`. Returns `null`
67
- * when pricing is not configured for the model, in which case cost recording
68
- * becomes a no-op (the call still succeeds pricing data is the host's
69
- * responsibility, not the agent's).
66
+ * Reads a per-model rate from the flat cost constants the integration defines
67
+ * in its `config.yaml` (exposed on the config object), e.g.
68
+ * `claude-opus-4-7-input-token-costs`. Returns `null` when the constant is
69
+ * absent or not numeric, in which case cost recording becomes a no-op (the
70
+ * call still succeeds — pricing is the integration's configuration concern).
70
71
  */
71
- const getModelPrice = (modelName, config) => {
72
- const pricing = config?.modelPricing;
73
- if (!pricing)
72
+ const getModelRate = (modelName, config, kind) => {
73
+ if (!config || !modelName)
74
74
  return null;
75
- return pricing[modelName] || null;
75
+ const rate = Number(config[`${modelName}-${kind}-costs`]);
76
+ return Number.isFinite(rate) ? rate : null;
76
77
  };
77
78
  /**
78
- * Computes USD cost for a text LLM call. `config.modelPricing[modelName]` is
79
- * expected to provide `input` and `output` rates in dollars per million tokens.
79
+ * Accumulates the per-model cost breakdown on `global.quota.byModel` so the
80
+ * agent can report input vs output spend per model to the UI. No-ops when the
81
+ * quota global is absent.
80
82
  */
81
- const calculateTextCost = (modelName, usage, config) => {
83
+ const recordModelBreakdown = (modelName, inputCost, outputCost, imageCost) => {
84
+ const quota = globalThis.quota;
85
+ if (!quota || !modelName)
86
+ return;
87
+ if (!quota.byModel || typeof quota.byModel !== "object")
88
+ quota.byModel = {};
89
+ const entry = (quota.byModel[modelName] ||= {
90
+ inputCost: 0,
91
+ outputCost: 0,
92
+ imageCost: 0,
93
+ });
94
+ if (Number.isFinite(inputCost) && inputCost > 0)
95
+ entry.inputCost += inputCost;
96
+ if (Number.isFinite(outputCost) && outputCost > 0)
97
+ entry.outputCost += outputCost;
98
+ if (Number.isFinite(imageCost) && imageCost > 0)
99
+ entry.imageCost += imageCost;
100
+ };
101
+ /**
102
+ * Computes and records the USD cost of a text LLM call from the
103
+ * `<model>-input-token-costs` / `<model>-output-token-costs` constants
104
+ * (dollars per million tokens). Updates both the session total
105
+ * (`quota.usedThisSession` / `quota.remaining`) and the per-model breakdown
106
+ * (`quota.byModel`).
107
+ */
108
+ const recordTextCost = (modelName, usage, config) => {
82
109
  if (!usage)
83
- return 0;
84
- const price = getModelPrice(modelName, config);
85
- if (!price)
86
- return 0;
87
- const inputCost = ((usage.input_tokens || 0) / 1_000_000) * (price.input || 0);
88
- const outputCost = ((usage.output_tokens || 0) / 1_000_000) * (price.output || 0);
89
- return inputCost + outputCost;
110
+ return;
111
+ const inputRate = getModelRate(modelName, config, "input-token") || 0;
112
+ const outputRate = getModelRate(modelName, config, "output-token") || 0;
113
+ const inputCost = ((usage.input_tokens || 0) / 1_000_000) * inputRate;
114
+ const outputCost = ((usage.output_tokens || 0) / 1_000_000) * outputRate;
115
+ recordCost(inputCost + outputCost);
116
+ recordModelBreakdown(modelName, inputCost, outputCost, 0);
90
117
  };
91
118
  /**
92
- * Computes USD cost for an image generation call. `config.modelPricing[modelName]`
93
- * is expected to provide a `perImage` rate in dollars.
119
+ * Computes and records the USD cost of an image generation call from the
120
+ * `<model>-image-costs` constant (dollars per generated image). Updates both
121
+ * the session total and the per-model breakdown.
94
122
  */
95
- const calculateImageCost = (modelName, numImages, config) => {
96
- const price = getModelPrice(modelName, config);
97
- if (!price)
98
- return 0;
99
- return (numImages || 0) * (price.perImage || 0);
123
+ const recordImageCost = (modelName, numImages, config) => {
124
+ const rate = getModelRate(modelName, config, "image") || 0;
125
+ const imageCost = (numImages || 0) * rate;
126
+ recordCost(imageCost);
127
+ recordModelBreakdown(modelName, 0, 0, imageCost);
100
128
  };
101
129
  /**
102
130
  * Extracts a normalized token-usage object from a single LangChain `invoke()`
@@ -564,7 +592,7 @@ const runAgent = async (agent, prompt, config, onProgress = null) => {
564
592
  throw err;
565
593
  }
566
594
  }
567
- recordCost(calculateTextCost(modelName, sumAgentResponseUsage(response), config));
595
+ recordTextCost(modelName, sumAgentResponseUsage(response), config);
568
596
  const endTime = Date.now();
569
597
  const duration = endTime - startTime;
570
598
  logger.log(null, logger.levels.info, `Agent "${agent.options?.name}" completed in ${Math.round(duration / 1000)} seconds.`);
@@ -1039,7 +1067,7 @@ const runPromptWithModel = async (modelName, config, prompt, onProgressReport, m
1039
1067
  throw err;
1040
1068
  }
1041
1069
  }
1042
- recordCost(calculateTextCost(modelName, streamUsage, config));
1070
+ recordTextCost(modelName, streamUsage, config);
1043
1071
  if (!rawContent) {
1044
1072
  throw errorHandlingHelper.create(constants.HTTP_CODES.INTERNAL_SERVER_ERROR, "LLM returned no content");
1045
1073
  }
@@ -1139,7 +1167,7 @@ const runPromptWithModel = async (modelName, config, prompt, onProgressReport, m
1139
1167
  throw err;
1140
1168
  }
1141
1169
  }
1142
- recordCost(calculateTextCost(modelName, extractUsageFromInvoke(response), config));
1170
+ recordTextCost(modelName, extractUsageFromInvoke(response), config);
1143
1171
  const rawContent = response?.content || response;
1144
1172
  // If not expecting JSON, return raw content directly
1145
1173
  if (!expectsJsonResponse) {
@@ -1247,7 +1275,7 @@ const generateImageWithOpenAI = async (modelName, config, prompt, options) => {
1247
1275
  }
1248
1276
  assertQuotaAvailable();
1249
1277
  const response = await openai.images.generate(requestParams);
1250
- recordCost(calculateImageCost(modelName, response.data?.length || n, config));
1278
+ recordImageCost(modelName, response.data?.length || n, config);
1251
1279
  // Format response based on number of images
1252
1280
  if (n === 1) {
1253
1281
  const imageData = response.data[0];
@@ -1364,7 +1392,7 @@ const generateImageWithGoogle = async (modelName, config, prompt, options) => {
1364
1392
  if (images.length === 0) {
1365
1393
  throw errorHandlingHelper.create(constants.HTTP_CODES.INTERNAL_SERVER_ERROR, `No images were generated by ${modelName}`);
1366
1394
  }
1367
- recordCost(calculateImageCost(modelName, images.length, config));
1395
+ recordImageCost(modelName, images.length, config);
1368
1396
  if (numberOfImages === 1 || images.length === 1) {
1369
1397
  return images[0];
1370
1398
  }
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "restricted"
5
5
  },
6
- "version": "1.1.0",
6
+ "version": "1.1.1",
7
7
  "description": "",
8
8
  "main": "dist/cjs/index.js",
9
9
  "module": "dist/esm/index.js",