opencode-qoder-bridge 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.
Files changed (64) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +185 -0
  3. package/RELEASING.md +64 -0
  4. package/SECURITY.md +24 -0
  5. package/THIRD_PARTY_NOTICES.md +18 -0
  6. package/bin/statusline.mjs +33 -0
  7. package/bin/usage.mjs +35 -0
  8. package/dist/auth.d.ts +10 -0
  9. package/dist/auth.d.ts.map +1 -0
  10. package/dist/auth.js +75 -0
  11. package/dist/auth.js.map +1 -0
  12. package/dist/cost.d.ts +53 -0
  13. package/dist/cost.d.ts.map +1 -0
  14. package/dist/cost.js +173 -0
  15. package/dist/cost.js.map +1 -0
  16. package/dist/index.d.ts +4 -0
  17. package/dist/index.d.ts.map +1 -0
  18. package/dist/index.js +148 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/language-model.d.ts +15 -0
  21. package/dist/language-model.d.ts.map +1 -0
  22. package/dist/language-model.js +408 -0
  23. package/dist/language-model.js.map +1 -0
  24. package/dist/mcp-bridge.d.ts +8 -0
  25. package/dist/mcp-bridge.d.ts.map +1 -0
  26. package/dist/mcp-bridge.js +81 -0
  27. package/dist/mcp-bridge.js.map +1 -0
  28. package/dist/models.d.ts +27 -0
  29. package/dist/models.d.ts.map +1 -0
  30. package/dist/models.js +112 -0
  31. package/dist/models.js.map +1 -0
  32. package/dist/prompt-builder.d.ts +30 -0
  33. package/dist/prompt-builder.d.ts.map +1 -0
  34. package/dist/prompt-builder.js +308 -0
  35. package/dist/prompt-builder.js.map +1 -0
  36. package/dist/provider.d.ts +12 -0
  37. package/dist/provider.d.ts.map +1 -0
  38. package/dist/provider.js +17 -0
  39. package/dist/provider.js.map +1 -0
  40. package/dist/sdk-session.d.ts +3 -0
  41. package/dist/sdk-session.d.ts.map +1 -0
  42. package/dist/sdk-session.js +8 -0
  43. package/dist/sdk-session.js.map +1 -0
  44. package/dist/tool-normalizer.d.ts +13 -0
  45. package/dist/tool-normalizer.d.ts.map +1 -0
  46. package/dist/tool-normalizer.js +99 -0
  47. package/dist/tool-normalizer.js.map +1 -0
  48. package/dist/tui-register.d.ts +2 -0
  49. package/dist/tui-register.d.ts.map +1 -0
  50. package/dist/tui-register.js +67 -0
  51. package/dist/tui-register.js.map +1 -0
  52. package/dist/tui.d.ts +9 -0
  53. package/dist/tui.d.ts.map +1 -0
  54. package/dist/tui.js +190 -0
  55. package/dist/tui.js.map +1 -0
  56. package/dist/types.d.ts +34 -0
  57. package/dist/types.d.ts.map +1 -0
  58. package/dist/types.js +2 -0
  59. package/dist/types.js.map +1 -0
  60. package/dist/usage.d.ts +11 -0
  61. package/dist/usage.d.ts.map +1 -0
  62. package/dist/usage.js +87 -0
  63. package/dist/usage.js.map +1 -0
  64. package/package.json +78 -0
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Maps qodercli tool names to opencode tool names and normalizes their inputs.
3
+ *
4
+ * - Casing: Read -> read, Bash -> bash
5
+ * - Builtins: AskUserQuestion -> question, Agent -> task, ExitPlanMode -> plan_exit
6
+ * - MCP proxy: mcp__{server}__{tool} -> {server}_{tool}
7
+ */
8
+ const NAME_MAP = {
9
+ askuserquestion: "question",
10
+ agent: "task",
11
+ exitplanmode: "plan_exit",
12
+ str_replace_based_edit_tool: "edit",
13
+ };
14
+ const KEY_RENAMES = {
15
+ read: { file_path: "filePath" },
16
+ write: { file_path: "filePath" },
17
+ task: { subagentType: "subagent_type" },
18
+ edit: {
19
+ file_path: "filePath",
20
+ old_string: "oldString",
21
+ new_string: "newString",
22
+ replace_all: "replaceAll",
23
+ },
24
+ skill: { skill: "name" },
25
+ };
26
+ const SUBAGENT_TYPE_MAP = {
27
+ "general-purpose": "general",
28
+ "general_purpose": "general",
29
+ "general purpose": "general",
30
+ "code-reviewer": "general",
31
+ "task-executor": "general",
32
+ "design-agent": "general",
33
+ "spec-review-agent": "explore",
34
+ "qoder-guide": "explore",
35
+ explorer: "explore",
36
+ };
37
+ const UNSAFE_KEYS = new Set(["__proto__", "prototype", "constructor"]);
38
+ function isRecord(v) {
39
+ return typeof v === "object" && v !== null && !Array.isArray(v);
40
+ }
41
+ export function normalizeToolName(name) {
42
+ const lower = name.toLowerCase();
43
+ const mapped = Object.hasOwn(NAME_MAP, lower) ? NAME_MAP[lower] : undefined;
44
+ if (mapped)
45
+ return mapped;
46
+ if (lower.startsWith("mcp__")) {
47
+ const rest = lower.slice(5);
48
+ const sep = rest.indexOf("__");
49
+ if (sep > 0)
50
+ return `${rest.slice(0, sep)}_${rest.slice(sep + 2)}`;
51
+ return rest;
52
+ }
53
+ return lower;
54
+ }
55
+ /** Normalize a parsed tool-input object for the given (already normalized) tool name. */
56
+ export function normalizeToolInput(toolName, input) {
57
+ if (!isRecord(input))
58
+ return input;
59
+ const renames = Object.hasOwn(KEY_RENAMES, toolName) ? KEY_RENAMES[toolName] : undefined;
60
+ const out = {};
61
+ for (const [k, v] of Object.entries(input)) {
62
+ if (UNSAFE_KEYS.has(k))
63
+ continue;
64
+ const renamed = renames && Object.hasOwn(renames, k) ? renames[k] : k;
65
+ if (!UNSAFE_KEYS.has(renamed))
66
+ out[renamed] = v;
67
+ }
68
+ if (toolName === "task" && typeof out.subagent_type === "string") {
69
+ const type = out.subagent_type.trim().toLowerCase();
70
+ out.subagent_type = Object.hasOwn(SUBAGENT_TYPE_MAP, type) ? SUBAGENT_TYPE_MAP[type] : type;
71
+ }
72
+ if (toolName === "grep") {
73
+ if (typeof out.include !== "string") {
74
+ const glob = out.glob;
75
+ const type = out.type;
76
+ if (typeof glob === "string")
77
+ out.include = glob;
78
+ else if (typeof type === "string")
79
+ out.include = `*.${type}`;
80
+ }
81
+ for (const k of ["glob", "type", "output_mode", "multiline", "head_limit", "-i", "-n", "-B", "-A", "-C"]) {
82
+ delete out[k];
83
+ }
84
+ }
85
+ return out;
86
+ }
87
+ /** Parse a JSON input string, normalize, and re-serialize. Falls back to the original on parse error. */
88
+ export function normalizeToolInputString(toolName, input) {
89
+ const trimmed = input.trim();
90
+ if (!trimmed)
91
+ return "{}";
92
+ try {
93
+ return JSON.stringify(normalizeToolInput(toolName, JSON.parse(trimmed)));
94
+ }
95
+ catch {
96
+ return input;
97
+ }
98
+ }
99
+ //# sourceMappingURL=tool-normalizer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-normalizer.js","sourceRoot":"","sources":["../src/tool-normalizer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function ensureTuiRegistered(configPath?: string, entry?: string): Promise<"added" | "present">;
2
+ //# sourceMappingURL=tui-register.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tui-register.d.ts","sourceRoot":"","sources":["../src/tui-register.ts"],"names":[],"mappings":"AA8BA,wBAAsB,mBAAmB,CACvC,UAAU,SAAwB,EAClC,KAAK,SAAY,GAChB,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,CA6C9B"}
@@ -0,0 +1,67 @@
1
+ import { mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
2
+ import { randomUUID } from "node:crypto";
3
+ import { homedir } from "node:os";
4
+ import { dirname, join } from "node:path";
5
+ import { applyEdits, modify, parse } from "jsonc-parser";
6
+ const TUI_ENTRY = new URL("./tui.js", import.meta.url).href;
7
+ function globalTuiConfigPath() {
8
+ const configHome = process.env.XDG_CONFIG_HOME?.trim();
9
+ return join(configHome || join(homedir(), ".config"), "opencode", "tui.json");
10
+ }
11
+ function isMissingFile(error) {
12
+ return (error instanceof Error &&
13
+ "code" in error &&
14
+ error.code === "ENOENT");
15
+ }
16
+ async function readConfig(path) {
17
+ try {
18
+ return await readFile(path, "utf8");
19
+ }
20
+ catch (error) {
21
+ if (isMissingFile(error))
22
+ return "";
23
+ throw error;
24
+ }
25
+ }
26
+ export async function ensureTuiRegistered(configPath = globalTuiConfigPath(), entry = TUI_ENTRY) {
27
+ const source = await readConfig(configPath);
28
+ const errors = [];
29
+ const current = source.trim()
30
+ ? parse(source, errors, { allowTrailingComma: true })
31
+ : {};
32
+ if (errors.length > 0 || !current || Array.isArray(current)) {
33
+ throw new Error(`Cannot update invalid OpenCode TUI configuration: ${configPath}`);
34
+ }
35
+ const configured = current.plugin;
36
+ if (configured !== undefined && !Array.isArray(configured)) {
37
+ throw new Error(`OpenCode TUI "plugin" must be an array: ${configPath}`);
38
+ }
39
+ const plugins = (configured ?? []);
40
+ if (plugins.includes(entry) || plugins.includes("opencode-qoder-bridge")) {
41
+ return "present";
42
+ }
43
+ const base = source.trim()
44
+ ? source
45
+ : '{\n "$schema": "https://opencode.ai/tui.json"\n}\n';
46
+ const eol = base.includes("\r\n") ? "\r\n" : "\n";
47
+ const edits = modify(base, ["plugin"], [...plugins, entry], {
48
+ formattingOptions: {
49
+ insertSpaces: true,
50
+ tabSize: 2,
51
+ eol,
52
+ },
53
+ });
54
+ const updated = applyEdits(base, edits);
55
+ await mkdir(dirname(configPath), { recursive: true, mode: 0o700 });
56
+ const temporary = `${configPath}.${process.pid}.${randomUUID()}.tmp`;
57
+ try {
58
+ await writeFile(temporary, updated, { encoding: "utf8", mode: 0o600 });
59
+ await rename(temporary, configPath);
60
+ }
61
+ catch (error) {
62
+ await unlink(temporary).catch(() => undefined);
63
+ throw error;
64
+ }
65
+ return "added";
66
+ }
67
+ //# sourceMappingURL=tui-register.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tui-register.js","sourceRoot":"","sources":["../src/tui-register.ts"],"names":[],"mappings":"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"}
package/dist/tui.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ import type { TuiPlugin } from "@opencode-ai/plugin/tui";
2
+ export declare const id = "opencode-qoder-bridge-sidebar";
3
+ export declare const tui: TuiPlugin;
4
+ declare const _default: {
5
+ id: string;
6
+ tui: TuiPlugin;
7
+ };
8
+ export default _default;
9
+ //# sourceMappingURL=tui.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tui.d.ts","sourceRoot":"","sources":["../src/tui.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAmB,MAAM,yBAAyB,CAAC;AAkF1E,eAAO,MAAM,EAAE,kCAAkC,CAAC;AAElD,eAAO,MAAM,GAAG,EAAE,SAkIjB,CAAC;;;;;AAEF,wBAAqD"}
package/dist/tui.js ADDED
@@ -0,0 +1,190 @@
1
+ import { jsx, jsxs } from "@opentui/solid/jsx-runtime";
2
+ import { createEffect, Show, createSignal } from "solid-js";
3
+ import { getLiveUsage } from "./usage.js";
4
+ const REFRESH_MS = 30_000;
5
+ const POST_TURN_REFRESH_MS = 5_000;
6
+ function formatCredits(value) {
7
+ return Number.isInteger(value)
8
+ ? value.toString()
9
+ : value.toLocaleString("en-US", { maximumFractionDigits: 2 });
10
+ }
11
+ function formatSessionCredits(value) {
12
+ return value.toFixed(2);
13
+ }
14
+ function usesQoder(api, sessionID) {
15
+ const session = api.state.session.get(sessionID);
16
+ if (session?.model)
17
+ return session.model.providerID === "qoder";
18
+ const messages = api.state.session.messages(sessionID);
19
+ const latest = messages.at(-1);
20
+ if (!latest)
21
+ return false;
22
+ return latest.role === "user"
23
+ ? latest.model.providerID === "qoder"
24
+ : latest.providerID === "qoder";
25
+ }
26
+ function formatQuota(usage) {
27
+ const quota = usage?.userQuota;
28
+ if (!usage || !quota || quota.total == null) {
29
+ return {
30
+ error: "usage unavailable",
31
+ warning: true,
32
+ };
33
+ }
34
+ const used = quota.used ?? Math.max(0, quota.total - (quota.remaining ?? quota.total));
35
+ const remaining = quota.remaining ?? Math.max(0, quota.total - used);
36
+ const percent = quota.percentage ??
37
+ usage.totalUsagePercentage ??
38
+ (quota.total > 0 ? (used / quota.total) * 100 : 0);
39
+ return {
40
+ used,
41
+ total: quota.total,
42
+ remaining,
43
+ percent,
44
+ warning: usage.isQuotaExceeded === true || remaining <= Math.max(10, quota.total * 0.1),
45
+ };
46
+ }
47
+ function sessionSpent(api, sessionID) {
48
+ const sessionCost = api.state.session.get(sessionID)?.cost;
49
+ if (typeof sessionCost === "number")
50
+ return sessionCost;
51
+ return api.state.session
52
+ .messages(sessionID)
53
+ .reduce((total, message) => total + (message.role === "assistant" ? message.cost : 0), 0);
54
+ }
55
+ function estimatedSessionCredits(api, sessionID) {
56
+ // Qoder's Credits Log presents reference cost in dollars and Credits in
57
+ // cent-denominated units. The personal-account SDK exposes only a rounded
58
+ // whole-account quota, so session.cost * 100 is the best fractional signal
59
+ // available until the SDK exposes per-request credit events.
60
+ return sessionSpent(api, sessionID) * 100;
61
+ }
62
+ export const id = "opencode-qoder-bridge-sidebar";
63
+ export const tui = async (api) => {
64
+ const [quota, setQuota] = createSignal({
65
+ error: "loading…",
66
+ warning: false,
67
+ });
68
+ const sessionBaselines = new Map();
69
+ const sessionCredits = (sessionID) => {
70
+ const used = quota().used;
71
+ if (used == null)
72
+ return undefined;
73
+ const key = `opencode-qoder-bridge:credit-baseline:${sessionID}`;
74
+ let baseline = sessionBaselines.get(sessionID);
75
+ if (baseline == null) {
76
+ baseline = api.kv.get(key, undefined);
77
+ if (baseline == null || baseline > used) {
78
+ baseline = used;
79
+ api.kv.set(key, baseline);
80
+ }
81
+ sessionBaselines.set(sessionID, baseline);
82
+ }
83
+ return Math.max(0, used - baseline);
84
+ };
85
+ let refreshing = false;
86
+ let refreshedAt = 0;
87
+ const refresh = async () => {
88
+ if (refreshing)
89
+ return;
90
+ refreshing = true;
91
+ try {
92
+ setQuota(formatQuota(await getLiveUsage(true)));
93
+ refreshedAt = Date.now();
94
+ }
95
+ finally {
96
+ refreshing = false;
97
+ }
98
+ };
99
+ const refreshIfNeeded = () => {
100
+ if (Date.now() - refreshedAt >= REFRESH_MS)
101
+ void refresh();
102
+ };
103
+ const activeSessionUsesQoder = () => {
104
+ const route = api.route.current;
105
+ const sessionID = route.name === "session" &&
106
+ route.params &&
107
+ typeof route.params.sessionID === "string"
108
+ ? route.params.sessionID
109
+ : undefined;
110
+ return sessionID ? usesQoder(api, sessionID) : false;
111
+ };
112
+ const timer = setInterval(() => {
113
+ if (activeSessionUsesQoder())
114
+ void refresh();
115
+ }, REFRESH_MS);
116
+ let postTurnTimer;
117
+ const stopIdleRefresh = api.event.on("session.idle", (event) => {
118
+ if (!usesQoder(api, event.properties.sessionID))
119
+ return;
120
+ void refresh();
121
+ clearTimeout(postTurnTimer);
122
+ postTurnTimer = setTimeout(() => void refresh(), POST_TURN_REFRESH_MS);
123
+ });
124
+ api.lifecycle.onDispose(() => {
125
+ clearInterval(timer);
126
+ clearTimeout(postTurnTimer);
127
+ stopIdleRefresh();
128
+ });
129
+ api.slots.register({
130
+ order: 50,
131
+ slots: {
132
+ sidebar_content: ({ theme }, { session_id }) => {
133
+ const isQoder = () => usesQoder(api, session_id);
134
+ createEffect(() => {
135
+ if (isQoder())
136
+ refreshIfNeeded();
137
+ });
138
+ return jsx(Show, {
139
+ get when() {
140
+ return isQoder();
141
+ },
142
+ get children() {
143
+ return jsxs("box", {
144
+ flexDirection: "column",
145
+ marginTop: 1,
146
+ children: [
147
+ jsx("text", {
148
+ fg: theme.current.text,
149
+ children: jsx("b", { children: "Qoder Credits" }),
150
+ }),
151
+ jsx("text", {
152
+ get fg() {
153
+ return quota().warning ? theme.current.warning : theme.current.textMuted;
154
+ },
155
+ get children() {
156
+ const accountDelta = sessionCredits(session_id);
157
+ const estimate = estimatedSessionCredits(api, session_id);
158
+ if (estimate > 0) {
159
+ return `Session: ~${formatSessionCredits(estimate)} credits used`;
160
+ }
161
+ return accountDelta == null
162
+ ? `Session: ${quota().error ?? "loading…"}`
163
+ : `Session: ${formatSessionCredits(accountDelta)} credits used`;
164
+ },
165
+ }),
166
+ jsx("text", {
167
+ fg: theme.current.textMuted,
168
+ get children() {
169
+ return `Spent: $${sessionSpent(api, session_id).toFixed(4)}`;
170
+ },
171
+ }),
172
+ jsx("text", {
173
+ fg: theme.current.textMuted,
174
+ get children() {
175
+ const current = quota();
176
+ return current.used == null || current.total == null || current.remaining == null
177
+ ? "Account quota unavailable"
178
+ : `Account: ${formatCredits(current.used)}/${formatCredits(current.total)} · ${formatCredits(current.remaining)} left`;
179
+ },
180
+ }),
181
+ ],
182
+ });
183
+ },
184
+ });
185
+ },
186
+ },
187
+ });
188
+ };
189
+ export default { id, tui };
190
+ //# sourceMappingURL=tui.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tui.js","sourceRoot":"","sources":["../src/tui.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ export interface QoderModelDef {
2
+ id: string;
3
+ name: string;
4
+ attachment: boolean;
5
+ reasoning: boolean;
6
+ toolCall: boolean;
7
+ /** Billing multiplier relative to the base rate. 0 = free. */
8
+ multiplier: number;
9
+ cost: {
10
+ input: number;
11
+ output: number;
12
+ cacheRead: number;
13
+ cacheWrite: number;
14
+ };
15
+ limit: {
16
+ context: number;
17
+ output: number;
18
+ };
19
+ }
20
+ export interface QoderBridgeOptions {
21
+ /** Force a transport mode. `sdk` (default) streams via the agent SDK. */
22
+ mode?: "sdk";
23
+ /** Extra CLI flags forwarded to qodercli, e.g. { "--experimental-mcp-load": null }. */
24
+ extraArgs?: Record<string, string | null>;
25
+ /** Bridged MCP server configs keyed by server name. */
26
+ mcpServers?: Record<string, unknown>;
27
+ }
28
+ export interface ModelCost {
29
+ input: number;
30
+ output: number;
31
+ cacheRead: number;
32
+ cacheWrite: number;
33
+ }
34
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;IAClB,8DAA8D;IAC9D,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE;QACJ,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,KAAK,EAAE;QACL,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED,MAAM,WAAW,kBAAkB;IACjC,yEAAyE;IACzE,IAAI,CAAC,EAAE,KAAK,CAAC;IACb,uFAAuF;IACvF,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;IAC1C,uDAAuD;IACvD,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,11 @@
1
+ import type { UsageInfo } from "@qoder-ai/qoder-agent-sdk";
2
+ /**
3
+ * Fetch live account usage by opening a short-lived SDK session and calling
4
+ * `getUsageInfo()`. Results are cached for CACHE_TTL_MS and de-duplicated so
5
+ * concurrent callers share one session.
6
+ */
7
+ export declare function getLiveUsage(force?: boolean): Promise<UsageInfo | null>;
8
+ export declare function invalidateUsageCache(): void;
9
+ /** Format a UsageInfo into a compact human-readable report. */
10
+ export declare function formatUsageReport(u: UsageInfo): string;
11
+ //# sourceMappingURL=usage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage.d.ts","sourceRoot":"","sources":["../src/usage.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAU3D;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,UAAQ,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAqCrE;AAED,wBAAgB,oBAAoB,IAAI,IAAI,CAG3C;AAQD,+DAA+D;AAC/D,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,SAAS,GAAG,MAAM,CAsBtD"}
package/dist/usage.js ADDED
@@ -0,0 +1,87 @@
1
+ import { query, qodercliAuth } from "@qoder-ai/qoder-agent-sdk";
2
+ import { findQoderCLI } from "./auth.js";
3
+ import { idlePrompt } from "./sdk-session.js";
4
+ const CACHE_TTL_MS = 60_000;
5
+ let cached = null;
6
+ let cacheExpiry = 0;
7
+ let inflight = null;
8
+ /**
9
+ * Fetch live account usage by opening a short-lived SDK session and calling
10
+ * `getUsageInfo()`. Results are cached for CACHE_TTL_MS and de-duplicated so
11
+ * concurrent callers share one session.
12
+ */
13
+ export function getLiveUsage(force = false) {
14
+ const now = Date.now();
15
+ if (!force && cached && now < cacheExpiry)
16
+ return Promise.resolve(cached);
17
+ if (inflight)
18
+ return inflight;
19
+ inflight = (async () => {
20
+ const cli = findQoderCLI();
21
+ if (!cli)
22
+ return null;
23
+ const abortController = new AbortController();
24
+ const q = query({
25
+ prompt: idlePrompt(abortController.signal),
26
+ options: {
27
+ auth: qodercliAuth(),
28
+ pathToQoderCLIExecutable: cli,
29
+ abortController,
30
+ maxTurns: 1,
31
+ },
32
+ });
33
+ try {
34
+ const usage = await q.getUsageInfo();
35
+ if (usage) {
36
+ cached = usage;
37
+ cacheExpiry = Date.now() + CACHE_TTL_MS;
38
+ }
39
+ return usage;
40
+ }
41
+ catch {
42
+ return cached;
43
+ }
44
+ finally {
45
+ abortController.abort();
46
+ void q.return(undefined).catch(() => { });
47
+ inflight = null;
48
+ }
49
+ })();
50
+ return inflight;
51
+ }
52
+ export function invalidateUsageCache() {
53
+ cached = null;
54
+ cacheExpiry = 0;
55
+ }
56
+ function bucketLine(label, b) {
57
+ if (!b || b.total == null)
58
+ return null;
59
+ const unit = b.unit ?? "";
60
+ return `${label}: ${b.used ?? 0}/${b.total} ${unit} (${b.remaining ?? 0} remaining)`;
61
+ }
62
+ /** Format a UsageInfo into a compact human-readable report. */
63
+ export function formatUsageReport(u) {
64
+ const lines = ["Qoder Account Usage"];
65
+ if (u.userType)
66
+ lines.push(` Plan: ${u.userType}`);
67
+ if (typeof u.totalUsagePercentage === "number")
68
+ lines.push(` Usage: ${u.totalUsagePercentage.toFixed(1)}%`);
69
+ const quota = bucketLine(" Quota", u.userQuota);
70
+ if (quota)
71
+ lines.push(quota);
72
+ if (u.addOnQuota && (u.addOnQuota.total ?? 0) > 0) {
73
+ const addOn = bucketLine(" Add-on", u.addOnQuota);
74
+ if (addOn)
75
+ lines.push(addOn);
76
+ }
77
+ if (u.orgResourcePackage?.available) {
78
+ const org = u.orgResourcePackage;
79
+ lines.push(` Org Package: ${org.used ?? 0}/${org.cap ?? 0} ${org.unit ?? ""} (${org.remaining ?? 0} remaining)`);
80
+ }
81
+ if (u.isQuotaExceeded)
82
+ lines.push(" WARNING: quota exceeded");
83
+ if (u.expiresAt && u.expiresAt > 0)
84
+ lines.push(` Expires: ${new Date(u.expiresAt).toISOString().slice(0, 10)}`);
85
+ return lines.join("\n");
86
+ }
87
+ //# sourceMappingURL=usage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage.js","sourceRoot":"","sources":["../src/usage.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,78 @@
1
+ {
2
+ "name": "opencode-qoder-bridge",
3
+ "version": "0.1.0",
4
+ "description": "opencode plugin that bridges Qoder AI models via @qoder-ai/qoder-agent-sdk",
5
+ "author": "Naoufal El Bani",
6
+ "type": "module",
7
+ "license": "MIT",
8
+ "homepage": "https://github.com/naoufalelbani/opencode-qoder-bridge#readme",
9
+ "bugs": {
10
+ "url": "https://github.com/naoufalelbani/opencode-qoder-bridge/issues"
11
+ },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/naoufalelbani/opencode-qoder-bridge.git"
15
+ },
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "main": "dist/index.js",
20
+ "types": "dist/index.d.ts",
21
+ "bin": {
22
+ "opencode-qoder-bridge": "bin/statusline.mjs",
23
+ "qoder-usage": "bin/usage.mjs"
24
+ },
25
+ "files": [
26
+ "dist",
27
+ "bin",
28
+ "README.md",
29
+ "LICENSE",
30
+ "RELEASING.md",
31
+ "SECURITY.md",
32
+ "THIRD_PARTY_NOTICES.md"
33
+ ],
34
+ "exports": {
35
+ ".": {
36
+ "types": "./dist/index.d.ts",
37
+ "default": "./dist/index.js"
38
+ },
39
+ "./provider": {
40
+ "types": "./dist/provider.d.ts",
41
+ "default": "./dist/provider.js"
42
+ },
43
+ "./tui": {
44
+ "types": "./dist/tui.d.ts",
45
+ "default": "./dist/tui.js"
46
+ }
47
+ },
48
+ "scripts": {
49
+ "build": "tsc -p tsconfig.json",
50
+ "typecheck": "tsc -p tsconfig.json --noEmit",
51
+ "test": "npm run build && node --test test/bridge.test.mjs",
52
+ "check": "npm run typecheck && npm test",
53
+ "prepublishOnly": "npm run check"
54
+ },
55
+ "dependencies": {
56
+ "@ai-sdk/provider": "^3.0.14",
57
+ "@opencode-ai/plugin": "1.18.5",
58
+ "@opentui/solid": "0.4.5",
59
+ "@qoder-ai/qoder-agent-sdk": "^1.0.15",
60
+ "jsonc-parser": "^3.3.1",
61
+ "solid-js": "1.9.12",
62
+ "zod": "^3.25.0"
63
+ },
64
+ "devDependencies": {
65
+ "@types/node": "^22.10.0",
66
+ "typescript": "^5.7.0"
67
+ },
68
+ "engines": {
69
+ "node": ">=20"
70
+ },
71
+ "keywords": [
72
+ "opencode",
73
+ "opencode-plugin",
74
+ "qoder",
75
+ "ai",
76
+ "llm"
77
+ ]
78
+ }