@voxgig/sdkgen 4.2.6 → 4.2.8

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.
Files changed (64) hide show
  1. package/bin/voxgig-sdkgen +1 -1
  2. package/dist/helpers/naming.js +5 -4
  3. package/dist/helpers/naming.js.map +1 -1
  4. package/dist/tsconfig.tsbuildinfo +1 -1
  5. package/package.json +1 -1
  6. package/project/.sdk/model/feature/cost.aon +43 -0
  7. package/project/.sdk/model/feature/feature-index.aon +1 -0
  8. package/project/.sdk/src/cmp/js/fragment/EntityCreateOp.fragment.js +2 -1
  9. package/project/.sdk/src/cmp/js/fragment/EntityListOp.fragment.js +2 -1
  10. package/project/.sdk/src/cmp/js/fragment/EntityLoadOp.fragment.js +2 -1
  11. package/project/.sdk/src/cmp/js/fragment/EntityRemoveOp.fragment.js +2 -1
  12. package/project/.sdk/src/cmp/js/fragment/EntityUpdateOp.fragment.js +2 -1
  13. package/project/.sdk/src/cmp/py/ReadmeExamplesTest_py.ts +15 -5
  14. package/project/.sdk/src/cmp/ts/fragment/EntityCreateOp.fragment.ts +2 -1
  15. package/project/.sdk/src/cmp/ts/fragment/EntityListOp.fragment.ts +2 -1
  16. package/project/.sdk/src/cmp/ts/fragment/EntityLoadOp.fragment.ts +2 -1
  17. package/project/.sdk/src/cmp/ts/fragment/EntityRemoveOp.fragment.ts +2 -1
  18. package/project/.sdk/src/cmp/ts/fragment/EntityUpdateOp.fragment.ts +2 -1
  19. package/project/.sdk/tm/c/core/sdk.h +1 -0
  20. package/project/.sdk/tm/c/feature/cost.c +549 -0
  21. package/project/.sdk/tm/c/src/feature/cost/.gitkeep +0 -0
  22. package/project/.sdk/tm/clojure/src/feature/cost/.gitkeep +0 -0
  23. package/project/.sdk/tm/cpp/feature/cost.hpp +419 -0
  24. package/project/.sdk/tm/cpp/src/feature/cost/.gitkeep +0 -0
  25. package/project/.sdk/tm/csharp/feature/CostFeature.cs +518 -0
  26. package/project/.sdk/tm/csharp/src/feature/cost/.gitkeep +0 -0
  27. package/project/.sdk/tm/dart/lib/feature/cost/CostFeature.dart +446 -0
  28. package/project/.sdk/tm/dart/src/feature/cost/.gitkeep +0 -0
  29. package/project/.sdk/tm/elixir/lib/projectname/feature/cost.ex +442 -0
  30. package/project/.sdk/tm/elixir/src/feature/cost/.gitkeep +0 -0
  31. package/project/.sdk/tm/go/feature/cost_feature.go +424 -0
  32. package/project/.sdk/tm/go/src/feature/cost/.gitkeep +0 -0
  33. package/project/.sdk/tm/go-cli/src/feature/cost/.gitkeep +0 -0
  34. package/project/.sdk/tm/go-mcp/src/feature/cost/.gitkeep +0 -0
  35. package/project/.sdk/tm/java/feature/CostFeature.java +431 -0
  36. package/project/.sdk/tm/java/src/feature/cost/.gitkeep +0 -0
  37. package/project/.sdk/tm/js/src/feature/cost/CostFeature.js +413 -0
  38. package/project/.sdk/tm/kotlin/feature/CostFeature.kt +430 -0
  39. package/project/.sdk/tm/kotlin/src/feature/cost/.gitkeep +0 -0
  40. package/project/.sdk/tm/lean/src/feature/cost/.gitkeep +0 -0
  41. package/project/.sdk/tm/lua/feature/cost_feature.lua +457 -0
  42. package/project/.sdk/tm/lua/src/feature/cost/.gitkeep +0 -0
  43. package/project/.sdk/tm/ocaml/src/feature/cost/.gitkeep +0 -0
  44. package/project/.sdk/tm/perl/feature/cost_feature.pm +446 -0
  45. package/project/.sdk/tm/perl/src/feature/cost/.gitkeep +0 -0
  46. package/project/.sdk/tm/php/feature/CostFeature.php +439 -0
  47. package/project/.sdk/tm/php/feature/TestFeature.php +31 -1
  48. package/project/.sdk/tm/php/src/feature/cost/.gitkeep +0 -0
  49. package/project/.sdk/tm/py/pkg/feature/cost_feature.py +381 -0
  50. package/project/.sdk/tm/py/src/feature/cost/.gitkeep +0 -0
  51. package/project/.sdk/tm/py-data/src/feature/cost/.gitkeep +0 -0
  52. package/project/.sdk/tm/rb/feature/cost_feature.rb +386 -0
  53. package/project/.sdk/tm/rb/src/feature/cost/.gitkeep +0 -0
  54. package/project/.sdk/tm/rust/feature/cost.rs +467 -0
  55. package/project/.sdk/tm/rust/src/feature/cost/.gitkeep +0 -0
  56. package/project/.sdk/tm/scala/feature/CostFeature.scala +388 -0
  57. package/project/.sdk/tm/scala/src/feature/cost/.gitkeep +0 -0
  58. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/feature/CostFeature.swift +425 -0
  59. package/project/.sdk/tm/swift/src/feature/cost/.gitkeep +0 -0
  60. package/project/.sdk/tm/ts/src/feature/cost/CostFeature.ts +415 -0
  61. package/project/.sdk/tm/zig/feature/cost.zig +457 -0
  62. package/project/.sdk/tm/zig/src/feature/cost/.gitkeep +0 -0
  63. package/project/sdkgen-package.json +2 -1
  64. package/src/helpers/naming.ts +5 -4
@@ -0,0 +1,431 @@
1
+ package JAVAPACKAGE.feature;
2
+
3
+ import java.util.Arrays;
4
+ import java.util.LinkedHashMap;
5
+ import java.util.List;
6
+ import java.util.Map;
7
+ import java.util.function.Consumer;
8
+
9
+ import JAVAPACKAGE.core.Context;
10
+ import JAVAPACKAGE.core.SdkClient;
11
+ import JAVAPACKAGE.core.Utility;
12
+ import JAVAPACKAGE.utility.struct.Struct;
13
+
14
+ // Cost tracking and spend budget. Uses BOTH seams, which is the point of
15
+ // the feature: money is spent per HTTP ATTEMPT (a retried call is charged
16
+ // again, because the upstream API charges it again), but it is owed by an
17
+ // OPERATION. So the transport wrap prices each attempt, and preDone
18
+ // attributes the running total to `<entity>.<op>` and to the caller (the
19
+ // per-call ctrl actor, the same actor the audit feature records).
20
+ //
21
+ // The price of an attempt comes from the first source that answers: a
22
+ // response header (`header` x `perUnit`), the rate table (`rates`, keyed
23
+ // `<entity>.<op>` / `<op>` / `*`), then the flat `unit`. A body figure
24
+ // (`path` x `perUnit`, e.g. "usage.total_tokens") is read at preDone
25
+ // instead, from the already-parsed result, and describes the whole call, so
26
+ // it REPLACES the per-attempt estimate rather than adding to it.
27
+ //
28
+ // `budget` caps total spend. With `onBudget: "deny"` a further operation is
29
+ // refused at prePoint (via ctx.out["point"], which makePoint surfaces),
30
+ // before an endpoint is resolved and before anything reaches the network.
31
+ //
32
+ // ORDER MATTERS. Cost must sit INSIDE the cache, or a response served from
33
+ // cache is charged for money that was never spent. The default (map) order
34
+ // puts cache innermost and cost outside it, so activate them in list form
35
+ // with cost first.
36
+ public class CostFeature extends BaseFeature {
37
+
38
+ private SdkClient client;
39
+ private Map<String, Object> options;
40
+ private int seq = 0;
41
+
42
+ // Aggregates (mirrors the ts client._cost record).
43
+ public String currency = "USD";
44
+ public CostTotal total = new CostTotal();
45
+ public Map<String, CostBucket> ops = new LinkedHashMap<>();
46
+ public Map<String, CostBucket> actors = new LinkedHashMap<>();
47
+ public CostBudget budget = new CostBudget();
48
+ public Map<String, Object> last = null;
49
+
50
+ public static class CostTotal {
51
+ public int calls = 0;
52
+ public int attempts = 0;
53
+ public double amount = 0.0;
54
+ public double reported = 0.0;
55
+ public double estimated = 0.0;
56
+ }
57
+
58
+ public static class CostBucket {
59
+ public int calls = 0;
60
+ public double amount = 0.0;
61
+ }
62
+
63
+ public static class CostBudget {
64
+ public double limit = 0.0;
65
+ public double spent = 0.0;
66
+ public double remaining = 0.0;
67
+ public boolean exceeded = false;
68
+ }
69
+
70
+ // Per-operation accumulator, carried on ctx.out between the transport wrap
71
+ // and preDone.
72
+ private static class CostPending {
73
+ int attempts = 0;
74
+ double amount = 0.0;
75
+ double reported = 0.0;
76
+ double estimated = 0.0;
77
+ String source = "none";
78
+ // Set by prePoint. Its absence means the call never entered the pipeline
79
+ // (direct/graphql), so charge commits the spend itself.
80
+ boolean piped = false;
81
+ }
82
+
83
+ private static final String COST_PENDING_KEY = "cost_pending";
84
+
85
+ public CostFeature() {
86
+ super("cost", "0.0.1", true);
87
+ }
88
+
89
+ @Override
90
+ public void init(Context ctx, Map<String, Object> options) {
91
+ this.client = ctx.client;
92
+ this.options = options;
93
+ this.active = FeatureOptions.foptBool(options, "active", false);
94
+ this.seq = 0;
95
+
96
+ double limit = FeatureOptions.foptNum(options, "budget", 0.0);
97
+
98
+ this.currency = FeatureOptions.foptStr(options, "currency", "USD");
99
+ this.total = new CostTotal();
100
+ this.ops = new LinkedHashMap<>();
101
+ this.actors = new LinkedHashMap<>();
102
+ this.budget = new CostBudget();
103
+ this.budget.limit = limit;
104
+ this.budget.remaining = limit;
105
+ this.last = null;
106
+
107
+ if (!this.active) {
108
+ return;
109
+ }
110
+
111
+ final Utility.FetcherFn inner = ctx.utility.fetcher;
112
+
113
+ ctx.utility.fetcher = (ctx2, url, fetchdef) -> charge(ctx2, url, fetchdef, inner);
114
+ }
115
+
116
+ // The budget gate. Runs before endpoint resolution, so a refused call
117
+ // costs nothing at all.
118
+ @Override
119
+ public void prePoint(Context ctx) {
120
+ if (!this.active) {
121
+ return;
122
+ }
123
+
124
+ // Mark the context as running through the pipeline, so charge knows a
125
+ // preDone is coming and does not commit the spend itself.
126
+ Object raw = ctx.out.get(COST_PENDING_KEY);
127
+ CostPending pending = (raw instanceof CostPending) ? (CostPending) raw : new CostPending();
128
+ pending.piped = true;
129
+ ctx.out.put(COST_PENDING_KEY, pending);
130
+
131
+ double limit = this.budget.limit;
132
+ if (limit <= 0.0 || this.total.amount < limit) {
133
+ return;
134
+ }
135
+
136
+ this.budget.exceeded = true;
137
+
138
+ if (!"deny".equals(FeatureOptions.foptStr(this.options, "onBudget", "warn"))) {
139
+ return;
140
+ }
141
+
142
+ RuntimeException err = ctx.makeError("cost_budget",
143
+ "Cost budget of " + numstr(limit) + " " + this.currency + " is spent ("
144
+ + numstr(this.total.amount) + " " + this.currency + " used)");
145
+
146
+ // Short-circuit endpoint resolution; makePoint surfaces this error
147
+ // before any network activity.
148
+ ctx.out.put("point", err);
149
+ }
150
+
151
+ private Object charge(Context ctx, String url, Map<String, Object> fetchdef,
152
+ Utility.FetcherFn inner) {
153
+
154
+ // A throwing transport still costs an attempt. Without this, a run of
155
+ // connection-level failures under `retry` (which catches and tries
156
+ // again) would be charged nothing at all, and an onBudget "deny" ceiling
157
+ // could never stop it.
158
+ Object res = null;
159
+ RuntimeException threw = null;
160
+ try {
161
+ res = inner.fetch(ctx, url, fetchdef);
162
+ } catch (RuntimeException ex) {
163
+ threw = ex;
164
+ }
165
+
166
+ double[] priced = new double[1];
167
+ String source = price(ctx, res, priced);
168
+
169
+ Object raw = ctx.out.get(COST_PENDING_KEY);
170
+ CostPending pending = (raw instanceof CostPending) ? (CostPending) raw : new CostPending();
171
+ ctx.out.put(COST_PENDING_KEY, pending);
172
+
173
+ pending.attempts++;
174
+
175
+ // Accumulated here, committed once at preDone. Adding each attempt to
176
+ // the running total and then subtracting it again when a body figure
177
+ // supersedes it loses precision to catastrophic cancellation.
178
+ //
179
+ // Reported and estimated are kept apart per ATTEMPT: a 503 priced from
180
+ // the rate table followed by a 200 carrying the cost header is part
181
+ // estimate, part reported, and collapsing both into the final attempt's
182
+ // category would corrupt the split.
183
+ pending.amount += priced[0];
184
+ if ("header".equals(source) || "body".equals(source)) {
185
+ pending.reported += priced[0];
186
+ } else {
187
+ pending.estimated += priced[0];
188
+ }
189
+ pending.source = source;
190
+
191
+ this.total.attempts++;
192
+
193
+ // direct() and graphql() reach the transport without dispatching any
194
+ // pipeline hooks, so there is no prePoint to gate on and no preDone to
195
+ // commit. Their spend is committed here, or it would never be counted.
196
+ if (!pending.piped) {
197
+ commit(ctx, pending, "_", "direct");
198
+ ctx.out.remove(COST_PENDING_KEY);
199
+ }
200
+
201
+ if (threw != null) {
202
+ throw threw;
203
+ }
204
+
205
+ return res;
206
+ }
207
+
208
+ // Attribute the operation's spend once the call is finished.
209
+ @Override
210
+ public void preDone(Context ctx) {
211
+ finish(ctx, true);
212
+ }
213
+
214
+ // A failed operation still spent the money. When the pipeline errors,
215
+ // preDone never runs, so without this the attempts are counted and the
216
+ // spend is not, and a budget could never see the cost of a failed call.
217
+ // Whichever hook fires first consumes the pending entry, so it commits
218
+ // exactly once.
219
+ @Override
220
+ public void preUnexpected(Context ctx) {
221
+ finish(ctx, false);
222
+ }
223
+
224
+ private void finish(Context ctx, boolean done) {
225
+ if (!this.active) {
226
+ return;
227
+ }
228
+ Object raw = ctx.out.get(COST_PENDING_KEY);
229
+ if (!(raw instanceof CostPending)) {
230
+ return;
231
+ }
232
+ ctx.out.remove(COST_PENDING_KEY);
233
+ CostPending pending = (CostPending) raw;
234
+
235
+ // A FAILED operation that made no attempt never reached the network:
236
+ // prePoint creates the pending entry to mark the context as piped, and
237
+ // then the budget gate refuses the call (rbac, or an unresolvable
238
+ // endpoint, short-circuits just as early). Committing it would count a
239
+ // call that never happened and file a zero-amount record as `last`.
240
+ //
241
+ // A SUCCEEDED operation that made no attempt is the opposite case: it was
242
+ // served from the cache. That is a real call, and the fact that it cost
243
+ // nothing is the whole point of ordering cost inside the cache.
244
+ if (!done && 0 == pending.attempts) {
245
+ return;
246
+ }
247
+
248
+ String entity = (ctx.op != null && ctx.op.entity != null && !"".equals(ctx.op.entity))
249
+ ? ctx.op.entity : "_";
250
+ String opname = (ctx.op != null && ctx.op.name != null && !"".equals(ctx.op.name))
251
+ ? ctx.op.name : "_";
252
+
253
+ commit(ctx, pending, entity, opname);
254
+ }
255
+
256
+ // Commit one operation's spend: totals, budget, per-op and per-actor
257
+ // attribution, and the record. Shared by finish and the raw-request path in
258
+ // charge, which has no preDone to reach.
259
+ @SuppressWarnings("unchecked")
260
+ private void commit(Context ctx, CostPending pending, String entity, String opname) {
261
+ double amount = pending.amount;
262
+ double reported = pending.reported;
263
+ double estimated = pending.estimated;
264
+ String source = pending.source;
265
+
266
+ // A body figure prices the whole call, so it replaces the per-attempt
267
+ // estimate rather than adding to it, and being server-stated the whole
268
+ // amount counts as reported.
269
+ Double body = bodyAmount(ctx);
270
+ if (body != null) {
271
+ amount = body;
272
+ reported = body;
273
+ estimated = 0.0;
274
+ source = "body";
275
+ }
276
+
277
+ spend(amount, reported, estimated);
278
+
279
+ String actor = "anonymous";
280
+ String optActor = FeatureOptions.foptStr(this.options, "actor", "");
281
+ if (!"".equals(optActor)) {
282
+ actor = optActor;
283
+ }
284
+ if (ctx.ctrl != null && ctx.ctrl.actor != null && !"".equals(ctx.ctrl.actor)) {
285
+ actor = ctx.ctrl.actor;
286
+ }
287
+
288
+ this.total.calls++;
289
+ bump(this.ops, entity + "." + opname, amount);
290
+ bump(this.actors, actor, amount);
291
+
292
+ this.seq++;
293
+ Map<String, Object> record = new LinkedHashMap<>();
294
+ record.put("seq", this.seq);
295
+ record.put("entity", entity);
296
+ record.put("op", opname);
297
+ record.put("actor", actor);
298
+ record.put("amount", amount);
299
+ record.put("currency", this.currency);
300
+ record.put("source", source);
301
+ record.put("attempts", pending.attempts);
302
+ this.last = record;
303
+
304
+ if (this.options.get("sink") instanceof Consumer) {
305
+ ((Consumer<Map<String, Object>>) this.options.get("sink")).accept(record);
306
+ }
307
+ }
308
+
309
+ // Price one attempt: a reported header figure, else the rate table, else
310
+ // the flat unit. The amount is returned through `out[0]`, the source by
311
+ // return value.
312
+ private String price(Context ctx, Object res, double[] out) {
313
+ String header = FeatureOptions.foptStr(this.options, "header", "");
314
+ if (!"".equals(header)) {
315
+ String raw = FeatureOptions.fresHeader(res, header);
316
+ Double n = num(raw);
317
+ if (n != null) {
318
+ out[0] = n * perUnit();
319
+ return "header";
320
+ }
321
+ }
322
+
323
+ Double rate = rate(ctx);
324
+ if (rate != null) {
325
+ out[0] = rate;
326
+ return "table";
327
+ }
328
+
329
+ double unit = FeatureOptions.foptNum(this.options, "unit", 0.0);
330
+ if (0.0 != unit) {
331
+ out[0] = unit;
332
+ return "unit";
333
+ }
334
+
335
+ out[0] = 0.0;
336
+ return "none";
337
+ }
338
+
339
+ // The rate table uses the same lookup grammar as rbac's rules:
340
+ // `<entity>.<op>`, then `<op>`, then `*`.
341
+ private Double rate(Context ctx) {
342
+ Map<String, Object> rates = FeatureOptions.foptMap(this.options, "rates");
343
+ if (rates == null || rates.isEmpty()) {
344
+ return null;
345
+ }
346
+
347
+ String entity = "";
348
+ if (ctx.entity != null) {
349
+ entity = ctx.entity.getName();
350
+ } else if (ctx.op != null && ctx.op.entity != null) {
351
+ entity = ctx.op.entity;
352
+ }
353
+ String opname = (ctx.op != null && ctx.op.name != null) ? ctx.op.name : "";
354
+
355
+ for (String key : new String[] { entity + "." + opname, opname, "*" }) {
356
+ Object val = rates.get(key);
357
+ if (val instanceof Number) {
358
+ return ((Number) val).doubleValue();
359
+ }
360
+ }
361
+ return null;
362
+ }
363
+
364
+ // A usage figure from the parsed result body, priced by perUnit. Read
365
+ // here, not at the transport seam, because the body is one-shot.
366
+ private Double bodyAmount(Context ctx) {
367
+ String path = FeatureOptions.foptStr(this.options, "path", "");
368
+ if ("".equals(path) || ctx.result == null || ctx.result.body == null) {
369
+ return null;
370
+ }
371
+ List<Object> segs = Arrays.asList((Object[]) path.split("\\."));
372
+ Object val = Struct.getpath(ctx.result.body, segs);
373
+ Double n = (val instanceof Number) ? ((Number) val).doubleValue() : num(val);
374
+ if (n == null) {
375
+ return null;
376
+ }
377
+ return n * perUnit();
378
+ }
379
+
380
+ private void spend(double amount, double reported, double estimated) {
381
+ this.total.amount += amount;
382
+ this.total.reported += reported;
383
+ this.total.estimated += estimated;
384
+
385
+ double limit = this.budget.limit;
386
+ this.budget.spent = this.total.amount;
387
+ if (limit > 0.0) {
388
+ this.budget.remaining = Math.max(0.0, limit - this.total.amount);
389
+ if (this.total.amount >= limit) {
390
+ this.budget.exceeded = true;
391
+ }
392
+ } else {
393
+ this.budget.remaining = 0.0;
394
+ }
395
+ }
396
+
397
+ private void bump(Map<String, CostBucket> bucket, String key, double amount) {
398
+ CostBucket entry = bucket.get(key);
399
+ if (entry == null) {
400
+ entry = new CostBucket();
401
+ bucket.put(key, entry);
402
+ }
403
+ entry.calls++;
404
+ entry.amount += amount;
405
+ }
406
+
407
+ private Double num(Object val) {
408
+ if (val instanceof Number) {
409
+ return ((Number) val).doubleValue();
410
+ }
411
+ if (val instanceof String) {
412
+ try {
413
+ return Double.parseDouble(((String) val).trim());
414
+ } catch (NumberFormatException e) {
415
+ return null;
416
+ }
417
+ }
418
+ return null;
419
+ }
420
+
421
+ private double perUnit() {
422
+ return FeatureOptions.foptNum(this.options, "perUnit", 0.0);
423
+ }
424
+
425
+ // Render a money amount without an exponent or trailing zeros.
426
+ private String numstr(double n) {
427
+ String s = String.format("%.10f", n);
428
+ s = s.replaceAll("0+$", "").replaceAll("\\.$", "");
429
+ return "".equals(s) ? "0" : s;
430
+ }
431
+ }
File without changes