Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Sources/CodexBar/MenuCardView+ModelHelpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -678,9 +678,12 @@ extension UsageMenuCardView.Model {
} else {
L("Unavailable")
}
let title = input.provider == .doubao && namedWindow.id.contains("-team-")
? "\(L(namedWindow.title)) (\(L("Team")))"
: L(namedWindow.title)
return Metric(
id: namedWindow.id,
title: namedWindow.title,
title: title,
percent: Self.clamped(
input.usageBarsShowUsed
? namedWindow.window.usedPercent
Expand Down
47 changes: 42 additions & 5 deletions Sources/CodexBar/MenuCardView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -612,13 +612,50 @@ private struct UsageMenuCardUsageContentView: View {
let showBottomDivider: Bool
@Environment(\.menuItemHighlighted) private var isHighlighted

/// Doubao ships Coding Plan and Agent Plan subscriptions, each with personal
/// and team editions whose windows share period labels. Split the two plan
/// families here; team rows keep distinct ids and disclose their edition.
private var doubaoSplitMetrics: (
coding: [UsageMenuCardView.Model.Metric],
agent: [UsageMenuCardView.Model.Metric])?
{
guard self.model.provider == .doubao else { return nil }
let agent = self.model.metrics.filter { $0.id.hasPrefix("doubao-agent-") }
guard !agent.isEmpty else { return nil }
let coding = self.model.metrics.filter { !$0.id.hasPrefix("doubao-agent-") }
return (coding, agent)
}

private func groupHeader(_ title: String) -> some View {
Text(L(title))
.font(.caption.weight(.semibold))
.foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted))
.textCase(.uppercase)
}

private func metricRows(_ metrics: [UsageMenuCardView.Model.Metric]) -> some View {
ForEach(metrics, id: \.id) { metric in
MetricRow(
metric: metric,
title: UsageMenuCardView.popupMetricTitle(provider: self.model.provider, metric: metric),
progressColor: self.model.progressColor)
}
}

var body: some View {
VStack(alignment: .leading, spacing: 12) {
ForEach(self.model.metrics, id: \.id) { metric in
MetricRow(
metric: metric,
title: UsageMenuCardView.popupMetricTitle(provider: self.model.provider, metric: metric),
progressColor: self.model.progressColor)
if let split = self.doubaoSplitMetrics {
if !split.coding.isEmpty {
self.groupHeader("Coding Plan")
self.metricRows(split.coding)
}
if !split.coding.isEmpty {
Divider()
}
self.groupHeader("Agent Plan")
self.metricRows(split.agent)
} else {
self.metricRows(self.model.metrics)
}
if let resetCredits = self.model.codexResetCredits {
if !self.model.metrics.isEmpty {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ struct DoubaoProviderImplementation: ProviderImplementation {
ProviderSettingsFieldDescriptor(
id: "doubao-api-token",
title: "API key / Access key ID",
subtitle: "Use a Volcengine access key ID with the secret field for Coding Plan usage, "
+ "or leave the secret blank to use an Ark API key.",
subtitle: "Without configured API credentials, install and authenticate 'arkcli' for "
+ "Coding/Agent Plan usage. Existing API credentials remain authoritative.",
kind: .secure,
placeholder: "ark-... or AKLT...",
binding: context.stringBinding(\.doubaoAPIToken),
Expand All @@ -40,7 +40,7 @@ struct DoubaoProviderImplementation: ProviderImplementation {
ProviderSettingsFieldDescriptor(
id: "doubao-secret-access-key",
title: "Secret access key",
subtitle: "Volcengine secret access key for the signed Coding Plan usage API.",
subtitle: "Optional. Only needed if arkcli is unavailable and you use Volcengine AK/SK signing.",
kind: .secure,
placeholder: "",
binding: context.stringBinding(\.doubaoSecretAccessKey),
Expand Down
8 changes: 7 additions & 1 deletion Sources/CodexBar/Resources/ProviderIcon-doubao.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions Sources/CodexBar/Resources/ar.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1267,6 +1267,9 @@
"By subscription" = "حسب الاشتراك";
"No model-level history" = "لا يوجد سجل على مستوى النموذج";
"Daily estimated spend" = "الإنفاق اليومي التقديري";
"Coding Plan" = "خطة البرمجة";
"Agent Plan" = "خطة الوكيل";
"Team" = "فريق";

/* Menu bar layout editor */
"menu_bar_layout_title" = "التخطيط";
Expand Down
3 changes: 3 additions & 0 deletions Sources/CodexBar/Resources/ca.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1266,6 +1266,9 @@
"By subscription" = "Per subscripció";
"No model-level history" = "Sense historial per model";
"Daily estimated spend" = "Despesa diària estimada";
"Coding Plan" = "Pla de programació";
"Agent Plan" = "Pla d'agent";
"Team" = "Equip";

/* Menu bar layout editor */
"menu_bar_layout_title" = "Disposició";
Expand Down
3 changes: 3 additions & 0 deletions Sources/CodexBar/Resources/de.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1264,6 +1264,9 @@
"By subscription" = "Nach Abonnement";
"No model-level history" = "Kein Verlauf auf Modellebene";
"Daily estimated spend" = "Geschätzte tägliche Ausgaben";
"Coding Plan" = "Coding-Plan";
"Agent Plan" = "Agentenplan";
"Team" = "Team";

/* Menu bar layout editor */
"menu_bar_layout_title" = "Layout";
Expand Down
3 changes: 3 additions & 0 deletions Sources/CodexBar/Resources/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1268,6 +1268,9 @@
"By subscription" = "By subscription";
"No model-level history" = "No model-level history";
"Daily estimated spend" = "Daily estimated spend";
"Coding Plan" = "Coding Plan";
"Agent Plan" = "Agent Plan";
"Team" = "Team";

/* Menu bar layout editor */
"menu_bar_layout_title" = "Layout";
Expand Down
3 changes: 3 additions & 0 deletions Sources/CodexBar/Resources/es.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1262,6 +1262,9 @@
"By subscription" = "Por suscripción";
"No model-level history" = "No hay historial por modelo";
"Daily estimated spend" = "Gasto diario estimado";
"Coding Plan" = "Plan de programación";
"Agent Plan" = "Plan de agente";
"Team" = "Equipo";

/* Menu bar layout editor */
"menu_bar_layout_title" = "Disposición";
Expand Down
3 changes: 3 additions & 0 deletions Sources/CodexBar/Resources/fa.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1267,6 +1267,9 @@
"By subscription" = "بر اساس اشتراک";
"No model-level history" = "تاریخچه‌ای در سطح مدل وجود ندارد";
"Daily estimated spend" = "برآورد هزینه روزانه";
"Coding Plan" = "طرح کدنویسی";
"Agent Plan" = "طرح عامل";
"Team" = "تیم";

/* Menu bar layout editor */
"menu_bar_layout_title" = "چیدمان";
Expand Down
3 changes: 3 additions & 0 deletions Sources/CodexBar/Resources/fr.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1263,6 +1263,9 @@
"By subscription" = "Par abonnement";
"No model-level history" = "Aucun historique au niveau des modèles";
"Daily estimated spend" = "Dépenses quotidiennes estimées";
"Coding Plan" = "Plan de codage";
"Agent Plan" = "Plan d'agent";
"Team" = "Équipe";

/* Menu bar layout editor */
"menu_bar_layout_title" = "Disposition";
Expand Down
3 changes: 3 additions & 0 deletions Sources/CodexBar/Resources/gl.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1263,6 +1263,9 @@
"By subscription" = "Por subscrición";
"No model-level history" = "Sen historial por modelo";
"Daily estimated spend" = "Gasto diario estimado";
"Coding Plan" = "Plan de programación";
"Agent Plan" = "Plan de axente";
"Team" = "Equipo";

/* Menu bar layout editor */
"menu_bar_layout_title" = "Disposición";
Expand Down
3 changes: 3 additions & 0 deletions Sources/CodexBar/Resources/id.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1267,6 +1267,9 @@
"By subscription" = "Berdasarkan langganan";
"No model-level history" = "Tidak ada riwayat tingkat model";
"Daily estimated spend" = "Perkiraan pengeluaran harian";
"Coding Plan" = "Paket Coding";
"Agent Plan" = "Paket Agen";
"Team" = "Tim";

/* Menu bar layout editor */
"menu_bar_layout_title" = "Tata letak";
Expand Down
3 changes: 3 additions & 0 deletions Sources/CodexBar/Resources/it.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1267,6 +1267,9 @@
"By subscription" = "Per abbonamento";
"No model-level history" = "Nessuna cronologia a livello di modello";
"Daily estimated spend" = "Spesa giornaliera stimata";
"Coding Plan" = "Piano di codifica";
"Agent Plan" = "Piano agente";
"Team" = "Squadra";

/* Menu bar layout editor */
"menu_bar_layout_title" = "Disposizione";
Expand Down
3 changes: 3 additions & 0 deletions Sources/CodexBar/Resources/ja.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1264,6 +1264,9 @@
"By subscription" = "サブスクリプション別";
"No model-level history" = "モデル別の履歴はありません";
"Daily estimated spend" = "日別推定支出";
"Coding Plan" = "コーディングプラン";
"Agent Plan" = "エージェントプラン";
"Team" = "チーム";

/* Menu bar layout editor */
"menu_bar_layout_title" = "レイアウト";
Expand Down
3 changes: 3 additions & 0 deletions Sources/CodexBar/Resources/ko.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1231,6 +1231,9 @@
"By subscription" = "구독별";
"No model-level history" = "모델별 내역이 없습니다";
"Daily estimated spend" = "일별 예상 지출";
"Coding Plan" = "코딩 요금제";
"Agent Plan" = "에이전트 요금제";
"Team" = "팀";

/* Menu bar layout editor */
"menu_bar_layout_title" = "레이아웃";
Expand Down
3 changes: 3 additions & 0 deletions Sources/CodexBar/Resources/nl.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1263,6 +1263,9 @@
"By subscription" = "Per abonnement";
"No model-level history" = "Geen geschiedenis op modelniveau";
"Daily estimated spend" = "Geschatte dagelijkse uitgaven";
"Coding Plan" = "Codeerplan";
"Agent Plan" = "Agentplan";
"Team" = "Team";

/* Menu bar layout editor */
"menu_bar_layout_title" = "Indeling";
Expand Down
3 changes: 3 additions & 0 deletions Sources/CodexBar/Resources/pl.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1267,6 +1267,9 @@
"By subscription" = "Według subskrypcji";
"No model-level history" = "Brak historii na poziomie modeli";
"Daily estimated spend" = "Szacowane dzienne wydatki";
"Coding Plan" = "Plan kodowania";
"Agent Plan" = "Plan agenta";
"Team" = "Zespół";

/* Menu bar layout editor */
"menu_bar_layout_title" = "Układ";
Expand Down
3 changes: 3 additions & 0 deletions Sources/CodexBar/Resources/pt-BR.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1264,6 +1264,9 @@
"By subscription" = "Por assinatura";
"No model-level history" = "Sem histórico por modelo";
"Daily estimated spend" = "Gasto diário estimado";
"Coding Plan" = "Plano de codificação";
"Agent Plan" = "Plano de agente";
"Team" = "Equipe";

/* Menu bar layout editor */
"menu_bar_layout_title" = "Layout";
Expand Down
3 changes: 3 additions & 0 deletions Sources/CodexBar/Resources/ru.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1265,6 +1265,9 @@
"By subscription" = "По подпискам";
"No model-level history" = "Нет истории по моделям";
"Daily estimated spend" = "Предполагаемые ежедневные расходы";
"Coding Plan" = "План программирования";
"Agent Plan" = "План агента";
"Team" = "Команда";

/* Menu bar layout editor */
"menu_bar_layout_title" = "Компоновка";
Expand Down
3 changes: 3 additions & 0 deletions Sources/CodexBar/Resources/sv.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1262,6 +1262,9 @@
"By subscription" = "Per abonnemang";
"No model-level history" = "Ingen historik på modellnivå";
"Daily estimated spend" = "Uppskattade dagliga utgifter";
"Coding Plan" = "Kodningsplan";
"Agent Plan" = "Agentplan";
"Team" = "Team";

/* Menu bar layout editor */
"menu_bar_layout_title" = "Layout";
Expand Down
3 changes: 3 additions & 0 deletions Sources/CodexBar/Resources/th.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1267,6 +1267,9 @@
"By subscription" = "แยกตามการสมัครสมาชิก";
"No model-level history" = "ไม่มีประวัติระดับโมเดล";
"Daily estimated spend" = "ค่าใช้จ่ายรายวันโดยประมาณ";
"Coding Plan" = "แผนการเขียนโค้ด";
"Agent Plan" = "แผนเอเจนต์";
"Team" = "ทีม";

/* Menu bar layout editor */
"menu_bar_layout_title" = "เค้าโครง";
Expand Down
3 changes: 3 additions & 0 deletions Sources/CodexBar/Resources/tr.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1265,6 +1265,9 @@
"By subscription" = "Aboneliğe göre";
"No model-level history" = "Model düzeyinde geçmiş yok";
"Daily estimated spend" = "Günlük tahmini harcama";
"Coding Plan" = "Kodlama Planı";
"Agent Plan" = "Ajan Planı";
"Team" = "Ekip";

/* Menu bar layout editor */
"menu_bar_layout_title" = "Düzen";
Expand Down
3 changes: 3 additions & 0 deletions Sources/CodexBar/Resources/uk.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1263,6 +1263,9 @@
"By subscription" = "За підписками";
"No model-level history" = "Немає історії за моделями";
"Daily estimated spend" = "Орієнтовні щоденні витрати";
"Coding Plan" = "План кодування";
"Agent Plan" = "План агента";
"Team" = "Команда";

/* Menu bar layout editor */
"menu_bar_layout_title" = "Компонування";
Expand Down
3 changes: 3 additions & 0 deletions Sources/CodexBar/Resources/vi.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1264,6 +1264,9 @@
"By subscription" = "Theo gói đăng ký";
"No model-level history" = "Không có lịch sử theo mô hình";
"Daily estimated spend" = "Chi tiêu ước tính hằng ngày";
"Coding Plan" = "Gói lập trình";
"Agent Plan" = "Gói tác nhân";
"Team" = "Nhóm";

/* Menu bar layout editor */
"menu_bar_layout_title" = "Bố cục";
Expand Down
3 changes: 3 additions & 0 deletions Sources/CodexBar/Resources/zh-Hans.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1239,6 +1239,9 @@
"By subscription" = "按订阅";
"No model-level history" = "暂无模型级历史";
"Daily estimated spend" = "每日估算支出";
"Coding Plan" = "编程套餐";
"Agent Plan" = "智能体套餐";
"Team" = "团队";

/* Menu bar layout editor */
"menu_bar_layout_title" = "布局";
Expand Down
3 changes: 3 additions & 0 deletions Sources/CodexBar/Resources/zh-Hant.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1294,6 +1294,9 @@
"By subscription" = "依訂閱";
"No model-level history" = "尚無模型層級歷史";
"Daily estimated spend" = "每日預估支出";
"Coding Plan" = "程式設計方案";
"Agent Plan" = "智慧體方案";
"Team" = "團隊";

/* Menu bar layout editor */
"menu_bar_layout_title" = "佈局";
Expand Down
1 change: 1 addition & 0 deletions Sources/CodexBarCore/Hooks/HookRunner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ public enum HookRunner {
case .binaryNotFound: return "executable not found"
case .launchFailed: return "launch failed"
case .timedOut: return "timed out"
case .outputTooLarge: return "output too large"
case let .nonZeroExit(code, _): return "exit \(code)"
}
}
Expand Down
28 changes: 24 additions & 4 deletions Sources/CodexBarCore/Host/Process/SubprocessRunner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public enum SubprocessRunnerError: LocalizedError, Sendable {
case binaryNotFound(String)
case launchFailed(String)
case timedOut(String)
case outputTooLarge(String)
case nonZeroExit(code: Int32, stderr: String)

public var errorDescription: String? {
Expand All @@ -21,6 +22,8 @@ public enum SubprocessRunnerError: LocalizedError, Sendable {
return "Failed to launch process: \(details)"
case let .timedOut(label):
return "Command timed out: \(label)"
case let .outputTooLarge(label):
return "Command produced too much output: \(label)"
case let .nonZeroExit(code, stderr):
let trimmed = stderr.trimmingCharacters(in: .whitespacesAndNewlines)
if trimmed.isEmpty {
Expand Down Expand Up @@ -176,6 +179,9 @@ public enum SubprocessRunner {
arguments: [String],
environment: [String: String],
timeout: TimeInterval,
// Preserve the legacy bounded-prefix capture when omitted. Structured-output callers can opt into
// fail-closed rejection by supplying an explicit limit.
maxOutputBytes: Int? = nil,
standardInput: Any? = nil,
currentDirectoryURL: URL? = nil,
acceptsNonZeroExit: Bool = false,
Expand All @@ -202,8 +208,12 @@ public enum SubprocessRunner {
process.standardOutput = stdoutPipe
process.standardError = stderrPipe
process.standardInput = standardInput
let stdoutCapture = ProcessPipeCapture(pipe: stdoutPipe)
let stderrCapture = ProcessPipeCapture(pipe: stderrPipe)
let normalizedMaxOutputBytes = maxOutputBytes.map { max(0, $0) }
let captureMaxBytes = normalizedMaxOutputBytes.map { limit in
limit == Int.max ? Int.max : limit + 1
} ?? ProcessPipeCapture.defaultMaxBytes
let stdoutCapture = ProcessPipeCapture(pipe: stdoutPipe, maxBytes: captureMaxBytes)
let stderrCapture = ProcessPipeCapture(pipe: stderrPipe, maxBytes: captureMaxBytes)

let termination = ProcessTermination()
process.terminationHandler = { process in
Expand Down Expand Up @@ -276,8 +286,18 @@ public enum SubprocessRunner {

async let stdoutData = stdoutCapture.finish(timeout: .seconds(1))
async let stderrData = stderrCapture.finish(timeout: .seconds(1))
let stdout = await ProcessPipeCapture.decodeUTF8(stdoutData)
let stderr = await ProcessPipeCapture.decodeUTF8(stderrData)
let capturedStdout = await stdoutData
let capturedStderr = await stderrData
if let normalizedMaxOutputBytes,
capturedStdout.count > normalizedMaxOutputBytes || capturedStderr.count > normalizedMaxOutputBytes
{
self.log.warning(
"Subprocess output exceeded memory limit",
metadata: ["label": label, "binary": binaryName])
throw SubprocessRunnerError.outputTooLarge(label)
}
let stdout = ProcessPipeCapture.decodeUTF8(capturedStdout)
let stderr = ProcessPipeCapture.decodeUTF8(capturedStderr)

if exitCode != 0, !acceptsNonZeroExit {
let duration = Date().timeIntervalSince(start)
Expand Down
Loading