tines 0.0.207 → 0.0.209

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 (2) hide show
  1. package/dist/index.js +161 -2
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4153,6 +4153,9 @@ function createApiClient(options) {
4153
4153
  `/api/v1/runs${query(filters)}`
4154
4154
  ),
4155
4155
  getUsage: (filters = {}) => get(`/api/v1/usage${query(filters)}`),
4156
+ getIssueUsage: (issue) => get(`/api/v1/usage${query({ mode: "issue", issue })}`),
4157
+ getUsageScope: (scope) => get(`/api/v1/usage${query({ scope })}`),
4158
+ getUsageEvidence: (filters) => get(`/api/v1/usage/evidence${query(filters)}`),
4156
4159
  getRun: (id2) => get(`/api/v1/runs/${id2}`),
4157
4160
  /**
4158
4161
  * The run's complete log (not the 256 KB tail `getRun` returns) as a
@@ -11009,9 +11012,11 @@ function usageAggregateLines(label, aggregate) {
11009
11012
  function usageEvidenceLines(id2, dimensions, accounting) {
11010
11013
  const dims = ["project", "workflow", "state", "outcome", "runner", "tier"].map((name2) => `${name2}=${dimensions[name2].name} [${dimensions[name2].id ?? "unknown"}]`).join(" \xB7 ");
11011
11014
  const diagnostics = Object.entries(accounting.diagnostics).filter(([, count]) => count > 0).map(([name2, count]) => `${name2}=${count}`).join(" \xB7 ");
11015
+ const tokens = ["input_tokens", "output_tokens", "cache_read_tokens", "cache_write_tokens"].map((name2) => `${name2}=${present(accounting.tokens[name2])}`).join(" \xB7 ");
11012
11016
  return [
11013
11017
  `Evidence ${id2}: ${dims}`,
11014
11018
  `Accounting ${id2}: ${accounting.status} \xB7 source ${accounting.source ?? "unavailable"} \xB7 exact cost ${accounting.cost_exact ?? "unavailable"}${accounting.pricing_reason ? ` \xB7 reason ${accounting.pricing_reason}` : ""}${diagnostics ? ` \xB7 diagnostics ${diagnostics}` : ""}`,
11019
+ `Tokens ${id2}: ${tokens} \xB7 invalid_tokens=${accounting.invalid_tokens.length ? accounting.invalid_tokens.join(",") : "none"}`,
11015
11020
  ...accounting.source === "calculated" ? [`Rate ${id2}: ${basisLine(accounting.basis)}`] : []
11016
11021
  ];
11017
11022
  }
@@ -12740,6 +12745,62 @@ function normalizeUrl(value) {
12740
12745
  }
12741
12746
 
12742
12747
  // src/commands/usage.ts
12748
+ function printEvidence(page) {
12749
+ console.log(
12750
+ `Usage evidence \xB7 ${page.kind} \xB7 ${page.population} \xB7 ${page.total_count} total \xB7 ${page.sort} ${page.direction}`
12751
+ );
12752
+ if (page.kind === "issues")
12753
+ table([
12754
+ ["ISSUE", "SPEND", "COVERAGE", "RUNS"],
12755
+ ...page.items.map((item) => [
12756
+ item.issue_ref ? `${item.issue_ref.project_name}/${item.issue_ref.number} ${item.issue_ref.title}` : item.issue_id ? `Unavailable issue (${item.issue_id})` : "Unknown issue",
12757
+ money(item.aggregate.cost_usd),
12758
+ item.aggregate.coverage,
12759
+ String(item.attempt_count)
12760
+ ])
12761
+ ]);
12762
+ else
12763
+ table([
12764
+ ["RUN", "ISSUE", "SPEND", "AT"],
12765
+ ...page.items.map((item) => {
12766
+ const run2 = item;
12767
+ return [
12768
+ run2.id,
12769
+ run2.issue_id,
12770
+ page.population === "pending" ? "Pending" : money(run2.usage_accounting?.cost ?? null),
12771
+ new Date(run2.ended_at ?? run2.created_at).toISOString()
12772
+ ];
12773
+ })
12774
+ ]);
12775
+ if (page.kind === "runs" && page.population === "finalized")
12776
+ for (const item of page.items) {
12777
+ if ("id" in item && "usage_accounting" in item && item.usage_dimensions)
12778
+ for (const line of usageEvidenceLines(
12779
+ item.id,
12780
+ item.usage_dimensions,
12781
+ item.usage_accounting
12782
+ ))
12783
+ console.log(line);
12784
+ }
12785
+ console.log(
12786
+ `Matching total: ${money(page.matching_total.cost_usd)} \xB7 ${page.attempt_count} attempts \xB7 ${page.pending_count} pending`
12787
+ );
12788
+ if (page.next_cursor) console.log(`Next cursor: ${page.next_cursor}`);
12789
+ }
12790
+ function printIssueReport(report) {
12791
+ const { issue } = report;
12792
+ console.log(
12793
+ `Usage \xB7 ${issue.issue_ref ? `${issue.issue_ref.project_name}/${issue.issue_ref.number}` : issue.issue_id} \xB7 Lifetime through now`
12794
+ );
12795
+ console.log(`as of ${new Date(report.cutoff).toISOString()} \xB7 direct retained attempts`);
12796
+ if (issue.attempt_count === 0) console.log("No agent runs");
12797
+ else {
12798
+ console.log(
12799
+ `${money(issue.aggregate.cost_usd)} \xB7 ${issue.aggregate.coverage} \xB7 ${issue.aggregate.finalized_run_count} finalized \xB7 ${issue.pending_count} pending at cutoff`
12800
+ );
12801
+ for (const line of usageAggregateLines("Lifetime", issue.aggregate)) console.log(line);
12802
+ }
12803
+ }
12743
12804
  var money = (value) => usageCostLabel(value);
12744
12805
  function printReport(report) {
12745
12806
  console.log(`Usage \xB7 ${report.filters.project ?? "All projects"} \xB7 ${report.by}`);
@@ -12798,7 +12859,12 @@ function register12(program3) {
12798
12859
  withCommon(
12799
12860
  program3.command("usage").description("Compare finalized run spend for a period").addOption(
12800
12861
  new Option("--window <window>", "today, 7d, or 30d").choices(["today", "7d", "30d"])
12801
- ).option("--from <bound>", "custom inclusive start (date or offset timestamp)").option("--to <bound>", "custom exclusive end (date or offset timestamp)").option("--project <name-or-id>", "project scope (including archived), or unknown").option("--workflow <name-or-id>", "workflow filter, or unknown").option("--state <id>", "starting state id (requires --workflow)").option("--runner <name-or-id>", "runner filter, or unknown").option("--tier <tier>", "tier filter").addOption(
12862
+ ).option("--from <bound>", "custom inclusive start (date or offset timestamp)").option("--to <bound>", "custom exclusive end (date or offset timestamp)").option("--issue <ref>", "direct issue lifetime through now").option("--scope <token>", "replay a frozen usage scope").addOption(new Option("--evidence <kind>", "list evidence").choices(["issues", "runs"])).option("--member <issue-id>", "narrow run evidence to one contributing issue").addOption(
12863
+ new Option("--population <population>", "finalized or pending").choices([
12864
+ "finalized",
12865
+ "pending"
12866
+ ])
12867
+ ).addOption(new Option("--sort <sort>", "evidence sort").choices(["cost", "time"])).addOption(new Option("--direction <direction>", "sort direction").choices(["asc", "desc"])).option("--cursor <token>", "evidence page cursor").option("--limit <count>", "evidence page size (1-100)").option("--all-pages", "fetch every evidence page").option("--project <name-or-id>", "project scope (including archived), or unknown").option("--workflow <name-or-id>", "workflow filter, or unknown").option("--state <id>", "starting state id (requires --workflow)").option("--runner <name-or-id>", "runner filter, or unknown").option("--tier <tier>", "tier filter").addOption(
12802
12868
  new Option("--outcome <outcome>", "recorded outcome").choices([
12803
12869
  "advanced",
12804
12870
  "stalled",
@@ -12814,7 +12880,100 @@ function register12(program3) {
12814
12880
  ).addOption(
12815
12881
  new Option("--by <dimension>", "group dimension").choices(["project", "workflow", "state", "outcome", "runner", "tier"]).default("workflow")
12816
12882
  )
12817
- ).action(async (opts) => {
12883
+ ).action(async (opts, command) => {
12884
+ const evidenceOnly = [
12885
+ opts.member,
12886
+ opts.population,
12887
+ opts.sort,
12888
+ opts.direction,
12889
+ opts.cursor,
12890
+ opts.limit,
12891
+ opts.allPages
12892
+ ];
12893
+ if (evidenceOnly.some((value) => value !== void 0) && !opts.evidence)
12894
+ throw new Error(
12895
+ "--member/--population/--sort/--direction/--cursor/--limit/--all-pages require --evidence"
12896
+ );
12897
+ if (opts.evidence && !opts.scope) throw new Error("--evidence requires --scope");
12898
+ if (opts.scope) {
12899
+ const reportFlags = [
12900
+ opts.issue,
12901
+ opts.window,
12902
+ opts.from,
12903
+ opts.to,
12904
+ opts.project,
12905
+ opts.workflow,
12906
+ opts.state,
12907
+ opts.runner,
12908
+ opts.tier,
12909
+ opts.outcome,
12910
+ opts.accountingStatus
12911
+ ];
12912
+ if (command.getOptionValueSource("by") === "cli") reportFlags.push(opts.by);
12913
+ if (reportFlags.some((value) => value !== void 0))
12914
+ throw new Error("--scope cannot be combined with report options");
12915
+ const api2 = client(opts);
12916
+ if (!opts.evidence) {
12917
+ const report2 = await api2.getUsageScope(opts.scope);
12918
+ if (opts.json) return printJson(report2);
12919
+ if (report2.mode === "issue") printIssueReport(report2);
12920
+ else printReport(report2);
12921
+ return;
12922
+ }
12923
+ let cursor = opts.cursor;
12924
+ const seenCursors = /* @__PURE__ */ new Set();
12925
+ const seenIds = /* @__PURE__ */ new Set();
12926
+ let combined = null;
12927
+ do {
12928
+ if (cursor && seenCursors.has(cursor))
12929
+ throw new Error("Evidence pagination repeated a cursor");
12930
+ if (cursor) seenCursors.add(cursor);
12931
+ const page = await api2.getUsageEvidence({
12932
+ scope: opts.scope,
12933
+ kind: opts.evidence,
12934
+ population: opts.population,
12935
+ member: opts.member,
12936
+ sort: opts.sort,
12937
+ direction: opts.direction,
12938
+ limit: opts.limit ? Number(opts.limit) : void 0,
12939
+ cursor
12940
+ });
12941
+ for (const item of page.items) {
12942
+ const id2 = "id" in item ? item.id : item.issue_id ?? "unknown";
12943
+ if (seenIds.has(id2)) throw new Error(`Evidence pagination repeated ${id2}`);
12944
+ seenIds.add(id2);
12945
+ }
12946
+ combined = combined ? { ...page, items: [...combined.items, ...page.items], previous_cursor: null } : page;
12947
+ cursor = page.next_cursor ?? void 0;
12948
+ } while (opts.allPages && cursor);
12949
+ if (!combined) throw new Error("Evidence response was empty");
12950
+ if (opts.json) return printJson(combined);
12951
+ printEvidence(combined);
12952
+ return;
12953
+ }
12954
+ if (opts.issue) {
12955
+ const contradictions = [
12956
+ opts.window,
12957
+ opts.from,
12958
+ opts.to,
12959
+ opts.project,
12960
+ opts.workflow,
12961
+ opts.state,
12962
+ opts.runner,
12963
+ opts.tier,
12964
+ opts.outcome,
12965
+ opts.accountingStatus
12966
+ ];
12967
+ if (command.getOptionValueSource("by") === "cli") contradictions.push(opts.by);
12968
+ if (contradictions.some((value) => value !== void 0))
12969
+ throw new Error("--issue cannot be combined with period or filter options");
12970
+ const api2 = client(opts);
12971
+ const issueId = isUsageIdentity(opts.issue, "iss") ? opts.issue : (await resolveIssue(api2, opts.issue)).id;
12972
+ const report2 = await api2.getIssueUsage(issueId);
12973
+ if (opts.json) return printJson(report2);
12974
+ printIssueReport(report2);
12975
+ return;
12976
+ }
12818
12977
  if (opts.from === void 0 !== (opts.to === void 0))
12819
12978
  throw new Error("--from and --to are required together");
12820
12979
  if (opts.window && opts.from) throw new Error("--window cannot be combined with --from/--to");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tines",
3
- "version": "0.0.207",
3
+ "version": "0.0.209",
4
4
  "description": "CLI for Tines, an orchestration layer for AI agents",
5
5
  "repository": {
6
6
  "type": "git",