baro-ai 0.34.0 → 0.36.0

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.
package/dist/cli.mjs CHANGED
@@ -7672,17 +7672,6 @@ var AgenticEnvironment = class {
7672
7672
  this.isActive = false;
7673
7673
  }
7674
7674
  };
7675
- var OpenAIInferenceRunner = class {
7676
- constructor() {
7677
- this.runtime = new OpenAIResponses();
7678
- }
7679
- async *run(context, model, signal) {
7680
- const response = await this.runtime.infer(new InferenceRequest(model, context));
7681
- for (const item of response.contextItems) {
7682
- yield item;
7683
- }
7684
- }
7685
- };
7686
7675
  var Gpt55 = class {
7687
7676
  constructor() {
7688
7677
  this.specification = {
@@ -8560,6 +8549,7 @@ function markStoryPassed(prd, storyId, durationSecs) {
8560
8549
  )
8561
8550
  };
8562
8551
  }
8552
+ var BARO_COAUTHOR_TRAILER = "Co-Authored-By: baro <285254893+baro-rs@users.noreply.github.com>";
8563
8553
  function buildDefaultStoryPrompt(story) {
8564
8554
  const acceptance = story.acceptance.length ? story.acceptance.map((a, i) => `${i + 1}. ${a}`).join("\n") : "(none specified)";
8565
8555
  const tests = story.tests.length ? story.tests.map((t) => `- ${t}`).join("\n") : "(no test commands specified)";
@@ -8593,7 +8583,18 @@ function buildDefaultStoryPrompt(story) {
8593
8583
  " - If pyproject.toml or requirements.txt: ensure code is import-clean",
8594
8584
  " - Otherwise: ensure linting/typecheck passes",
8595
8585
  "",
8596
- "When done with the story, commit your changes with a clear message."
8586
+ "When done with the story, commit your changes with a clear message.",
8587
+ "",
8588
+ "COMMIT MESSAGE TRAILER (mandatory):",
8589
+ "Every commit you create as part of this story MUST end with a blank line",
8590
+ "followed by this exact trailer line \u2014 no edits, no surrounding text:",
8591
+ "",
8592
+ ` ${BARO_COAUTHOR_TRAILER}`,
8593
+ "",
8594
+ 'Use `git commit -m "\u2026" -m "" -m "' + BARO_COAUTHOR_TRAILER + '"` so the',
8595
+ "trailer lands on its own paragraph at the bottom (git collapses the empty",
8596
+ "middle `-m` to a blank line between the subject and the trailer). This",
8597
+ "attributes the commit to the baro account in the contributors view."
8597
8598
  ].join("\n");
8598
8599
  }
8599
8600
 
@@ -9876,6 +9877,58 @@ function extractVerdictJson(text) {
9876
9877
  throw new Error(`unbalanced JSON object in critic response: ${trimmed.slice(0, 200)}`);
9877
9878
  }
9878
9879
 
9880
+ // ../baro-orchestrator/src/planning/openai-runtime.ts
9881
+ var runtime = new OpenAIResponses();
9882
+ async function runInferenceRound(context, model) {
9883
+ const response = await runtime.infer(new InferenceRequest(model, context));
9884
+ return {
9885
+ items: response.contextItems,
9886
+ usage: response.tokenUsage
9887
+ };
9888
+ }
9889
+ var UsageAccumulator = class {
9890
+ input = 0;
9891
+ output = 0;
9892
+ total = 0;
9893
+ cached = 0;
9894
+ reasoning = 0;
9895
+ rounds = 0;
9896
+ add(usage) {
9897
+ if (!usage) return;
9898
+ this.rounds += 1;
9899
+ this.input += usage.inputTokens ?? 0;
9900
+ this.output += usage.outputTokens ?? 0;
9901
+ this.total += usage.totalTokens ?? 0;
9902
+ this.cached += usage.inputTokenDetails?.cached_tokens ?? 0;
9903
+ this.reasoning += usage.outputTokenDetails?.reasoning_tokens ?? 0;
9904
+ }
9905
+ get isEmpty() {
9906
+ return this.rounds === 0;
9907
+ }
9908
+ /**
9909
+ * Plain-object snapshot suitable for embedding in
9910
+ * `AgentResultItem.usage` (which is typed `any` to allow per-
9911
+ * provider shapes). Keys are snake_case to line up with what the
9912
+ * Claude side's stream-json mapper produces from Anthropic
9913
+ * usage frames.
9914
+ */
9915
+ toJSON() {
9916
+ return {
9917
+ input_tokens: this.input,
9918
+ output_tokens: this.output,
9919
+ total_tokens: this.total,
9920
+ cached_input_tokens: this.cached,
9921
+ reasoning_tokens: this.reasoning,
9922
+ rounds: this.rounds
9923
+ };
9924
+ }
9925
+ /** One-line summary for the stderr / log path. */
9926
+ summary() {
9927
+ if (this.isEmpty) return "(no token usage reported)";
9928
+ return `${this.total} total tokens (${this.input} in, ${this.output} out${this.cached ? `, ${this.cached} cached` : ""}${this.reasoning ? `, ${this.reasoning} reasoning` : ""}) across ${this.rounds} round(s)`;
9929
+ }
9930
+ };
9931
+
9879
9932
  // ../baro-orchestrator/src/participants/critic-openai.ts
9880
9933
  function pickModel(name) {
9881
9934
  switch (name) {
@@ -9896,7 +9949,6 @@ function pickModel(name) {
9896
9949
  var CriticOpenAI = class extends BaroParticipant {
9897
9950
  opts;
9898
9951
  model;
9899
- runner = new OpenAIInferenceRunner();
9900
9952
  emissions = /* @__PURE__ */ new Map();
9901
9953
  turnCount = /* @__PURE__ */ new Map();
9902
9954
  pending = /* @__PURE__ */ new Set();
@@ -9967,9 +10019,12 @@ var CriticOpenAI = class extends BaroParticipant {
9967
10019
  const userPrompt = buildEvalPrompt(criteria, resultText);
9968
10020
  const context = ModelContext.create("critic").addContextItem(SystemMessageItem.create(VERDICT_SYSTEM_PROMPT)).addContextItem(UserMessageItem.create(userPrompt));
9969
10021
  try {
10022
+ const round = await runInferenceRound(context, this.model);
10023
+ const usage = new UsageAccumulator();
10024
+ usage.add(round.usage);
9970
10025
  let assistantText = "";
9971
- for await (const item of this.runner.run(context, this.model)) {
9972
- if (item.type === "message" && item.role === "assistant") {
10026
+ for (const item of round.items) {
10027
+ if (item.type === "message") {
9973
10028
  const json = item.toJSON();
9974
10029
  assistantText += json.content?.[0]?.text ?? "";
9975
10030
  }
@@ -9977,6 +10032,8 @@ var CriticOpenAI = class extends BaroParticipant {
9977
10032
  if (!assistantText.trim()) {
9978
10033
  throw new Error("OpenAI returned empty assistant text");
9979
10034
  }
10035
+ process.stderr.write(`[critic-openai] ${usage.summary()}
10036
+ `);
9980
10037
  const verdictJson = extractVerdictJson(assistantText);
9981
10038
  const parsed = JSON.parse(verdictJson);
9982
10039
  return {
@@ -10365,6 +10422,8 @@ var Finalizer = class extends BaroParticipant {
10365
10422
  lines.push("---");
10366
10423
  lines.push("");
10367
10424
  lines.push("\u{1F916} Plan. Parallelize. Review. Ship. \u2014 opened by baro");
10425
+ lines.push("");
10426
+ lines.push(BARO_COAUTHOR_TRAILER);
10368
10427
  return lines.join("\n");
10369
10428
  }
10370
10429
  async hasGhBinary() {
@@ -11266,7 +11325,6 @@ var OpenAIStoryAgent = class extends BaroParticipant {
11266
11325
  spec;
11267
11326
  opts;
11268
11327
  model;
11269
- runner = new OpenAIInferenceRunner();
11270
11328
  tools;
11271
11329
  envRef = null;
11272
11330
  currentPhase = "idle";
@@ -11381,6 +11439,7 @@ var OpenAIStoryAgent = class extends BaroParticipant {
11381
11439
  for (let turn = 1; turn <= this.spec.maxTurns; turn++) {
11382
11440
  const turnResult = await this.runOneTurn(context);
11383
11441
  context = turnResult.context;
11442
+ const usageJson = turnResult.usage.isEmpty ? null : turnResult.usage.toJSON();
11384
11443
  this.envRef?.deliverBusEvent(
11385
11444
  this,
11386
11445
  new AgentResultItem(
@@ -11390,14 +11449,17 @@ var OpenAIStoryAgent = class extends BaroParticipant {
11390
11449
  // session id — not applicable for OpenAI
11391
11450
  !turnResult.success,
11392
11451
  turnResult.assistantText,
11393
- null,
11394
- // usage info — not surfaced this phase
11452
+ usageJson,
11395
11453
  null,
11396
11454
  null,
11397
11455
  null,
11398
11456
  {}
11399
11457
  )
11400
11458
  );
11459
+ process.stderr.write(
11460
+ `[story-openai/${this.spec.id}] turn ${turn}: ${turnResult.usage.summary()}
11461
+ `
11462
+ );
11401
11463
  if (!turnResult.success) {
11402
11464
  this.transition("failed", turnResult.error ?? "turn failed");
11403
11465
  return;
@@ -11428,19 +11490,27 @@ var OpenAIStoryAgent = class extends BaroParticipant {
11428
11490
  let context = initialContext;
11429
11491
  let assistantText = null;
11430
11492
  const perRoundMs = this.opts.perRoundTimeoutSecs * 1e3;
11493
+ const usage = new UsageAccumulator();
11431
11494
  for (let round = 1; round <= this.opts.maxRoundsPerTurn; round++) {
11432
- const ac = new AbortController();
11433
- const timer = setTimeout(() => ac.abort(), perRoundMs);
11434
11495
  const calls = [];
11435
11496
  let sawMessage = false;
11436
11497
  let lastMessageText = null;
11437
11498
  try {
11438
- for await (const item of this.runner.run(context, this.model, ac.signal)) {
11499
+ const roundPromise = runInferenceRound(context, this.model);
11500
+ const timeoutPromise = new Promise(
11501
+ (_, rej) => setTimeout(
11502
+ () => rej(new Error(`round ${round} timed out after ${perRoundMs}ms`)),
11503
+ perRoundMs
11504
+ )
11505
+ );
11506
+ const result = await Promise.race([roundPromise, timeoutPromise]);
11507
+ usage.add(result.usage);
11508
+ for (const item of result.items) {
11439
11509
  if (item.type === "function_call") {
11440
11510
  await this.envRef?.deliverFunctionCall(this, item);
11441
11511
  context = context.addContextItem(item);
11442
11512
  calls.push(item);
11443
- } else if (item.type === "message" && item.role === "assistant") {
11513
+ } else if (item.type === "message") {
11444
11514
  await this.envRef?.deliverModelMessage(this, item);
11445
11515
  context = context.addContextItem(item);
11446
11516
  const json = item.toJSON();
@@ -11452,15 +11522,13 @@ var OpenAIStoryAgent = class extends BaroParticipant {
11452
11522
  }
11453
11523
  }
11454
11524
  } catch (e) {
11455
- clearTimeout(timer);
11456
11525
  return {
11457
11526
  context,
11458
11527
  success: false,
11459
11528
  assistantText,
11529
+ usage,
11460
11530
  error: `inference round ${round} failed: ${e?.message ?? String(e)}`
11461
11531
  };
11462
- } finally {
11463
- clearTimeout(timer);
11464
11532
  }
11465
11533
  for (const call of calls) {
11466
11534
  const tool = this.tools.find((t) => t.name === call.name);
@@ -11473,7 +11541,8 @@ var OpenAIStoryAgent = class extends BaroParticipant {
11473
11541
  return {
11474
11542
  context,
11475
11543
  success: true,
11476
- assistantText: lastMessageText
11544
+ assistantText: lastMessageText,
11545
+ usage
11477
11546
  };
11478
11547
  }
11479
11548
  if (!sawMessage && calls.length === 0) {
@@ -11481,6 +11550,7 @@ var OpenAIStoryAgent = class extends BaroParticipant {
11481
11550
  context,
11482
11551
  success: false,
11483
11552
  assistantText,
11553
+ usage,
11484
11554
  error: `round ${round} returned no items`
11485
11555
  };
11486
11556
  }
@@ -11490,6 +11560,7 @@ var OpenAIStoryAgent = class extends BaroParticipant {
11490
11560
  context,
11491
11561
  success: false,
11492
11562
  assistantText,
11563
+ usage,
11493
11564
  error: `exceeded maxRoundsPerTurn=${this.opts.maxRoundsPerTurn}`
11494
11565
  };
11495
11566
  }
@@ -11852,7 +11923,6 @@ function pickModel3(name) {
11852
11923
  var SurgeonOpenAI = class extends BaroParticipant {
11853
11924
  opts;
11854
11925
  model;
11855
- runner = new OpenAIInferenceRunner();
11856
11926
  replansEmitted = 0;
11857
11927
  pending = /* @__PURE__ */ new Set();
11858
11928
  constructor(opts) {
@@ -11896,9 +11966,12 @@ var SurgeonOpenAI = class extends BaroParticipant {
11896
11966
  const userPrompt = buildSurgeonPrompt(snap, failure);
11897
11967
  const context = ModelContext.create("surgeon").addContextItem(SystemMessageItem.create(SURGEON_SYSTEM_PROMPT)).addContextItem(UserMessageItem.create(userPrompt));
11898
11968
  try {
11969
+ const round = await runInferenceRound(context, this.model);
11970
+ const usage = new UsageAccumulator();
11971
+ usage.add(round.usage);
11899
11972
  let assistantText = "";
11900
- for await (const item of this.runner.run(context, this.model)) {
11901
- if (item.type === "message" && item.role === "assistant") {
11973
+ for (const item of round.items) {
11974
+ if (item.type === "message") {
11902
11975
  const json = item.toJSON();
11903
11976
  assistantText += json.content?.[0]?.text ?? "";
11904
11977
  }
@@ -11906,6 +11979,8 @@ var SurgeonOpenAI = class extends BaroParticipant {
11906
11979
  if (!assistantText.trim()) {
11907
11980
  throw new Error("OpenAI returned empty assistant text");
11908
11981
  }
11982
+ process.stderr.write(`[surgeon-openai] ${usage.summary()}
11983
+ `);
11909
11984
  const verdictJson = extractJsonObject(assistantText);
11910
11985
  const parsed = JSON.parse(verdictJson);
11911
11986
  if (parsed.action === "abort") return null;