opencode-kiro 0.4.0 → 0.5.0-beta.2
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/README.md +200 -161
- package/dist/chunk-M7OUVABX.js +40 -0
- package/dist/credits-box-view-GLFJSV6M.js +37 -0
- package/dist/credits-chip-view-YP4RPX26.js +20 -0
- package/dist/server.d.ts +5 -18
- package/dist/server.js +391 -227
- package/dist/tui.d.ts +3 -50
- package/dist/tui.js +160 -27
- package/package.json +7 -8
- package/dist/chunk-MQVMKLNA.js +0 -70
- package/dist/credits-box-view-BYKGQHSS.js +0 -40
- package/dist/credits-chip-view-RL5JAEZ5.js +0 -20
package/dist/tui.d.ts
CHANGED
|
@@ -1,52 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Plugin } from '@opencode-ai/plugin/tui';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
type CreditPart = object;
|
|
5
|
-
/** Minimal message shape; SDK `Message` is assignable. */
|
|
6
|
-
interface CreditMessage {
|
|
7
|
-
readonly id: string;
|
|
8
|
-
readonly role: string;
|
|
9
|
-
}
|
|
10
|
-
/** One part's credit metadata: the turn total plus the SDK-reported unit. */
|
|
11
|
-
interface PartCredits {
|
|
12
|
-
credits: number;
|
|
13
|
-
unit?: string;
|
|
14
|
-
}
|
|
15
|
-
/** Session-wide rollup. `unit` stays undefined until metadata reports one. */
|
|
16
|
-
interface SessionCredits {
|
|
17
|
-
total: number;
|
|
18
|
-
unit?: string;
|
|
19
|
-
/**
|
|
20
|
-
* True once any assistant message carried kiro credit metadata. Lets the view pick credits over the
|
|
21
|
-
* "$X spent" fallback, since a 0-credit kiro turn is indistinguishable from no metadata by `total` alone.
|
|
22
|
-
*/
|
|
23
|
-
present: boolean;
|
|
24
|
-
}
|
|
25
|
-
/** Read `{ kiro: { credits, creditsUnit } }` from a part; only finite numeric credits count. */
|
|
26
|
-
declare function readPartCredits(part: CreditPart): PartCredits | undefined;
|
|
27
|
-
/** One message's credits, deduped by the last-carrier-wins rule (parts never summed); unit from the most recent carrier. */
|
|
28
|
-
declare function messageCredits(parts: ReadonlyArray<CreditPart>): PartCredits | undefined;
|
|
29
|
-
/** Per-message credit total, or undefined when no part carries credits. */
|
|
30
|
-
declare function creditsForMessage(parts: ReadonlyArray<CreditPart>): number | undefined;
|
|
31
|
-
/** Sum per-message totals across a session's assistant messages (one value each); unit from the most recent carrier. */
|
|
32
|
-
declare function sumSessionCredits(messages: ReadonlyArray<CreditMessage>, partsByMessage: (messageID: string) => ReadonlyArray<CreditPart>): SessionCredits;
|
|
33
|
-
/** Render credits with the unit, e.g. "12.5 credits", "1 credit". Unit is naively pluralized unless it ends in "s"; with no unit, only the number renders. */
|
|
34
|
-
declare function formatCredits(value: number, unit?: string): string;
|
|
35
|
-
/**
|
|
36
|
-
* Muted cost lines as an array (one per rendered row). Three states:
|
|
37
|
-
* - both (credits present + non-zero cost): ["$X.XX spent", "N credits"]
|
|
38
|
-
* - credits only (cost 0): ["N credits"]
|
|
39
|
-
* - dollars only (no credits): ["$X.XX spent"] (also ["$0.00 spent"] when empty)
|
|
40
|
-
* Keys off `credits.present` and `cost > 0`, never `credits.total` alone (a 0-credit kiro turn is present).
|
|
41
|
-
*/
|
|
42
|
-
declare function spendLines(input: {
|
|
43
|
-
cost: number;
|
|
44
|
-
credits: SessionCredits;
|
|
45
|
-
}): string[];
|
|
3
|
+
declare const plugin: Plugin.Definition;
|
|
46
4
|
|
|
47
|
-
|
|
48
|
-
id: string;
|
|
49
|
-
tui: TuiPlugin;
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
export { type CreditMessage, type CreditPart, type PartCredits, type SessionCredits, creditsForMessage, _default as default, formatCredits, messageCredits, readPartCredits, spendLines, sumSessionCredits };
|
|
5
|
+
export { plugin as default };
|
package/dist/tui.js
CHANGED
|
@@ -1,35 +1,168 @@
|
|
|
1
1
|
import {
|
|
2
|
-
creditsForMessage,
|
|
3
|
-
formatCredits,
|
|
4
2
|
messageCredits,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
readCreditState
|
|
4
|
+
} from "./chunk-M7OUVABX.js";
|
|
5
|
+
|
|
6
|
+
// src/tui/transient-credits.ts
|
|
7
|
+
var SEPARATOR = "\0";
|
|
8
|
+
function createTransientStore() {
|
|
9
|
+
return { entries: /* @__PURE__ */ new Map() };
|
|
10
|
+
}
|
|
11
|
+
function transientKey(...parts) {
|
|
12
|
+
const [sessionID, assistantMessageID, ordinal] = parts;
|
|
13
|
+
return `${sessionID}${SEPARATOR}${assistantMessageID}${SEPARATOR}${ordinal}`;
|
|
14
|
+
}
|
|
15
|
+
function parseKey(key) {
|
|
16
|
+
const parts = key.split(SEPARATOR);
|
|
17
|
+
if (parts.length !== 3) return void 0;
|
|
18
|
+
return [parts[0], parts[1], Number(parts[2])];
|
|
19
|
+
}
|
|
20
|
+
function isValidKeyPart(value) {
|
|
21
|
+
return typeof value === "string" && value.length > 0 && !value.includes(SEPARATOR);
|
|
22
|
+
}
|
|
23
|
+
function recordTextEnded(store, event) {
|
|
24
|
+
const data = event?.data;
|
|
25
|
+
if (typeof data !== "object" || data === null) return;
|
|
26
|
+
const { sessionID, assistantMessageID, ordinal, state } = data;
|
|
27
|
+
if (!isValidKeyPart(sessionID) || !isValidKeyPart(assistantMessageID)) return;
|
|
28
|
+
if (typeof ordinal !== "number" || !Number.isInteger(ordinal) || ordinal < 0) return;
|
|
29
|
+
const credits = readCreditState(state);
|
|
30
|
+
if (!credits) return;
|
|
31
|
+
store.entries.set(transientKey(sessionID, assistantMessageID, ordinal), credits);
|
|
32
|
+
}
|
|
33
|
+
function transientForMessage(store, sessionID, assistantMessageID) {
|
|
34
|
+
let best;
|
|
35
|
+
let bestOrdinal = -1;
|
|
36
|
+
for (const [key, value] of store.entries) {
|
|
37
|
+
const parsed = parseKey(key);
|
|
38
|
+
if (!parsed || parsed[0] !== sessionID || parsed[1] !== assistantMessageID) continue;
|
|
39
|
+
if (parsed[2] > bestOrdinal) {
|
|
40
|
+
bestOrdinal = parsed[2];
|
|
41
|
+
best = value;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return best;
|
|
45
|
+
}
|
|
46
|
+
function mergedMessageCredits(store, sessionID, durableMessages) {
|
|
47
|
+
return durableMessages.filter((message) => message.type === "assistant").reduce(
|
|
48
|
+
(acc, message) => {
|
|
49
|
+
const durable = messageCredits(message.content ?? []);
|
|
50
|
+
const hit = durable ?? transientForMessage(store, sessionID, message.id);
|
|
51
|
+
if (!hit) return acc;
|
|
52
|
+
return {
|
|
53
|
+
total: acc.total + hit.credits,
|
|
54
|
+
unit: hit.unit ?? acc.unit,
|
|
55
|
+
present: true
|
|
56
|
+
};
|
|
57
|
+
},
|
|
58
|
+
{ total: 0, unit: void 0, present: false }
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
function reconcile(store, sessionID, durableMessages) {
|
|
62
|
+
const byID = new Map(durableMessages.map((message) => [message.id, message]));
|
|
63
|
+
for (const key of [...store.entries.keys()]) {
|
|
64
|
+
const parsed = parseKey(key);
|
|
65
|
+
if (!parsed || parsed[0] !== sessionID) continue;
|
|
66
|
+
const message = byID.get(parsed[1]);
|
|
67
|
+
if (!message) {
|
|
68
|
+
store.entries.delete(key);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (messageCredits(message.content ?? []) !== void 0) store.entries.delete(key);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function clear(store) {
|
|
75
|
+
store.entries.clear();
|
|
76
|
+
}
|
|
9
77
|
|
|
10
78
|
// src/tui.ts
|
|
11
|
-
var
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
79
|
+
var EMPTY_CREDITS = { total: 0, unit: void 0, present: false };
|
|
80
|
+
function readSessionID(props) {
|
|
81
|
+
const sessionID = props.sessionID;
|
|
82
|
+
return typeof sessionID === "string" && sessionID.length > 0 ? sessionID : void 0;
|
|
83
|
+
}
|
|
84
|
+
function readColorToken(value) {
|
|
85
|
+
if (typeof value === "string" && value.length > 0) return value;
|
|
86
|
+
if (typeof value === "object" && value !== null) return value;
|
|
87
|
+
return void 0;
|
|
88
|
+
}
|
|
89
|
+
function readThemeTokens(theme) {
|
|
90
|
+
if (typeof theme !== "object" || theme === null) return void 0;
|
|
91
|
+
const text = theme.text;
|
|
92
|
+
if (typeof text !== "object" || text === null) return void 0;
|
|
93
|
+
const tokens = text;
|
|
94
|
+
const defaultFg = readColorToken(tokens.default);
|
|
95
|
+
const subduedFg = readColorToken(tokens.subdued);
|
|
96
|
+
if (defaultFg === void 0 && subduedFg === void 0) return void 0;
|
|
97
|
+
return { default: defaultFg, subdued: subduedFg };
|
|
98
|
+
}
|
|
99
|
+
var plugin = {
|
|
100
|
+
// `id` is required for file-source installs (opencode rejects them without one);
|
|
101
|
+
// matching the package name keeps it identical across path and npm installs
|
|
102
|
+
id: "opencode-kiro",
|
|
103
|
+
async setup(context) {
|
|
104
|
+
const disposers = [];
|
|
105
|
+
let disposed = false;
|
|
106
|
+
const memoryStore = context.storage?.memory?.("transient-credits", {
|
|
107
|
+
initial: createTransientStore()
|
|
108
|
+
});
|
|
109
|
+
const store = memoryStore ? memoryStore[0] : createTransientStore();
|
|
110
|
+
if (!memoryStore) disposers.push(() => clear(store));
|
|
111
|
+
const [{ createCreditsBoxView }, { createCreditsChipView }, { createSignal }] = await Promise.all([
|
|
112
|
+
import("./credits-box-view-GLFJSV6M.js"),
|
|
113
|
+
import("./credits-chip-view-YP4RPX26.js"),
|
|
114
|
+
import("solid-js")
|
|
115
|
+
]);
|
|
116
|
+
const [transientVersion, setTransientVersion] = createSignal(0);
|
|
117
|
+
const unsubscribeTextEnded = context.data.on("session.text.ended", (event) => {
|
|
118
|
+
try {
|
|
119
|
+
recordTextEnded(store, event);
|
|
120
|
+
setTransientVersion((version) => version + 1);
|
|
121
|
+
} catch {
|
|
22
122
|
}
|
|
23
|
-
}
|
|
24
|
-
|
|
123
|
+
});
|
|
124
|
+
disposers.push(unsubscribeTextEnded);
|
|
125
|
+
const creditsFor = (sessionID) => () => {
|
|
126
|
+
if (!sessionID) return EMPTY_CREDITS;
|
|
127
|
+
transientVersion();
|
|
128
|
+
const messages = context.data.session.message.list(sessionID) ?? [];
|
|
129
|
+
reconcile(store, sessionID, messages);
|
|
130
|
+
return mergedMessageCredits(store, sessionID, messages);
|
|
131
|
+
};
|
|
132
|
+
const unregisterSidebar = context.ui.slot({
|
|
133
|
+
append: "sidebar.content",
|
|
134
|
+
render: (props) => {
|
|
135
|
+
const credits = creditsFor(readSessionID(props));
|
|
136
|
+
const view = createCreditsBoxView(credits, readThemeTokens(context.theme));
|
|
137
|
+
return (() => credits().present ? view : null);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
disposers.push(unregisterSidebar);
|
|
141
|
+
const unregisterChip = context.ui.slot({
|
|
142
|
+
append: "prompt.footer.status",
|
|
143
|
+
render: (props) => {
|
|
144
|
+
const credits = creditsFor(readSessionID(props));
|
|
145
|
+
const view = createCreditsChipView(credits, readThemeTokens(context.theme));
|
|
146
|
+
return (() => credits().present ? view : null);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
disposers.push(unregisterChip);
|
|
150
|
+
return async () => {
|
|
151
|
+
if (disposed) return;
|
|
152
|
+
disposed = true;
|
|
153
|
+
const errors = [];
|
|
154
|
+
for (const dispose of disposers.reverse()) {
|
|
155
|
+
try {
|
|
156
|
+
await dispose();
|
|
157
|
+
} catch (error) {
|
|
158
|
+
errors.push(error);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (errors.length > 0) throw new AggregateError(errors, "opencode-kiro tui cleanup failures");
|
|
162
|
+
};
|
|
163
|
+
}
|
|
25
164
|
};
|
|
26
|
-
var tui_default =
|
|
165
|
+
var tui_default = plugin;
|
|
27
166
|
export {
|
|
28
|
-
|
|
29
|
-
tui_default as default,
|
|
30
|
-
formatCredits,
|
|
31
|
-
messageCredits,
|
|
32
|
-
readPartCredits,
|
|
33
|
-
spendLines,
|
|
34
|
-
sumSessionCredits
|
|
167
|
+
tui_default as default
|
|
35
168
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-kiro",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0-beta.2",
|
|
4
4
|
"description": "The ACP-compliant Kiro plugin for opencode: auth via the official kiro-cli login, the live Kiro model lineup through the Agent Client Protocol, and TUI credits display",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Nacho F. Lizaur (https://github.com/NachoFLizaur)",
|
|
@@ -36,20 +36,19 @@
|
|
|
36
36
|
"prepublishOnly": "npm run build"
|
|
37
37
|
},
|
|
38
38
|
"engines": {
|
|
39
|
-
"node": ">=20"
|
|
40
|
-
"opencode": ">=1.16.0"
|
|
39
|
+
"node": ">=20"
|
|
41
40
|
},
|
|
42
41
|
"peerDependencies": {
|
|
43
|
-
"@opencode-ai/plugin": "
|
|
42
|
+
"@opencode-ai/plugin": "0.0.0-dev-17968"
|
|
44
43
|
},
|
|
45
44
|
"dependencies": {
|
|
46
|
-
"
|
|
45
|
+
"@opentui/solid": "0.5.7",
|
|
46
|
+
"kiro-acp-ai-provider": "3.0.0",
|
|
47
|
+
"solid-js": "1.9.12"
|
|
47
48
|
},
|
|
48
49
|
"devDependencies": {
|
|
49
|
-
"@opencode-ai/plugin": "
|
|
50
|
-
"@opentui/solid": "^0.3.4",
|
|
50
|
+
"@opencode-ai/plugin": "0.0.0-dev-17968",
|
|
51
51
|
"@types/node": "^20.19.42",
|
|
52
|
-
"solid-js": "^1.9.12",
|
|
53
52
|
"tsup": "^8.0.0",
|
|
54
53
|
"typescript": "^5.7.0",
|
|
55
54
|
"vitest": "^4.1.8"
|
package/dist/chunk-MQVMKLNA.js
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
// src/tui/credits.ts
|
|
2
|
-
function isRecord(value) {
|
|
3
|
-
return typeof value === "object" && value !== null;
|
|
4
|
-
}
|
|
5
|
-
function readPartCredits(part) {
|
|
6
|
-
if (!isRecord(part)) return void 0;
|
|
7
|
-
const metadata = part.metadata;
|
|
8
|
-
if (!isRecord(metadata)) return void 0;
|
|
9
|
-
const kiro = metadata.kiro;
|
|
10
|
-
if (!isRecord(kiro)) return void 0;
|
|
11
|
-
if (typeof kiro.credits !== "number" || !Number.isFinite(kiro.credits)) return void 0;
|
|
12
|
-
return {
|
|
13
|
-
credits: kiro.credits,
|
|
14
|
-
unit: typeof kiro.creditsUnit === "string" && kiro.creditsUnit.length > 0 ? kiro.creditsUnit : void 0
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
function messageCredits(parts) {
|
|
18
|
-
const carriers = parts.map(readPartCredits).filter((value) => value !== void 0);
|
|
19
|
-
const last = carriers.at(-1);
|
|
20
|
-
if (!last) return void 0;
|
|
21
|
-
return {
|
|
22
|
-
credits: last.credits,
|
|
23
|
-
unit: last.unit ?? carriers.findLast((carrier) => carrier.unit !== void 0)?.unit
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
function creditsForMessage(parts) {
|
|
27
|
-
return messageCredits(parts)?.credits;
|
|
28
|
-
}
|
|
29
|
-
function sumSessionCredits(messages, partsByMessage) {
|
|
30
|
-
return messages.filter((message) => message.role === "assistant").reduce(
|
|
31
|
-
(acc, message) => {
|
|
32
|
-
const hit = messageCredits(partsByMessage(message.id));
|
|
33
|
-
if (!hit) return acc;
|
|
34
|
-
return {
|
|
35
|
-
total: acc.total + hit.credits,
|
|
36
|
-
unit: hit.unit ?? acc.unit,
|
|
37
|
-
present: true
|
|
38
|
-
};
|
|
39
|
-
},
|
|
40
|
-
{ total: 0, unit: void 0, present: false }
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
var creditsAmount = new Intl.NumberFormat("en-US", { maximumFractionDigits: 2 });
|
|
44
|
-
function formatCredits(value, unit) {
|
|
45
|
-
const amount = creditsAmount.format(Number.isFinite(value) ? value : 0);
|
|
46
|
-
if (!unit) return amount;
|
|
47
|
-
const label = value === 1 || unit.endsWith("s") ? unit : `${unit}s`;
|
|
48
|
-
return `${amount} ${label}`;
|
|
49
|
-
}
|
|
50
|
-
var money = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" });
|
|
51
|
-
function spendLines(input) {
|
|
52
|
-
const { cost, credits } = input;
|
|
53
|
-
const dollars = money.format(Number.isFinite(cost) ? cost : 0);
|
|
54
|
-
if (credits.present && cost > 0) {
|
|
55
|
-
return [`${dollars} spent`, formatCredits(credits.total, credits.unit)];
|
|
56
|
-
}
|
|
57
|
-
if (credits.present) {
|
|
58
|
-
return [formatCredits(credits.total, credits.unit)];
|
|
59
|
-
}
|
|
60
|
-
return [`${dollars} spent`];
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export {
|
|
64
|
-
readPartCredits,
|
|
65
|
-
messageCredits,
|
|
66
|
-
creditsForMessage,
|
|
67
|
-
sumSessionCredits,
|
|
68
|
-
formatCredits,
|
|
69
|
-
spendLines
|
|
70
|
-
};
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
formatCredits,
|
|
3
|
-
sumSessionCredits
|
|
4
|
-
} from "./chunk-MQVMKLNA.js";
|
|
5
|
-
|
|
6
|
-
// src/tui/credits-box-view.ts
|
|
7
|
-
import { createElement, effect, insert, insertNode, setProp } from "@opentui/solid";
|
|
8
|
-
import { createMemo } from "solid-js";
|
|
9
|
-
function createCreditsBoxView(api, sessionID) {
|
|
10
|
-
const theme = () => api.theme.current;
|
|
11
|
-
const messages = createMemo(() => api.state.session.messages(sessionID));
|
|
12
|
-
const credits = createMemo(() => sumSessionCredits(messages(), (messageID) => api.state.part(messageID)));
|
|
13
|
-
const root = createElement("box");
|
|
14
|
-
insertNode(
|
|
15
|
-
root,
|
|
16
|
-
headerLine(theme, () => credits().present ? "Kiro" : "")
|
|
17
|
-
);
|
|
18
|
-
insertNode(
|
|
19
|
-
root,
|
|
20
|
-
mutedLine(theme, () => credits().present ? formatCredits(credits().total, credits().unit) : "")
|
|
21
|
-
);
|
|
22
|
-
return root;
|
|
23
|
-
}
|
|
24
|
-
function headerLine(theme, content) {
|
|
25
|
-
const line = createElement("text");
|
|
26
|
-
effect(() => setProp(line, "fg", theme().text));
|
|
27
|
-
const bold = createElement("b");
|
|
28
|
-
insert(bold, content);
|
|
29
|
-
insertNode(line, bold);
|
|
30
|
-
return line;
|
|
31
|
-
}
|
|
32
|
-
function mutedLine(theme, content) {
|
|
33
|
-
const line = createElement("text");
|
|
34
|
-
effect(() => setProp(line, "fg", theme().textMuted));
|
|
35
|
-
insert(line, content);
|
|
36
|
-
return line;
|
|
37
|
-
}
|
|
38
|
-
export {
|
|
39
|
-
createCreditsBoxView
|
|
40
|
-
};
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
formatCredits,
|
|
3
|
-
sumSessionCredits
|
|
4
|
-
} from "./chunk-MQVMKLNA.js";
|
|
5
|
-
|
|
6
|
-
// src/tui/credits-chip-view.ts
|
|
7
|
-
import { createElement, effect, insert, setProp } from "@opentui/solid";
|
|
8
|
-
import { createMemo } from "solid-js";
|
|
9
|
-
function createCreditsChipView(api, sessionID) {
|
|
10
|
-
const messages = createMemo(() => api.state.session.messages(sessionID));
|
|
11
|
-
const credits = createMemo(() => sumSessionCredits(messages(), (messageID) => api.state.part(messageID)));
|
|
12
|
-
const chip = createElement("text");
|
|
13
|
-
effect(() => setProp(chip, "fg", api.theme.current.textMuted));
|
|
14
|
-
setProp(chip, "wrapMode", "none");
|
|
15
|
-
insert(chip, () => credits().present ? formatCredits(credits().total, credits().unit) : "");
|
|
16
|
-
return chip;
|
|
17
|
-
}
|
|
18
|
-
export {
|
|
19
|
-
createCreditsChipView
|
|
20
|
-
};
|