Initial commit: Paperclip adapter for Claude Code on Kubernetes
Adapter plugin that runs Claude Code agents as Kubernetes Jobs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Vendored
+152
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* Self-contained stdout parser for Claude stream-json output.
|
||||
* Zero external imports — required by the Paperclip adapter plugin UI parser contract.
|
||||
*/
|
||||
function asRecord(value) {
|
||||
if (typeof value !== "object" || value === null || Array.isArray(value))
|
||||
return null;
|
||||
return value;
|
||||
}
|
||||
function asNumber(value) {
|
||||
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
||||
}
|
||||
function errorText(value) {
|
||||
if (typeof value === "string")
|
||||
return value;
|
||||
const rec = asRecord(value);
|
||||
if (!rec)
|
||||
return "";
|
||||
const msg = (typeof rec.message === "string" && rec.message) ||
|
||||
(typeof rec.error === "string" && rec.error) ||
|
||||
(typeof rec.code === "string" && rec.code) ||
|
||||
"";
|
||||
if (msg)
|
||||
return msg;
|
||||
try {
|
||||
return JSON.stringify(rec);
|
||||
}
|
||||
catch {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
function safeJsonParse(text) {
|
||||
try {
|
||||
return JSON.parse(text);
|
||||
}
|
||||
catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
export function parseStdoutLine(line, ts) {
|
||||
const parsed = asRecord(safeJsonParse(line));
|
||||
if (!parsed) {
|
||||
return [{ kind: "stdout", ts, text: line }];
|
||||
}
|
||||
const type = typeof parsed.type === "string" ? parsed.type : "";
|
||||
if (type === "system" && parsed.subtype === "init") {
|
||||
return [
|
||||
{
|
||||
kind: "init",
|
||||
ts,
|
||||
model: typeof parsed.model === "string" ? parsed.model : "unknown",
|
||||
sessionId: typeof parsed.session_id === "string" ? parsed.session_id : "",
|
||||
},
|
||||
];
|
||||
}
|
||||
if (type === "assistant") {
|
||||
const message = asRecord(parsed.message) ?? {};
|
||||
const content = Array.isArray(message.content) ? message.content : [];
|
||||
const entries = [];
|
||||
for (const blockRaw of content) {
|
||||
const block = asRecord(blockRaw);
|
||||
if (!block)
|
||||
continue;
|
||||
const blockType = typeof block.type === "string" ? block.type : "";
|
||||
if (blockType === "text") {
|
||||
const text = typeof block.text === "string" ? block.text : "";
|
||||
if (text)
|
||||
entries.push({ kind: "assistant", ts, text });
|
||||
}
|
||||
else if (blockType === "thinking") {
|
||||
const text = typeof block.thinking === "string" ? block.thinking : "";
|
||||
if (text)
|
||||
entries.push({ kind: "thinking", ts, text });
|
||||
}
|
||||
else if (blockType === "tool_use") {
|
||||
entries.push({
|
||||
kind: "tool_call",
|
||||
ts,
|
||||
name: typeof block.name === "string" ? block.name : "unknown",
|
||||
toolUseId: typeof block.id === "string"
|
||||
? block.id
|
||||
: typeof block.tool_use_id === "string"
|
||||
? block.tool_use_id
|
||||
: undefined,
|
||||
input: block.input ?? {},
|
||||
});
|
||||
}
|
||||
}
|
||||
return entries.length > 0 ? entries : [{ kind: "stdout", ts, text: line }];
|
||||
}
|
||||
if (type === "user") {
|
||||
const message = asRecord(parsed.message) ?? {};
|
||||
const content = Array.isArray(message.content) ? message.content : [];
|
||||
const entries = [];
|
||||
for (const blockRaw of content) {
|
||||
const block = asRecord(blockRaw);
|
||||
if (!block)
|
||||
continue;
|
||||
const blockType = typeof block.type === "string" ? block.type : "";
|
||||
if (blockType === "text") {
|
||||
const text = typeof block.text === "string" ? block.text : "";
|
||||
if (text)
|
||||
entries.push({ kind: "user", ts, text });
|
||||
}
|
||||
else if (blockType === "tool_result") {
|
||||
const toolUseId = typeof block.tool_use_id === "string" ? block.tool_use_id : "";
|
||||
const isError = block.is_error === true;
|
||||
let text = "";
|
||||
if (typeof block.content === "string") {
|
||||
text = block.content;
|
||||
}
|
||||
else if (Array.isArray(block.content)) {
|
||||
const parts = [];
|
||||
for (const part of block.content) {
|
||||
const p = asRecord(part);
|
||||
if (p && typeof p.text === "string")
|
||||
parts.push(p.text);
|
||||
}
|
||||
text = parts.join("\n");
|
||||
}
|
||||
entries.push({ kind: "tool_result", ts, toolUseId, content: text, isError });
|
||||
}
|
||||
}
|
||||
if (entries.length > 0)
|
||||
return entries;
|
||||
}
|
||||
if (type === "result") {
|
||||
const usage = asRecord(parsed.usage) ?? {};
|
||||
const inputTokens = asNumber(usage.input_tokens);
|
||||
const outputTokens = asNumber(usage.output_tokens);
|
||||
const cachedTokens = asNumber(usage.cache_read_input_tokens);
|
||||
const costUsd = asNumber(parsed.total_cost_usd);
|
||||
const subtype = typeof parsed.subtype === "string" ? parsed.subtype : "";
|
||||
const isError = parsed.is_error === true;
|
||||
const errors = Array.isArray(parsed.errors) ? parsed.errors.map(errorText).filter(Boolean) : [];
|
||||
const text = typeof parsed.result === "string" ? parsed.result : "";
|
||||
return [{
|
||||
kind: "result",
|
||||
ts,
|
||||
text,
|
||||
inputTokens,
|
||||
outputTokens,
|
||||
cachedTokens,
|
||||
costUsd,
|
||||
subtype,
|
||||
isError,
|
||||
errors,
|
||||
}];
|
||||
}
|
||||
return [{ kind: "stdout", ts, text: line }];
|
||||
}
|
||||
//# sourceMappingURL=ui-parser.js.map
|
||||
Reference in New Issue
Block a user