Move adapter implementations into shared workspace packages
Extract claude-local and codex-local adapter code from cli/server/ui into packages/adapters/ and packages/adapter-utils/. CLI, server, and UI now import shared adapter logic instead of duplicating it. Removes ~1100 lines of duplicated code across packages. Register new packages in pnpm workspace. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,99 +0,0 @@
|
||||
import pc from "picocolors";
|
||||
|
||||
function asErrorText(value: unknown): string {
|
||||
if (typeof value === "string") return value;
|
||||
if (typeof value !== "object" || value === null || Array.isArray(value)) return "";
|
||||
const obj = value as Record<string, unknown>;
|
||||
const message =
|
||||
(typeof obj.message === "string" && obj.message) ||
|
||||
(typeof obj.error === "string" && obj.error) ||
|
||||
(typeof obj.code === "string" && obj.code) ||
|
||||
"";
|
||||
if (message) return message;
|
||||
try {
|
||||
return JSON.stringify(obj);
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
export function printClaudeStreamEvent(raw: string, debug: boolean): void {
|
||||
const line = raw.trim();
|
||||
if (!line) return;
|
||||
|
||||
let parsed: Record<string, unknown> | null = null;
|
||||
try {
|
||||
parsed = JSON.parse(line) as Record<string, unknown>;
|
||||
} catch {
|
||||
console.log(line);
|
||||
return;
|
||||
}
|
||||
|
||||
const type = typeof parsed.type === "string" ? parsed.type : "";
|
||||
|
||||
if (type === "system" && parsed.subtype === "init") {
|
||||
const model = typeof parsed.model === "string" ? parsed.model : "unknown";
|
||||
const sessionId = typeof parsed.session_id === "string" ? parsed.session_id : "";
|
||||
console.log(pc.blue(`Claude initialized (model: ${model}${sessionId ? `, session: ${sessionId}` : ""})`));
|
||||
return;
|
||||
}
|
||||
|
||||
if (type === "assistant") {
|
||||
const message =
|
||||
typeof parsed.message === "object" && parsed.message !== null && !Array.isArray(parsed.message)
|
||||
? (parsed.message as Record<string, unknown>)
|
||||
: {};
|
||||
const content = Array.isArray(message.content) ? message.content : [];
|
||||
for (const blockRaw of content) {
|
||||
if (typeof blockRaw !== "object" || blockRaw === null || Array.isArray(blockRaw)) continue;
|
||||
const block = blockRaw as Record<string, unknown>;
|
||||
const blockType = typeof block.type === "string" ? block.type : "";
|
||||
if (blockType === "text") {
|
||||
const text = typeof block.text === "string" ? block.text : "";
|
||||
if (text) console.log(pc.green(`assistant: ${text}`));
|
||||
} else if (blockType === "tool_use") {
|
||||
const name = typeof block.name === "string" ? block.name : "unknown";
|
||||
console.log(pc.yellow(`tool_call: ${name}`));
|
||||
if (block.input !== undefined) {
|
||||
console.log(pc.gray(JSON.stringify(block.input, null, 2)));
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (type === "result") {
|
||||
const usage =
|
||||
typeof parsed.usage === "object" && parsed.usage !== null && !Array.isArray(parsed.usage)
|
||||
? (parsed.usage as Record<string, unknown>)
|
||||
: {};
|
||||
const input = Number(usage.input_tokens ?? 0);
|
||||
const output = Number(usage.output_tokens ?? 0);
|
||||
const cached = Number(usage.cache_read_input_tokens ?? 0);
|
||||
const cost = Number(parsed.total_cost_usd ?? 0);
|
||||
const subtype = typeof parsed.subtype === "string" ? parsed.subtype : "";
|
||||
const isError = parsed.is_error === true;
|
||||
const resultText = typeof parsed.result === "string" ? parsed.result : "";
|
||||
if (resultText) {
|
||||
console.log(pc.green("result:"));
|
||||
console.log(resultText);
|
||||
}
|
||||
const errors = Array.isArray(parsed.errors) ? parsed.errors.map(asErrorText).filter(Boolean) : [];
|
||||
if (subtype.startsWith("error") || isError || errors.length > 0) {
|
||||
console.log(pc.red(`claude_result: subtype=${subtype || "unknown"} is_error=${isError ? "true" : "false"}`));
|
||||
if (errors.length > 0) {
|
||||
console.log(pc.red(`claude_errors: ${errors.join(" | ")}`));
|
||||
}
|
||||
}
|
||||
console.log(
|
||||
pc.blue(
|
||||
`tokens: in=${Number.isFinite(input) ? input : 0} out=${Number.isFinite(output) ? output : 0} cached=${Number.isFinite(cached) ? cached : 0} cost=$${Number.isFinite(cost) ? cost.toFixed(6) : "0.000000"}`,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
console.log(pc.gray(line));
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import type { CLIAdapterModule } from "../types.js";
|
||||
import { printClaudeStreamEvent } from "./format-event.js";
|
||||
|
||||
export const claudeLocalCLIAdapter: CLIAdapterModule = {
|
||||
type: "claude_local",
|
||||
formatStdoutEvent: printClaudeStreamEvent,
|
||||
};
|
||||
@@ -1,56 +0,0 @@
|
||||
import pc from "picocolors";
|
||||
|
||||
export function printCodexStreamEvent(raw: string, _debug: boolean): void {
|
||||
const line = raw.trim();
|
||||
if (!line) return;
|
||||
|
||||
let parsed: Record<string, unknown> | null = null;
|
||||
try {
|
||||
parsed = JSON.parse(line) as Record<string, unknown>;
|
||||
} catch {
|
||||
console.log(line);
|
||||
return;
|
||||
}
|
||||
|
||||
const type = typeof parsed.type === "string" ? parsed.type : "";
|
||||
|
||||
if (type === "thread.started") {
|
||||
const threadId = typeof parsed.thread_id === "string" ? parsed.thread_id : "";
|
||||
console.log(pc.blue(`Codex thread started${threadId ? ` (session: ${threadId})` : ""}`));
|
||||
return;
|
||||
}
|
||||
|
||||
if (type === "item.completed") {
|
||||
const item =
|
||||
typeof parsed.item === "object" && parsed.item !== null && !Array.isArray(parsed.item)
|
||||
? (parsed.item as Record<string, unknown>)
|
||||
: null;
|
||||
if (item) {
|
||||
const itemType = typeof item.type === "string" ? item.type : "";
|
||||
if (itemType === "agent_message") {
|
||||
const text = typeof item.text === "string" ? item.text : "";
|
||||
if (text) console.log(pc.green(`assistant: ${text}`));
|
||||
} else if (itemType === "tool_use") {
|
||||
const name = typeof item.name === "string" ? item.name : "unknown";
|
||||
console.log(pc.yellow(`tool_call: ${name}`));
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (type === "turn.completed") {
|
||||
const usage =
|
||||
typeof parsed.usage === "object" && parsed.usage !== null && !Array.isArray(parsed.usage)
|
||||
? (parsed.usage as Record<string, unknown>)
|
||||
: {};
|
||||
const input = Number(usage.input_tokens ?? 0);
|
||||
const output = Number(usage.output_tokens ?? 0);
|
||||
const cached = Number(usage.cached_input_tokens ?? 0);
|
||||
console.log(
|
||||
pc.blue(`tokens: in=${input} out=${output} cached=${cached}`),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(line);
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import type { CLIAdapterModule } from "../types.js";
|
||||
import { printCodexStreamEvent } from "./format-event.js";
|
||||
|
||||
export const codexLocalCLIAdapter: CLIAdapterModule = {
|
||||
type: "codex_local",
|
||||
formatStdoutEvent: printCodexStreamEvent,
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { CLIAdapterModule } from "../types.js";
|
||||
import type { CLIAdapterModule } from "@paperclip/adapter-utils";
|
||||
import { printHttpStdoutEvent } from "./format-event.js";
|
||||
|
||||
export const httpCLIAdapter: CLIAdapterModule = {
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export { getCLIAdapter } from "./registry.js";
|
||||
export type { CLIAdapterModule } from "./types.js";
|
||||
export type { CLIAdapterModule } from "@paperclip/adapter-utils";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { CLIAdapterModule } from "../types.js";
|
||||
import type { CLIAdapterModule } from "@paperclip/adapter-utils";
|
||||
import { printProcessStdoutEvent } from "./format-event.js";
|
||||
|
||||
export const processCLIAdapter: CLIAdapterModule = {
|
||||
|
||||
@@ -1,9 +1,19 @@
|
||||
import type { CLIAdapterModule } from "./types.js";
|
||||
import { claudeLocalCLIAdapter } from "./claude-local/index.js";
|
||||
import { codexLocalCLIAdapter } from "./codex-local/index.js";
|
||||
import type { CLIAdapterModule } from "@paperclip/adapter-utils";
|
||||
import { printClaudeStreamEvent } from "@paperclip/adapter-claude-local/cli";
|
||||
import { printCodexStreamEvent } from "@paperclip/adapter-codex-local/cli";
|
||||
import { processCLIAdapter } from "./process/index.js";
|
||||
import { httpCLIAdapter } from "./http/index.js";
|
||||
|
||||
const claudeLocalCLIAdapter: CLIAdapterModule = {
|
||||
type: "claude_local",
|
||||
formatStdoutEvent: printClaudeStreamEvent,
|
||||
};
|
||||
|
||||
const codexLocalCLIAdapter: CLIAdapterModule = {
|
||||
type: "codex_local",
|
||||
formatStdoutEvent: printCodexStreamEvent,
|
||||
};
|
||||
|
||||
const adaptersByType = new Map<string, CLIAdapterModule>(
|
||||
[claudeLocalCLIAdapter, codexLocalCLIAdapter, processCLIAdapter, httpCLIAdapter].map((a) => [a.type, a]),
|
||||
);
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
export interface CLIAdapterModule {
|
||||
type: string;
|
||||
formatStdoutEvent: (line: string, debug: boolean) => void;
|
||||
}
|
||||
Reference in New Issue
Block a user