agents 0.7.4 → 0.7.6
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/dist/ai-chat-agent.js +3 -4
- package/dist/ai-chat-agent.js.map +1 -1
- package/dist/ai-chat-v5-migration.js +3 -4
- package/dist/ai-chat-v5-migration.js.map +1 -1
- package/dist/ai-react.js +3 -4
- package/dist/ai-react.js.map +1 -1
- package/dist/ai-types.js +1 -2
- package/dist/ai-types.js.map +1 -1
- package/dist/cli/index.js +2 -4
- package/dist/cli/index.js.map +1 -1
- package/dist/client-K8Z-u76l.js +1468 -0
- package/dist/client-K8Z-u76l.js.map +1 -0
- package/dist/client.js +1 -2
- package/dist/client.js.map +1 -1
- package/dist/codemode/ai.js +2 -2
- package/dist/do-oauth-client-provider-C2jurFjW.d.ts +78 -0
- package/dist/email-U_MG7UET.d.ts +157 -0
- package/dist/email.d.ts +16 -146
- package/dist/email.js +2 -2
- package/dist/email.js.map +1 -1
- package/dist/experimental/forever.d.ts +26 -71
- package/dist/experimental/forever.js +2 -3
- package/dist/experimental/forever.js.map +1 -1
- package/dist/experimental/memory/session/index.js +3 -12
- package/dist/experimental/memory/session/index.js.map +1 -1
- package/dist/experimental/workspace.d.ts +273 -0
- package/dist/experimental/workspace.js +1265 -0
- package/dist/experimental/workspace.js.map +1 -0
- package/dist/index-BS_jL8MI.d.ts +492 -0
- package/dist/index-WBy5hmm3.d.ts +2840 -0
- package/dist/index.d.ts +49 -1320
- package/dist/index.js +281 -138
- package/dist/index.js.map +1 -1
- package/dist/internal_context-DgcmHqS1.d.ts +37 -0
- package/dist/internal_context.d.ts +5 -32
- package/dist/internal_context.js +1 -2
- package/dist/internal_context.js.map +1 -1
- package/dist/mcp/client.d.ts +2 -575
- package/dist/mcp/client.js +1 -847
- package/dist/mcp/do-oauth-client-provider.d.ts +2 -61
- package/dist/mcp/do-oauth-client-provider.js +1 -2
- package/dist/mcp/do-oauth-client-provider.js.map +1 -1
- package/dist/mcp/index.d.ts +2 -95
- package/dist/mcp/index.js +60 -57
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/x402.js +1 -2
- package/dist/mcp/x402.js.map +1 -1
- package/dist/observability/index.d.ts +2 -93
- package/dist/observability/index.js +4 -3
- package/dist/observability/index.js.map +1 -1
- package/dist/react.d.ts +1 -2
- package/dist/react.js +1 -2
- package/dist/react.js.map +1 -1
- package/dist/retries-DXMQGhG3.d.ts +79 -0
- package/dist/retries.d.ts +7 -72
- package/dist/retries.js +1 -1
- package/dist/retries.js.map +1 -1
- package/dist/schedule.js +1 -2
- package/dist/schedule.js.map +1 -1
- package/dist/serializable.js +1 -1
- package/dist/types-BB1plA51.d.ts +15 -0
- package/dist/types.d.ts +1 -14
- package/dist/types.js +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils.js +1 -1
- package/dist/workflow-types-CZNXKj_D.d.ts +260 -0
- package/dist/workflow-types.d.ts +23 -235
- package/dist/workflow-types.js +1 -1
- package/dist/workflow-types.js.map +1 -1
- package/dist/workflows.d.ts +22 -23
- package/dist/workflows.js +5 -6
- package/dist/workflows.js.map +1 -1
- package/package.json +25 -13
- package/dist/agent-eZnMHidZ.d.ts +0 -273
- package/dist/client-connection-D3Wcd6Q6.js +0 -603
- package/dist/client-connection-D3Wcd6Q6.js.map +0 -1
- package/dist/client-storage-BPjfP_is.d.ts +0 -604
- package/dist/experimental/sub-agent.d.ts +0 -205
- package/dist/experimental/sub-agent.js +0 -191
- package/dist/experimental/sub-agent.js.map +0 -1
- package/dist/mcp/client.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../../src/experimental/memory/utils/compaction.ts","../../../../src/experimental/memory/utils/tokens.ts","../../../../src/experimental/memory/session/session.ts","../../../../src/experimental/memory/session/providers/agent.ts"],"sourcesContent":["/**\n * MicroCompaction Utilities\n *\n * Internal pure functions for lightweight compaction (no LLM).\n * Not exported to users — used by the Session wrapper.\n */\n\nimport type { UIMessage } from \"ai\";\nimport type { MicroCompactionRules } from \"../session/types\";\n\n/** Default thresholds for microCompaction rules (in chars) */\nexport const DEFAULTS = {\n truncateToolOutputs: 30000,\n truncateText: 10000,\n keepRecent: 4\n};\n\n/** Resolved microCompaction rules with actual numeric thresholds */\nexport interface ResolvedMicroCompactionRules {\n truncateToolOutputs: number | false;\n truncateText: number | false;\n keepRecent: number;\n}\n\n/**\n * Parse microCompaction config into resolved rules.\n * Returns null if disabled.\n */\nexport function parseMicroCompactionRules(\n config: boolean | MicroCompactionRules\n): ResolvedMicroCompactionRules | null {\n if (config === false) return null;\n\n if (config === true) {\n return {\n truncateToolOutputs: DEFAULTS.truncateToolOutputs,\n truncateText: DEFAULTS.truncateText,\n keepRecent: DEFAULTS.keepRecent\n };\n }\n\n // Custom rules object — validate numeric values\n const keepRecent = config.keepRecent ?? DEFAULTS.keepRecent;\n if (!Number.isInteger(keepRecent) || keepRecent < 0) {\n throw new Error(\"keepRecent must be a non-negative integer\");\n }\n\n const truncateToolOutputs =\n config.truncateToolOutputs === false\n ? false\n : config.truncateToolOutputs === true ||\n config.truncateToolOutputs === undefined\n ? DEFAULTS.truncateToolOutputs\n : config.truncateToolOutputs;\n if (typeof truncateToolOutputs === \"number\" && truncateToolOutputs <= 0) {\n throw new Error(\"truncateToolOutputs must be a positive number\");\n }\n\n const truncateText =\n config.truncateText === false\n ? false\n : config.truncateText === true || config.truncateText === undefined\n ? DEFAULTS.truncateText\n : config.truncateText;\n if (typeof truncateText === \"number\" && truncateText <= 0) {\n throw new Error(\"truncateText must be a positive number\");\n }\n\n return { truncateToolOutputs, truncateText, keepRecent };\n}\n\n/**\n * Truncate oversized parts in a single message.\n * Returns the same reference if nothing changed (allows callers to skip no-op updates).\n */\nfunction truncateMessageParts(\n msg: UIMessage,\n rules: ResolvedMicroCompactionRules\n): UIMessage {\n let changed = false;\n\n const compactedParts = msg.parts.map((part) => {\n // Truncate tool outputs\n if (\n rules.truncateToolOutputs !== false &&\n (part.type.startsWith(\"tool-\") || part.type === \"dynamic-tool\") &&\n \"output\" in part\n ) {\n const toolPart = part as { output?: unknown };\n if (toolPart.output !== undefined) {\n const outputJson = JSON.stringify(toolPart.output);\n if (outputJson.length > rules.truncateToolOutputs) {\n changed = true;\n return {\n ...part,\n output: `[Truncated ${outputJson.length} bytes] ${outputJson.slice(0, 500)}...`\n };\n }\n }\n }\n\n // Truncate long text parts\n if (\n rules.truncateText !== false &&\n part.type === \"text\" &&\n \"text\" in part\n ) {\n const textPart = part as { type: \"text\"; text: string };\n if (textPart.text.length > rules.truncateText) {\n changed = true;\n return {\n ...part,\n text: `${textPart.text.slice(0, rules.truncateText)}... [truncated ${textPart.text.length} chars]`\n };\n }\n }\n\n return part;\n });\n\n return changed ? ({ ...msg, parts: compactedParts } as UIMessage) : msg;\n}\n\n/**\n * Apply microCompaction to an array of messages.\n * Returns same reference for unchanged messages (enables skip-update optimization).\n *\n * No keepRecent logic — the caller decides which messages to pass.\n */\nexport function microCompact(\n messages: UIMessage[],\n rules: ResolvedMicroCompactionRules\n): UIMessage[] {\n return messages.map((msg) => truncateMessageParts(msg, rules));\n}\n","/**\n * Token Estimation Utilities\n *\n * IMPORTANT: These are heuristic estimates, not actual tokenizer counts.\n *\n * We intentionally avoid real tokenizers (e.g. tiktoken, sentencepiece) because:\n * - A single tiktoken instance costs ~80-120MB of heap\n * - Cloudflare Workers have tight memory limits (128MB)\n * - For compaction thresholds, a conservative estimate is sufficient\n *\n * The hybrid approach (max of character-based and word-based estimates) handles\n * both dense token content (JSON, code) and natural language reasonably well.\n *\n * Calibration notes:\n * - Character-based: ~4 chars per token (conservative, from OpenAI guidance)\n * - Word-based: ~1.3 tokens per word (empirical, from Mastra's memory system)\n * - Per-message overhead: ~4 tokens for role/framing (empirical)\n *\n * These ratios are tuned for English. CJK, emoji-heavy, or highly technical\n * content may have different ratios. The conservative estimates help ensure\n * compaction triggers before context windows are actually exceeded.\n */\n\nimport type { UIMessage } from \"ai\";\n\n/** Approximate characters per token for English text */\nexport const CHARS_PER_TOKEN = 4;\n\n/** Approximate token multiplier per whitespace-separated word */\nexport const WORDS_TOKEN_MULTIPLIER = 1.3;\n\n/** Approximate overhead tokens per message (role, framing) */\nexport const TOKENS_PER_MESSAGE = 4;\n\n/**\n * Estimate token count for a string using a hybrid heuristic.\n *\n * Takes the max of two estimates:\n * - Character-based: `length / 4` — better for dense content (JSON, code, URLs)\n * - Word-based: `words * 1.3` — better for natural language prose\n *\n * This is a heuristic. Do not use where exact counts are required.\n */\nexport function estimateStringTokens(text: string): number {\n if (!text) return 0;\n const charEstimate = text.length / CHARS_PER_TOKEN;\n const wordEstimate =\n text.split(/\\s+/).filter(Boolean).length * WORDS_TOKEN_MULTIPLIER;\n return Math.ceil(Math.max(charEstimate, wordEstimate));\n}\n\n/**\n * Estimate total token count for an array of UIMessages.\n *\n * Walks each message's parts (text, tool invocations, tool results)\n * and applies per-message overhead.\n *\n * This is a heuristic. Do not use where exact counts are required.\n */\nexport function estimateMessageTokens(messages: UIMessage[]): number {\n let tokens = 0;\n for (const msg of messages) {\n tokens += TOKENS_PER_MESSAGE;\n for (const part of msg.parts) {\n if (part.type === \"text\") {\n tokens += estimateStringTokens(\n (part as { type: \"text\"; text: string }).text\n );\n } else if (\n part.type.startsWith(\"tool-\") ||\n part.type === \"dynamic-tool\"\n ) {\n const toolPart = part as { input?: unknown; output?: unknown };\n if (toolPart.input) {\n tokens += estimateStringTokens(JSON.stringify(toolPart.input));\n }\n if (toolPart.output) {\n tokens += estimateStringTokens(JSON.stringify(toolPart.output));\n }\n }\n }\n }\n return tokens;\n}\n","/**\n * Session — top-level API for conversation history with compaction.\n *\n * Wraps any SessionProvider (pure storage) and orchestrates compaction:\n * - microCompaction on every append() — cheap, no LLM\n * - full compaction when token threshold exceeded — user-supplied fn\n */\n\nimport type { UIMessage } from \"ai\";\nimport type { SessionProvider } from \"./provider\";\nimport type {\n MessageQueryOptions,\n SessionProviderOptions,\n CompactResult\n} from \"./types\";\nimport {\n parseMicroCompactionRules,\n microCompact,\n type ResolvedMicroCompactionRules\n} from \"../utils/compaction\";\nimport { estimateMessageTokens } from \"../utils/tokens\";\n\nexport class Session {\n private storage: SessionProvider;\n private microCompactionRules: ResolvedMicroCompactionRules | null;\n private compactionConfig: SessionProviderOptions[\"compaction\"] | null;\n\n constructor(storage: SessionProvider, options?: SessionProviderOptions) {\n this.storage = storage;\n\n const mc = options?.microCompaction ?? true;\n this.microCompactionRules = parseMicroCompactionRules(mc);\n this.compactionConfig = options?.compaction ?? null;\n }\n\n // ── Read (delegated to storage) ────────────────────────────────────\n\n getMessages(options?: MessageQueryOptions): UIMessage[] {\n return this.storage.getMessages(options);\n }\n\n getMessage(id: string): UIMessage | null {\n return this.storage.getMessage(id);\n }\n\n getLastMessages(n: number): UIMessage[] {\n return this.storage.getLastMessages(n);\n }\n\n // ── Write (delegated + compaction) ─────────────────────────────────\n\n async append(messages: UIMessage | UIMessage[]): Promise<void> {\n // 1. Storage inserts\n await this.storage.appendMessages(messages);\n\n // 2. Full compaction if token threshold exceeded — runs instead of microCompaction\n if (this.shouldAutoCompact()) {\n const result = await this.compact();\n if (result.success) return;\n // Fall through to microCompaction if full compaction failed\n }\n\n // 3. MicroCompaction on older messages (only if no full compaction)\n if (this.microCompactionRules) {\n const rules = this.microCompactionRules;\n const older = this.storage.getOlderMessages(rules.keepRecent);\n\n if (older.length > 0) {\n const compacted = microCompact(older, rules);\n for (let i = 0; i < older.length; i++) {\n if (compacted[i] !== older[i]) {\n this.storage.updateMessage(compacted[i]);\n }\n }\n }\n }\n }\n\n updateMessage(message: UIMessage): void {\n this.storage.updateMessage(message);\n }\n\n deleteMessages(messageIds: string[]): void {\n this.storage.deleteMessages(messageIds);\n }\n\n clearMessages(): void {\n this.storage.clearMessages();\n }\n\n // ── Compaction ─────────────────────────────────────────────────────\n\n async compact(): Promise<CompactResult> {\n const messages = this.storage.getMessages();\n\n if (messages.length === 0) {\n return { success: true };\n }\n\n try {\n let result = messages;\n\n if (this.compactionConfig?.fn) {\n result = await this.compactionConfig.fn(result);\n }\n\n await this.storage.replaceMessages(result);\n\n return { success: true };\n } catch (err) {\n return {\n success: false,\n error: err instanceof Error ? err.message : String(err)\n };\n }\n }\n\n /**\n * Pre-check for auto-compaction using token estimate heuristic.\n */\n private shouldAutoCompact(): boolean {\n if (!this.compactionConfig?.tokenThreshold) return false;\n\n const messages = this.storage.getMessages();\n const approxTokens = estimateMessageTokens(messages);\n return approxTokens > this.compactionConfig.tokenThreshold;\n }\n}\n","/**\n * Agent Session Provider\n *\n * Pure storage provider that uses the Agent's DO SQLite storage.\n * Compaction is orchestrated by the Session wrapper, not here.\n */\n\nimport type { UIMessage } from \"ai\";\nimport type { SessionProvider } from \"../provider\";\nimport type { MessageQueryOptions } from \"../types\";\n\n/**\n * Interface for objects that provide a sql tagged template method.\n * This matches the Agent class's sql method signature.\n */\nexport interface SqlProvider {\n sql<T = Record<string, string | number | boolean | null>>(\n strings: TemplateStringsArray,\n ...values: (string | number | boolean | null)[]\n ): T[];\n}\n\n/**\n * Session provider that wraps an Agent's SQLite storage.\n * Provides pure CRUD — compaction is handled by the Session wrapper.\n *\n * @example\n * ```typescript\n * import { Session, AgentSessionProvider } from \"agents/experimental/memory/session\";\n *\n * // In your Agent class:\n * session = new Session(new AgentSessionProvider(this));\n *\n * // With compaction options:\n * session = new Session(new AgentSessionProvider(this), {\n * microCompaction: { truncateToolOutputs: 2000, keepRecent: 10 },\n * compaction: { tokenThreshold: 20000, fn: summarize }\n * });\n * ```\n */\nexport class AgentSessionProvider implements SessionProvider {\n private agent: SqlProvider;\n private initialized = false;\n\n constructor(agent: SqlProvider) {\n this.agent = agent;\n }\n\n /**\n * Ensure the messages table exists\n */\n private ensureTable(): void {\n if (this.initialized) return;\n\n this.agent.sql`\n CREATE TABLE IF NOT EXISTS cf_agents_session_messages (\n id TEXT PRIMARY KEY,\n message TEXT NOT NULL,\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP\n )\n `;\n this.initialized = true;\n }\n\n /**\n * Get all messages in AI SDK format\n */\n getMessages(options?: MessageQueryOptions): UIMessage[] {\n this.ensureTable();\n\n if (\n options?.limit !== undefined &&\n (!Number.isInteger(options.limit) || options.limit < 0)\n ) {\n throw new Error(\"limit must be a non-negative integer\");\n }\n if (\n options?.offset !== undefined &&\n (!Number.isInteger(options.offset) || options.offset < 0)\n ) {\n throw new Error(\"offset must be a non-negative integer\");\n }\n\n type Row = { id: string; message: string; created_at: string };\n const role = options?.role ?? null;\n const before = options?.before?.toISOString() ?? null;\n const after = options?.after?.toISOString() ?? null;\n const limit = options?.limit ?? -1;\n const offset = options?.offset ?? 0;\n\n const rows = this.agent.sql<Row>`\n SELECT id, message, created_at FROM cf_agents_session_messages\n WHERE (${role} IS NULL OR json_extract(message, '$.role') = ${role})\n AND (${before} IS NULL OR created_at < ${before})\n AND (${after} IS NULL OR created_at > ${after})\n ORDER BY created_at ASC, rowid ASC\n LIMIT ${limit} OFFSET ${offset}\n `;\n\n return this.parseRows(rows);\n }\n\n /**\n * Append one or more messages to storage.\n */\n async appendMessages(messages: UIMessage | UIMessage[]): Promise<void> {\n this.ensureTable();\n\n const messageArray = Array.isArray(messages) ? messages : [messages];\n const now = new Date().toISOString();\n\n for (const message of messageArray) {\n const json = JSON.stringify(message);\n this.agent.sql`\n INSERT INTO cf_agents_session_messages (id, message, created_at)\n VALUES (${message.id}, ${json}, ${now})\n ON CONFLICT(id) DO UPDATE SET message = excluded.message\n `;\n }\n }\n\n /**\n * Update an existing message\n */\n updateMessage(message: UIMessage): void {\n this.ensureTable();\n\n const json = JSON.stringify(message);\n this.agent.sql`\n UPDATE cf_agents_session_messages\n SET message = ${json}\n WHERE id = ${message.id}\n `;\n }\n\n /**\n * Delete messages by their IDs\n */\n deleteMessages(messageIds: string[]): void {\n this.ensureTable();\n\n for (const id of messageIds) {\n this.agent.sql`DELETE FROM cf_agents_session_messages WHERE id = ${id}`;\n }\n }\n\n /**\n * Clear all messages from the session\n */\n clearMessages(): void {\n this.ensureTable();\n this.agent.sql`DELETE FROM cf_agents_session_messages`;\n }\n\n /**\n * Get a single message by ID\n */\n getMessage(id: string): UIMessage | null {\n this.ensureTable();\n\n const rows = this.agent.sql<{ message: string }>`\n SELECT message FROM cf_agents_session_messages WHERE id = ${id}\n `;\n\n if (rows.length === 0) return null;\n\n try {\n const parsed = JSON.parse(rows[0].message);\n return this.isValidMessage(parsed) ? parsed : null;\n } catch {\n return null;\n }\n }\n\n /**\n * Get the last N messages (most recent)\n */\n getLastMessages(n: number): UIMessage[] {\n this.ensureTable();\n\n const rows = this.agent.sql<{ message: string }>`\n SELECT message FROM cf_agents_session_messages\n ORDER BY created_at DESC, rowid DESC\n LIMIT ${n}\n `;\n\n return this.parseRows([...rows].reverse());\n }\n\n /**\n * Fetch messages outside the recent window (for microCompaction).\n * Returns all messages except the most recent `keepRecent`.\n */\n getOlderMessages(keepRecent: number): UIMessage[] {\n this.ensureTable();\n\n type Row = { id: string; message: string };\n const rows = this.agent.sql<Row>`\n SELECT id, message FROM cf_agents_session_messages\n WHERE rowid NOT IN (\n SELECT rowid FROM cf_agents_session_messages\n ORDER BY created_at DESC, rowid DESC\n LIMIT ${keepRecent}\n )\n `;\n\n return this.parseRows(rows);\n }\n\n /**\n * Bulk replace all messages.\n * Preserves original created_at timestamps for surviving messages.\n */\n async replaceMessages(messages: UIMessage[]): Promise<void> {\n this.ensureTable();\n\n // Build timestamp map from existing messages before clearing\n type Row = { id: string; created_at: string };\n const existingRows = this.agent.sql<Row>`\n SELECT id, created_at FROM cf_agents_session_messages\n `;\n const timestampMap = new Map<string, string>();\n for (const row of existingRows) {\n timestampMap.set(row.id, row.created_at);\n }\n\n // Durable Objects auto-coalesces all synchronous SQL writes within\n // a single I/O gate into one atomic batch — no explicit transaction needed.\n this.agent.sql`DELETE FROM cf_agents_session_messages`;\n\n const now = new Date().toISOString();\n for (const message of messages) {\n const json = JSON.stringify(message);\n const created_at = timestampMap.get(message.id) ?? now;\n this.agent.sql`\n INSERT INTO cf_agents_session_messages (id, message, created_at)\n VALUES (${message.id}, ${json}, ${created_at})\n ON CONFLICT(id) DO UPDATE SET message = excluded.message\n `;\n }\n }\n\n /**\n * Validate message structure\n */\n private isValidMessage(msg: unknown): msg is UIMessage {\n if (typeof msg !== \"object\" || msg === null) return false;\n const m = msg as Record<string, unknown>;\n\n if (typeof m.id !== \"string\" || m.id.length === 0) return false;\n if (m.role !== \"user\" && m.role !== \"assistant\" && m.role !== \"system\") {\n return false;\n }\n if (!Array.isArray(m.parts)) return false;\n\n return true;\n }\n\n /**\n * Parse message rows from SQL results into UIMessages.\n */\n private parseRows(rows: { id?: string; message: string }[]): UIMessage[] {\n const messages: UIMessage[] = [];\n for (const row of rows) {\n try {\n const parsed = JSON.parse(row.message);\n if (this.isValidMessage(parsed)) {\n messages.push(parsed);\n }\n } catch {\n if (row.id) {\n console.warn(\n `[AgentSessionProvider] Skipping malformed message ${row.id}`\n );\n }\n }\n }\n return messages;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../../src/experimental/memory/utils/compaction.ts","../../../../src/experimental/memory/utils/tokens.ts","../../../../src/experimental/memory/session/session.ts","../../../../src/experimental/memory/session/providers/agent.ts"],"sourcesContent":["/**\n * MicroCompaction Utilities\n *\n * Internal pure functions for lightweight compaction (no LLM).\n * Not exported to users — used by the Session wrapper.\n */\n\nimport type { UIMessage } from \"ai\";\nimport type { MicroCompactionRules } from \"../session/types\";\n\n/** Default thresholds for microCompaction rules (in chars) */\nexport const DEFAULTS = {\n truncateToolOutputs: 30000,\n truncateText: 10000,\n keepRecent: 4\n};\n\n/** Resolved microCompaction rules with actual numeric thresholds */\nexport interface ResolvedMicroCompactionRules {\n truncateToolOutputs: number | false;\n truncateText: number | false;\n keepRecent: number;\n}\n\n/**\n * Parse microCompaction config into resolved rules.\n * Returns null if disabled.\n */\nexport function parseMicroCompactionRules(\n config: boolean | MicroCompactionRules\n): ResolvedMicroCompactionRules | null {\n if (config === false) return null;\n\n if (config === true) {\n return {\n truncateToolOutputs: DEFAULTS.truncateToolOutputs,\n truncateText: DEFAULTS.truncateText,\n keepRecent: DEFAULTS.keepRecent\n };\n }\n\n // Custom rules object — validate numeric values\n const keepRecent = config.keepRecent ?? DEFAULTS.keepRecent;\n if (!Number.isInteger(keepRecent) || keepRecent < 0) {\n throw new Error(\"keepRecent must be a non-negative integer\");\n }\n\n const truncateToolOutputs =\n config.truncateToolOutputs === false\n ? false\n : config.truncateToolOutputs === true ||\n config.truncateToolOutputs === undefined\n ? DEFAULTS.truncateToolOutputs\n : config.truncateToolOutputs;\n if (typeof truncateToolOutputs === \"number\" && truncateToolOutputs <= 0) {\n throw new Error(\"truncateToolOutputs must be a positive number\");\n }\n\n const truncateText =\n config.truncateText === false\n ? false\n : config.truncateText === true || config.truncateText === undefined\n ? DEFAULTS.truncateText\n : config.truncateText;\n if (typeof truncateText === \"number\" && truncateText <= 0) {\n throw new Error(\"truncateText must be a positive number\");\n }\n\n return { truncateToolOutputs, truncateText, keepRecent };\n}\n\n/**\n * Truncate oversized parts in a single message.\n * Returns the same reference if nothing changed (allows callers to skip no-op updates).\n */\nfunction truncateMessageParts(\n msg: UIMessage,\n rules: ResolvedMicroCompactionRules\n): UIMessage {\n let changed = false;\n\n const compactedParts = msg.parts.map((part) => {\n // Truncate tool outputs\n if (\n rules.truncateToolOutputs !== false &&\n (part.type.startsWith(\"tool-\") || part.type === \"dynamic-tool\") &&\n \"output\" in part\n ) {\n const toolPart = part as { output?: unknown };\n if (toolPart.output !== undefined) {\n const outputJson = JSON.stringify(toolPart.output);\n if (outputJson.length > rules.truncateToolOutputs) {\n changed = true;\n return {\n ...part,\n output: `[Truncated ${outputJson.length} bytes] ${outputJson.slice(0, 500)}...`\n };\n }\n }\n }\n\n // Truncate long text parts\n if (\n rules.truncateText !== false &&\n part.type === \"text\" &&\n \"text\" in part\n ) {\n const textPart = part as { type: \"text\"; text: string };\n if (textPart.text.length > rules.truncateText) {\n changed = true;\n return {\n ...part,\n text: `${textPart.text.slice(0, rules.truncateText)}... [truncated ${textPart.text.length} chars]`\n };\n }\n }\n\n return part;\n });\n\n return changed ? ({ ...msg, parts: compactedParts } as UIMessage) : msg;\n}\n\n/**\n * Apply microCompaction to an array of messages.\n * Returns same reference for unchanged messages (enables skip-update optimization).\n *\n * No keepRecent logic — the caller decides which messages to pass.\n */\nexport function microCompact(\n messages: UIMessage[],\n rules: ResolvedMicroCompactionRules\n): UIMessage[] {\n return messages.map((msg) => truncateMessageParts(msg, rules));\n}\n","/**\n * Token Estimation Utilities\n *\n * IMPORTANT: These are heuristic estimates, not actual tokenizer counts.\n *\n * We intentionally avoid real tokenizers (e.g. tiktoken, sentencepiece) because:\n * - A single tiktoken instance costs ~80-120MB of heap\n * - Cloudflare Workers have tight memory limits (128MB)\n * - For compaction thresholds, a conservative estimate is sufficient\n *\n * The hybrid approach (max of character-based and word-based estimates) handles\n * both dense token content (JSON, code) and natural language reasonably well.\n *\n * Calibration notes:\n * - Character-based: ~4 chars per token (conservative, from OpenAI guidance)\n * - Word-based: ~1.3 tokens per word (empirical, from Mastra's memory system)\n * - Per-message overhead: ~4 tokens for role/framing (empirical)\n *\n * These ratios are tuned for English. CJK, emoji-heavy, or highly technical\n * content may have different ratios. The conservative estimates help ensure\n * compaction triggers before context windows are actually exceeded.\n */\n\nimport type { UIMessage } from \"ai\";\n\n/** Approximate characters per token for English text */\nexport const CHARS_PER_TOKEN = 4;\n\n/** Approximate token multiplier per whitespace-separated word */\nexport const WORDS_TOKEN_MULTIPLIER = 1.3;\n\n/** Approximate overhead tokens per message (role, framing) */\nexport const TOKENS_PER_MESSAGE = 4;\n\n/**\n * Estimate token count for a string using a hybrid heuristic.\n *\n * Takes the max of two estimates:\n * - Character-based: `length / 4` — better for dense content (JSON, code, URLs)\n * - Word-based: `words * 1.3` — better for natural language prose\n *\n * This is a heuristic. Do not use where exact counts are required.\n */\nexport function estimateStringTokens(text: string): number {\n if (!text) return 0;\n const charEstimate = text.length / CHARS_PER_TOKEN;\n const wordEstimate =\n text.split(/\\s+/).filter(Boolean).length * WORDS_TOKEN_MULTIPLIER;\n return Math.ceil(Math.max(charEstimate, wordEstimate));\n}\n\n/**\n * Estimate total token count for an array of UIMessages.\n *\n * Walks each message's parts (text, tool invocations, tool results)\n * and applies per-message overhead.\n *\n * This is a heuristic. Do not use where exact counts are required.\n */\nexport function estimateMessageTokens(messages: UIMessage[]): number {\n let tokens = 0;\n for (const msg of messages) {\n tokens += TOKENS_PER_MESSAGE;\n for (const part of msg.parts) {\n if (part.type === \"text\") {\n tokens += estimateStringTokens(\n (part as { type: \"text\"; text: string }).text\n );\n } else if (\n part.type.startsWith(\"tool-\") ||\n part.type === \"dynamic-tool\"\n ) {\n const toolPart = part as { input?: unknown; output?: unknown };\n if (toolPart.input) {\n tokens += estimateStringTokens(JSON.stringify(toolPart.input));\n }\n if (toolPart.output) {\n tokens += estimateStringTokens(JSON.stringify(toolPart.output));\n }\n }\n }\n }\n return tokens;\n}\n","/**\n * Session — top-level API for conversation history with compaction.\n *\n * Wraps any SessionProvider (pure storage) and orchestrates compaction:\n * - microCompaction on every append() — cheap, no LLM\n * - full compaction when token threshold exceeded — user-supplied fn\n */\n\nimport type { UIMessage } from \"ai\";\nimport type { SessionProvider } from \"./provider\";\nimport type {\n MessageQueryOptions,\n SessionProviderOptions,\n CompactResult\n} from \"./types\";\nimport {\n parseMicroCompactionRules,\n microCompact,\n type ResolvedMicroCompactionRules\n} from \"../utils/compaction\";\nimport { estimateMessageTokens } from \"../utils/tokens\";\n\nexport class Session {\n private storage: SessionProvider;\n private microCompactionRules: ResolvedMicroCompactionRules | null;\n private compactionConfig: SessionProviderOptions[\"compaction\"] | null;\n\n constructor(storage: SessionProvider, options?: SessionProviderOptions) {\n this.storage = storage;\n\n const mc = options?.microCompaction ?? true;\n this.microCompactionRules = parseMicroCompactionRules(mc);\n this.compactionConfig = options?.compaction ?? null;\n }\n\n // ── Read (delegated to storage) ────────────────────────────────────\n\n getMessages(options?: MessageQueryOptions): UIMessage[] {\n return this.storage.getMessages(options);\n }\n\n getMessage(id: string): UIMessage | null {\n return this.storage.getMessage(id);\n }\n\n getLastMessages(n: number): UIMessage[] {\n return this.storage.getLastMessages(n);\n }\n\n // ── Write (delegated + compaction) ─────────────────────────────────\n\n async append(messages: UIMessage | UIMessage[]): Promise<void> {\n // 1. Storage inserts\n await this.storage.appendMessages(messages);\n\n // 2. Full compaction if token threshold exceeded — runs instead of microCompaction\n if (this.shouldAutoCompact()) {\n const result = await this.compact();\n if (result.success) return;\n // Fall through to microCompaction if full compaction failed\n }\n\n // 3. MicroCompaction on older messages (only if no full compaction)\n if (this.microCompactionRules) {\n const rules = this.microCompactionRules;\n const older = this.storage.getOlderMessages(rules.keepRecent);\n\n if (older.length > 0) {\n const compacted = microCompact(older, rules);\n for (let i = 0; i < older.length; i++) {\n if (compacted[i] !== older[i]) {\n this.storage.updateMessage(compacted[i]);\n }\n }\n }\n }\n }\n\n updateMessage(message: UIMessage): void {\n this.storage.updateMessage(message);\n }\n\n deleteMessages(messageIds: string[]): void {\n this.storage.deleteMessages(messageIds);\n }\n\n clearMessages(): void {\n this.storage.clearMessages();\n }\n\n // ── Compaction ─────────────────────────────────────────────────────\n\n async compact(): Promise<CompactResult> {\n const messages = this.storage.getMessages();\n\n if (messages.length === 0) {\n return { success: true };\n }\n\n try {\n let result = messages;\n\n if (this.compactionConfig?.fn) {\n result = await this.compactionConfig.fn(result);\n }\n\n await this.storage.replaceMessages(result);\n\n return { success: true };\n } catch (err) {\n return {\n success: false,\n error: err instanceof Error ? err.message : String(err)\n };\n }\n }\n\n /**\n * Pre-check for auto-compaction using token estimate heuristic.\n */\n private shouldAutoCompact(): boolean {\n if (!this.compactionConfig?.tokenThreshold) return false;\n\n const messages = this.storage.getMessages();\n const approxTokens = estimateMessageTokens(messages);\n return approxTokens > this.compactionConfig.tokenThreshold;\n }\n}\n","/**\n * Agent Session Provider\n *\n * Pure storage provider that uses the Agent's DO SQLite storage.\n * Compaction is orchestrated by the Session wrapper, not here.\n */\n\nimport type { UIMessage } from \"ai\";\nimport type { SessionProvider } from \"../provider\";\nimport type { MessageQueryOptions } from \"../types\";\n\n/**\n * Interface for objects that provide a sql tagged template method.\n * This matches the Agent class's sql method signature.\n */\nexport interface SqlProvider {\n sql<T = Record<string, string | number | boolean | null>>(\n strings: TemplateStringsArray,\n ...values: (string | number | boolean | null)[]\n ): T[];\n}\n\n/**\n * Session provider that wraps an Agent's SQLite storage.\n * Provides pure CRUD — compaction is handled by the Session wrapper.\n *\n * @example\n * ```typescript\n * import { Session, AgentSessionProvider } from \"agents/experimental/memory/session\";\n *\n * // In your Agent class:\n * session = new Session(new AgentSessionProvider(this));\n *\n * // With compaction options:\n * session = new Session(new AgentSessionProvider(this), {\n * microCompaction: { truncateToolOutputs: 2000, keepRecent: 10 },\n * compaction: { tokenThreshold: 20000, fn: summarize }\n * });\n * ```\n */\nexport class AgentSessionProvider implements SessionProvider {\n private agent: SqlProvider;\n private initialized = false;\n\n constructor(agent: SqlProvider) {\n this.agent = agent;\n }\n\n /**\n * Ensure the messages table exists\n */\n private ensureTable(): void {\n if (this.initialized) return;\n\n this.agent.sql`\n CREATE TABLE IF NOT EXISTS cf_agents_session_messages (\n id TEXT PRIMARY KEY,\n message TEXT NOT NULL,\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP\n )\n `;\n this.initialized = true;\n }\n\n /**\n * Get all messages in AI SDK format\n */\n getMessages(options?: MessageQueryOptions): UIMessage[] {\n this.ensureTable();\n\n if (\n options?.limit !== undefined &&\n (!Number.isInteger(options.limit) || options.limit < 0)\n ) {\n throw new Error(\"limit must be a non-negative integer\");\n }\n if (\n options?.offset !== undefined &&\n (!Number.isInteger(options.offset) || options.offset < 0)\n ) {\n throw new Error(\"offset must be a non-negative integer\");\n }\n\n type Row = { id: string; message: string; created_at: string };\n const role = options?.role ?? null;\n const before = options?.before?.toISOString() ?? null;\n const after = options?.after?.toISOString() ?? null;\n const limit = options?.limit ?? -1;\n const offset = options?.offset ?? 0;\n\n const rows = this.agent.sql<Row>`\n SELECT id, message, created_at FROM cf_agents_session_messages\n WHERE (${role} IS NULL OR json_extract(message, '$.role') = ${role})\n AND (${before} IS NULL OR created_at < ${before})\n AND (${after} IS NULL OR created_at > ${after})\n ORDER BY created_at ASC, rowid ASC\n LIMIT ${limit} OFFSET ${offset}\n `;\n\n return this.parseRows(rows);\n }\n\n /**\n * Append one or more messages to storage.\n */\n async appendMessages(messages: UIMessage | UIMessage[]): Promise<void> {\n this.ensureTable();\n\n const messageArray = Array.isArray(messages) ? messages : [messages];\n const now = new Date().toISOString();\n\n for (const message of messageArray) {\n const json = JSON.stringify(message);\n this.agent.sql`\n INSERT INTO cf_agents_session_messages (id, message, created_at)\n VALUES (${message.id}, ${json}, ${now})\n ON CONFLICT(id) DO UPDATE SET message = excluded.message\n `;\n }\n }\n\n /**\n * Update an existing message\n */\n updateMessage(message: UIMessage): void {\n this.ensureTable();\n\n const json = JSON.stringify(message);\n this.agent.sql`\n UPDATE cf_agents_session_messages\n SET message = ${json}\n WHERE id = ${message.id}\n `;\n }\n\n /**\n * Delete messages by their IDs\n */\n deleteMessages(messageIds: string[]): void {\n this.ensureTable();\n\n for (const id of messageIds) {\n this.agent.sql`DELETE FROM cf_agents_session_messages WHERE id = ${id}`;\n }\n }\n\n /**\n * Clear all messages from the session\n */\n clearMessages(): void {\n this.ensureTable();\n this.agent.sql`DELETE FROM cf_agents_session_messages`;\n }\n\n /**\n * Get a single message by ID\n */\n getMessage(id: string): UIMessage | null {\n this.ensureTable();\n\n const rows = this.agent.sql<{ message: string }>`\n SELECT message FROM cf_agents_session_messages WHERE id = ${id}\n `;\n\n if (rows.length === 0) return null;\n\n try {\n const parsed = JSON.parse(rows[0].message);\n return this.isValidMessage(parsed) ? parsed : null;\n } catch {\n return null;\n }\n }\n\n /**\n * Get the last N messages (most recent)\n */\n getLastMessages(n: number): UIMessage[] {\n this.ensureTable();\n\n const rows = this.agent.sql<{ message: string }>`\n SELECT message FROM cf_agents_session_messages\n ORDER BY created_at DESC, rowid DESC\n LIMIT ${n}\n `;\n\n return this.parseRows([...rows].reverse());\n }\n\n /**\n * Fetch messages outside the recent window (for microCompaction).\n * Returns all messages except the most recent `keepRecent`.\n */\n getOlderMessages(keepRecent: number): UIMessage[] {\n this.ensureTable();\n\n type Row = { id: string; message: string };\n const rows = this.agent.sql<Row>`\n SELECT id, message FROM cf_agents_session_messages\n WHERE rowid NOT IN (\n SELECT rowid FROM cf_agents_session_messages\n ORDER BY created_at DESC, rowid DESC\n LIMIT ${keepRecent}\n )\n `;\n\n return this.parseRows(rows);\n }\n\n /**\n * Bulk replace all messages.\n * Preserves original created_at timestamps for surviving messages.\n */\n async replaceMessages(messages: UIMessage[]): Promise<void> {\n this.ensureTable();\n\n // Build timestamp map from existing messages before clearing\n type Row = { id: string; created_at: string };\n const existingRows = this.agent.sql<Row>`\n SELECT id, created_at FROM cf_agents_session_messages\n `;\n const timestampMap = new Map<string, string>();\n for (const row of existingRows) {\n timestampMap.set(row.id, row.created_at);\n }\n\n // Durable Objects auto-coalesces all synchronous SQL writes within\n // a single I/O gate into one atomic batch — no explicit transaction needed.\n this.agent.sql`DELETE FROM cf_agents_session_messages`;\n\n const now = new Date().toISOString();\n for (const message of messages) {\n const json = JSON.stringify(message);\n const created_at = timestampMap.get(message.id) ?? now;\n this.agent.sql`\n INSERT INTO cf_agents_session_messages (id, message, created_at)\n VALUES (${message.id}, ${json}, ${created_at})\n ON CONFLICT(id) DO UPDATE SET message = excluded.message\n `;\n }\n }\n\n /**\n * Validate message structure\n */\n private isValidMessage(msg: unknown): msg is UIMessage {\n if (typeof msg !== \"object\" || msg === null) return false;\n const m = msg as Record<string, unknown>;\n\n if (typeof m.id !== \"string\" || m.id.length === 0) return false;\n if (m.role !== \"user\" && m.role !== \"assistant\" && m.role !== \"system\") {\n return false;\n }\n if (!Array.isArray(m.parts)) return false;\n\n return true;\n }\n\n /**\n * Parse message rows from SQL results into UIMessages.\n */\n private parseRows(rows: { id?: string; message: string }[]): UIMessage[] {\n const messages: UIMessage[] = [];\n for (const row of rows) {\n try {\n const parsed = JSON.parse(row.message);\n if (this.isValidMessage(parsed)) {\n messages.push(parsed);\n }\n } catch {\n if (row.id) {\n console.warn(\n `[AgentSessionProvider] Skipping malformed message ${row.id}`\n );\n }\n }\n }\n return messages;\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { Command, CommandContext, CustomCommand, ExecResult, NetworkConfig, defineCommand } from "just-bash";
|
|
2
|
+
|
|
3
|
+
//#region src/experimental/workspace.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Workspace — durable file storage for any Agent.
|
|
6
|
+
*
|
|
7
|
+
* Hybrid storage:
|
|
8
|
+
* - Files < threshold: stored inline in SQLite (fast, no external calls)
|
|
9
|
+
* - Files ≥ threshold: metadata in SQLite, content in R2 (avoids row limit)
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* ```ts
|
|
13
|
+
* import { Agent } from "agents";
|
|
14
|
+
* import { Workspace } from "agents/experimental/workspace";
|
|
15
|
+
*
|
|
16
|
+
* class MyAgent extends Agent<Env> {
|
|
17
|
+
* workspace = new Workspace(this, {
|
|
18
|
+
* r2: this.env.WORKSPACE_FILES,
|
|
19
|
+
* // r2Prefix defaults to this.name (the Durable Object ID)
|
|
20
|
+
* });
|
|
21
|
+
*
|
|
22
|
+
* async onMessage(conn, msg) {
|
|
23
|
+
* await this.workspace.writeFile("/hello.txt", "world");
|
|
24
|
+
* const content = await this.workspace.readFile("/hello.txt");
|
|
25
|
+
* }
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* R2 is optional — if the configured binding isn't present, all files are
|
|
30
|
+
* stored inline regardless of size (with a warning for large files).
|
|
31
|
+
*
|
|
32
|
+
* @module agents/workspace
|
|
33
|
+
*/
|
|
34
|
+
interface WorkspaceHost {
|
|
35
|
+
sql: <T = Record<string, string | number | boolean | null>>(strings: TemplateStringsArray, ...values: (string | number | boolean | null)[]) => T[];
|
|
36
|
+
/** Durable Object ID / name — used as the default R2 key prefix when r2Prefix is not set. Read lazily (not at construction time). */
|
|
37
|
+
name?: string;
|
|
38
|
+
}
|
|
39
|
+
interface WorkspaceOptions {
|
|
40
|
+
/** Namespace to isolate this workspace's tables (default: "default"). */
|
|
41
|
+
namespace?: string;
|
|
42
|
+
/** R2 bucket for large-file storage (optional). */
|
|
43
|
+
r2?: R2Bucket;
|
|
44
|
+
/** Prefix for R2 object keys. Defaults to `host.name` (the Durable Object ID) when omitted. */
|
|
45
|
+
r2Prefix?: string;
|
|
46
|
+
/** Byte threshold for spilling files to R2 (default: 1_500_000 = 1.5 MB). */
|
|
47
|
+
inlineThreshold?: number;
|
|
48
|
+
/** Bash execution limits (requires just-bash). */
|
|
49
|
+
bashLimits?: {
|
|
50
|
+
maxCommandCount?: number;
|
|
51
|
+
maxLoopIterations?: number;
|
|
52
|
+
maxCallDepth?: number;
|
|
53
|
+
};
|
|
54
|
+
/** Custom commands available in every bash() call. */
|
|
55
|
+
commands?: CustomCommand[];
|
|
56
|
+
/** Environment variables available in every bash() call. */
|
|
57
|
+
env?: Record<string, string>;
|
|
58
|
+
/** Network configuration for curl (URL allow-list, methods, timeouts). */
|
|
59
|
+
network?: NetworkConfig;
|
|
60
|
+
/** Called when files/directories change. Wire to agent.broadcast() for real-time sync. */
|
|
61
|
+
onChange?: (event: WorkspaceChangeEvent) => void;
|
|
62
|
+
}
|
|
63
|
+
type EntryType = "file" | "directory" | "symlink";
|
|
64
|
+
type FileInfo = {
|
|
65
|
+
path: string;
|
|
66
|
+
name: string;
|
|
67
|
+
type: EntryType;
|
|
68
|
+
mimeType: string;
|
|
69
|
+
size: number;
|
|
70
|
+
createdAt: number;
|
|
71
|
+
updatedAt: number;
|
|
72
|
+
target?: string;
|
|
73
|
+
};
|
|
74
|
+
type FileStat = FileInfo;
|
|
75
|
+
type BashResult = {
|
|
76
|
+
stdout: string;
|
|
77
|
+
stderr: string;
|
|
78
|
+
exitCode: number;
|
|
79
|
+
};
|
|
80
|
+
interface BashOptions {
|
|
81
|
+
cwd?: string;
|
|
82
|
+
commands?: CustomCommand[];
|
|
83
|
+
env?: Record<string, string>;
|
|
84
|
+
network?: NetworkConfig;
|
|
85
|
+
}
|
|
86
|
+
/** @deprecated Use {@link BashOptions} instead. */
|
|
87
|
+
type BashSessionOptions = BashOptions;
|
|
88
|
+
type WorkspaceChangeType = "create" | "update" | "delete";
|
|
89
|
+
type WorkspaceChangeEvent = {
|
|
90
|
+
type: WorkspaceChangeType;
|
|
91
|
+
path: string;
|
|
92
|
+
entryType: EntryType;
|
|
93
|
+
};
|
|
94
|
+
declare class Workspace {
|
|
95
|
+
private readonly host;
|
|
96
|
+
private readonly namespace;
|
|
97
|
+
private readonly tableName;
|
|
98
|
+
private readonly indexName;
|
|
99
|
+
private readonly r2;
|
|
100
|
+
private readonly r2Prefix;
|
|
101
|
+
private readonly threshold;
|
|
102
|
+
private readonly bashLimits;
|
|
103
|
+
private readonly commands;
|
|
104
|
+
private readonly env;
|
|
105
|
+
private readonly network;
|
|
106
|
+
private readonly onChange;
|
|
107
|
+
private initialized;
|
|
108
|
+
private readonly sqlCache;
|
|
109
|
+
/**
|
|
110
|
+
* @param host - Any object with a `sql` tagged-template method (typically your Agent: `this`).
|
|
111
|
+
* @param options - Optional configuration (namespace, R2 bucket, thresholds, etc.).
|
|
112
|
+
*
|
|
113
|
+
* ```ts
|
|
114
|
+
* class MyAgent extends Agent<Env> {
|
|
115
|
+
* workspace = new Workspace(this, {
|
|
116
|
+
* r2: this.env.WORKSPACE_FILES,
|
|
117
|
+
* // r2Prefix defaults to this.name (the Durable Object ID)
|
|
118
|
+
* });
|
|
119
|
+
* }
|
|
120
|
+
* ```
|
|
121
|
+
*/
|
|
122
|
+
constructor(host: WorkspaceHost, options?: WorkspaceOptions);
|
|
123
|
+
private emit;
|
|
124
|
+
private _observe;
|
|
125
|
+
private sql;
|
|
126
|
+
private ensureInit;
|
|
127
|
+
private getR2;
|
|
128
|
+
private resolveR2Prefix;
|
|
129
|
+
private r2Key;
|
|
130
|
+
private resolveSymlink;
|
|
131
|
+
symlink(target: string, linkPath: string): void;
|
|
132
|
+
readlink(path: string): string;
|
|
133
|
+
lstat(path: string): FileStat | null;
|
|
134
|
+
stat(path: string): FileStat | null;
|
|
135
|
+
readFile(path: string): Promise<string | null>;
|
|
136
|
+
readFileBytes(path: string): Promise<Uint8Array | null>;
|
|
137
|
+
writeFileBytes(path: string, data: Uint8Array | ArrayBuffer, mimeType?: string): Promise<void>;
|
|
138
|
+
writeFile(path: string, content: string, mimeType?: string): Promise<void>;
|
|
139
|
+
readFileStream(path: string): Promise<ReadableStream<Uint8Array> | null>;
|
|
140
|
+
writeFileStream(path: string, stream: ReadableStream<Uint8Array>, mimeType?: string): Promise<void>;
|
|
141
|
+
appendFile(path: string, content: string, mimeType?: string): Promise<void>;
|
|
142
|
+
deleteFile(path: string): Promise<boolean>;
|
|
143
|
+
fileExists(path: string): boolean;
|
|
144
|
+
exists(path: string): boolean;
|
|
145
|
+
readDir(dir?: string, opts?: {
|
|
146
|
+
limit?: number;
|
|
147
|
+
offset?: number;
|
|
148
|
+
}): FileInfo[];
|
|
149
|
+
glob(pattern: string): FileInfo[];
|
|
150
|
+
mkdir(path: string, opts?: {
|
|
151
|
+
recursive?: boolean;
|
|
152
|
+
}, _depth?: number): void;
|
|
153
|
+
rm(path: string, opts?: {
|
|
154
|
+
recursive?: boolean;
|
|
155
|
+
force?: boolean;
|
|
156
|
+
}): Promise<void>;
|
|
157
|
+
cp(src: string, dest: string, opts?: {
|
|
158
|
+
recursive?: boolean;
|
|
159
|
+
}): Promise<void>;
|
|
160
|
+
mv(src: string, dest: string, opts?: {
|
|
161
|
+
recursive?: boolean;
|
|
162
|
+
}): Promise<void>;
|
|
163
|
+
diff(pathA: string, pathB: string): Promise<string>;
|
|
164
|
+
diffContent(path: string, newContent: string): Promise<string>;
|
|
165
|
+
private _resolveBashConfig;
|
|
166
|
+
bash(command: string, options?: BashOptions): Promise<BashResult>;
|
|
167
|
+
createBashSession(options?: BashOptions): BashSession;
|
|
168
|
+
getWorkspaceInfo(): {
|
|
169
|
+
fileCount: number;
|
|
170
|
+
directoryCount: number;
|
|
171
|
+
totalBytes: number;
|
|
172
|
+
r2FileCount: number;
|
|
173
|
+
};
|
|
174
|
+
/** @internal */
|
|
175
|
+
_getAllPaths(): string[];
|
|
176
|
+
/** @internal */
|
|
177
|
+
_updateModifiedAt(path: string, mtime: Date): void;
|
|
178
|
+
private ensureParentDir;
|
|
179
|
+
private deleteDescendants;
|
|
180
|
+
}
|
|
181
|
+
interface BashSessionInit {
|
|
182
|
+
ws: Workspace;
|
|
183
|
+
fs: WorkspaceFileSystem;
|
|
184
|
+
bashLimits: {
|
|
185
|
+
maxCommandCount: number;
|
|
186
|
+
maxLoopIterations: number;
|
|
187
|
+
maxCallDepth: number;
|
|
188
|
+
};
|
|
189
|
+
commands: CustomCommand[] | undefined;
|
|
190
|
+
env: Record<string, string>;
|
|
191
|
+
network: NetworkConfig | undefined;
|
|
192
|
+
cwd: string;
|
|
193
|
+
observe: (type: string, payload: Record<string, unknown>) => void;
|
|
194
|
+
}
|
|
195
|
+
declare class BashSession {
|
|
196
|
+
private readonly _ws;
|
|
197
|
+
private readonly _fs;
|
|
198
|
+
private readonly _bashLimits;
|
|
199
|
+
private readonly _customCommands;
|
|
200
|
+
private readonly _networkConfig;
|
|
201
|
+
private readonly _observe;
|
|
202
|
+
private _currentCwd;
|
|
203
|
+
private _currentEnv;
|
|
204
|
+
private _closed;
|
|
205
|
+
/** @internal — use workspace.createBashSession() instead */
|
|
206
|
+
constructor(init: BashSessionInit);
|
|
207
|
+
exec(command: string): Promise<BashResult>;
|
|
208
|
+
get cwd(): string;
|
|
209
|
+
get env(): Record<string, string>;
|
|
210
|
+
get isClosed(): boolean;
|
|
211
|
+
close(): void;
|
|
212
|
+
[Symbol.dispose](): void;
|
|
213
|
+
}
|
|
214
|
+
interface FsStat {
|
|
215
|
+
isFile: boolean;
|
|
216
|
+
isDirectory: boolean;
|
|
217
|
+
isSymbolicLink: boolean;
|
|
218
|
+
mode: number;
|
|
219
|
+
size: number;
|
|
220
|
+
mtime: Date;
|
|
221
|
+
}
|
|
222
|
+
interface DirentEntry {
|
|
223
|
+
name: string;
|
|
224
|
+
isFile: boolean;
|
|
225
|
+
isDirectory: boolean;
|
|
226
|
+
isSymbolicLink: boolean;
|
|
227
|
+
}
|
|
228
|
+
type FileContent = string | Uint8Array;
|
|
229
|
+
type BufferEncoding = "utf-8" | "utf8" | "ascii" | "base64" | "hex" | "latin1";
|
|
230
|
+
type ReadFileOptions = {
|
|
231
|
+
encoding?: BufferEncoding | null;
|
|
232
|
+
};
|
|
233
|
+
type WriteFileOptions = {
|
|
234
|
+
encoding?: BufferEncoding;
|
|
235
|
+
};
|
|
236
|
+
type MkdirOptions = {
|
|
237
|
+
recursive?: boolean;
|
|
238
|
+
};
|
|
239
|
+
type RmOptions = {
|
|
240
|
+
recursive?: boolean;
|
|
241
|
+
force?: boolean;
|
|
242
|
+
};
|
|
243
|
+
type CpOptions = {
|
|
244
|
+
recursive?: boolean;
|
|
245
|
+
};
|
|
246
|
+
declare class WorkspaceFileSystem {
|
|
247
|
+
private ws;
|
|
248
|
+
constructor(ws: Workspace);
|
|
249
|
+
readFile(path: string, _options?: ReadFileOptions | BufferEncoding): Promise<string>;
|
|
250
|
+
readFileBuffer(path: string): Promise<Uint8Array>;
|
|
251
|
+
writeFile(path: string, content: FileContent, _options?: WriteFileOptions | BufferEncoding): Promise<void>;
|
|
252
|
+
appendFile(path: string, content: FileContent, _options?: WriteFileOptions | BufferEncoding): Promise<void>;
|
|
253
|
+
exists(path: string): Promise<boolean>;
|
|
254
|
+
stat(path: string): Promise<FsStat>;
|
|
255
|
+
lstat(path: string): Promise<FsStat>;
|
|
256
|
+
mkdir(path: string, options?: MkdirOptions): Promise<void>;
|
|
257
|
+
readdir(path: string): Promise<string[]>;
|
|
258
|
+
readdirWithFileTypes(path: string): Promise<DirentEntry[]>;
|
|
259
|
+
rm(path: string, options?: RmOptions): Promise<void>;
|
|
260
|
+
cp(src: string, dest: string, options?: CpOptions): Promise<void>;
|
|
261
|
+
mv(src: string, dest: string): Promise<void>;
|
|
262
|
+
resolvePath(base: string, path: string): string;
|
|
263
|
+
getAllPaths(): string[];
|
|
264
|
+
chmod(_path: string, _mode: number): Promise<void>;
|
|
265
|
+
symlink(target: string, linkPath: string): Promise<void>;
|
|
266
|
+
link(_existingPath: string, _newPath: string): Promise<void>;
|
|
267
|
+
readlink(path: string): Promise<string>;
|
|
268
|
+
realpath(path: string): Promise<string>;
|
|
269
|
+
utimes(_path: string, _atime: Date, mtime: Date): Promise<void>;
|
|
270
|
+
}
|
|
271
|
+
//#endregion
|
|
272
|
+
export { BashOptions, BashResult, BashSession, BashSessionOptions, type Command, type CommandContext, EntryType, type ExecResult, FileInfo, FileStat, type NetworkConfig, Workspace, WorkspaceChangeEvent, WorkspaceChangeType, WorkspaceHost, WorkspaceOptions, defineCommand };
|
|
273
|
+
//# sourceMappingURL=workspace.d.ts.map
|