openmates 0.15.0-alpha.73 → 0.15.0-alpha.75

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.
@@ -1356,6 +1356,9 @@ function websocketProtocolError(envelope) {
1356
1356
  }
1357
1357
  return null;
1358
1358
  }
1359
+ function numberMetric(value) {
1360
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
1361
+ }
1359
1362
  function errorFrameBelongsToAiResponse(envelope, userMessageId, chatId, recoveryTurnId) {
1360
1363
  if (envelope.type === "client_update_required") return true;
1361
1364
  if (envelope.type !== "error") return true;
@@ -1722,6 +1725,8 @@ var OpenMatesWsClient = class {
1722
1725
  let taskId = null;
1723
1726
  let category = null;
1724
1727
  let modelName = null;
1728
+ let tokenUsage = null;
1729
+ let promptBudget = null;
1725
1730
  let recoveryJobId = null;
1726
1731
  const compressionCheckpoints = [];
1727
1732
  let followUpSuggestions = [];
@@ -1767,6 +1772,24 @@ var OpenMatesWsClient = class {
1767
1772
  if (typeof p.category === "string" && p.category) category = p.category;
1768
1773
  if (typeof p.model_name === "string" && p.model_name)
1769
1774
  modelName = p.model_name;
1775
+ const promptTokens = numberMetric(p.prompt_tokens);
1776
+ const completionTokens = numberMetric(p.completion_tokens);
1777
+ const userInputTokens = numberMetric(p.user_input_tokens);
1778
+ const systemPromptTokens = numberMetric(p.system_prompt_tokens);
1779
+ const totalCredits = numberMetric(p.total_credits);
1780
+ const hasTokenUsage = promptTokens !== null || completionTokens !== null || userInputTokens !== null || systemPromptTokens !== null || totalCredits !== null;
1781
+ if (hasTokenUsage) {
1782
+ tokenUsage = {
1783
+ ...promptTokens !== null ? { promptTokens } : {},
1784
+ ...completionTokens !== null ? { completionTokens } : {},
1785
+ ...userInputTokens !== null ? { userInputTokens } : {},
1786
+ ...systemPromptTokens !== null ? { systemPromptTokens } : {},
1787
+ ...totalCredits !== null ? { totalCredits } : {}
1788
+ };
1789
+ }
1790
+ if (systemPromptTokens !== null) {
1791
+ promptBudget = { systemPromptTokens };
1792
+ }
1770
1793
  if (typeof p.recovery_job_id === "string" && p.recovery_job_id && p.recovery_protocol_version === 1)
1771
1794
  recoveryJobId = p.recovery_job_id;
1772
1795
  };
@@ -1809,7 +1832,9 @@ var OpenMatesWsClient = class {
1809
1832
  embeds: [...embeds.values()],
1810
1833
  subChatEvents,
1811
1834
  recoveryJobId,
1812
- compressionCheckpoints
1835
+ compressionCheckpoints,
1836
+ tokenUsage,
1837
+ promptBudget
1813
1838
  });
1814
1839
  return;
1815
1840
  }
@@ -1839,7 +1864,9 @@ var OpenMatesWsClient = class {
1839
1864
  embeds: [...embeds.values()],
1840
1865
  subChatEvents,
1841
1866
  recoveryJobId,
1842
- compressionCheckpoints
1867
+ compressionCheckpoints,
1868
+ tokenUsage,
1869
+ promptBudget
1843
1870
  });
1844
1871
  }, asyncEmbedWaitMs);
1845
1872
  return;
@@ -1865,7 +1892,9 @@ var OpenMatesWsClient = class {
1865
1892
  embeds: [...embeds.values()],
1866
1893
  subChatEvents,
1867
1894
  recoveryJobId,
1868
- compressionCheckpoints
1895
+ compressionCheckpoints,
1896
+ tokenUsage,
1897
+ promptBudget
1869
1898
  });
1870
1899
  };
1871
1900
  const handleSubChatEvent = (type, p) => {
@@ -2139,7 +2168,9 @@ var OpenMatesWsClient = class {
2139
2168
  embeds: [...embeds.values()],
2140
2169
  subChatEvents,
2141
2170
  recoveryJobId,
2142
- compressionCheckpoints
2171
+ compressionCheckpoints,
2172
+ tokenUsage,
2173
+ promptBudget
2143
2174
  });
2144
2175
  return;
2145
2176
  }
@@ -6388,7 +6419,9 @@ var OpenMatesClient = class _OpenMatesClient {
6388
6419
  taskProposals: [],
6389
6420
  taskUpdateProposals: [],
6390
6421
  subChatEvents: [],
6391
- appSettingsMemoryRequests: []
6422
+ appSettingsMemoryRequests: [],
6423
+ tokenUsage: null,
6424
+ promptBudget: null
6392
6425
  };
6393
6426
  }
6394
6427
  async createTurnTokenRefs(params) {
@@ -6574,7 +6607,7 @@ var OpenMatesClient = class _OpenMatesClient {
6574
6607
  if (!userId) {
6575
6608
  throw new Error("Could not resolve current user id for local connected-account connector.");
6576
6609
  }
6577
- const { createHash: createHash14 } = await import("crypto");
6610
+ const { createHash: createHash15 } = await import("crypto");
6578
6611
  const accountId = randomUUID5();
6579
6612
  const masterKey = this.getMasterKeyBytes();
6580
6613
  const encryptLocalConnectorValue = async (value) => {
@@ -6583,9 +6616,9 @@ var OpenMatesClient = class _OpenMatesClient {
6583
6616
  };
6584
6617
  const payload = {
6585
6618
  id: accountId,
6586
- hashed_user_id: createHash14("sha256").update(userId).digest("hex"),
6619
+ hashed_user_id: createHash15("sha256").update(userId).digest("hex"),
6587
6620
  encrypted_provider_type: await encryptLocalConnectorValue(input.provider_id),
6588
- provider_type_hash: createHash14("sha256").update(input.provider_id).digest("hex"),
6621
+ provider_type_hash: createHash15("sha256").update(input.provider_id).digest("hex"),
6589
6622
  encrypted_account_label: await encryptLocalConnectorValue(input.label),
6590
6623
  encrypted_capabilities: await encryptLocalConnectorValue(input.capabilities),
6591
6624
  encrypted_app_permissions: await encryptLocalConnectorValue({
@@ -7970,8 +8003,8 @@ var OpenMatesClient = class _OpenMatesClient {
7970
8003
  );
7971
8004
  }
7972
8005
  const embedId = String(embed.embed_id ?? embed.id ?? "");
7973
- const { createHash: createHash14 } = await import("crypto");
7974
- const hashedEmbedId = createHash14("sha256").update(embedId).digest("hex");
8006
+ const { createHash: createHash15 } = await import("crypto");
8007
+ const hashedEmbedId = createHash15("sha256").update(embedId).digest("hex");
7975
8008
  const embedKeyBytes = await this.resolveEmbedKey(
7976
8009
  cache,
7977
8010
  masterKey,
@@ -8079,7 +8112,7 @@ var OpenMatesClient = class _OpenMatesClient {
8079
8112
  async resolveEmbedKey(cache, masterKey, embed, embedId, hashedEmbedId, visited = /* @__PURE__ */ new Set()) {
8080
8113
  if (visited.has(embedId)) return null;
8081
8114
  visited.add(embedId);
8082
- const { createHash: createHash14 } = await import("crypto");
8115
+ const { createHash: createHash15 } = await import("crypto");
8083
8116
  const masterKeyEntry = cache.embedKeys.find(
8084
8117
  (ek) => ek.hashed_embed_id === hashedEmbedId && String(ek.key_type) === "master"
8085
8118
  );
@@ -8096,7 +8129,7 @@ var OpenMatesClient = class _OpenMatesClient {
8096
8129
  if (chatKeyEntry && typeof chatKeyEntry.encrypted_embed_key === "string") {
8097
8130
  const hashedChatId = String(chatKeyEntry.hashed_chat_id ?? "");
8098
8131
  const owningChat = cache.chats.find((c) => {
8099
- const chatHash = createHash14("sha256").update(String(c.details.id ?? "")).digest("hex");
8132
+ const chatHash = createHash15("sha256").update(String(c.details.id ?? "")).digest("hex");
8100
8133
  return chatHash === hashedChatId;
8101
8134
  });
8102
8135
  if (owningChat) {
@@ -8125,7 +8158,7 @@ var OpenMatesClient = class _OpenMatesClient {
8125
8158
  const parentFullId = String(
8126
8159
  parentEmbed2.embed_id ?? parentEmbed2.id ?? ""
8127
8160
  );
8128
- const parentHashedId = createHash14("sha256").update(parentFullId).digest("hex");
8161
+ const parentHashedId = createHash15("sha256").update(parentFullId).digest("hex");
8129
8162
  const parentKey = await this.resolveEmbedKey(
8130
8163
  cache,
8131
8164
  masterKey,
@@ -8555,6 +8588,8 @@ var OpenMatesClient = class _OpenMatesClient {
8555
8588
  let assistantMessageId = null;
8556
8589
  let category = null;
8557
8590
  let modelName = null;
8591
+ let tokenUsage = null;
8592
+ let promptBudget = null;
8558
8593
  let followUpSuggestions = [];
8559
8594
  let taskProposals = [];
8560
8595
  let taskUpdateProposals = [];
@@ -8786,6 +8821,8 @@ var OpenMatesClient = class _OpenMatesClient {
8786
8821
  assistant = resp.content;
8787
8822
  category = resp.category;
8788
8823
  modelName = resp.modelName;
8824
+ tokenUsage = resp.tokenUsage;
8825
+ promptBudget = resp.promptBudget;
8789
8826
  taskEvents = resp.taskEvents;
8790
8827
  pendingTaskUpdateJobs = taskUpdateJobsEnabled ? resp.pendingTaskUpdateJobs : [];
8791
8828
  subChatEvents = resp.subChatEvents;
@@ -8805,7 +8842,9 @@ var OpenMatesClient = class _OpenMatesClient {
8805
8842
  taskEvents,
8806
8843
  pendingTaskUpdateJobs,
8807
8844
  subChatEvents,
8808
- appSettingsMemoryRequests
8845
+ appSettingsMemoryRequests,
8846
+ tokenUsage,
8847
+ promptBudget
8809
8848
  };
8810
8849
  }
8811
8850
  } finally {
@@ -8822,6 +8861,8 @@ var OpenMatesClient = class _OpenMatesClient {
8822
8861
  assistant = resp.content;
8823
8862
  category = resp.category;
8824
8863
  modelName = resp.modelName;
8864
+ tokenUsage = resp.tokenUsage;
8865
+ promptBudget = resp.promptBudget;
8825
8866
  followUpSuggestions = resp.followUpSuggestions;
8826
8867
  taskProposals = resp.taskProposals;
8827
8868
  taskUpdateProposals = resp.taskUpdateProposals;
@@ -8844,7 +8885,9 @@ var OpenMatesClient = class _OpenMatesClient {
8844
8885
  taskEvents,
8845
8886
  pendingTaskUpdateJobs,
8846
8887
  subChatEvents,
8847
- appSettingsMemoryRequests
8888
+ appSettingsMemoryRequests,
8889
+ tokenUsage,
8890
+ promptBudget
8848
8891
  };
8849
8892
  }
8850
8893
  if (chatKeyBytes) {
@@ -8898,7 +8941,9 @@ var OpenMatesClient = class _OpenMatesClient {
8898
8941
  taskEvents,
8899
8942
  pendingTaskUpdateJobs,
8900
8943
  subChatEvents,
8901
- appSettingsMemoryRequests
8944
+ appSettingsMemoryRequests,
8945
+ tokenUsage,
8946
+ promptBudget
8902
8947
  };
8903
8948
  }
8904
8949
  if (claim.state !== "LEASED" || !leaseToken || leaseGeneration === null || leaseGeneration < 1 || !assistantId || keyVersion === null || keyVersion !== 1 || claim.chat_id !== chatId || claim.turn_id !== savedTurnId || typeof claim.sealed_payload !== "string") {
@@ -9072,7 +9117,9 @@ var OpenMatesClient = class _OpenMatesClient {
9072
9117
  taskEvents,
9073
9118
  pendingTaskUpdateJobs,
9074
9119
  subChatEvents,
9075
- appSettingsMemoryRequests
9120
+ appSettingsMemoryRequests,
9121
+ tokenUsage,
9122
+ promptBudget
9076
9123
  };
9077
9124
  }
9078
9125
  async persistEncryptedSystemMessage(ws, systemMessage, targetChatId) {
@@ -11752,7 +11799,7 @@ Required: ${schema.required.join(", ")}`
11752
11799
  const session = this.requireSession();
11753
11800
  const masterKey = base64ToBytes(session.masterKeyExportedB64);
11754
11801
  const cache = await this.ensureSynced();
11755
- const { createHash: createHash14 } = await import("crypto");
11802
+ const { createHash: createHash15 } = await import("crypto");
11756
11803
  const embed = cache.embeds.find(
11757
11804
  (e) => String(e.embed_id ?? "").startsWith(embedIdOrShort) || String(e.id ?? "").startsWith(embedIdOrShort)
11758
11805
  );
@@ -11760,7 +11807,7 @@ Required: ${schema.required.join(", ")}`
11760
11807
  throw new Error(`Embed '${embedIdOrShort}' not found in local cache.`);
11761
11808
  }
11762
11809
  const embedId = String(embed.embed_id ?? embed.id ?? "");
11763
- const hashedEmbedId = createHash14("sha256").update(embedId).digest("hex");
11810
+ const hashedEmbedId = createHash15("sha256").update(embedId).digest("hex");
11764
11811
  const embedKeyBytes = await this.resolveEmbedKey(
11765
11812
  cache,
11766
11813
  masterKey,
@@ -11868,8 +11915,8 @@ Required: ${schema.required.join(", ")}`
11868
11915
  if (!embed) {
11869
11916
  throw new Error(`Embed '${embedId}' not found in local cache. Run 'openmates chats list' to sync first.`);
11870
11917
  }
11871
- const { createHash: createHash14 } = await import("crypto");
11872
- const hashedEmbedId = createHash14("sha256").update(embedId).digest("hex");
11918
+ const { createHash: createHash15 } = await import("crypto");
11919
+ const hashedEmbedId = createHash15("sha256").update(embedId).digest("hex");
11873
11920
  const embedKey = await this.resolveEmbedKey(
11874
11921
  cache,
11875
11922
  masterKey,
@@ -11985,8 +12032,8 @@ Required: ${schema.required.join(", ")}`
11985
12032
  if (!embed) {
11986
12033
  throw new Error(`Embed '${embedId}' not found in local cache. Run 'openmates chats list' to sync first.`);
11987
12034
  }
11988
- const { createHash: createHash14 } = await import("crypto");
11989
- const hashedEmbedId = createHash14("sha256").update(embedId).digest("hex");
12035
+ const { createHash: createHash15 } = await import("crypto");
12036
+ const hashedEmbedId = createHash15("sha256").update(embedId).digest("hex");
11990
12037
  const embedKey = await this.resolveEmbedKey(
11991
12038
  cache,
11992
12039
  masterKey,
@@ -12425,9 +12472,9 @@ Required: ${schema.required.join(", ")}`
12425
12472
  chats.splice(0, chats.length, ...reconciledChats);
12426
12473
  if (totalChatCount === 0) totalChatCount = chats.length;
12427
12474
  if (reconciliation.deleted_chat_ids?.length) {
12428
- const { createHash: createHash14 } = await import("crypto");
12475
+ const { createHash: createHash15 } = await import("crypto");
12429
12476
  const deletedHashes = new Set(
12430
- reconciliation.deleted_chat_ids.map((id) => createHash14("sha256").update(id).digest("hex"))
12477
+ reconciliation.deleted_chat_ids.map((id) => createHash15("sha256").update(id).digest("hex"))
12431
12478
  );
12432
12479
  const keptEmbeds = embeds.filter((embed) => !deletedHashes.has(String(embed.hashed_chat_id ?? "")));
12433
12480
  const keptKeys = embedKeys.filter((key) => !deletedHashes.has(String(key.hashed_chat_id ?? "")));
@@ -21185,7 +21232,7 @@ import { stdin as stdin3, stdout as stdout2 } from "process";
21185
21232
  import { readFileSync as readFileSync11, realpathSync as realpathSync2, writeFileSync as writeFileSync7 } from "fs";
21186
21233
  import { fileURLToPath as fileURLToPath2 } from "url";
21187
21234
  import { basename as basename3, dirname as dirname5 } from "path";
21188
- import { createHash as createHash13, randomBytes as randomBytes8, randomUUID as randomUUID10 } from "crypto";
21235
+ import { createHash as createHash14, randomBytes as randomBytes8, randomUUID as randomUUID10 } from "crypto";
21189
21236
  import { arch as arch2, platform as platform3 } from "os";
21190
21237
  import WebSocket2 from "ws";
21191
21238
 
@@ -45994,6 +46041,9 @@ Only output the final Markdown table. Do NOT include explanations, notes, or any
45994
46041
  },
45995
46042
  all_examples_subtitle: {
45996
46043
  text: "Pick one to see how the workspace feels in practice."
46044
+ },
46045
+ back_to_recent: {
46046
+ text: "Back to recent"
45997
46047
  }
45998
46048
  }
45999
46049
  },
@@ -46107,6 +46157,51 @@ Only output the final Markdown table. Do NOT include explanations, notes, or any
46107
46157
  }
46108
46158
  },
46109
46159
  chats: {
46160
+ chat: {
46161
+ welcome: {
46162
+ back_to_recent: {
46163
+ text: "Back to recent"
46164
+ }
46165
+ },
46166
+ sub_chats: {
46167
+ confirmation_title: {
46168
+ text: "Start {count} background chats?"
46169
+ },
46170
+ confirmation_description: {
46171
+ text: "OpenMates starts up to {auto} automatically. Larger batches need your approval, and each chat can have up to {max} direct sub-chats."
46172
+ },
46173
+ confirmation_more: {
46174
+ text: "+{count} more"
46175
+ },
46176
+ start_all: {
46177
+ text: "Start all {count}"
46178
+ },
46179
+ progress_label: {
46180
+ text: "Sequential sub-chats: {completed} / {total} complete"
46181
+ },
46182
+ progress_stopping: {
46183
+ text: "Stopping sequential sub-chats..."
46184
+ },
46185
+ stop_queue: {
46186
+ text: "Stop queue"
46187
+ },
46188
+ broadcast_to_siblings: {
46189
+ text: "Send to sibling sub-chats"
46190
+ },
46191
+ return_to_parent: {
46192
+ text: "Return to parent chat"
46193
+ },
46194
+ start_first: {
46195
+ text: "Start first {count}"
46196
+ },
46197
+ confirmation_failed: {
46198
+ text: "Failed to update sub-chat approval."
46199
+ },
46200
+ limit_exceeded: {
46201
+ text: "This chat has reached its sub-chat limit."
46202
+ }
46203
+ }
46204
+ },
46110
46205
  context_menu: {
46111
46206
  download: {
46112
46207
  text: "Download"
@@ -46213,46 +46308,6 @@ Only output the final Markdown table. Do NOT include explanations, notes, or any
46213
46308
  text: "Could not start the explanation chat. Please try again."
46214
46309
  }
46215
46310
  },
46216
- chat: {
46217
- sub_chats: {
46218
- confirmation_title: {
46219
- text: "Start {count} background chats?"
46220
- },
46221
- confirmation_description: {
46222
- text: "OpenMates starts up to {auto} automatically. Larger batches need your approval, and each chat can have up to {max} direct sub-chats."
46223
- },
46224
- confirmation_more: {
46225
- text: "+{count} more"
46226
- },
46227
- start_all: {
46228
- text: "Start all {count}"
46229
- },
46230
- progress_label: {
46231
- text: "Sequential sub-chats: {completed} / {total} complete"
46232
- },
46233
- progress_stopping: {
46234
- text: "Stopping sequential sub-chats..."
46235
- },
46236
- stop_queue: {
46237
- text: "Stop queue"
46238
- },
46239
- broadcast_to_siblings: {
46240
- text: "Send to sibling sub-chats"
46241
- },
46242
- return_to_parent: {
46243
- text: "Return to parent chat"
46244
- },
46245
- start_first: {
46246
- text: "Start first {count}"
46247
- },
46248
- confirmation_failed: {
46249
- text: "Failed to update sub-chat approval."
46250
- },
46251
- limit_exceeded: {
46252
- text: "This chat has reached its sub-chat limit."
46253
- }
46254
- }
46255
- },
46256
46311
  hidden_chats: {
46257
46312
  unlock: {
46258
46313
  text: "Unlock hidden chats"
@@ -62823,14 +62878,126 @@ function buildAssistantFeedbackDecision(rating) {
62823
62878
  }
62824
62879
 
62825
62880
  // src/benchmark.ts
62826
- import { randomUUID as randomUUID8 } from "crypto";
62881
+ import { createHash as createHash13, randomUUID as randomUUID8 } from "crypto";
62827
62882
  import { existsSync as existsSync8, mkdtempSync as mkdtempSync2, readFileSync as readFileSync7, readdirSync as readdirSync3, writeFileSync as writeFileSync5 } from "fs";
62828
62883
  import { tmpdir } from "os";
62829
62884
  import { dirname as dirname4, join as join5, resolve as resolve5 } from "path";
62830
62885
  import { fileURLToPath } from "url";
62831
62886
  var DEFAULT_JUDGE_MODEL = "google/gemini-3-flash-preview";
62887
+ var DEFAULT_PROMPT_BUDGET_MODEL = "google/gemini-3.5-flash";
62832
62888
  var DEFAULT_EXTENSIVE_SIZE = 10;
62833
62889
  var DEFAULT_PARALLEL = 4;
62890
+ var PROMPT_BUDGET_TARGET_INPUT_TOKENS_PER_TURN = 16e3;
62891
+ var PROMPT_BUDGET_TARGET_OUTPUT_TOKENS_PER_TURN = 800;
62892
+ var PROMPT_BUDGET_JUDGE_INPUT_TOKENS_PER_RESPONSE = 3e3;
62893
+ var PROMPT_BUDGET_JUDGE_OUTPUT_TOKENS_PER_RESPONSE = 300;
62894
+ var PROMPT_BUDGET_CASES = [
62895
+ {
62896
+ id: "single-writing-concise-email",
62897
+ title: "Concise email rewrite",
62898
+ category: "writing",
62899
+ qualityRubric: "Warm, concise, under 120 words, directly asks to move the Tuesday sync to Thursday afternoon.",
62900
+ turns: [
62901
+ "Write a concise but warm email to a colleague asking to move our Tuesday 10:00 project sync to Thursday afternoon. Keep it under 120 words."
62902
+ ]
62903
+ },
62904
+ {
62905
+ id: "single-code-palindrome",
62906
+ title: "TypeScript palindrome helper",
62907
+ category: "coding",
62908
+ qualityRubric: "Produces valid TypeScript, ignores spaces/punctuation/case, includes one short usage example, and avoids unrelated prose.",
62909
+ turns: [
62910
+ "Write a TypeScript function isPalindrome(input: string): boolean that ignores spaces, punctuation, and case. Include only the function and one short usage example."
62911
+ ]
62912
+ },
62913
+ {
62914
+ id: "single-current-web-ai-news",
62915
+ title: "Current AI release search",
62916
+ category: "web",
62917
+ qualityRubric: "Uses current web/search behavior, cites sources, and summarizes a major AI model release from the last 30 days without fabricating certainty.",
62918
+ turns: [
62919
+ "Search the web for the latest major AI model release from the last 30 days and summarize what changed, with sources."
62920
+ ]
62921
+ },
62922
+ {
62923
+ id: "single-travel-filtered-flight",
62924
+ title: "Filtered flight search",
62925
+ category: "travel",
62926
+ qualityRubric: "Attempts appropriate travel/tool routing, honors direct flight/date/budget constraints where possible, and explains tradeoffs or uncertainty.",
62927
+ turns: [
62928
+ "Find direct flights from Berlin to Barcelona on 2026-09-10 under 220 EUR if possible, and explain the best tradeoff."
62929
+ ]
62930
+ },
62931
+ {
62932
+ id: "single-events-accessibility",
62933
+ title: "Accessible family events",
62934
+ category: "events",
62935
+ qualityRubric: "Attempts event/tool routing, separates confirmed from unknown accessibility, and avoids claiming accessibility without evidence.",
62936
+ turns: [
62937
+ "Find wheelchair-accessible family events in Berlin this weekend. Separate confirmed accessibility from unknown accessibility."
62938
+ ]
62939
+ },
62940
+ {
62941
+ id: "single-openmates-help",
62942
+ title: "OpenMates skill selection",
62943
+ category: "openmates",
62944
+ qualityRubric: "Correctly explains a suitable OpenMates app/skill path for product comparison and suggests a useful next step.",
62945
+ turns: [
62946
+ "Explain which OpenMates app or skill I should use to compare products before buying a new laptop, and what you would do next."
62947
+ ]
62948
+ },
62949
+ {
62950
+ id: "single-safety-privacy-review",
62951
+ title: "Privacy review",
62952
+ category: "safety",
62953
+ qualityRubric: "Identifies privacy risks in benchmark logging and gives practical safer defaults without overclaiming compliance.",
62954
+ turns: [
62955
+ "Review this benchmark design for privacy risks: it logs prompts, outputs, model ids, usage costs, and debug metadata to a shared file. List risks and safer defaults."
62956
+ ]
62957
+ },
62958
+ {
62959
+ id: "multiturn-model-evaluation-plan",
62960
+ title: "Model evaluation follow-up plan",
62961
+ category: "planning",
62962
+ qualityRubric: "Maintains continuity across all six turns, refines prior outputs instead of restarting, and produces concrete evaluation criteria.",
62963
+ turns: [
62964
+ "Create a three-step plan for evaluating whether a new AI model is ready for production use.",
62965
+ "Make step 2 more concrete with two measurable checks.",
62966
+ "Add a risk register with severity and mitigation for the top three risks.",
62967
+ "Turn the plan into a one-week execution schedule.",
62968
+ "Identify which tasks could be automated with OpenMates skills.",
62969
+ "Summarize the final plan in one sentence."
62970
+ ]
62971
+ },
62972
+ {
62973
+ id: "multiturn-startup-launch",
62974
+ title: "Freelancer AI assistant launch",
62975
+ category: "business",
62976
+ qualityRubric: "Builds on previous launch context, keeps the privacy-first freelancer positioning, and produces specific GTM artifacts.",
62977
+ turns: [
62978
+ "Help me plan the launch of a privacy-first personal AI assistant for freelancers.",
62979
+ "Define the ideal first customer profile and their top pains.",
62980
+ "Draft a landing page hero section for that customer.",
62981
+ "Create a simple pricing experiment with three tiers.",
62982
+ "Write five outbound messages for early customer interviews.",
62983
+ "Summarize the riskiest assumption and how to test it this week."
62984
+ ]
62985
+ },
62986
+ {
62987
+ id: "multiturn-debugging-workflow",
62988
+ title: "SvelteKit stale chat debugging",
62989
+ category: "debugging",
62990
+ qualityRubric: "Maintains the stale-chat bug context, distinguishes frontend cache from backend sync, and uses data-testid selector guidance.",
62991
+ turns: [
62992
+ "A SvelteKit page sometimes shows stale chat messages after refresh. Give me a debugging plan.",
62993
+ "Add likely root causes involving IndexedDB and WebSocket sync.",
62994
+ "Suggest instrumentation I should add before fixing code.",
62995
+ "Write a minimal regression test outline using data-testid selectors.",
62996
+ "Explain how to tell if the bug is backend sync or frontend cache.",
62997
+ "Condense this into a checklist I can hand to another engineer."
62998
+ ]
62999
+ }
63000
+ ];
62834
63001
  var FIXTURE_IMAGE_SVG = `<?xml version="1.0" encoding="UTF-8"?>
62835
63002
  <svg xmlns="http://www.w3.org/2000/svg" width="1200" height="800" viewBox="0 0 1200 800">
62836
63003
  <rect width="1200" height="800" fill="#d8ecff"/>
@@ -63101,6 +63268,10 @@ async function handleBenchmark(client, subcommand, rest, flags) {
63101
63268
  printBenchmarkHelp();
63102
63269
  return;
63103
63270
  }
63271
+ if (subcommand === "prompt-budget") {
63272
+ await handlePromptBudgetBenchmark(client, rest, flags);
63273
+ return;
63274
+ }
63104
63275
  if (subcommand !== "model") {
63105
63276
  throw new Error(`Unknown benchmark command '${subcommand}'. Run 'openmates benchmark --help'.`);
63106
63277
  }
@@ -63175,6 +63346,8 @@ async function handleBenchmark(client, subcommand, rest, flags) {
63175
63346
  function printBenchmarkHelp() {
63176
63347
  console.log(`Benchmark commands:
63177
63348
  openmates benchmark model <provider/model> [provider/model...] --confirm-spend-credits [--compare] [--suite quick|extensive|all] [--json]
63349
+ openmates benchmark prompt-budget --phase baseline|postchange --confirm-spend-credits [--model provider/model] [--resume-from <path>] [--json]
63350
+ openmates benchmark prompt-budget compare --baseline <path> --postchange <path> [--json]
63178
63351
 
63179
63352
  Runs real incognito chat requests through the OpenMates product path. Live runs
63180
63353
  spend the logged-in user's credits and usage entries are grouped as benchmark spend.
@@ -63194,6 +63367,74 @@ Options:
63194
63367
  --output <path> Save JSON result to a file
63195
63368
  --json Print JSON result`);
63196
63369
  }
63370
+ async function handlePromptBudgetBenchmark(client, rest, flags) {
63371
+ if (rest[0] === "compare") {
63372
+ writePromptBudgetComparison(comparePromptBudgetArtifacts(flags), flags, stringFlag(flags.output));
63373
+ return;
63374
+ }
63375
+ if (rest.length > 0) {
63376
+ throw new Error(`Unknown prompt-budget benchmark argument '${rest[0]}'. Run 'openmates benchmark --help'.`);
63377
+ }
63378
+ const phase = parsePromptBudgetPhase(flags.phase);
63379
+ const targetModel = stringFlag(flags.model) ?? DEFAULT_PROMPT_BUDGET_MODEL;
63380
+ const judgeModel = stringFlag(flags["judge-model"]) ?? DEFAULT_JUDGE_MODEL;
63381
+ const output = stringFlag(flags.output);
63382
+ const resumeFrom = stringFlag(flags["resume-from"]);
63383
+ const dryRun = flags["dry-run"] === true;
63384
+ const responseTimeoutMs = parseResponseTimeoutMs(flags["response-timeout-seconds"]);
63385
+ if (!dryRun && flags["confirm-spend-credits"] !== true) {
63386
+ throw new Error(
63387
+ "Prompt-budget benchmark runs spend real credits from the logged-in account. Rerun with --confirm-spend-credits, or use --dry-run to preview the plan."
63388
+ );
63389
+ }
63390
+ if (dryRun && resumeFrom) {
63391
+ throw new Error("--resume-from is only supported for live prompt-budget runs");
63392
+ }
63393
+ const pricing = loadPricingForModels([targetModel, judgeModel]);
63394
+ const estimate = estimatePromptBudgetCredits(PROMPT_BUDGET_CASES, targetModel, judgeModel, pricing);
63395
+ const resumeArtifact = resumeFrom ? readPromptBudgetResult(resumeFrom) : null;
63396
+ if (resumeArtifact) {
63397
+ validatePromptBudgetResumeArtifact(resumeArtifact, { phase, targetModel, judgeModel });
63398
+ }
63399
+ const runId = stringFlag(flags["run-id"]) ?? resumeArtifact?.runId ?? randomUUID8();
63400
+ const result = makePromptBudgetBaseResult({
63401
+ phase,
63402
+ runId,
63403
+ targetModel,
63404
+ judgeModel,
63405
+ dryRun,
63406
+ estimate
63407
+ });
63408
+ if (resumeArtifact) {
63409
+ result.cases = resumeArtifact.cases.filter(isCompletePromptBudgetCase);
63410
+ recomputePromptBudgetResult(result);
63411
+ }
63412
+ if (dryRun) {
63413
+ writePromptBudgetResult(result, flags, output);
63414
+ return;
63415
+ }
63416
+ if (!client.hasSession()) {
63417
+ throw new Error("Prompt-budget benchmark runs require login. Run 'openmates login' first.");
63418
+ }
63419
+ const completedCaseIds = new Set(result.cases.map((benchmarkCase) => benchmarkCase.id));
63420
+ for (const benchmarkCase of PROMPT_BUDGET_CASES) {
63421
+ if (completedCaseIds.has(benchmarkCase.id)) continue;
63422
+ const caseResult = await runPromptBudgetCase({
63423
+ client,
63424
+ benchmarkCase,
63425
+ phase,
63426
+ runId,
63427
+ targetModel,
63428
+ judgeModel,
63429
+ responseTimeoutMs
63430
+ });
63431
+ result.cases.push(caseResult);
63432
+ recomputePromptBudgetResult(result);
63433
+ if (caseResult.error) break;
63434
+ }
63435
+ recomputePromptBudgetResult(result);
63436
+ writePromptBudgetResult(result, flags, output);
63437
+ }
63197
63438
  function parseSuites(value) {
63198
63439
  if (value === void 0 || value === false) return ["quick"];
63199
63440
  if (value === true) throw new Error("--suite requires a value");
@@ -63240,6 +63481,24 @@ function parseCaseIds(value) {
63240
63481
  if (caseIds.length === 0) throw new Error("--case requires at least one case id");
63241
63482
  return [...new Set(caseIds)];
63242
63483
  }
63484
+ function parsePromptBudgetPhase(value) {
63485
+ if (value === void 0 || value === false) return "baseline";
63486
+ if (value === true) throw new Error("--phase requires baseline or postchange");
63487
+ if (value === "baseline" || value === "postchange") return value;
63488
+ throw new Error("--phase must be baseline or postchange");
63489
+ }
63490
+ function parseResponseTimeoutMs(value) {
63491
+ if (value === void 0 || value === false) return void 0;
63492
+ if (value === true) throw new Error("--response-timeout-seconds requires a value");
63493
+ const parsed = Number.parseInt(value, 10);
63494
+ if (!Number.isInteger(parsed) || parsed < 30 || parsed > 1200) {
63495
+ throw new Error("--response-timeout-seconds must be an integer from 30 to 1200");
63496
+ }
63497
+ return parsed * 1e3;
63498
+ }
63499
+ function stringFlag(value) {
63500
+ return typeof value === "string" && value.length > 0 ? value : void 0;
63501
+ }
63243
63502
  function filterCases(cases, caseIds) {
63244
63503
  if (caseIds.length === 0) return cases;
63245
63504
  const availableIds = new Set(cases.map((benchmarkCase) => benchmarkCase.id));
@@ -63296,6 +63555,95 @@ function dedupeCases(cases) {
63296
63555
  }
63297
63556
  return result;
63298
63557
  }
63558
+ async function runPromptBudgetCase(params) {
63559
+ const { client, benchmarkCase, phase, runId, targetModel, judgeModel, responseTimeoutMs } = params;
63560
+ const startedAt = Date.now();
63561
+ const history = [];
63562
+ const turns = [];
63563
+ let chatId;
63564
+ try {
63565
+ for (const [index, prompt] of benchmarkCase.turns.entries()) {
63566
+ const turnNumber = index + 1;
63567
+ const rawPrompt = `${modelMention(targetModel)} ${prompt}`;
63568
+ const turnStartedAt = Date.now();
63569
+ const response = await client.sendMessage({
63570
+ message: rawPrompt,
63571
+ chatId,
63572
+ incognito: true,
63573
+ autoApproveSubChats: true,
63574
+ benchmarkMetadata: benchmarkMetadata({
63575
+ runId,
63576
+ suite: `prompt_budget_${phase}`,
63577
+ caseId: `${benchmarkCase.id}:turn-${turnNumber}`,
63578
+ targetModel,
63579
+ judgeModel
63580
+ }),
63581
+ messageHistory: history,
63582
+ precollectResponse: true,
63583
+ responseTimeoutMs
63584
+ });
63585
+ chatId = response.chatId;
63586
+ const turnResult = {
63587
+ turn: turnNumber,
63588
+ prompt,
63589
+ promptHash: stableHash(prompt),
63590
+ assistant: response.assistant,
63591
+ durationMs: Date.now() - turnStartedAt,
63592
+ chatId: response.chatId,
63593
+ messageId: response.messageId,
63594
+ modelName: response.modelName,
63595
+ mateName: response.mateName,
63596
+ category: response.category,
63597
+ tokenUsage: response.tokenUsage,
63598
+ promptBudget: response.promptBudget
63599
+ };
63600
+ turnResult.judge = await judgePromptBudgetTurn({
63601
+ client,
63602
+ benchmarkCase,
63603
+ turn: turnResult,
63604
+ turnNumber,
63605
+ targetModel,
63606
+ judgeModel,
63607
+ runId,
63608
+ phase
63609
+ });
63610
+ turns.push(turnResult);
63611
+ appendHistory(history, "user", rawPrompt);
63612
+ appendHistory(history, "assistant", response.assistant);
63613
+ }
63614
+ const continuityJudge = benchmarkCase.turns.length > 1 ? await judgePromptBudgetContinuity({
63615
+ client,
63616
+ benchmarkCase,
63617
+ turns,
63618
+ targetModel,
63619
+ judgeModel,
63620
+ runId,
63621
+ phase
63622
+ }) : void 0;
63623
+ const passed = turns.every((turn) => (turn.judge?.score ?? 0) >= 4) && (!continuityJudge || (continuityJudge.score ?? 0) >= 4);
63624
+ return {
63625
+ id: benchmarkCase.id,
63626
+ title: benchmarkCase.title,
63627
+ category: benchmarkCase.category,
63628
+ promptHashes: benchmarkCase.turns.map(stableHash),
63629
+ turns,
63630
+ continuityJudge,
63631
+ passed,
63632
+ durationMs: Date.now() - startedAt
63633
+ };
63634
+ } catch (error) {
63635
+ return {
63636
+ id: benchmarkCase.id,
63637
+ title: benchmarkCase.title,
63638
+ category: benchmarkCase.category,
63639
+ promptHashes: benchmarkCase.turns.map(stableHash),
63640
+ turns,
63641
+ passed: false,
63642
+ durationMs: Date.now() - startedAt,
63643
+ error: error instanceof Error ? error.message : String(error)
63644
+ };
63645
+ }
63646
+ }
63299
63647
  async function runCaseJob(params) {
63300
63648
  const { client, job, judgeModel, runId, imagePath } = params;
63301
63649
  const { model, benchmarkCase } = job;
@@ -63488,6 +63836,82 @@ async function judgeCase(params) {
63488
63836
  durationMs: Date.now() - startedAt
63489
63837
  };
63490
63838
  }
63839
+ async function judgePromptBudgetTurn(params) {
63840
+ const startedAt = Date.now();
63841
+ const judgeResponse = await params.client.sendMessage({
63842
+ message: `${modelMention(params.judgeModel)} ${promptBudgetTurnJudgePrompt(params)}`,
63843
+ incognito: true,
63844
+ autoApproveSubChats: true,
63845
+ benchmarkMetadata: benchmarkMetadata({
63846
+ runId: params.runId,
63847
+ suite: `prompt_budget_${params.phase}`,
63848
+ caseId: `${params.benchmarkCase.id}:turn-${params.turnNumber}:judge`,
63849
+ targetModel: params.targetModel,
63850
+ judgeModel: params.judgeModel
63851
+ }),
63852
+ precollectResponse: true
63853
+ });
63854
+ const judgment = parseJudgment(judgeResponse.assistant);
63855
+ return {
63856
+ model: params.judgeModel,
63857
+ score: judgment.score,
63858
+ reason: judgment.reason,
63859
+ raw: judgeResponse.assistant,
63860
+ durationMs: Date.now() - startedAt
63861
+ };
63862
+ }
63863
+ async function judgePromptBudgetContinuity(params) {
63864
+ const startedAt = Date.now();
63865
+ const judgeResponse = await params.client.sendMessage({
63866
+ message: `${modelMention(params.judgeModel)} ${promptBudgetContinuityJudgePrompt(params)}`,
63867
+ incognito: true,
63868
+ autoApproveSubChats: true,
63869
+ benchmarkMetadata: benchmarkMetadata({
63870
+ runId: params.runId,
63871
+ suite: `prompt_budget_${params.phase}`,
63872
+ caseId: `${params.benchmarkCase.id}:continuity-judge`,
63873
+ targetModel: params.targetModel,
63874
+ judgeModel: params.judgeModel
63875
+ }),
63876
+ precollectResponse: true
63877
+ });
63878
+ const judgment = parseJudgment(judgeResponse.assistant);
63879
+ return {
63880
+ model: params.judgeModel,
63881
+ score: judgment.score,
63882
+ reason: judgment.reason,
63883
+ raw: judgeResponse.assistant,
63884
+ durationMs: Date.now() - startedAt
63885
+ };
63886
+ }
63887
+ function promptBudgetTurnJudgePrompt(params) {
63888
+ return [
63889
+ "You are judging a real OpenMates prompt-budget benchmark response.",
63890
+ "Return exactly two plain-text lines, with no markdown, no code block, and no tool use.",
63891
+ "Line 1 format: BENCHMARK_SCORE=<integer from 1 to 5>",
63892
+ "Line 2 format: BENCHMARK_REASON=<one short sentence>",
63893
+ "Score for helpfulness, instruction following, factuality, safety, routing/tool appropriateness, concision, and whether the answer preserves needed context.",
63894
+ `Target model: ${params.targetModel}`,
63895
+ `Case: ${params.benchmarkCase.id}`,
63896
+ `Turn: ${params.turnNumber} of ${params.benchmarkCase.turns.length}`,
63897
+ `Rubric: ${params.benchmarkCase.qualityRubric}`,
63898
+ `User prompt: ${JSON.stringify(params.turn.prompt)}`,
63899
+ `Assistant response: ${JSON.stringify(params.turn.assistant)}`
63900
+ ].join("\n");
63901
+ }
63902
+ function promptBudgetContinuityJudgePrompt(params) {
63903
+ return [
63904
+ "You are judging multi-turn continuity for a real OpenMates prompt-budget benchmark conversation.",
63905
+ "Return exactly two plain-text lines, with no markdown, no code block, and no tool use.",
63906
+ "Line 1 format: BENCHMARK_SCORE=<integer from 1 to 5>",
63907
+ "Line 2 format: BENCHMARK_REASON=<one short sentence>",
63908
+ "Score whether the assistant consistently builds on earlier turns, avoids restarting, and satisfies the final requested artifact.",
63909
+ `Target model: ${params.targetModel}`,
63910
+ `Case: ${params.benchmarkCase.id}`,
63911
+ `Rubric: ${params.benchmarkCase.qualityRubric}`,
63912
+ `Turns: ${JSON.stringify(params.turns.map((turn) => ({ prompt: turn.prompt, assistant: turn.assistant })))}`
63913
+ ].join("\n");
63914
+ }
63491
63915
  async function runPool(items, parallel, worker) {
63492
63916
  let index = 0;
63493
63917
  const workers = Array.from({ length: Math.min(parallel, items.length) }, async () => {
@@ -63725,6 +64149,27 @@ function estimateCredits(cases, targetModels, judgeModel, pricing) {
63725
64149
  assumptions: { targetInputTokens, targetOutputTokens, judgeInputTokens, judgeOutputTokens }
63726
64150
  };
63727
64151
  }
64152
+ function estimatePromptBudgetCredits(cases, targetModel, judgeModel, pricing) {
64153
+ const targetPricing = pricing.get(targetModel);
64154
+ const judgePricing = pricing.get(judgeModel);
64155
+ if (!targetPricing || !judgePricing) {
64156
+ throw new Error("Prompt-budget benchmark pricing preflight failed after model pricing lookup.");
64157
+ }
64158
+ const targetTurnCount = cases.reduce((sum, benchmarkCase) => sum + benchmarkCase.turns.length, 0);
64159
+ const judgeTurnCount = targetTurnCount + cases.filter((benchmarkCase) => benchmarkCase.turns.length > 1).length;
64160
+ const targetInputTokens = targetTurnCount * PROMPT_BUDGET_TARGET_INPUT_TOKENS_PER_TURN;
64161
+ const targetOutputTokens = targetTurnCount * PROMPT_BUDGET_TARGET_OUTPUT_TOKENS_PER_TURN;
64162
+ const judgeInputTokens = judgeTurnCount * PROMPT_BUDGET_JUDGE_INPUT_TOKENS_PER_RESPONSE;
64163
+ const judgeOutputTokens = judgeTurnCount * PROMPT_BUDGET_JUDGE_OUTPUT_TOKENS_PER_RESPONSE;
64164
+ const targetCredits = creditsFor(targetPricing, targetInputTokens, targetOutputTokens);
64165
+ const judgeCredits = creditsFor(judgePricing, judgeInputTokens, judgeOutputTokens);
64166
+ return {
64167
+ targetCredits,
64168
+ judgeCredits,
64169
+ totalCredits: targetCredits + judgeCredits,
64170
+ assumptions: { targetInputTokens, targetOutputTokens, judgeInputTokens, judgeOutputTokens }
64171
+ };
64172
+ }
63728
64173
  function creditsFor(pricing, inputTokens, outputTokens) {
63729
64174
  return Math.ceil(inputTokens / pricing.inputTokensPerCredit) + Math.ceil(outputTokens / pricing.outputTokensPerCredit);
63730
64175
  }
@@ -63762,6 +64207,21 @@ function makeBaseResult(params) {
63762
64207
  }
63763
64208
  };
63764
64209
  }
64210
+ function makePromptBudgetBaseResult(params) {
64211
+ return {
64212
+ command: "benchmark prompt-budget",
64213
+ status: params.dryRun ? "planned" : "completed",
64214
+ phase: params.phase,
64215
+ runId: params.runId,
64216
+ corpusHash: hashPromptBudgetCorpus(PROMPT_BUDGET_CASES),
64217
+ targetModel: params.targetModel,
64218
+ judgeModel: params.judgeModel,
64219
+ spendsCredits: !params.dryRun,
64220
+ estimatedCredits: params.estimate,
64221
+ cases: [],
64222
+ summary: promptBudgetSummary([])
64223
+ };
64224
+ }
63765
64225
  function recomputeResult(result, totalJobs, interrupted) {
63766
64226
  const completed = result.cases.length;
63767
64227
  const passed = result.cases.filter((caseResult) => caseResult.passed).length;
@@ -63804,6 +64264,56 @@ function buildComparison(summaries) {
63804
64264
  function round2(value) {
63805
64265
  return Math.round(value * 100) / 100;
63806
64266
  }
64267
+ function recomputePromptBudgetResult(result) {
64268
+ result.summary = promptBudgetSummary(result.cases);
64269
+ result.status = result.summary.completedTurns < result.summary.totalTurns || result.cases.some((benchmarkCase) => Boolean(benchmarkCase.error)) ? "partial" : "completed";
64270
+ }
64271
+ function promptBudgetSummary(cases) {
64272
+ const completedTurns = cases.reduce((sum, benchmarkCase) => sum + benchmarkCase.turns.length, 0);
64273
+ const durations = cases.flatMap((benchmarkCase) => benchmarkCase.turns.map((turn) => turn.durationMs)).filter((value) => value > 0);
64274
+ const judgeScores = cases.flatMap((benchmarkCase) => benchmarkCase.turns.map((turn) => turn.judge?.score)).filter((score) => typeof score === "number" && Number.isFinite(score));
64275
+ const continuityScores = cases.map((benchmarkCase) => benchmarkCase.continuityJudge?.score).filter((score) => typeof score === "number" && Number.isFinite(score));
64276
+ return {
64277
+ totalCases: PROMPT_BUDGET_CASES.length,
64278
+ completedCases: cases.length,
64279
+ totalTurns: PROMPT_BUDGET_CASES.reduce((sum, benchmarkCase) => sum + benchmarkCase.turns.length, 0),
64280
+ completedTurns,
64281
+ passedCases: cases.filter((benchmarkCase) => benchmarkCase.passed).length,
64282
+ failedCases: cases.filter((benchmarkCase) => !benchmarkCase.passed).length,
64283
+ averageTurnDurationMs: durations.length > 0 ? Math.round(durations.reduce((sum, value) => sum + value, 0) / durations.length) : null,
64284
+ averageJudgeScore: judgeScores.length > 0 ? round2(judgeScores.reduce((sum, score) => sum + score, 0) / judgeScores.length) : null,
64285
+ averageContinuityScore: continuityScores.length > 0 ? round2(continuityScores.reduce((sum, score) => sum + score, 0) / continuityScores.length) : null
64286
+ };
64287
+ }
64288
+ function validatePromptBudgetResumeArtifact(artifact, expected) {
64289
+ if (artifact.command !== "benchmark prompt-budget") {
64290
+ throw new Error("--resume-from must point to a prompt-budget benchmark artifact");
64291
+ }
64292
+ if (artifact.corpusHash !== hashPromptBudgetCorpus(PROMPT_BUDGET_CASES)) {
64293
+ throw new Error("--resume-from artifact uses a different prompt-budget corpus");
64294
+ }
64295
+ if (artifact.phase !== expected.phase) {
64296
+ throw new Error(`--resume-from artifact phase is ${artifact.phase}, expected ${expected.phase}`);
64297
+ }
64298
+ if (artifact.targetModel !== expected.targetModel || artifact.judgeModel !== expected.judgeModel) {
64299
+ throw new Error("--resume-from artifact target or judge model does not match this run");
64300
+ }
64301
+ }
64302
+ function isCompletePromptBudgetCase(caseResult) {
64303
+ if (caseResult.error) return false;
64304
+ const definition = PROMPT_BUDGET_CASES.find((benchmarkCase) => benchmarkCase.id === caseResult.id);
64305
+ if (!definition) return false;
64306
+ if (caseResult.promptHashes.join("|") !== definition.turns.map(stableHash).join("|")) return false;
64307
+ if (caseResult.turns.length !== definition.turns.length) return false;
64308
+ if (caseResult.turns.some((turn) => !turn.judge)) return false;
64309
+ return definition.turns.length === 1 || Boolean(caseResult.continuityJudge);
64310
+ }
64311
+ function hashPromptBudgetCorpus(cases) {
64312
+ return stableHash(cases.map((benchmarkCase) => ({ id: benchmarkCase.id, turns: benchmarkCase.turns })));
64313
+ }
64314
+ function stableHash(value) {
64315
+ return createHash13("sha256").update(JSON.stringify(value)).digest("hex");
64316
+ }
63807
64317
  function defaultImageFixturePath() {
63808
64318
  const fixtureDir = join5(dirname4(fileURLToPath(import.meta.url)), "..", "fixtures");
63809
64319
  const fixturePath = join5(fixtureDir, "brandenburger-tor.png");
@@ -63837,6 +64347,143 @@ function writeBenchmarkResult(result, flags, output) {
63837
64347
  }
63838
64348
  }
63839
64349
  }
64350
+ function writePromptBudgetResult(result, flags, output) {
64351
+ const json = `${JSON.stringify(result, null, 2)}
64352
+ `;
64353
+ if (output) writeFileSync5(output, json, "utf-8");
64354
+ if (flags.json === true || output) {
64355
+ process.stdout.write(json);
64356
+ return;
64357
+ }
64358
+ console.log(`Prompt-budget benchmark ${result.status}: ${result.phase}`);
64359
+ console.log(`Run ID: ${result.runId}`);
64360
+ console.log(`Corpus: ${result.corpusHash}`);
64361
+ console.log(`Target model: ${result.targetModel}`);
64362
+ console.log(`Judge: ${result.judgeModel}`);
64363
+ console.log(`Estimated credits: ${result.estimatedCredits.totalCredits}`);
64364
+ console.log(`Spend credits: ${result.spendsCredits ? "yes" : "no"}`);
64365
+ if (result.status !== "planned") {
64366
+ console.log(`Passed cases: ${result.summary.passedCases}/${result.summary.completedCases}`);
64367
+ console.log(`Average turn duration: ${result.summary.averageTurnDurationMs ?? "n/a"}ms`);
64368
+ console.log(`Average judge score: ${result.summary.averageJudgeScore ?? "n/a"}`);
64369
+ for (const benchmarkCase of result.cases) {
64370
+ const mark = benchmarkCase.passed ? "PASS" : "FAIL";
64371
+ const error = benchmarkCase.error ? ` error=${benchmarkCase.error}` : "";
64372
+ console.log(`${mark} ${benchmarkCase.id} (${benchmarkCase.durationMs}ms)${error}`);
64373
+ }
64374
+ }
64375
+ }
64376
+ function writePromptBudgetComparison(result, flags, output) {
64377
+ const json = `${JSON.stringify(result, null, 2)}
64378
+ `;
64379
+ if (output) writeFileSync5(output, json, "utf-8");
64380
+ if (flags.json === true || output) {
64381
+ process.stdout.write(json);
64382
+ return;
64383
+ }
64384
+ console.log(`Prompt-budget comparison ${result.status}: ${result.recommendation}`);
64385
+ console.log(`Corpus: ${result.corpusHash}`);
64386
+ console.log(`Duration delta: ${formatMetricDelta(result.deltas.averageTurnDurationMs, "ms")}`);
64387
+ console.log(`Quality delta: ${formatMetricDelta(result.deltas.averageJudgeScore, "")}`);
64388
+ if (result.regressions.length > 0) {
64389
+ console.log("Regressions:");
64390
+ for (const regression of result.regressions) console.log(`- ${regression}`);
64391
+ }
64392
+ }
64393
+ function comparePromptBudgetArtifacts(flags) {
64394
+ const baselinePath = stringFlag(flags.baseline);
64395
+ const postchangePath = stringFlag(flags.postchange);
64396
+ if (!baselinePath || !postchangePath) {
64397
+ throw new Error("prompt-budget compare requires --baseline <path> and --postchange <path>");
64398
+ }
64399
+ const baseline = readPromptBudgetResult(baselinePath);
64400
+ const postchange = readPromptBudgetResult(postchangePath);
64401
+ if (baseline.command !== "benchmark prompt-budget" || postchange.command !== "benchmark prompt-budget") {
64402
+ throw new Error("prompt-budget compare expects prompt-budget benchmark artifacts");
64403
+ }
64404
+ if (baseline.corpusHash !== postchange.corpusHash) {
64405
+ throw new Error("prompt-budget artifacts use different corpus hashes and cannot be compared");
64406
+ }
64407
+ const regressions = findPromptBudgetRegressions(baseline, postchange);
64408
+ const durationDelta = metricDelta(baseline.summary.averageTurnDurationMs, postchange.summary.averageTurnDurationMs);
64409
+ const qualityDelta = metricDelta(baseline.summary.averageJudgeScore, postchange.summary.averageJudgeScore);
64410
+ const continuityDelta = metricDelta(baseline.summary.averageContinuityScore, postchange.summary.averageContinuityScore);
64411
+ const latencyImproved = durationDelta.delta !== null && durationDelta.delta < 0;
64412
+ const qualityRegressed = regressions.length > 0;
64413
+ return {
64414
+ command: "benchmark prompt-budget compare",
64415
+ status: qualityRegressed ? "needs_fix" : "passed",
64416
+ corpusHash: baseline.corpusHash,
64417
+ baseline: baselinePath,
64418
+ postchange: postchangePath,
64419
+ deltas: {
64420
+ averageTurnDurationMs: durationDelta,
64421
+ averageJudgeScore: qualityDelta,
64422
+ averageContinuityScore: continuityDelta
64423
+ },
64424
+ regressions,
64425
+ recommendation: !qualityRegressed && latencyImproved ? "keep" : "adjust_or_revert"
64426
+ };
64427
+ }
64428
+ function readPromptBudgetResult(path) {
64429
+ const parsed = JSON.parse(readFileSync7(path, "utf-8"));
64430
+ if (!parsed || typeof parsed !== "object") throw new Error(`Invalid prompt-budget artifact: ${path}`);
64431
+ return parsed;
64432
+ }
64433
+ function findPromptBudgetRegressions(baseline, postchange) {
64434
+ const regressions = [];
64435
+ const postCases = new Map(postchange.cases.map((benchmarkCase) => [benchmarkCase.id, benchmarkCase]));
64436
+ for (const baselineCase of baseline.cases) {
64437
+ const postCase = postCases.get(baselineCase.id);
64438
+ if (!postCase) {
64439
+ regressions.push(`${baselineCase.id}: missing post-change case`);
64440
+ continue;
64441
+ }
64442
+ if (baselineCase.promptHashes.join("|") !== postCase.promptHashes.join("|")) {
64443
+ regressions.push(`${baselineCase.id}: prompt hashes differ`);
64444
+ }
64445
+ for (const baselineTurn of baselineCase.turns) {
64446
+ const postTurn = postCase.turns.find((turn) => turn.turn === baselineTurn.turn);
64447
+ if (!postTurn) {
64448
+ regressions.push(`${baselineCase.id} turn ${baselineTurn.turn}: missing post-change turn`);
64449
+ continue;
64450
+ }
64451
+ const baselineScore = baselineTurn.judge?.score;
64452
+ const postScore = postTurn.judge?.score;
64453
+ if (typeof baselineScore === "number" && typeof postScore === "number") {
64454
+ if (baselineScore >= 4 && postScore < 4) {
64455
+ regressions.push(`${baselineCase.id} turn ${baselineTurn.turn}: score dropped below pass threshold (${baselineScore} -> ${postScore})`);
64456
+ } else if (postScore < baselineScore - 1) {
64457
+ regressions.push(`${baselineCase.id} turn ${baselineTurn.turn}: score dropped materially (${baselineScore} -> ${postScore})`);
64458
+ }
64459
+ }
64460
+ }
64461
+ const baselineContinuity = baselineCase.continuityJudge?.score;
64462
+ const postContinuity = postCase.continuityJudge?.score;
64463
+ if (typeof baselineContinuity === "number" && typeof postContinuity === "number") {
64464
+ if (baselineContinuity >= 4 && postContinuity < 4) {
64465
+ regressions.push(`${baselineCase.id}: continuity score dropped below pass threshold (${baselineContinuity} -> ${postContinuity})`);
64466
+ } else if (postContinuity < baselineContinuity - 1) {
64467
+ regressions.push(`${baselineCase.id}: continuity score dropped materially (${baselineContinuity} -> ${postContinuity})`);
64468
+ }
64469
+ }
64470
+ }
64471
+ return regressions;
64472
+ }
64473
+ function metricDelta(baseline, postchange) {
64474
+ if (baseline === null || postchange === null) {
64475
+ return { baseline, postchange, delta: null, percent: null };
64476
+ }
64477
+ const delta = round2(postchange - baseline);
64478
+ const percent = baseline === 0 ? null : round2(delta / baseline * 100);
64479
+ return { baseline, postchange, delta, percent };
64480
+ }
64481
+ function formatMetricDelta(delta, suffix) {
64482
+ if (delta.delta === null) return "n/a";
64483
+ const unit = suffix ? suffix : "";
64484
+ const percent = delta.percent === null ? "" : ` (${delta.percent}%)`;
64485
+ return `${delta.baseline}${unit} -> ${delta.postchange}${unit} (${delta.delta}${unit})${percent}`;
64486
+ }
63840
64487
 
63841
64488
  // src/tui.ts
63842
64489
  import { createInterface as createInterface4 } from "readline/promises";
@@ -66263,7 +66910,7 @@ Starting a new chat...
66263
66910
  acceptTaskProposals: flags["accept-task-proposals"] === true,
66264
66911
  piiDetection: flags["no-pii-detection"] !== true,
66265
66912
  taskUpdateJobs: false,
66266
- responseTimeoutMs: parseResponseTimeoutMs(flags),
66913
+ responseTimeoutMs: parseResponseTimeoutMs2(flags),
66267
66914
  ...teamContextFromFlags(flags)
66268
66915
  },
66269
66916
  redactor
@@ -67233,7 +67880,7 @@ async function handleGoalChat(client, flags, redactor) {
67233
67880
  autoApproveMemories: flags["auto-approve-memories"] === true,
67234
67881
  acceptTaskProposals: flags["accept-task-proposals"] === true,
67235
67882
  piiDetection: flags["no-pii-detection"] !== true,
67236
- responseTimeoutMs: parseResponseTimeoutMs(flags),
67883
+ responseTimeoutMs: parseResponseTimeoutMs2(flags),
67237
67884
  ...teamContextFromFlags(flags)
67238
67885
  },
67239
67886
  redactor
@@ -67816,7 +68463,7 @@ function parsePositiveIntegerFlag(value, flagName) {
67816
68463
  }
67817
68464
  return parsed;
67818
68465
  }
67819
- function parseResponseTimeoutMs(flags) {
68466
+ function parseResponseTimeoutMs2(flags) {
67820
68467
  const seconds = parsePositiveIntegerFlag(flags["response-timeout-seconds"], "--response-timeout-seconds");
67821
68468
  return seconds === void 0 ? void 0 : seconds * 1e3;
67822
68469
  }
@@ -68110,7 +68757,7 @@ async function handleChats(client, subcommand, rest, flags, redactor) {
68110
68757
  send,
68111
68758
  dryRun,
68112
68759
  confirmDestructive: flags.yes === true,
68113
- responseTimeoutMs: parseResponseTimeoutMs(flags),
68760
+ responseTimeoutMs: parseResponseTimeoutMs2(flags),
68114
68761
  ...teamContext
68115
68762
  });
68116
68763
  if (flags.json === true) {
@@ -68134,7 +68781,7 @@ async function handleChats(client, subcommand, rest, flags, redactor) {
68134
68781
  chatId,
68135
68782
  dryRun,
68136
68783
  confirmDestructive: flags.yes === true,
68137
- responseTimeoutMs: parseResponseTimeoutMs(flags),
68784
+ responseTimeoutMs: parseResponseTimeoutMs2(flags),
68138
68785
  ...teamContext
68139
68786
  });
68140
68787
  if (flags.json === true) {
@@ -68161,7 +68808,7 @@ async function handleChats(client, subcommand, rest, flags, redactor) {
68161
68808
  autoApproveMemories: flags["auto-approve-memories"] === true,
68162
68809
  acceptTaskProposals: flags["accept-task-proposals"] === true,
68163
68810
  piiDetection: flags["no-pii-detection"] !== true,
68164
- responseTimeoutMs: parseResponseTimeoutMs(flags),
68811
+ responseTimeoutMs: parseResponseTimeoutMs2(flags),
68165
68812
  ...teamContext,
68166
68813
  anonymousLearningMode: client.hasSession() ? void 0 : parseAnonymousLearningModeFlags(flags)
68167
68814
  },
@@ -68239,7 +68886,7 @@ Run 'openmates chats show ` + chatId + "' to check if suggestions have been save
68239
68886
  autoApproveMemories: flags["auto-approve-memories"] === true,
68240
68887
  acceptTaskProposals: flags["accept-task-proposals"] === true,
68241
68888
  piiDetection: flags["no-pii-detection"] !== true,
68242
- responseTimeoutMs: parseResponseTimeoutMs(flags),
68889
+ responseTimeoutMs: parseResponseTimeoutMs2(flags),
68243
68890
  ...teamContext
68244
68891
  },
68245
68892
  redactor
@@ -68270,7 +68917,7 @@ Run 'openmates chats show ` + chatId + "' to check if suggestions have been save
68270
68917
  autoApproveMemories: flags["auto-approve-memories"] === true,
68271
68918
  acceptTaskProposals: flags["accept-task-proposals"] === true,
68272
68919
  piiDetection: flags["no-pii-detection"] !== true,
68273
- responseTimeoutMs: parseResponseTimeoutMs(flags),
68920
+ responseTimeoutMs: parseResponseTimeoutMs2(flags),
68274
68921
  ...teamContext
68275
68922
  },
68276
68923
  redactor
@@ -68293,7 +68940,7 @@ Run 'openmates chats show ` + chatId + "' to check if suggestions have been save
68293
68940
  autoApproveSubChats: flags["auto-approve"] === true,
68294
68941
  autoApproveMemories: flags["auto-approve-memories"] === true,
68295
68942
  piiDetection: flags["no-pii-detection"] !== true,
68296
- responseTimeoutMs: parseResponseTimeoutMs(flags)
68943
+ responseTimeoutMs: parseResponseTimeoutMs2(flags)
68297
68944
  },
68298
68945
  redactor
68299
68946
  );
@@ -68856,7 +69503,7 @@ async function sendApiKeyChatNew(client, apiKey, message, flags) {
68856
69503
  try {
68857
69504
  response = await sdk.chats.send(message, {
68858
69505
  saveToAccount: false,
68859
- recoveryTimeoutMs: parseResponseTimeoutMs(flags)
69506
+ recoveryTimeoutMs: parseResponseTimeoutMs2(flags)
68860
69507
  });
68861
69508
  } catch (err) {
68862
69509
  if (!isSdkChatScopeDenied(err)) throw err;
@@ -68998,7 +69645,7 @@ function normalizeApiKeyLoadedChat(payload) {
68998
69645
  const messages = rawMessages.map((message) => {
68999
69646
  const messageId = String(message.client_message_id ?? message.message_id ?? message.id ?? "");
69000
69647
  const directEmbedIds = Array.isArray(message.embedIds) ? message.embedIds : Array.isArray(message.embed_ids) ? message.embed_ids : [];
69001
- const hashedMessageId = messageId ? createHash13("sha256").update(messageId).digest("hex") : null;
69648
+ const hashedMessageId = messageId ? createHash14("sha256").update(messageId).digest("hex") : null;
69002
69649
  const matchedEmbedIds = hashedMessageId ? rawEmbeds.filter((embed) => embed.hashed_message_id === hashedMessageId && !embed.parent_embed_id).map((embed) => String(embed.embed_id ?? embed.id ?? "")).filter(Boolean) : [];
69003
69650
  return {
69004
69651
  id: messageId,
@@ -69658,9 +70305,9 @@ Run: openmates apps ${command.app_id} ${command.skill_id} --help`
69658
70305
  return value;
69659
70306
  }
69660
70307
  function buildTravelConnectionsRequest(positionals, flags) {
69661
- const origin = stringFlag(flags, "origin") ?? positionals[0];
69662
- const destination = stringFlag(flags, "destination") ?? positionals[1];
69663
- const date = stringFlag(flags, "date") ?? positionals[2];
70308
+ const origin = stringFlag2(flags, "origin") ?? positionals[0];
70309
+ const destination = stringFlag2(flags, "destination") ?? positionals[1];
70310
+ const date = stringFlag2(flags, "date") ?? positionals[2];
69664
70311
  if (!origin || !destination || !date) {
69665
70312
  throw new Error(
69666
70313
  "Missing travel route. Use --origin <place> --destination <place> --date <YYYY-MM-DD>.\n\nExample: openmates apps travel search_connections --origin Berlin --destination Munich --date 2026-08-01 --json"
@@ -69669,12 +70316,12 @@ function buildTravelConnectionsRequest(positionals, flags) {
69669
70316
  const request = {
69670
70317
  legs: [{ origin, destination, date }]
69671
70318
  };
69672
- const transport = stringFlag(flags, "transport") ?? stringFlag(flags, "transport-method");
70319
+ const transport = stringFlag2(flags, "transport") ?? stringFlag2(flags, "transport-method");
69673
70320
  if (transport) request.transport_methods = [transport];
69674
70321
  return request;
69675
70322
  }
69676
70323
  function buildCodeImageToHtmlRequest(positionals, flags) {
69677
- const file = stringFlag(flags, "file") ?? positionals[0];
70324
+ const file = stringFlag2(flags, "file") ?? positionals[0];
69678
70325
  if (!file) {
69679
70326
  throw new Error(
69680
70327
  "Missing screenshot file. Use --file <path>.\n\nExample: openmates apps code image_to_html --file ./mockup.png --json"
@@ -69722,7 +70369,7 @@ function primaryPositionalProperty(properties, required) {
69722
70369
  function readFlag(flags, name) {
69723
70370
  return flags[name] ?? flags[kebabCase(name)];
69724
70371
  }
69725
- function stringFlag(flags, name) {
70372
+ function stringFlag2(flags, name) {
69726
70373
  const raw = readFlag(flags, name);
69727
70374
  return typeof raw === "string" && raw.trim() ? raw.trim() : void 0;
69728
70375
  }
@@ -70078,7 +70725,7 @@ async function handleDesignIconExport(client, positionals, flags, apiKey) {
70078
70725
  printDesignIconExportHelp();
70079
70726
  return;
70080
70727
  }
70081
- const outputPath = stringFlag(flags, "output");
70728
+ const outputPath = stringFlag2(flags, "output");
70082
70729
  if (!outputPath) {
70083
70730
  console.error(
70084
70731
  "Missing --output flag.\n\nUsage:\n openmates apps design export-icon lucide:home --output home.svg [--color '#111827']\n openmates apps design export-icon --prefix lucide --name home --format png --size 64 --output home.png\n"
@@ -70095,7 +70742,7 @@ async function handleDesignIconExport(client, positionals, flags, apiKey) {
70095
70742
  ...iconRef,
70096
70743
  outputPath,
70097
70744
  format,
70098
- color: stringFlag(flags, "color"),
70745
+ color: stringFlag2(flags, "color"),
70099
70746
  palette: flags.palette === true,
70100
70747
  allowPaletteRecolor: flags["allow-palette-recolor"] === true,
70101
70748
  size,
@@ -70122,9 +70769,9 @@ async function handleDesignIconExport(client, positionals, flags, apiKey) {
70122
70769
  }
70123
70770
  }
70124
70771
  function parseDesignIconReference(positionals, flags) {
70125
- const svgPath = stringFlag(flags, "svg-path");
70772
+ const svgPath = stringFlag2(flags, "svg-path");
70126
70773
  if (svgPath) return { svgPath };
70127
- const icon = stringFlag(flags, "icon") ?? positionals[0];
70774
+ const icon = stringFlag2(flags, "icon") ?? positionals[0];
70128
70775
  if (icon) {
70129
70776
  const [prefix, name, extra] = icon.split(":");
70130
70777
  if (!prefix || !name || extra !== void 0) {
@@ -70133,8 +70780,8 @@ function parseDesignIconReference(positionals, flags) {
70133
70780
  return { prefix, name };
70134
70781
  }
70135
70782
  return {
70136
- prefix: stringFlag(flags, "prefix"),
70137
- name: stringFlag(flags, "name")
70783
+ prefix: stringFlag2(flags, "prefix"),
70784
+ name: stringFlag2(flags, "name")
70138
70785
  };
70139
70786
  }
70140
70787
  function parseDesignIconExportFormat(value) {
@@ -71172,12 +71819,12 @@ async function handleFinance(client, subcommand, _rest, flags) {
71172
71819
  if (flags["csv-only"] !== true) {
71173
71820
  try {
71174
71821
  connectedAccount = await client.decryptConnectedAccountForSkill({
71175
- accountId: stringFlag(flags, "connected-account"),
71822
+ accountId: stringFlag2(flags, "connected-account"),
71176
71823
  appId: "finance",
71177
71824
  providerId: "revolut_business"
71178
71825
  });
71179
71826
  } catch (error) {
71180
- if (csvStatements.length === 0 || stringFlag(flags, "connected-account")) throw error;
71827
+ if (csvStatements.length === 0 || stringFlag2(flags, "connected-account")) throw error;
71181
71828
  }
71182
71829
  }
71183
71830
  if (!connectedAccount && csvStatements.length === 0) {
@@ -71215,11 +71862,11 @@ function readFinanceCsvStatements(flags) {
71215
71862
  }
71216
71863
  function buildFinanceCheckAccountsInput(flags, csvStatements, connectedAccount) {
71217
71864
  const input = {
71218
- period: stringFlag(flags, "period") ?? "monthly",
71219
- projection_horizon: stringFlag(flags, "projection-horizon") ?? "monthly"
71865
+ period: stringFlag2(flags, "period") ?? "monthly",
71866
+ projection_horizon: stringFlag2(flags, "projection-horizon") ?? "monthly"
71220
71867
  };
71221
- const startDate = stringFlag(flags, "from") ?? stringFlag(flags, "start-date");
71222
- const endDate = stringFlag(flags, "to") ?? stringFlag(flags, "end-date");
71868
+ const startDate = stringFlag2(flags, "from") ?? stringFlag2(flags, "start-date");
71869
+ const endDate = stringFlag2(flags, "to") ?? stringFlag2(flags, "end-date");
71223
71870
  if (startDate) input.start_date = startDate;
71224
71871
  if (endDate) input.end_date = endDate;
71225
71872
  addFinanceListFilter(input, "account_filters", flags.account);
@@ -71227,7 +71874,7 @@ function buildFinanceCheckAccountsInput(flags, csvStatements, connectedAccount)
71227
71874
  addFinanceListFilter(input, "category_filters", flags.category ?? flags.categories);
71228
71875
  addFinanceListFilter(input, "state_filters", flags.state ?? flags.states);
71229
71876
  addFinanceListFilter(input, "placeholder_filters", flags.placeholder ?? flags.placeholders);
71230
- const direction = stringFlag(flags, "direction");
71877
+ const direction = stringFlag2(flags, "direction");
71231
71878
  if (direction) input.direction_filter = direction;
71232
71879
  if (csvStatements.length > 0) input.csv_statements = csvStatements;
71233
71880
  if (connectedAccount) {
package/dist/cli.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  getExtForLang,
4
4
  serializeToYaml
5
- } from "./chunk-VJLMFEX6.js";
5
+ } from "./chunk-U4XICRVH.js";
6
6
  import "./chunk-MLWBMJEN.js";
7
7
  export {
8
8
  getExtForLang,
package/dist/index.d.ts CHANGED
@@ -80,6 +80,16 @@ interface StreamEvent {
80
80
  /** Human-readable model name. */
81
81
  modelName: string | null;
82
82
  }
83
+ type AiResponseTokenUsage = {
84
+ promptTokens?: number;
85
+ completionTokens?: number;
86
+ userInputTokens?: number;
87
+ systemPromptTokens?: number;
88
+ totalCredits?: number;
89
+ };
90
+ type AiResponsePromptBudget = {
91
+ systemPromptTokens?: number;
92
+ };
83
93
  interface ChatCompressionCheckpointEvent {
84
94
  chatId: string;
85
95
  taskId: string | null;
@@ -242,6 +252,8 @@ declare class OpenMatesWsClient {
242
252
  subChatEvents: SubChatEvent[];
243
253
  recoveryJobId: string | null;
244
254
  compressionCheckpoints: ChatCompressionCheckpointEvent[];
255
+ tokenUsage: AiResponseTokenUsage | null;
256
+ promptBudget: AiResponsePromptBudget | null;
245
257
  }>;
246
258
  }
247
259
 
@@ -1634,6 +1646,10 @@ declare class OpenMatesClient {
1634
1646
  approvedKeys: string[];
1635
1647
  entryCount: number;
1636
1648
  }>;
1649
+ /** Final-frame token usage surfaced by the backend when provider usage metadata is available. */
1650
+ tokenUsage: AiResponseTokenUsage | null;
1651
+ /** Prompt-budget metrics surfaced by the backend when available. */
1652
+ promptBudget: AiResponsePromptBudget | null;
1637
1653
  }>;
1638
1654
  createTurnTokenRefs(params: {
1639
1655
  chatId: string;
@@ -1985,6 +2001,10 @@ declare class OpenMatesClient {
1985
2001
  approvedKeys: string[];
1986
2002
  entryCount: number;
1987
2003
  }>;
2004
+ /** Final-frame token usage surfaced by the backend when provider usage metadata is available. */
2005
+ tokenUsage: AiResponseTokenUsage | null;
2006
+ /** Prompt-budget metrics surfaced by the backend when available. */
2007
+ promptBudget: AiResponsePromptBudget | null;
1988
2008
  }>;
1989
2009
  private persistEncryptedSystemMessage;
1990
2010
  private persistPendingTaskUpdateJobs;
package/dist/index.js CHANGED
@@ -36,7 +36,7 @@ import {
36
36
  renderPlanList,
37
37
  renderSupportInfo,
38
38
  serializeToYaml
39
- } from "./chunk-VJLMFEX6.js";
39
+ } from "./chunk-U4XICRVH.js";
40
40
  import "./chunk-MLWBMJEN.js";
41
41
  export {
42
42
  APP_SKILL_METADATA,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openmates",
3
- "version": "0.15.0-alpha.73",
3
+ "version": "0.15.0-alpha.75",
4
4
  "description": "OpenMates CLI and SDK",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",