@buyong/pi-devin 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +214 -2
- package/index.ts +16 -0
- package/package.json +62 -3
- package/proto/README.md +78 -0
- package/proto/buf/validate/validate.proto +453 -0
- package/proto/cel/expr/checked.proto +109 -0
- package/proto/cel/expr/eval.proto +44 -0
- package/proto/cel/expr/explain.proto +22 -0
- package/proto/cel/expr/syntax.proto +119 -0
- package/proto/cel/expr/value.proto +47 -0
- package/proto/connectext/grpc/status/v1/status.proto +21 -0
- package/proto/errorspb/errors.proto +58 -0
- package/proto/errorspb/hintdetail.proto +9 -0
- package/proto/errorspb/markers.proto +12 -0
- package/proto/errorspb/tags.proto +14 -0
- package/proto/errorspb/testing.proto +8 -0
- package/proto/exa/analytics_pb/analytics.proto +190 -0
- package/proto/exa/api_server_pb/api_server.proto +2590 -0
- package/proto/exa/auth_pb/auth.proto +21 -0
- package/proto/exa/auto_cascade_common_pb/auto_cascade_common.proto +81 -0
- package/proto/exa/browser_preview_pb/browser_preview.proto +26 -0
- package/proto/exa/bug_checker_pb/bug_checker.proto +24 -0
- package/proto/exa/cascade_plugins_pb/cascade_plugins.proto +264 -0
- package/proto/exa/chat_client_server_pb/chat_client_server.proto +57 -0
- package/proto/exa/chat_pb/chat.proto +452 -0
- package/proto/exa/code_edit/code_edit_pb/code_edit.proto +186 -0
- package/proto/exa/codeium_common_pb/browser_context.proto +31 -0
- package/proto/exa/codeium_common_pb/codeium_common.proto +4183 -0
- package/proto/exa/context_module_pb/context_module.proto +174 -0
- package/proto/exa/cortex_pb/cortex.proto +3288 -0
- package/proto/exa/dev_pb/dev.proto +28 -0
- package/proto/exa/diff_action_pb/diff_action.proto +76 -0
- package/proto/exa/eval/pr_eval/datasets_pb/datasets.proto +106 -0
- package/proto/exa/eval_pb/eval.proto +1318 -0
- package/proto/exa/extension_server_pb/extension_server.proto +555 -0
- package/proto/exa/file_system_provider_pb/file_system_provider.proto +75 -0
- package/proto/exa/index_pb/index.proto +469 -0
- package/proto/exa/knowledge_base_pb/knowledge_base.proto +146 -0
- package/proto/exa/language_server_pb/language_server.proto +2435 -0
- package/proto/exa/model_management_pb/model_management.proto +184 -0
- package/proto/exa/opensearch_clients_pb/opensearch_clients.proto +495 -0
- package/proto/exa/product_analytics_pb/product_analytics.proto +39 -0
- package/proto/exa/prompt_pb/prompt.proto +97 -0
- package/proto/exa/reactive_component_pb/reactive_component.proto +98 -0
- package/proto/exa/seat_management_pb/seat_management.proto +2910 -0
- package/proto/exa/tokenizer_pb/tokenizer.proto +39 -0
- package/proto/exa/trainer_pb/config.proto +643 -0
- package/proto/exa/tree_sitter/language_data_pb/language_data.proto +14 -0
- package/proto/exa/trust_pb/trust.proto +192 -0
- package/proto/exa/user_analytics_pb/user_analytics.proto +752 -0
- package/proto/google/api/expr/v1alpha1/checked.proto +108 -0
- package/proto/google/api/expr/v1alpha1/eval.proto +38 -0
- package/proto/google/api/expr/v1alpha1/explain.proto +22 -0
- package/proto/google/api/expr/v1alpha1/syntax.proto +126 -0
- package/proto/google/api/expr/v1alpha1/value.proto +47 -0
- package/proto/google/protobuf/any.proto +15 -0
- package/proto/google/protobuf/cpp_features.proto +22 -0
- package/proto/google/protobuf/descriptor.proto +473 -0
- package/proto/google/protobuf/duration.proto +16 -0
- package/proto/google/protobuf/empty.proto +14 -0
- package/proto/google/protobuf/go_features.proto +31 -0
- package/proto/google/protobuf/struct.proto +34 -0
- package/proto/google/protobuf/timestamp.proto +16 -0
- package/proto/google/protobuf/wrappers.proto +47 -0
- package/proto/google/rpc/status.proto +18 -0
- package/proto/grpc/binlog/v1/binarylog.proto +89 -0
- package/proto/io/prometheus/client/metrics.proto +103 -0
- package/src/core/auth/access.ts +6 -0
- package/src/core/auth/index.ts +2 -0
- package/src/core/auth/login.ts +177 -0
- package/src/core/auth/rest-exchange.ts +35 -0
- package/src/core/client.ts +153 -0
- package/src/core/index.ts +12 -0
- package/src/core/inference/chat.ts +229 -0
- package/src/core/inference/images.ts +24 -0
- package/src/core/inference/messages.ts +34 -0
- package/src/core/inference/retry.ts +68 -0
- package/src/core/inference/state.ts +8 -0
- package/src/core/models/access.ts +7 -0
- package/src/core/models/catalog.ts +35 -0
- package/src/core/models/index.ts +4 -0
- package/src/core/models/select.ts +43 -0
- package/src/core/models/variants.ts +163 -0
- package/src/core/protocol/device.ts +73 -0
- package/src/core/protocol/fingerprint.ts +59 -0
- package/src/core/protocol/http.ts +109 -0
- package/src/core/protocol/protobuf.ts +112 -0
- package/src/core/protocol/transport.ts +501 -0
- package/src/core/types.ts +115 -0
- package/src/pi/auth.ts +134 -0
- package/src/pi/devin-command.ts +76 -0
- package/src/pi/devin-panel.ts +77 -0
- package/src/pi/devin-settings.ts +50 -0
- package/src/pi/extension.ts +80 -0
- package/src/pi/fusion-command.ts +83 -0
- package/src/pi/fusion-picker.ts +171 -0
- package/src/pi/fusion-settings.ts +44 -0
- package/src/pi/lifecycle.ts +217 -0
- package/src/pi/messages.ts +107 -0
- package/src/pi/models.ts +378 -0
- package/src/pi/provider.ts +241 -0
- package/src/pi/sidekick-report.ts +42 -0
- package/src/pi/sidekick-runtime.ts +287 -0
- package/src/pi/sidekick.ts +190 -0
- package/src/pi/stream.ts +124 -0
- package/src/pi/usage.ts +153 -0
package/src/pi/usage.ts
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import type { JsonObject } from "../core/types.ts";
|
|
2
|
+
|
|
3
|
+
/** Quota from the account status (`GetUserStatus`). Values may have changed since it was fetched. */
|
|
4
|
+
export interface AccountUsage {
|
|
5
|
+
billingStrategy?: string;
|
|
6
|
+
dailyRemainingPercent?: number;
|
|
7
|
+
weeklyRemainingPercent?: number;
|
|
8
|
+
dailyResetAtMs?: number;
|
|
9
|
+
weeklyResetAtMs?: number;
|
|
10
|
+
fetchedAtMs: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Totals of Devin assistant responses recorded in the current session branch. Sidekick child conversations are separate sessions and are not included. */
|
|
14
|
+
export interface SessionUsage {
|
|
15
|
+
requests: number;
|
|
16
|
+
input: number;
|
|
17
|
+
output: number;
|
|
18
|
+
cacheRead: number;
|
|
19
|
+
cacheWrite: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface BudgetUsage { used: number; limit: number; isActive: boolean }
|
|
23
|
+
|
|
24
|
+
/** Color level by remaining amount. Matches the Pi theme color names `success`, `warning` and `error`. */
|
|
25
|
+
export type UsageTone = "success" | "warning" | "error";
|
|
26
|
+
export interface UsageLine { text: string; isHeading?: boolean; tone?: UsageTone }
|
|
27
|
+
/** Colors a segment. The default returns it uncolored. */
|
|
28
|
+
export type UsagePainter = (tone: UsageTone, text: string) => string;
|
|
29
|
+
const plain: UsagePainter = (_tone, text) => text;
|
|
30
|
+
|
|
31
|
+
const QUOTA_STRATEGY = "BILLING_STRATEGY_QUOTA";
|
|
32
|
+
|
|
33
|
+
function finiteNumber(value: unknown): number | undefined {
|
|
34
|
+
const number = typeof value === "string" && value.trim() ? Number(value) : value;
|
|
35
|
+
return typeof number === "number" && Number.isFinite(number) ? number : undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function unixToMs(value: unknown): number | undefined {
|
|
39
|
+
const seconds = finiteNumber(value);
|
|
40
|
+
return seconds !== undefined && seconds > 0 ? seconds * 1000 : undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Reads only the fields that `DevinClient.status()` exposes. No identifiers or raw response. */
|
|
44
|
+
export function accountUsage(status: JsonObject, fetchedAtMs: number): AccountUsage {
|
|
45
|
+
return {
|
|
46
|
+
...(typeof status.billingStrategy === "string" ? { billingStrategy: status.billingStrategy } : {}),
|
|
47
|
+
dailyRemainingPercent: finiteNumber(status.dailyQuotaRemainingPercent),
|
|
48
|
+
weeklyRemainingPercent: finiteNumber(status.weeklyQuotaRemainingPercent),
|
|
49
|
+
dailyResetAtMs: unixToMs(status.dailyQuotaResetAtUnix),
|
|
50
|
+
weeklyResetAtMs: unixToMs(status.weeklyQuotaResetAtUnix),
|
|
51
|
+
fetchedAtMs,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
type EntryLike = { type?: unknown; message?: { role?: unknown; provider?: unknown; usage?: Record<string, unknown> } } | null | undefined;
|
|
56
|
+
|
|
57
|
+
export function sessionUsage(entries: readonly unknown[]): SessionUsage {
|
|
58
|
+
const total: SessionUsage = { requests: 0, input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
59
|
+
for (const entry of entries as readonly EntryLike[]) {
|
|
60
|
+
const message = entry?.type === "message" ? entry.message : undefined;
|
|
61
|
+
if (message?.role !== "assistant" || message.provider !== "devin") continue;
|
|
62
|
+
total.requests++;
|
|
63
|
+
for (const key of ["input", "output", "cacheRead", "cacheWrite"] as const) {
|
|
64
|
+
const value = finiteNumber(message.usage?.[key]);
|
|
65
|
+
if (value !== undefined && value > 0) total[key] += value;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return total;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Same abbreviation as the Pi footer: 999, 1.2k, 12k, 1.2M. */
|
|
72
|
+
export function formatTokens(count: number): string {
|
|
73
|
+
if (count < 1000) return String(Math.round(count));
|
|
74
|
+
if (count < 10_000) return `${(count / 1000).toFixed(1)}k`;
|
|
75
|
+
if (count < 1_000_000) return `${Math.round(count / 1000)}k`;
|
|
76
|
+
return `${(count / 1_000_000).toFixed(1)}M`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** 10-cell bar, same as the `pi-ollama-cloud` status. Example: 98% → `▕█████████░▏`. */
|
|
80
|
+
export function progressBar(percent: number, cells = 10): string {
|
|
81
|
+
const filled = Math.min(Math.max(Math.floor((percent / 100) * cells), 0), cells);
|
|
82
|
+
return `▕${"█".repeat(filled)}${"░".repeat(cells - filled)}▏`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** The less remains, the stronger the warning color. */
|
|
86
|
+
export function remainingTone(percent: number): UsageTone {
|
|
87
|
+
return percent <= 20 ? "error" : percent <= 40 ? "warning" : "success";
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function budgetTone(used: number, limit: number): UsageTone {
|
|
91
|
+
return used >= limit ? "error" : used / limit >= 0.75 ? "warning" : "success";
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function hasQuota(account: AccountUsage | undefined): account is AccountUsage & { dailyRemainingPercent: number; weeklyRemainingPercent: number } {
|
|
95
|
+
return account?.billingStrategy === QUOTA_STRATEGY && account.dailyRemainingPercent !== undefined && account.weeklyRemainingPercent !== undefined;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* One-line status text. Example: `devin day ▕█████████░▏98% week ▕█████████░▏98% · ↑263 ↓49`.
|
|
100
|
+
* Bars show the remaining quota; without quota data only tokens are shown.
|
|
101
|
+
*/
|
|
102
|
+
export function usageStatusText(account: AccountUsage | undefined, session: SessionUsage, paint: UsagePainter = plain): string {
|
|
103
|
+
const quota = hasQuota(account)
|
|
104
|
+
? [["day", account.dailyRemainingPercent], ["week", account.weeklyRemainingPercent]]
|
|
105
|
+
.map(([label, percent]) => " " + paint(remainingTone(percent as number), `${label} ${progressBar(percent as number)}${percent}%`)).join("")
|
|
106
|
+
: "";
|
|
107
|
+
return `devin${quota} · ↑${formatTokens(session.input)} ↓${formatTokens(session.output)}`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const pad = (value: number) => String(value).padStart(2, "0");
|
|
111
|
+
function clock(ms: number): string {
|
|
112
|
+
const date = new Date(ms);
|
|
113
|
+
return `${pad(date.getMonth() + 1)}/${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function relativeTime(targetMs: number, nowMs: number): string {
|
|
117
|
+
const minutes = Math.round((targetMs - nowMs) / 60_000), size = Math.abs(minutes);
|
|
118
|
+
// Rounding to 0 minutes loses the sign, so the raw difference decides past or future.
|
|
119
|
+
if (size < 1) return targetMs >= nowMs ? "soon" : "just now";
|
|
120
|
+
const phrase = (amount: string) => minutes > 0 ? `in ${amount}` : `${amount} ago`;
|
|
121
|
+
if (size < 60) return phrase(`${size}m`);
|
|
122
|
+
if (size < 24 * 60) {
|
|
123
|
+
const hours = Math.floor(size / 60), rest = size % 60;
|
|
124
|
+
return phrase(`${hours}h${rest ? ` ${rest}m` : ""}`);
|
|
125
|
+
}
|
|
126
|
+
return phrase(`${Math.floor(size / (24 * 60))}d`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Details shown by `/devin`. Built only from the last account status and session records; sends no requests. */
|
|
130
|
+
export function usageReport(account: AccountUsage | undefined, session: SessionUsage, budget: BudgetUsage, nowMs: number): UsageLine[] {
|
|
131
|
+
const lines: UsageLine[] = [];
|
|
132
|
+
if (!account) {
|
|
133
|
+
lines.push({ text: "Account quota · not received yet", isHeading: true });
|
|
134
|
+
lines.push({ text: " Shown after login validation or the first request." });
|
|
135
|
+
} else {
|
|
136
|
+
lines.push({ text: `Account quota · fetched ${clock(account.fetchedAtMs)} (${relativeTime(account.fetchedAtMs, nowMs)}) · not live`, isHeading: true });
|
|
137
|
+
if (hasQuota(account)) {
|
|
138
|
+
const reset = (ms: number | undefined) => ms === undefined ? "reset time unknown" : `resets ${clock(ms)} (${relativeTime(ms, nowMs)})`;
|
|
139
|
+
const quota = (label: string, percent: number, resetAtMs: number | undefined): UsageLine =>
|
|
140
|
+
({ text: ` ${label} ${progressBar(percent)} ${percent}% left · ${reset(resetAtMs)}`, tone: remainingTone(percent) });
|
|
141
|
+
lines.push(quota("Daily ", account.dailyRemainingPercent, account.dailyResetAtMs));
|
|
142
|
+
lines.push(quota("Weekly", account.weeklyRemainingPercent, account.weeklyResetAtMs));
|
|
143
|
+
} else {
|
|
144
|
+
lines.push({ text: ` Not a quota-billed account (${account.billingStrategy ?? "billing strategy unknown"}). Credits are not shown.` });
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
lines.push({ text: "This session · lead conversation (sidekick excluded)", isHeading: true });
|
|
148
|
+
lines.push({ text: ` ${session.requests} ${session.requests === 1 ? "request" : "requests"} · input ${formatTokens(session.input)} · output ${formatTokens(session.output)}`
|
|
149
|
+
+ ` · cache read ${formatTokens(session.cacheRead)} · cache write ${formatTokens(session.cacheWrite)}` });
|
|
150
|
+
lines.push({ text: `Run budget · ${budget.isActive ? "in progress" : "last run"}`, isHeading: true });
|
|
151
|
+
lines.push({ text: ` ${progressBar((budget.used / budget.limit) * 100)} ${budget.used}/${budget.limit} model calls used`, tone: budgetTone(budget.used, budget.limit) });
|
|
152
|
+
return lines;
|
|
153
|
+
}
|