@germanescobar/anita 0.3.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 (66) hide show
  1. package/README.md +353 -0
  2. package/dist/agent/agents.d.ts +16 -0
  3. package/dist/agent/agents.js +115 -0
  4. package/dist/agent/context-budget.d.ts +7 -0
  5. package/dist/agent/context-budget.js +17 -0
  6. package/dist/agent/context-builder.d.ts +34 -0
  7. package/dist/agent/context-builder.js +175 -0
  8. package/dist/agent/executor.d.ts +13 -0
  9. package/dist/agent/executor.js +65 -0
  10. package/dist/agent/loop.d.ts +54 -0
  11. package/dist/agent/loop.js +548 -0
  12. package/dist/agent/policies.d.ts +25 -0
  13. package/dist/agent/policies.js +177 -0
  14. package/dist/agent/session.d.ts +12 -0
  15. package/dist/agent/session.js +42 -0
  16. package/dist/attachments.d.ts +3 -0
  17. package/dist/attachments.js +73 -0
  18. package/dist/cli/index.d.ts +5 -0
  19. package/dist/cli/index.js +327 -0
  20. package/dist/index.d.ts +2 -0
  21. package/dist/index.js +4 -0
  22. package/dist/models/anthropic.d.ts +15 -0
  23. package/dist/models/anthropic.js +195 -0
  24. package/dist/models/openai-responses.d.ts +62 -0
  25. package/dist/models/openai-responses.js +377 -0
  26. package/dist/models/openai.d.ts +32 -0
  27. package/dist/models/openai.js +330 -0
  28. package/dist/models/provider.d.ts +33 -0
  29. package/dist/models/provider.js +1 -0
  30. package/dist/models/resolve.d.ts +48 -0
  31. package/dist/models/resolve.js +211 -0
  32. package/dist/security/sensitive-content.d.ts +6 -0
  33. package/dist/security/sensitive-content.js +59 -0
  34. package/dist/skills/skills.d.ts +62 -0
  35. package/dist/skills/skills.js +371 -0
  36. package/dist/storage/event-store.d.ts +7 -0
  37. package/dist/storage/event-store.js +36 -0
  38. package/dist/storage/session-store.d.ts +11 -0
  39. package/dist/storage/session-store.js +64 -0
  40. package/dist/tools/delete-file.d.ts +2 -0
  41. package/dist/tools/delete-file.js +25 -0
  42. package/dist/tools/edit-file.d.ts +2 -0
  43. package/dist/tools/edit-file.js +50 -0
  44. package/dist/tools/read-file.d.ts +2 -0
  45. package/dist/tools/read-file.js +122 -0
  46. package/dist/tools/registry.d.ts +9 -0
  47. package/dist/tools/registry.js +122 -0
  48. package/dist/tools/run-command.d.ts +2 -0
  49. package/dist/tools/run-command.js +103 -0
  50. package/dist/tools/write-file.d.ts +2 -0
  51. package/dist/tools/write-file.js +29 -0
  52. package/dist/types/agent.d.ts +44 -0
  53. package/dist/types/agent.js +1 -0
  54. package/dist/types/conversation.d.ts +43 -0
  55. package/dist/types/conversation.js +201 -0
  56. package/dist/types/events.d.ts +8 -0
  57. package/dist/types/events.js +1 -0
  58. package/dist/types/messages.d.ts +39 -0
  59. package/dist/types/messages.js +1 -0
  60. package/dist/types/output.d.ts +19 -0
  61. package/dist/types/output.js +1 -0
  62. package/dist/types/stream.d.ts +55 -0
  63. package/dist/types/stream.js +1 -0
  64. package/dist/types/tools.d.ts +28 -0
  65. package/dist/types/tools.js +1 -0
  66. package/package.json +45 -0
@@ -0,0 +1,122 @@
1
+ import fs from "node:fs/promises";
2
+ export const readFileTool = {
3
+ name: "read_file",
4
+ description: "Read the contents of a file at the given path. Supports optional 1-based inclusive line ranges and max_chars bounds.",
5
+ inputSchema: {
6
+ type: "object",
7
+ properties: {
8
+ path: { type: "string", description: "Absolute or relative file path" },
9
+ start_line: {
10
+ type: "integer",
11
+ minimum: 1,
12
+ description: "Optional 1-based first line to read",
13
+ },
14
+ end_line: {
15
+ type: "integer",
16
+ minimum: 1,
17
+ description: "Optional 1-based last line to read, inclusive",
18
+ },
19
+ max_chars: {
20
+ type: "integer",
21
+ minimum: 1,
22
+ description: "Optional maximum number of characters to return",
23
+ },
24
+ },
25
+ required: ["path"],
26
+ },
27
+ async execute(input) {
28
+ const filePath = input.path;
29
+ try {
30
+ const fullContent = await fs.readFile(filePath, "utf-8");
31
+ const lineCount = countLines(fullContent);
32
+ const startLine = readPositiveInteger(input.start_line);
33
+ const endLine = readPositiveInteger(input.end_line);
34
+ const maxChars = readPositiveInteger(input.max_chars);
35
+ if (startLine !== undefined && endLine !== undefined && endLine < startLine) {
36
+ return {
37
+ content: "Error reading file: end_line must be greater than or equal to start_line.",
38
+ isError: true,
39
+ metadata: {
40
+ path: filePath,
41
+ requestedStartLine: startLine,
42
+ requestedEndLine: endLine,
43
+ },
44
+ };
45
+ }
46
+ const ranged = applyLineRange(fullContent, startLine, endLine);
47
+ const truncated = maxChars !== undefined && ranged.content.length > maxChars;
48
+ const returnedContent = truncated
49
+ ? ranged.content.slice(0, maxChars)
50
+ : ranged.content;
51
+ const content = truncated
52
+ ? `${returnedContent}\n[truncated: output exceeded ${maxChars} characters]`
53
+ : returnedContent;
54
+ const returnedRange = calculateReturnedLineRange(returnedContent, ranged.returnedLineStart);
55
+ return {
56
+ content,
57
+ metadata: {
58
+ path: filePath,
59
+ bytes: Buffer.byteLength(fullContent, "utf-8"),
60
+ lineCount,
61
+ returnedBytes: Buffer.byteLength(content, "utf-8"),
62
+ returnedLineStart: returnedRange.start,
63
+ returnedLineEnd: returnedRange.end,
64
+ truncated,
65
+ maxChars: maxChars ?? null,
66
+ },
67
+ };
68
+ }
69
+ catch (err) {
70
+ return {
71
+ content: `Error reading file: ${err.message}`,
72
+ isError: true,
73
+ metadata: {
74
+ path: filePath,
75
+ },
76
+ };
77
+ }
78
+ },
79
+ };
80
+ function applyLineRange(content, startLine, endLine) {
81
+ if (startLine === undefined && endLine === undefined) {
82
+ return {
83
+ content,
84
+ returnedLineStart: content.length > 0 ? 1 : null,
85
+ returnedLineEnd: content.length > 0 ? countLines(content) : null,
86
+ };
87
+ }
88
+ const lines = content.split("\n");
89
+ const startIndex = (startLine ?? 1) - 1;
90
+ const endIndex = endLine ?? lines.length;
91
+ const selectedLines = lines.slice(startIndex, endIndex);
92
+ return {
93
+ content: selectedLines.join("\n"),
94
+ returnedLineStart: selectedLines.length > 0 ? startIndex + 1 : null,
95
+ returnedLineEnd: selectedLines.length > 0 ? startIndex + selectedLines.length : null,
96
+ };
97
+ }
98
+ function countLines(content) {
99
+ if (content.length === 0)
100
+ return 0;
101
+ return content.split("\n").length;
102
+ }
103
+ function calculateReturnedLineRange(content, startLine) {
104
+ if (content.length === 0 || startLine === null) {
105
+ return { start: null, end: null };
106
+ }
107
+ const returnedLineCount = content.endsWith("\n")
108
+ ? countLines(content) - 1
109
+ : countLines(content);
110
+ return {
111
+ start: startLine,
112
+ end: startLine + returnedLineCount - 1,
113
+ };
114
+ }
115
+ function readPositiveInteger(value) {
116
+ if (value === undefined)
117
+ return undefined;
118
+ if (!Number.isInteger(value) || typeof value !== "number" || value < 1) {
119
+ return undefined;
120
+ }
121
+ return value;
122
+ }
@@ -0,0 +1,9 @@
1
+ import type { ToolDefinition, ToolExecuteOptions, ToolResult, ToolSchema } from "../types/tools.js";
2
+ export declare class ToolRegistry {
3
+ private tools;
4
+ register(tool: ToolDefinition): void;
5
+ get(name: string): ToolDefinition | undefined;
6
+ toSchemas(): ToolSchema[];
7
+ execute(name: string, input: Record<string, unknown>, options?: ToolExecuteOptions): Promise<ToolResult>;
8
+ validateInput(name: string, input: Record<string, unknown>): ToolResult | null;
9
+ }
@@ -0,0 +1,122 @@
1
+ const SUPPORTED_PROPERTY_TYPES = new Set([
2
+ "string",
3
+ "number",
4
+ "integer",
5
+ "boolean",
6
+ "object",
7
+ "array",
8
+ ]);
9
+ export class ToolRegistry {
10
+ tools = new Map();
11
+ register(tool) {
12
+ this.tools.set(tool.name, tool);
13
+ }
14
+ get(name) {
15
+ return this.tools.get(name);
16
+ }
17
+ toSchemas() {
18
+ return Array.from(this.tools.values())
19
+ .sort((a, b) => a.name.localeCompare(b.name))
20
+ .map((tool) => ({
21
+ name: tool.name,
22
+ description: tool.description,
23
+ parameters: tool.inputSchema,
24
+ }));
25
+ }
26
+ async execute(name, input, options) {
27
+ const tool = this.tools.get(name);
28
+ if (!tool) {
29
+ return { content: `Unknown tool: ${name}`, isError: true };
30
+ }
31
+ const validationError = this.validateInput(name, input);
32
+ if (validationError)
33
+ return validationError;
34
+ return tool.execute(input, options);
35
+ }
36
+ validateInput(name, input) {
37
+ const tool = this.tools.get(name);
38
+ if (!tool)
39
+ return null;
40
+ const errors = validateAgainstSchema(tool.inputSchema, input);
41
+ if (errors.length === 0)
42
+ return null;
43
+ return {
44
+ content: `Invalid input for tool "${name}": ${errors.join(" ")}`,
45
+ isError: true,
46
+ metadata: {
47
+ validationErrors: errors,
48
+ },
49
+ };
50
+ }
51
+ }
52
+ function validateAgainstSchema(schema, input) {
53
+ const errors = [];
54
+ const required = readRequiredFields(schema.required);
55
+ for (const field of required) {
56
+ if (!Object.prototype.hasOwnProperty.call(input, field) ||
57
+ input[field] === undefined) {
58
+ errors.push(`missing required field "${field}".`);
59
+ }
60
+ }
61
+ const properties = readProperties(schema.properties);
62
+ for (const [field, propertySchema] of Object.entries(properties)) {
63
+ if (!Object.prototype.hasOwnProperty.call(input, field) ||
64
+ input[field] === undefined) {
65
+ continue;
66
+ }
67
+ const expectedType = readExpectedType(propertySchema);
68
+ if (!expectedType)
69
+ continue;
70
+ const value = input[field];
71
+ if (!matchesType(value, expectedType)) {
72
+ errors.push(`field "${field}" must be ${formatExpectedType(expectedType)}.`);
73
+ }
74
+ }
75
+ return errors;
76
+ }
77
+ function readRequiredFields(required) {
78
+ if (!Array.isArray(required))
79
+ return [];
80
+ return required.filter((field) => typeof field === "string");
81
+ }
82
+ function readProperties(properties) {
83
+ if (!isPlainObject(properties))
84
+ return {};
85
+ const result = {};
86
+ for (const [field, propertySchema] of Object.entries(properties)) {
87
+ if (isPlainObject(propertySchema)) {
88
+ result[field] = propertySchema;
89
+ }
90
+ }
91
+ return result;
92
+ }
93
+ function readExpectedType(schema) {
94
+ const type = schema.type;
95
+ if (typeof type !== "string")
96
+ return null;
97
+ return SUPPORTED_PROPERTY_TYPES.has(type) ? type : null;
98
+ }
99
+ function matchesType(value, expectedType) {
100
+ switch (expectedType) {
101
+ case "array":
102
+ return Array.isArray(value);
103
+ case "boolean":
104
+ return typeof value === "boolean";
105
+ case "integer":
106
+ return Number.isInteger(value);
107
+ case "number":
108
+ return typeof value === "number" && Number.isFinite(value);
109
+ case "object":
110
+ return isPlainObject(value);
111
+ case "string":
112
+ return typeof value === "string";
113
+ default:
114
+ return true;
115
+ }
116
+ }
117
+ function formatExpectedType(expectedType) {
118
+ return expectedType === "integer" ? "an integer" : `a ${expectedType}`;
119
+ }
120
+ function isPlainObject(value) {
121
+ return typeof value === "object" && value !== null && !Array.isArray(value);
122
+ }
@@ -0,0 +1,2 @@
1
+ import type { ToolDefinition } from "../types/tools.js";
2
+ export declare const runCommandTool: ToolDefinition;
@@ -0,0 +1,103 @@
1
+ import { exec } from "node:child_process";
2
+ import process from "node:process";
3
+ const DEFAULT_TIMEOUT = 30_000; // 30 seconds
4
+ const MAX_OUTPUT_CHARS = 10_000;
5
+ export const runCommandTool = {
6
+ name: "run_command",
7
+ description: "Run a shell command and return stdout/stderr. Has a 30 second timeout.",
8
+ inputSchema: {
9
+ type: "object",
10
+ properties: {
11
+ command: { type: "string", description: "The shell command to run" },
12
+ },
13
+ required: ["command"],
14
+ },
15
+ async execute(input, options) {
16
+ const command = input.command;
17
+ const signal = options?.signal;
18
+ if (signal?.aborted) {
19
+ return abortedResult(command);
20
+ }
21
+ return new Promise((resolve) => {
22
+ exec(command, { timeout: DEFAULT_TIMEOUT, signal }, (error, stdout, stderr) => {
23
+ const aborted = Boolean(signal?.aborted);
24
+ const timedOut = Boolean(error?.killed) && !aborted;
25
+ const parts = [];
26
+ if (stdout)
27
+ parts.push(stdout);
28
+ if (stderr)
29
+ parts.push(`[stderr]\n${stderr}`);
30
+ if (aborted) {
31
+ parts.push("[cancelled] Command was cancelled before completion");
32
+ }
33
+ else if (timedOut) {
34
+ parts.push(`[timeout] Command timed out after ${DEFAULT_TIMEOUT}ms`);
35
+ }
36
+ else if (error && error.signal) {
37
+ parts.push(`[signal: ${error.signal}]`);
38
+ }
39
+ else if (error) {
40
+ parts.push(`[exit code: ${error.code}]`);
41
+ }
42
+ const rawContent = parts.join("\n") || "(no output)";
43
+ let content = rawContent;
44
+ const truncated = content.length > MAX_OUTPUT_CHARS;
45
+ if (content.length > MAX_OUTPUT_CHARS) {
46
+ content = content.slice(0, MAX_OUTPUT_CHARS) + `\n[truncated: output exceeded ${MAX_OUTPUT_CHARS} characters]`;
47
+ }
48
+ resolve({
49
+ content,
50
+ isError: !!error,
51
+ metadata: {
52
+ command,
53
+ cwd: process.cwd(),
54
+ exitCode: getExitCode(error),
55
+ signal: error?.signal ?? null,
56
+ timedOut,
57
+ aborted,
58
+ truncated,
59
+ bytes: Buffer.byteLength(rawContent, "utf-8"),
60
+ lineCount: countLines(rawContent),
61
+ stdoutBytes: Buffer.byteLength(stdout, "utf-8"),
62
+ stderrBytes: Buffer.byteLength(stderr, "utf-8"),
63
+ timeoutMs: DEFAULT_TIMEOUT,
64
+ maxOutputChars: MAX_OUTPUT_CHARS,
65
+ },
66
+ });
67
+ });
68
+ });
69
+ },
70
+ };
71
+ function abortedResult(command) {
72
+ const content = "[cancelled] Command was cancelled before completion";
73
+ return {
74
+ content,
75
+ isError: true,
76
+ metadata: {
77
+ command,
78
+ cwd: process.cwd(),
79
+ exitCode: null,
80
+ signal: null,
81
+ timedOut: false,
82
+ aborted: true,
83
+ truncated: false,
84
+ bytes: Buffer.byteLength(content, "utf-8"),
85
+ lineCount: countLines(content),
86
+ stdoutBytes: 0,
87
+ stderrBytes: 0,
88
+ timeoutMs: DEFAULT_TIMEOUT,
89
+ maxOutputChars: MAX_OUTPUT_CHARS,
90
+ },
91
+ };
92
+ }
93
+ function countLines(content) {
94
+ if (content.length === 0)
95
+ return 0;
96
+ return content.split("\n").length;
97
+ }
98
+ function getExitCode(error) {
99
+ if (!error)
100
+ return 0;
101
+ const maybeCode = error;
102
+ return typeof maybeCode.code === "number" ? maybeCode.code : null;
103
+ }
@@ -0,0 +1,2 @@
1
+ import type { ToolDefinition } from "../types/tools.js";
2
+ export declare const writeFileTool: ToolDefinition;
@@ -0,0 +1,29 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ export const writeFileTool = {
4
+ name: "write_file",
5
+ description: "Write content to a file. Creates the file and any parent directories if they don't exist. Overwrites existing content.",
6
+ inputSchema: {
7
+ type: "object",
8
+ properties: {
9
+ path: { type: "string", description: "Absolute or relative file path" },
10
+ content: { type: "string", description: "Content to write to the file" },
11
+ },
12
+ required: ["path", "content"],
13
+ },
14
+ async execute(input) {
15
+ const filePath = input.path;
16
+ const content = input.content;
17
+ try {
18
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
19
+ await fs.writeFile(filePath, content);
20
+ return { content: `File written: ${filePath}` };
21
+ }
22
+ catch (err) {
23
+ return {
24
+ content: `Error writing file: ${err.message}`,
25
+ isError: true,
26
+ };
27
+ }
28
+ },
29
+ };
@@ -0,0 +1,44 @@
1
+ import type { ConversationItem, ConversationReasoningItem } from "./conversation.js";
2
+ import type { Message, ContentBlock } from "./messages.js";
3
+ export type SessionStatus = "active" | "paused" | "completed" | "archived";
4
+ export interface SessionState {
5
+ id: string;
6
+ title?: string;
7
+ workingDirectory: string;
8
+ model: string;
9
+ conversationItems: ConversationItem[];
10
+ messages: Message[];
11
+ contextBudget?: SessionContextBudget;
12
+ createdAt: string;
13
+ lastActiveAt: string;
14
+ status: SessionStatus;
15
+ }
16
+ export interface SessionContextBudget {
17
+ approximateTokens: number;
18
+ thresholdTokens: number;
19
+ compactAtRatio: number;
20
+ reservedResponseTokens: number;
21
+ keepRecentTokens: number;
22
+ minSummarizableTokens: number;
23
+ targetSummaryTokens: number;
24
+ preservedRecentTokens?: number;
25
+ summaryTokens?: number;
26
+ compactionSummary?: string;
27
+ summarizedItemCount?: number;
28
+ compactedAt?: string;
29
+ lastProviderUsage?: ModelUsage;
30
+ }
31
+ export type StopReason = "end_turn" | "tool_use" | "max_tokens" | "error";
32
+ export interface ModelResponse {
33
+ stopReason: StopReason;
34
+ content: ContentBlock[];
35
+ reasoning?: string;
36
+ reasoningItems?: ConversationReasoningItem[];
37
+ usage?: ModelUsage;
38
+ }
39
+ export interface ModelUsage {
40
+ inputTokens: number;
41
+ outputTokens: number;
42
+ cachedTokens?: number;
43
+ cacheWriteTokens?: number;
44
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,43 @@
1
+ import type { ToolResultMetadata } from "./tools.js";
2
+ import type { AttachmentContentBlock, ContentBlock, Message, MessageRole } from "./messages.js";
3
+ export type ConversationItem = ConversationMessageItem | ConversationAttachmentItem | ConversationReasoningItem | ConversationFunctionCallItem | ConversationFunctionOutputItem | ConversationCompactionSummaryItem;
4
+ export interface ConversationMessageItem {
5
+ type: "message";
6
+ role: MessageRole;
7
+ content: string;
8
+ contentFormat?: "string" | "block";
9
+ }
10
+ export interface ConversationAttachmentItem {
11
+ type: "attachment";
12
+ role: "user";
13
+ attachment: AttachmentContentBlock;
14
+ }
15
+ export interface ConversationReasoningItem {
16
+ type: "reasoning";
17
+ id?: string;
18
+ summary: string;
19
+ encryptedContent?: string;
20
+ }
21
+ export interface ConversationFunctionCallItem {
22
+ type: "function_call";
23
+ id: string;
24
+ name: string;
25
+ input: Record<string, unknown>;
26
+ }
27
+ export interface ConversationFunctionOutputItem {
28
+ type: "function_output";
29
+ callId: string;
30
+ name?: string;
31
+ content: string;
32
+ isError?: boolean;
33
+ metadata?: Record<string, ToolResultMetadata>;
34
+ }
35
+ export interface ConversationCompactionSummaryItem {
36
+ type: "compaction_summary";
37
+ summary: string;
38
+ }
39
+ export declare function messagesToConversationItems(messages: Message[]): ConversationItem[];
40
+ export declare function conversationItemsToMessages(items: ConversationItem[]): Message[];
41
+ export declare function contentBlocksToConversationItems(content: ContentBlock[], reasoning?: string, reasoningItems?: ConversationReasoningItem[]): ConversationItem[];
42
+ export declare function conversationItemsToText(items: ConversationItem[]): string;
43
+ export declare function conversationItemToText(item: ConversationItem): string;
@@ -0,0 +1,201 @@
1
+ export function messagesToConversationItems(messages) {
2
+ const items = [];
3
+ for (const message of messages) {
4
+ if (typeof message.content === "string") {
5
+ items.push({
6
+ type: "message",
7
+ role: message.role,
8
+ content: message.content,
9
+ contentFormat: "string",
10
+ });
11
+ continue;
12
+ }
13
+ for (const block of message.content) {
14
+ switch (block.type) {
15
+ case "text":
16
+ items.push({
17
+ type: "message",
18
+ role: message.role,
19
+ content: block.text,
20
+ contentFormat: "block",
21
+ });
22
+ break;
23
+ case "image":
24
+ case "file":
25
+ items.push({
26
+ type: "attachment",
27
+ role: "user",
28
+ attachment: block,
29
+ });
30
+ break;
31
+ case "tool_use":
32
+ items.push({
33
+ type: "function_call",
34
+ id: block.id,
35
+ name: block.name,
36
+ input: block.input,
37
+ });
38
+ break;
39
+ case "tool_result":
40
+ items.push({
41
+ type: "function_output",
42
+ callId: block.toolUseId,
43
+ content: block.content,
44
+ ...(block.isError !== undefined ? { isError: block.isError } : {}),
45
+ ...(block.metadata !== undefined ? { metadata: block.metadata } : {}),
46
+ });
47
+ break;
48
+ }
49
+ }
50
+ }
51
+ return items;
52
+ }
53
+ export function conversationItemsToMessages(items) {
54
+ const messages = [];
55
+ let pendingAssistantBlocks = [];
56
+ let pendingUserBlocks = [];
57
+ const flushAssistantBlocks = () => {
58
+ if (pendingAssistantBlocks.length === 0)
59
+ return;
60
+ messages.push({ role: "assistant", content: pendingAssistantBlocks });
61
+ pendingAssistantBlocks = [];
62
+ };
63
+ const flushUserBlocks = () => {
64
+ if (pendingUserBlocks.length === 0)
65
+ return;
66
+ messages.push({ role: "user", content: pendingUserBlocks });
67
+ pendingUserBlocks = [];
68
+ };
69
+ for (const item of items) {
70
+ switch (item.type) {
71
+ case "message":
72
+ if (item.contentFormat === "block") {
73
+ if (item.role === "assistant") {
74
+ flushUserBlocks();
75
+ pendingAssistantBlocks.push({ type: "text", text: item.content });
76
+ }
77
+ else {
78
+ flushAssistantBlocks();
79
+ pendingUserBlocks.push({ type: "text", text: item.content });
80
+ }
81
+ break;
82
+ }
83
+ flushAssistantBlocks();
84
+ flushUserBlocks();
85
+ messages.push({
86
+ role: item.role,
87
+ content: item.content,
88
+ });
89
+ break;
90
+ case "attachment":
91
+ flushAssistantBlocks();
92
+ pendingUserBlocks.push(item.attachment);
93
+ break;
94
+ case "reasoning":
95
+ break;
96
+ case "function_call":
97
+ flushUserBlocks();
98
+ pendingAssistantBlocks.push({
99
+ type: "tool_use",
100
+ id: item.id,
101
+ name: item.name,
102
+ input: item.input,
103
+ });
104
+ break;
105
+ case "function_output":
106
+ flushAssistantBlocks();
107
+ pendingUserBlocks.push({
108
+ type: "tool_result",
109
+ toolUseId: item.callId,
110
+ content: item.content,
111
+ ...(item.isError !== undefined ? { isError: item.isError } : {}),
112
+ ...(item.metadata !== undefined ? { metadata: item.metadata } : {}),
113
+ });
114
+ break;
115
+ case "compaction_summary":
116
+ flushAssistantBlocks();
117
+ flushUserBlocks();
118
+ messages.push({
119
+ role: "user",
120
+ content: [{ type: "text", text: item.summary }],
121
+ });
122
+ break;
123
+ }
124
+ }
125
+ flushAssistantBlocks();
126
+ flushUserBlocks();
127
+ return messages;
128
+ }
129
+ export function contentBlocksToConversationItems(content, reasoning, reasoningItems) {
130
+ const items = [];
131
+ if (reasoningItems && reasoningItems.length > 0) {
132
+ items.push(...reasoningItems);
133
+ }
134
+ else if (reasoning) {
135
+ items.push({ type: "reasoning", summary: reasoning });
136
+ }
137
+ for (const block of content) {
138
+ switch (block.type) {
139
+ case "text":
140
+ items.push({
141
+ type: "message",
142
+ role: "assistant",
143
+ content: block.text,
144
+ contentFormat: "block",
145
+ });
146
+ break;
147
+ case "image":
148
+ case "file":
149
+ items.push({
150
+ type: "attachment",
151
+ role: "user",
152
+ attachment: block,
153
+ });
154
+ break;
155
+ case "tool_use":
156
+ items.push({
157
+ type: "function_call",
158
+ id: block.id,
159
+ name: block.name,
160
+ input: block.input,
161
+ });
162
+ break;
163
+ case "tool_result":
164
+ items.push({
165
+ type: "function_output",
166
+ callId: block.toolUseId,
167
+ content: block.content,
168
+ ...(block.isError !== undefined ? { isError: block.isError } : {}),
169
+ ...(block.metadata !== undefined ? { metadata: block.metadata } : {}),
170
+ });
171
+ break;
172
+ }
173
+ }
174
+ return items;
175
+ }
176
+ export function conversationItemsToText(items) {
177
+ return items.map(conversationItemToText).join("\n");
178
+ }
179
+ export function conversationItemToText(item) {
180
+ switch (item.type) {
181
+ case "message":
182
+ return item.content;
183
+ case "attachment":
184
+ return `${item.attachment.type} attachment ${formatAttachmentName(item.attachment)}`;
185
+ case "reasoning":
186
+ return `reasoning ${item.summary}`;
187
+ case "function_call":
188
+ return `function_call ${item.id} ${item.name} ${JSON.stringify(item.input)}`;
189
+ case "function_output":
190
+ return `function_output ${item.callId} ${item.isError ? "error" : "ok"} ${item.content} ${JSON.stringify(item.metadata ?? {})}`;
191
+ case "compaction_summary":
192
+ return item.summary;
193
+ }
194
+ }
195
+ function formatAttachmentName(attachment) {
196
+ const name = attachment.name ? `${attachment.name} ` : "";
197
+ if (attachment.source.type === "url") {
198
+ return `${name}${attachment.source.url}`;
199
+ }
200
+ return `${name}${attachment.source.mediaType}`;
201
+ }