zidane 6.0.1 → 6.1.1

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 (165) hide show
  1. package/README.md +27 -0
  2. package/dist/{acp-Irby04Us.js → acp-DK382vzL.js} +8 -8
  3. package/dist/{acp-Irby04Us.js.map → acp-DK382vzL.js.map} +1 -1
  4. package/dist/acp-cli.js +7 -7
  5. package/dist/acp-cli.js.map +1 -1
  6. package/dist/acp.d.ts +1 -1
  7. package/dist/acp.js +1 -1
  8. package/dist/{agent-DkSmGkDM.js → agent-Bq32ZJSo.js} +392 -63
  9. package/dist/agent-Bq32ZJSo.js.map +1 -0
  10. package/dist/agent.d.ts +1 -1
  11. package/dist/agent.js +1 -1
  12. package/dist/{anthropic-CTHsdFG9.js → anthropic-CV_OSYVD.js} +10 -9
  13. package/dist/anthropic-CV_OSYVD.js.map +1 -0
  14. package/dist/atomic-write-B7zr1ddF.js.map +1 -1
  15. package/dist/{auth-BDSu_0t3.js → auth-D5FfJtLf.js} +7 -7
  16. package/dist/auth-D5FfJtLf.js.map +1 -0
  17. package/dist/cache-telemetry-BKAwsNxm.js.map +1 -1
  18. package/dist/chat/pure.d.ts +4 -4
  19. package/dist/chat/pure.js +3 -3
  20. package/dist/chat.d.ts +44 -21
  21. package/dist/chat.d.ts.map +1 -1
  22. package/dist/chat.js +7 -7
  23. package/dist/chat.js.map +1 -1
  24. package/dist/completion-core-CXYSVhZo.js.map +1 -1
  25. package/dist/context-breakdown-Dzo25N45.js.map +1 -1
  26. package/dist/contexts/daytona.d.ts +3 -1
  27. package/dist/contexts/daytona.d.ts.map +1 -1
  28. package/dist/contexts/daytona.js +2 -2
  29. package/dist/contexts/daytona.js.map +1 -1
  30. package/dist/contexts/docker.d.ts.map +1 -1
  31. package/dist/contexts/docker.js +2 -2
  32. package/dist/contexts/docker.js.map +1 -1
  33. package/dist/contexts/e2b.d.ts +3 -1
  34. package/dist/contexts/e2b.d.ts.map +1 -1
  35. package/dist/contexts/e2b.js +1 -1
  36. package/dist/contexts/e2b.js.map +1 -1
  37. package/dist/{contexts-Vone3qQQ.js → contexts-BIbcfBFH.js} +2 -2
  38. package/dist/contexts-BIbcfBFH.js.map +1 -0
  39. package/dist/contexts.js +1 -1
  40. package/dist/edit-utils-EGosADZq.js.map +1 -1
  41. package/dist/eval.d.ts +1 -1
  42. package/dist/eval.js +4 -3
  43. package/dist/eval.js.map +1 -1
  44. package/dist/exec-file-size-PXkr-MGA.js.map +1 -1
  45. package/dist/extensions.d.ts +1 -1
  46. package/dist/extensions.js +1 -1
  47. package/dist/{fetch-url-CWE8X5OD.js → fetch-url-_5WKWpbQ.js} +2 -2
  48. package/dist/{fetch-url-CWE8X5OD.js.map → fetch-url-_5WKWpbQ.js.map} +1 -1
  49. package/dist/{format-BNOXpl-1.js → format-BF745pLL.js} +9 -2
  50. package/dist/format-BF745pLL.js.map +1 -0
  51. package/dist/generate-data-WL_yGVL6.js.map +1 -1
  52. package/dist/hash-DJRzmcPI.js.map +1 -1
  53. package/dist/headless.d.ts +1 -1
  54. package/dist/headless.js +4 -4
  55. package/dist/headless.js.map +1 -1
  56. package/dist/{index-B8UtiZy7.d.ts → index-C6Il4kBD.d.ts} +388 -5
  57. package/dist/index-C6Il4kBD.d.ts.map +1 -0
  58. package/dist/index.d.ts +2 -2
  59. package/dist/index.js +11 -11
  60. package/dist/index.js.map +1 -1
  61. package/dist/logger-DBX9uYOw.js.map +1 -1
  62. package/dist/{login-DOF8pMVM.js → login-DZLG__2f.js} +4 -3
  63. package/dist/{login-DOF8pMVM.js.map → login-DZLG__2f.js.map} +1 -1
  64. package/dist/mcp-CcvuVXB9.js.map +1 -1
  65. package/dist/mcp.d.ts +1 -1
  66. package/dist/media-sniff-Bn76JxAu.js.map +1 -1
  67. package/dist/{messages-CwujiS74.js → messages-Ce4yRjd3.js} +2 -2
  68. package/dist/{messages-CwujiS74.js.map → messages-Ce4yRjd3.js.map} +1 -1
  69. package/dist/oauth-8LqbeI_Q.js.map +1 -1
  70. package/dist/{openai-compat-BvgYGSR1.js → openai-compat-TA7aihJ-.js} +6 -4
  71. package/dist/openai-compat-TA7aihJ-.js.map +1 -0
  72. package/dist/output/stream-json.d.ts +1 -1
  73. package/dist/output/terminal.d.ts +1 -1
  74. package/dist/output/terminal.js +1 -1
  75. package/dist/output/terminal.js.map +1 -1
  76. package/dist/output-cap-kBUHBlnq.js.map +1 -1
  77. package/dist/presets.d.ts +1 -1
  78. package/dist/presets.js +1 -1
  79. package/dist/providers/anthropic.d.ts +1 -1
  80. package/dist/providers/anthropic.js +1 -1
  81. package/dist/providers/arcee.d.ts +1 -1
  82. package/dist/providers/arcee.js +1 -1
  83. package/dist/providers/baseten.d.ts +1 -1
  84. package/dist/providers/baseten.js +2 -2
  85. package/dist/providers/baseten.js.map +1 -1
  86. package/dist/providers/cerebras.d.ts +1 -1
  87. package/dist/providers/cerebras.js +1 -1
  88. package/dist/providers/local.d.ts +1 -1
  89. package/dist/providers/local.js +1 -1
  90. package/dist/providers/openai-compat.d.ts +1 -1
  91. package/dist/providers/openai-compat.js +1 -1
  92. package/dist/providers/openai.d.ts +1 -1
  93. package/dist/providers/openai.js +8 -8
  94. package/dist/providers/openai.js.map +1 -1
  95. package/dist/providers/openrouter.d.ts +1 -1
  96. package/dist/providers/openrouter.js +1 -1
  97. package/dist/providers/xai.d.ts +1 -1
  98. package/dist/providers/xai.js +1 -1
  99. package/dist/{providers-z6LDMAr3.js → providers-Dln69sQ_.js} +3 -3
  100. package/dist/{providers-z6LDMAr3.js.map → providers-Dln69sQ_.js.map} +1 -1
  101. package/dist/providers.d.ts +1 -1
  102. package/dist/providers.js +4 -4
  103. package/dist/read-state-Dq_TXUX1.js.map +1 -1
  104. package/dist/{resolve-Cr8JSL_O.js → resolve-CC8Lmwcj.js} +3 -3
  105. package/dist/{resolve-Cr8JSL_O.js.map → resolve-CC8Lmwcj.js.map} +1 -1
  106. package/dist/restate.d.ts +1 -1
  107. package/dist/restate.d.ts.map +1 -1
  108. package/dist/restate.js.map +1 -1
  109. package/dist/session/sqlite.d.ts +1 -1
  110. package/dist/session/sqlite.d.ts.map +1 -1
  111. package/dist/session/sqlite.js +75 -20
  112. package/dist/session/sqlite.js.map +1 -1
  113. package/dist/{session-CsdDIPY8.js → session-D7JSEMME.js} +3 -3
  114. package/dist/session-D7JSEMME.js.map +1 -0
  115. package/dist/session.d.ts +2 -2
  116. package/dist/session.js +2 -2
  117. package/dist/{skills-Bsb0rvWI.js → skills-BjeXSRlQ.js} +2 -2
  118. package/dist/{skills-Bsb0rvWI.js.map → skills-BjeXSRlQ.js.map} +1 -1
  119. package/dist/skills.d.ts +1 -1
  120. package/dist/skills.js +2 -2
  121. package/dist/stdio-loader-Ce68wUmM.js.map +1 -1
  122. package/dist/{tool-formatters-DY0TDb1r.d.ts → tool-formatters-ETbcC9DM.d.ts} +2 -2
  123. package/dist/tool-formatters-ETbcC9DM.d.ts.map +1 -0
  124. package/dist/tools/fetch-url.d.ts +1 -1
  125. package/dist/tools/fetch-url.d.ts.map +1 -1
  126. package/dist/tools/fetch-url.js +1 -1
  127. package/dist/tools/web-search.d.ts +1 -1
  128. package/dist/tools/web-search.js +1 -1
  129. package/dist/tools/web-search.js.map +1 -1
  130. package/dist/tools.d.ts +2 -2
  131. package/dist/tools.js +2 -2
  132. package/dist/{transcript-anchors-DSRPWjiH.js → transcript-anchors-CBaghfwl.js} +364 -96
  133. package/dist/transcript-anchors-CBaghfwl.js.map +1 -0
  134. package/dist/{transcript-anchors-Cb-bDbNH.d.ts → transcript-anchors-e_30Kvk0.d.ts} +37 -3
  135. package/dist/transcript-anchors-e_30Kvk0.d.ts.map +1 -0
  136. package/dist/tui.d.ts +21 -6
  137. package/dist/tui.d.ts.map +1 -1
  138. package/dist/tui.js +505 -83
  139. package/dist/tui.js.map +1 -1
  140. package/dist/{turn-operations-Dq9Kx7m2.js → turn-operations-CEt_l3fy.js} +23 -8
  141. package/dist/turn-operations-CEt_l3fy.js.map +1 -0
  142. package/dist/{turn-operations-CgMROLsr.d.ts → turn-operations-D-aaiUOo.d.ts} +27 -9
  143. package/dist/turn-operations-D-aaiUOo.d.ts.map +1 -0
  144. package/dist/types-BIarq1qE.js.map +1 -1
  145. package/dist/types.d.ts +1 -1
  146. package/dist/{xai-B_e6TOA8.js → xai-kGdhWmds.js} +3 -3
  147. package/dist/{xai-B_e6TOA8.js.map → xai-kGdhWmds.js.map} +1 -1
  148. package/docs/ARCHITECTURE.md +4 -0
  149. package/docs/CHAT.md +12 -10
  150. package/docs/SKILL.md +1 -1
  151. package/docs/TUI.md +3 -3
  152. package/package.json +2 -2
  153. package/dist/agent-DkSmGkDM.js.map +0 -1
  154. package/dist/anthropic-CTHsdFG9.js.map +0 -1
  155. package/dist/auth-BDSu_0t3.js.map +0 -1
  156. package/dist/contexts-Vone3qQQ.js.map +0 -1
  157. package/dist/format-BNOXpl-1.js.map +0 -1
  158. package/dist/index-B8UtiZy7.d.ts.map +0 -1
  159. package/dist/openai-compat-BvgYGSR1.js.map +0 -1
  160. package/dist/session-CsdDIPY8.js.map +0 -1
  161. package/dist/tool-formatters-DY0TDb1r.d.ts.map +0 -1
  162. package/dist/transcript-anchors-Cb-bDbNH.d.ts.map +0 -1
  163. package/dist/transcript-anchors-DSRPWjiH.js.map +0 -1
  164. package/dist/turn-operations-CgMROLsr.d.ts.map +0 -1
  165. package/dist/turn-operations-Dq9Kx7m2.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"completion-core-CXYSVhZo.js","names":[],"sources":["../src/chat/completion-core.ts"],"sourcesContent":["/**\n * Prompt autocompletion framework — pure core.\n *\n * Provider contract types + the renderer-agnostic span/reference helpers\n * (`findActiveTrigger`, `applyInsert`, `mergeReferences`, `collectReferences`).\n * No React, no node — so a browser-context renderer imports these via\n * `zidane/chat/pure`. The `useCompletion` React hook lives in `./completion`\n * (main barrel) and re-exports everything here for back-compat.\n *\n * Providers plug in by registering a `trigger` character (e.g. `/` for skills,\n * `@` for files) and exposing two operations:\n *\n * 1. `suggest(query)` — return ranked items for the live query.\n * 2. `parseReferences(text)` — find all references to the provider's\n * items in arbitrary text. Used to highlight in-prompt mentions and\n * drive submit-time side effects (activate the skill, attach the\n * file, …).\n */\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * A discoverable, selectable thing — one row in the autocomplete popover.\n * `TItem` is provider-specific; consumers can inspect `data` when they\n * need the originating payload (e.g. the full `SkillConfig` for tooltips).\n */\nexport interface CompletionItem<TItem = unknown> {\n /** Stable identifier within the provider. Used as React key + selection equality. */\n id: string\n /** User-visible primary string (\"research\"). */\n label: string\n /** Optional one-line secondary string (\"In-depth research with citations\"). */\n description?: string\n /**\n * Text that replaces the active span (trigger + query) on commit. Usually\n * `${trigger}${label}` with a trailing space so the cursor lands ready\n * for the next token.\n */\n insertText: string\n /**\n * Optional section header for popovers that host several providers on one\n * trigger (e.g. `/` → Commands + Skills). When set, the renderer groups\n * items under this label; when absent, items render flat. The engine\n * defaults it to the producing provider's `label` in {@link mergeSuggestions}.\n */\n section?: string\n /** Original provider-specific payload. */\n data: TItem\n}\n\n/**\n * A reference to a provider item inside arbitrary prompt text. Producers:\n * `provider.parseReferences(text)`. Consumers: the TUI for highlighting,\n * the run flow for \"activate every referenced skill before agent.run()\".\n *\n * Spans are half-open `[start, end)` codepoint offsets into the source\n * string. Overlapping spans from the same or different providers are\n * caller-resolved — the helpers below ship a \"first wins\" merger.\n */\nexport interface CompletionReference<TItem = unknown> {\n providerId: string\n start: number\n end: number\n itemId: string\n data: TItem\n metadata?: Record<string, unknown>\n}\n\nexport interface CompletionSubmitContext<TItem = unknown> extends CompletionContext {\n references: readonly CompletionReference<TItem>[]\n}\n\n/**\n * Provider contract. Implementations decide their own ranking, fuzzy-match\n * rules, async loading behavior, and reference grammar.\n */\nexport interface CompletionProvider<TItem = unknown> {\n /** Stable id used for tagging references + React keys. */\n id: string\n /**\n * Single character that activates this provider. The engine considers a\n * trigger \"active\" when it appears at the start of the buffer or\n * immediately after whitespace, and is not closed by whitespace before\n * the cursor (so a trigger plus query is a contiguous token).\n */\n trigger: string\n /** Human-readable name. Reserved for future multi-provider popover headers. */\n label: string\n /**\n * Returns items for the active `query` (the text between the trigger and\n * the cursor, excluding the trigger itself). Synchronous return is the\n * common case; promises let providers paginate or hit a backend.\n *\n * `signal` is aborted when the query changes or the popover closes —\n * use it to cancel in-flight network calls.\n */\n suggest: (\n query: string,\n ctx: CompletionContext,\n signal: AbortSignal,\n ) => CompletionItem<TItem>[] | Promise<CompletionItem<TItem>[]>\n /**\n * Keep the trigger active across spaces (single line only — a newline\n * still closes it). Default false: a space ends the query token, which is\n * right for file/skill/chat mentions. The commands provider opts in so\n * `/deploy stag▌` can complete ARGUMENTS: its query becomes\n * `deploy stag` and it decides internally what to suggest. When the\n * query contains whitespace, only opted-in providers are consulted.\n */\n allowSpacesInQuery?: boolean\n /**\n * Find every reference to this provider's items in `text`. Pure: must not\n * mutate ctx. The TUI calls this on every keystroke for highlighting, so\n * keep it cheap (linear scan, no I/O).\n */\n parseReferences: (\n text: string,\n ctx: CompletionContext,\n ) => CompletionReference<TItem>[]\n /**\n * Optional submit-time resolver. Unlike `parseReferences`, this may do async\n * work and enrich references before the host turns them into attachments,\n * activated skills, or other side effects.\n *\n * Declared with method syntax (not a property arrow) on purpose: it makes the\n * `TItem`-typed params bivariant, so a heterogeneous `CompletionProvider`\n * collection (skills + files + extensions) stays assignable to\n * `CompletionProvider<unknown>[]`.\n */\n // eslint-disable-next-line ts/method-signature-style\n resolveReferences?(\n references: readonly CompletionReference<TItem>[],\n ctx: CompletionSubmitContext<TItem>,\n signal: AbortSignal,\n ): CompletionReference<TItem>[] | Promise<CompletionReference<TItem>[]>\n /** Optional submit-time side effect hook. Runs after references are resolved. */\n // eslint-disable-next-line ts/method-signature-style\n onSubmit?(\n submission: CompletionSubmitContext<TItem>,\n signal: AbortSignal,\n ): void | Promise<void>\n}\n\n/**\n * Read-only view of the active prompt buffer + cursor passed to provider\n * callbacks. Kept minimal so providers stay portable across renderers.\n */\nexport interface CompletionContext {\n /** Full prompt text. */\n text: string\n /** Codepoint offset of the cursor (0-based). */\n cursor: number\n}\n\n/**\n * Identified active trigger span. Returned by `findActiveTrigger` so\n * callers can show the popover, query the provider, and on commit replace\n * the span with the selected item's `insertText`.\n */\nexport interface ActiveTrigger<TItem = unknown> {\n provider: CompletionProvider<TItem>\n /** Substring after the trigger, up to the cursor. Empty if cursor sits right after the trigger. */\n query: string\n /** `[start, end)` — span covered by the trigger + query in the source. */\n span: { start: number, end: number }\n}\n\n// ---------------------------------------------------------------------------\n// Pure helpers — exported for unit tests + alternate renderers\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve the provider trigger active at `cursor`, or `null` when none fits.\n *\n * Rules:\n * - The trigger character must sit at position 0 of the buffer OR be\n * preceded by whitespace. This prevents `http://` from triggering the\n * `/`-bound skills provider mid-URL.\n * - The cursor must be at or past the trigger position.\n * - Nothing between the trigger and the cursor may be whitespace (the\n * query is one contiguous token).\n * - The query length is bounded — `maxQueryLength` defaults to 64 — so\n * a runaway buffer scan can't pin the renderer.\n */\nexport function findActiveTrigger<TItem>(\n text: string,\n cursor: number,\n providers: readonly CompletionProvider<TItem>[],\n options: { maxQueryLength?: number } = {},\n): ActiveTrigger<TItem> | null {\n const multi = findActiveTriggers(text, cursor, providers, options)\n if (!multi)\n return null\n // Back-compat: the first matching provider is exactly what the old\n // `providers.find(...)` returned (filter preserves order).\n return { provider: multi.providers[0], query: multi.query, span: multi.span }\n}\n\n/** Like {@link ActiveTrigger} but carries EVERY provider bound to the active trigger char. */\nexport interface ActiveTriggers<TItem = unknown> {\n /** All providers whose `trigger` matches the active char, in registration order. */\n providers: readonly CompletionProvider<TItem>[]\n query: string\n span: { start: number, end: number }\n}\n\n/**\n * Multi-provider variant of {@link findActiveTrigger}: resolves ALL providers\n * bound to the trigger char active at `cursor` (e.g. `/` → a commands provider\n * AND the skills provider), sharing one query + span. The same trigger rules\n * apply (start-of-buffer or whitespace-preceded, contiguous query, bounded\n * length). Returns `null` when no trigger fits.\n */\nexport function findActiveTriggers<TItem>(\n text: string,\n cursor: number,\n providers: readonly CompletionProvider<TItem>[],\n options: { maxQueryLength?: number } = {},\n): ActiveTriggers<TItem> | null {\n if (providers.length === 0)\n return null\n const max = options.maxQueryLength ?? 64\n const safeCursor = Math.max(0, Math.min(cursor, text.length))\n // Whitespace test mirrors regex `\\s` so this surface agrees with the\n // built-in providers' `parseReferences` regex (which uses `\\s`). Without\n // this, pasting `text\\u00a0@README.md` would parse as a ref but the popover\n // wouldn't reopen on retype because NBSP wasn't recognized as a boundary.\n const isWhitespace = (ch: string | undefined) => ch === undefined ? false : /\\s/.test(ch)\n // Walk backward from the cursor to the nearest trigger char. Whitespace\n // normally closes the query token and ends the walk — unless a provider\n // opted into `allowSpacesInQuery` (argument completion), in which case\n // the walk continues across spaces (never newlines) and, once a trigger\n // is found, only space-tolerant providers are consulted for a spaced\n // query. Non-opted-in providers keep byte-identical behavior.\n const anyAllowsSpaces = providers.some(p => p.allowSpacesInQuery === true)\n let sawSpace = false\n for (let i = safeCursor - 1; i >= 0 && safeCursor - i <= max + 1; i--) {\n const ch = text[i]\n if (isWhitespace(ch)) {\n if (ch === '\\n' || ch === '\\r' || !anyAllowsSpaces)\n return null\n sawSpace = true\n continue\n }\n const matching = providers.filter(p => p.trigger === ch && (!sawSpace || p.allowSpacesInQuery === true))\n if (matching.length === 0)\n continue\n // Trigger must be at start of buffer or follow whitespace.\n const before = i > 0 ? text[i - 1] : ''\n if (before !== '' && !isWhitespace(before))\n continue\n const query = text.slice(i + 1, safeCursor)\n return {\n providers: matching,\n query,\n span: { start: i, end: safeCursor },\n }\n }\n return null\n}\n\n/**\n * Flatten several providers' suggestion lists into one, defaulting each\n * item's `section` to its producing provider's `label` so a multi-provider\n * popover can render group headers. Items that already carry a `section`\n * are left as-is.\n */\nexport function mergeSuggestions<TItem>(\n groups: readonly { provider: CompletionProvider<TItem>, items: readonly CompletionItem<TItem>[] }[],\n): CompletionItem<TItem>[] {\n const out: CompletionItem<TItem>[] = []\n for (const { provider, items } of groups) {\n for (const item of items)\n out.push(item.section === undefined ? { ...item, section: provider.label } : item)\n }\n return out\n}\n\n/**\n * Replace `[span.start, span.end)` in `text` with `insertText`. Returns the\n * mutated text and the new cursor position (end of insertion).\n */\nexport function applyInsert(\n text: string,\n span: { start: number, end: number },\n insertText: string,\n): { text: string, cursor: number } {\n const next = text.slice(0, span.start) + insertText + text.slice(span.end)\n return { text: next, cursor: span.start + insertText.length }\n}\n\n/**\n * Merge reference lists from multiple providers into one ordered list with\n * earlier-start-wins disambiguation when spans overlap. Ties broken by\n * insertion order. Spans are sorted ascending so renderers can walk them\n * sequentially with a cursor through the source string.\n */\nexport function mergeReferences<TItem>(\n refs: readonly CompletionReference<TItem>[],\n): CompletionReference<TItem>[] {\n const sorted = [...refs].sort((a, b) => a.start - b.start)\n const merged: CompletionReference<TItem>[] = []\n let lastEnd = -1\n for (const ref of sorted) {\n if (ref.start < lastEnd)\n continue\n merged.push(ref)\n lastEnd = ref.end\n }\n return merged\n}\n\n/**\n * Collect every provider's references in one pass. Convenience wrapper —\n * the TUI textarea component calls this on every keystroke to highlight\n * in-prompt mentions.\n */\nexport function collectReferences<TItem>(\n text: string,\n providers: readonly CompletionProvider<TItem>[],\n cursor = text.length,\n): CompletionReference<TItem>[] {\n const ctx: CompletionContext = { text, cursor }\n const refs: CompletionReference<TItem>[] = []\n for (const p of providers) {\n for (const ref of p.parseReferences(text, ctx))\n refs.push(ref)\n }\n return mergeReferences(refs)\n}\n\nexport async function resolveCompletionSubmit<TItem>(\n text: string,\n providers: readonly CompletionProvider<TItem>[],\n options: { cursor?: number, signal?: AbortSignal } = {},\n): Promise<CompletionReference<TItem>[]> {\n const cursor = options.cursor ?? text.length\n const signal = options.signal ?? new AbortController().signal\n const byProvider = new Map<string, CompletionReference<TItem>[]>()\n for (const ref of collectReferences(text, providers, cursor)) {\n const bucket = byProvider.get(ref.providerId) ?? []\n bucket.push(ref)\n byProvider.set(ref.providerId, bucket)\n }\n const resolved: CompletionReference<TItem>[] = []\n for (const provider of providers) {\n const refs = byProvider.get(provider.id)\n if (!refs?.length)\n continue\n const ctx: CompletionSubmitContext<TItem> = { text, cursor, references: refs }\n const next = provider.resolveReferences\n ? await provider.resolveReferences(refs, ctx, signal)\n : refs\n const submitCtx: CompletionSubmitContext<TItem> = { text, cursor, references: next }\n await provider.onSubmit?.(submitCtx, signal)\n resolved.push(...next)\n }\n return mergeReferences(resolved)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"completion-core-CXYSVhZo.js","names":[],"sources":["../src/chat/completion-core.ts"],"sourcesContent":["/**\n * Prompt autocompletion framework — pure core.\n *\n * Provider contract types + the renderer-agnostic span/reference helpers\n * (`findActiveTrigger`, `applyInsert`, `mergeReferences`, `collectReferences`).\n * No React, no node — so a browser-context renderer imports these via\n * `zidane/chat/pure`. The `useCompletion` React hook lives in `./completion`\n * (main barrel) and re-exports everything here for back-compat.\n *\n * Providers plug in by registering a `trigger` character (e.g. `/` for skills,\n * `@` for files) and exposing two operations:\n *\n * 1. `suggest(query)` — return ranked items for the live query.\n * 2. `parseReferences(text)` — find all references to the provider's\n * items in arbitrary text. Used to highlight in-prompt mentions and\n * drive submit-time side effects (activate the skill, attach the\n * file, …).\n */\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * A discoverable, selectable thing — one row in the autocomplete popover.\n * `TItem` is provider-specific; consumers can inspect `data` when they\n * need the originating payload (e.g. the full `SkillConfig` for tooltips).\n */\nexport interface CompletionItem<TItem = unknown> {\n /** Stable identifier within the provider. Used as React key + selection equality. */\n id: string\n /** User-visible primary string (\"research\"). */\n label: string\n /** Optional one-line secondary string (\"In-depth research with citations\"). */\n description?: string\n /**\n * Text that replaces the active span (trigger + query) on commit. Usually\n * `${trigger}${label}` with a trailing space so the cursor lands ready\n * for the next token.\n */\n insertText: string\n /**\n * Optional section header for popovers that host several providers on one\n * trigger (e.g. `/` → Commands + Skills). When set, the renderer groups\n * items under this label; when absent, items render flat. The engine\n * defaults it to the producing provider's `label` in {@link mergeSuggestions}.\n */\n section?: string\n /** Original provider-specific payload. */\n data: TItem\n}\n\n/**\n * A reference to a provider item inside arbitrary prompt text. Producers:\n * `provider.parseReferences(text)`. Consumers: the TUI for highlighting,\n * the run flow for \"activate every referenced skill before agent.run()\".\n *\n * Spans are half-open `[start, end)` codepoint offsets into the source\n * string. Overlapping spans from the same or different providers are\n * caller-resolved — the helpers below ship a \"first wins\" merger.\n */\nexport interface CompletionReference<TItem = unknown> {\n providerId: string\n start: number\n end: number\n itemId: string\n data: TItem\n metadata?: Record<string, unknown>\n}\n\nexport interface CompletionSubmitContext<TItem = unknown> extends CompletionContext {\n references: readonly CompletionReference<TItem>[]\n}\n\n/**\n * Provider contract. Implementations decide their own ranking, fuzzy-match\n * rules, async loading behavior, and reference grammar.\n */\nexport interface CompletionProvider<TItem = unknown> {\n /** Stable id used for tagging references + React keys. */\n id: string\n /**\n * Single character that activates this provider. The engine considers a\n * trigger \"active\" when it appears at the start of the buffer or\n * immediately after whitespace, and is not closed by whitespace before\n * the cursor (so a trigger plus query is a contiguous token).\n */\n trigger: string\n /** Human-readable name. Reserved for future multi-provider popover headers. */\n label: string\n /**\n * Returns items for the active `query` (the text between the trigger and\n * the cursor, excluding the trigger itself). Synchronous return is the\n * common case; promises let providers paginate or hit a backend.\n *\n * `signal` is aborted when the query changes or the popover closes —\n * use it to cancel in-flight network calls.\n */\n suggest: (\n query: string,\n ctx: CompletionContext,\n signal: AbortSignal,\n ) => CompletionItem<TItem>[] | Promise<CompletionItem<TItem>[]>\n /**\n * Keep the trigger active across spaces (single line only — a newline\n * still closes it). Default false: a space ends the query token, which is\n * right for file/skill/chat mentions. The commands provider opts in so\n * `/deploy stag▌` can complete ARGUMENTS: its query becomes\n * `deploy stag` and it decides internally what to suggest. When the\n * query contains whitespace, only opted-in providers are consulted.\n */\n allowSpacesInQuery?: boolean\n /**\n * Find every reference to this provider's items in `text`. Pure: must not\n * mutate ctx. The TUI calls this on every keystroke for highlighting, so\n * keep it cheap (linear scan, no I/O).\n */\n parseReferences: (\n text: string,\n ctx: CompletionContext,\n ) => CompletionReference<TItem>[]\n /**\n * Optional submit-time resolver. Unlike `parseReferences`, this may do async\n * work and enrich references before the host turns them into attachments,\n * activated skills, or other side effects.\n *\n * Declared with method syntax (not a property arrow) on purpose: it makes the\n * `TItem`-typed params bivariant, so a heterogeneous `CompletionProvider`\n * collection (skills + files + extensions) stays assignable to\n * `CompletionProvider<unknown>[]`.\n */\n // eslint-disable-next-line ts/method-signature-style\n resolveReferences?(\n references: readonly CompletionReference<TItem>[],\n ctx: CompletionSubmitContext<TItem>,\n signal: AbortSignal,\n ): CompletionReference<TItem>[] | Promise<CompletionReference<TItem>[]>\n /** Optional submit-time side effect hook. Runs after references are resolved. */\n // eslint-disable-next-line ts/method-signature-style\n onSubmit?(\n submission: CompletionSubmitContext<TItem>,\n signal: AbortSignal,\n ): void | Promise<void>\n}\n\n/**\n * Read-only view of the active prompt buffer + cursor passed to provider\n * callbacks. Kept minimal so providers stay portable across renderers.\n */\nexport interface CompletionContext {\n /** Full prompt text. */\n text: string\n /** Codepoint offset of the cursor (0-based). */\n cursor: number\n}\n\n/**\n * Identified active trigger span. Returned by `findActiveTrigger` so\n * callers can show the popover, query the provider, and on commit replace\n * the span with the selected item's `insertText`.\n */\nexport interface ActiveTrigger<TItem = unknown> {\n provider: CompletionProvider<TItem>\n /** Substring after the trigger, up to the cursor. Empty if cursor sits right after the trigger. */\n query: string\n /** `[start, end)` — span covered by the trigger + query in the source. */\n span: { start: number, end: number }\n}\n\n// ---------------------------------------------------------------------------\n// Pure helpers — exported for unit tests + alternate renderers\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve the provider trigger active at `cursor`, or `null` when none fits.\n *\n * Rules:\n * - The trigger character must sit at position 0 of the buffer OR be\n * preceded by whitespace. This prevents `http://` from triggering the\n * `/`-bound skills provider mid-URL.\n * - The cursor must be at or past the trigger position.\n * - Nothing between the trigger and the cursor may be whitespace (the\n * query is one contiguous token).\n * - The query length is bounded — `maxQueryLength` defaults to 64 — so\n * a runaway buffer scan can't pin the renderer.\n */\nexport function findActiveTrigger<TItem>(\n text: string,\n cursor: number,\n providers: readonly CompletionProvider<TItem>[],\n options: { maxQueryLength?: number } = {},\n): ActiveTrigger<TItem> | null {\n const multi = findActiveTriggers(text, cursor, providers, options)\n if (!multi)\n return null\n // Back-compat: the first matching provider is exactly what the old\n // `providers.find(...)` returned (filter preserves order).\n return { provider: multi.providers[0], query: multi.query, span: multi.span }\n}\n\n/** Like {@link ActiveTrigger} but carries EVERY provider bound to the active trigger char. */\nexport interface ActiveTriggers<TItem = unknown> {\n /** All providers whose `trigger` matches the active char, in registration order. */\n providers: readonly CompletionProvider<TItem>[]\n query: string\n span: { start: number, end: number }\n}\n\n/**\n * Multi-provider variant of {@link findActiveTrigger}: resolves ALL providers\n * bound to the trigger char active at `cursor` (e.g. `/` → a commands provider\n * AND the skills provider), sharing one query + span. The same trigger rules\n * apply (start-of-buffer or whitespace-preceded, contiguous query, bounded\n * length). Returns `null` when no trigger fits.\n */\nexport function findActiveTriggers<TItem>(\n text: string,\n cursor: number,\n providers: readonly CompletionProvider<TItem>[],\n options: { maxQueryLength?: number } = {},\n): ActiveTriggers<TItem> | null {\n if (providers.length === 0)\n return null\n const max = options.maxQueryLength ?? 64\n const safeCursor = Math.max(0, Math.min(cursor, text.length))\n // Whitespace test mirrors regex `\\s` so this surface agrees with the\n // built-in providers' `parseReferences` regex (which uses `\\s`). Without\n // this, pasting `text\\u00a0@README.md` would parse as a ref but the popover\n // wouldn't reopen on retype because NBSP wasn't recognized as a boundary.\n const isWhitespace = (ch: string | undefined) => ch === undefined ? false : /\\s/.test(ch)\n // Walk backward from the cursor to the nearest trigger char. Whitespace\n // normally closes the query token and ends the walk — unless a provider\n // opted into `allowSpacesInQuery` (argument completion), in which case\n // the walk continues across spaces (never newlines) and, once a trigger\n // is found, only space-tolerant providers are consulted for a spaced\n // query. Non-opted-in providers keep byte-identical behavior.\n const anyAllowsSpaces = providers.some(p => p.allowSpacesInQuery === true)\n let sawSpace = false\n for (let i = safeCursor - 1; i >= 0 && safeCursor - i <= max + 1; i--) {\n const ch = text[i]\n if (isWhitespace(ch)) {\n if (ch === '\\n' || ch === '\\r' || !anyAllowsSpaces)\n return null\n sawSpace = true\n continue\n }\n const matching = providers.filter(p => p.trigger === ch && (!sawSpace || p.allowSpacesInQuery === true))\n if (matching.length === 0)\n continue\n // Trigger must be at start of buffer or follow whitespace.\n const before = i > 0 ? text[i - 1] : ''\n if (before !== '' && !isWhitespace(before))\n continue\n const query = text.slice(i + 1, safeCursor)\n return {\n providers: matching,\n query,\n span: { start: i, end: safeCursor },\n }\n }\n return null\n}\n\n/**\n * Flatten several providers' suggestion lists into one, defaulting each\n * item's `section` to its producing provider's `label` so a multi-provider\n * popover can render group headers. Items that already carry a `section`\n * are left as-is.\n */\nexport function mergeSuggestions<TItem>(\n groups: readonly { provider: CompletionProvider<TItem>, items: readonly CompletionItem<TItem>[] }[],\n): CompletionItem<TItem>[] {\n const out: CompletionItem<TItem>[] = []\n for (const { provider, items } of groups) {\n for (const item of items)\n out.push(item.section === undefined ? { ...item, section: provider.label } : item)\n }\n return out\n}\n\n/**\n * Replace `[span.start, span.end)` in `text` with `insertText`. Returns the\n * mutated text and the new cursor position (end of insertion).\n */\nexport function applyInsert(\n text: string,\n span: { start: number, end: number },\n insertText: string,\n): { text: string, cursor: number } {\n const next = text.slice(0, span.start) + insertText + text.slice(span.end)\n return { text: next, cursor: span.start + insertText.length }\n}\n\n/**\n * Merge reference lists from multiple providers into one ordered list with\n * earlier-start-wins disambiguation when spans overlap. Ties broken by\n * insertion order. Spans are sorted ascending so renderers can walk them\n * sequentially with a cursor through the source string.\n */\nexport function mergeReferences<TItem>(\n refs: readonly CompletionReference<TItem>[],\n): CompletionReference<TItem>[] {\n const sorted = [...refs].sort((a, b) => a.start - b.start)\n const merged: CompletionReference<TItem>[] = []\n let lastEnd = -1\n for (const ref of sorted) {\n if (ref.start < lastEnd)\n continue\n merged.push(ref)\n lastEnd = ref.end\n }\n return merged\n}\n\n/**\n * Collect every provider's references in one pass. Convenience wrapper —\n * the TUI textarea component calls this on every keystroke to highlight\n * in-prompt mentions.\n */\nexport function collectReferences<TItem>(\n text: string,\n providers: readonly CompletionProvider<TItem>[],\n cursor = text.length,\n): CompletionReference<TItem>[] {\n const ctx: CompletionContext = { text, cursor }\n const refs: CompletionReference<TItem>[] = []\n for (const p of providers) {\n for (const ref of p.parseReferences(text, ctx))\n refs.push(ref)\n }\n return mergeReferences(refs)\n}\n\nexport async function resolveCompletionSubmit<TItem>(\n text: string,\n providers: readonly CompletionProvider<TItem>[],\n options: { cursor?: number, signal?: AbortSignal } = {},\n): Promise<CompletionReference<TItem>[]> {\n const cursor = options.cursor ?? text.length\n const signal = options.signal ?? new AbortController().signal\n const byProvider = new Map<string, CompletionReference<TItem>[]>()\n for (const ref of collectReferences(text, providers, cursor)) {\n const bucket = byProvider.get(ref.providerId) ?? []\n bucket.push(ref)\n byProvider.set(ref.providerId, bucket)\n }\n const resolved: CompletionReference<TItem>[] = []\n for (const provider of providers) {\n const refs = byProvider.get(provider.id)\n if (!refs?.length)\n continue\n const ctx: CompletionSubmitContext<TItem> = { text, cursor, references: refs }\n const next = provider.resolveReferences\n ? await provider.resolveReferences(refs, ctx, signal)\n : refs\n const submitCtx: CompletionSubmitContext<TItem> = { text, cursor, references: next }\n await provider.onSubmit?.(submitCtx, signal)\n resolved.push(...next)\n }\n return mergeReferences(resolved)\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"context-breakdown-Dzo25N45.js","names":[],"sources":["../src/chat/context-breakdown.ts"],"sourcesContent":["/**\n * Context-usage breakdown — pure transforms.\n *\n * Turns a {@link ContextSnapshot} (the raw pieces the agent assembled for a\n * run: system prompt, wire tools, deferred tools, MCP tools/instructions,\n * skills catalog, ...) plus the real last-turn token total into a categorized\n * {@link ContextBreakdown} the TUI panel / GUI popover render.\n *\n * No React, no node — a browser-context renderer imports these via\n * `zidane/chat/pure`. The snapshot is captured main-side (TUI direct, GUI in\n * `main/chat`) because building it reads provider/agent state; only the pure\n * shaping lives here.\n *\n * Accuracy: per-category counts are estimates when no provider `countTokens`\n * is available — the heuristic ({@link estimateTokens}, ~4 bytes/token) sizes\n * each static segment and the bar is reconciled so it always sums to the real\n * last-turn total (`conversation = realTotal - sum(static)`). When a provider\n * exposes exact counts (Anthropic, OpenAI), the caller passes them in via\n * {@link ContextSnapshot.exact} and the corresponding categories drop their\n * `estimated` flag. Exact buckets (free space, autocompact buffer) are never\n * estimated.\n */\n\nimport type { SessionTurn } from '../types'\nimport { estimateTokens } from '../compact/utils'\n\n/**\n * Stable identifiers for each breakdown category. Drives swatch color +\n * ordering in both UIs. `*Deferred` buckets are tools/MCP that exist but are\n * NOT currently on the wire (lazy disclosure); `autocompactBuffer` is the\n * reserved output headroom; `freeSpace` is the remaining window.\n */\nexport type ContextCategoryId\n = | 'systemPrompt'\n | 'rules'\n | 'tools'\n | 'mcpTools'\n | 'mcpInstructions'\n | 'skills'\n | 'subagentDefs'\n | 'conversation'\n | 'mcpToolsDeferred'\n | 'toolsDeferred'\n | 'autocompactBuffer'\n | 'freeSpace'\n\n/**\n * Exact last-turn token usage split (provider-reported — always exact). Drives\n * the cache breakdown shown below the categories. `cacheRead + cacheCreation +\n * input` equals the live context total (`used`).\n */\nexport interface ContextUsageSplit {\n /** Fresh, uncached input tokens this turn. */\n input: number\n /** Tokens served from the prompt cache (the cached prefix). */\n cacheRead: number\n /** Tokens written to the cache this turn. */\n cacheCreation: number\n /** Assistant output tokens (not part of the input window; informational). */\n output: number\n}\n\n/** One display row contributed by an extension to the context panel. */\nexport interface ContextExtensionRow {\n label: string\n value: string\n}\n\n/** Extension-owned context panel section. Core treats rows as opaque display data. */\nexport interface ContextExtensionSection {\n id: string\n label: string\n /** Optional section-title foreground color. UIs fall back to their theme accent. */\n accentColor?: string\n rows: readonly ContextExtensionRow[]\n}\n\n/** Data exposed to extension context-breakdown contributors. */\nexport interface ContextBreakdownContributionContext {\n turns: readonly SessionTurn[]\n}\n\n/** Extension hook for adding opaque rows to the context panel. */\nexport type ContextBreakdownContributor\n = (ctx: ContextBreakdownContributionContext) =>\n | ContextExtensionSection\n | readonly ContextExtensionSection[]\n | null\n | undefined\n | Promise<ContextExtensionSection | readonly ContextExtensionSection[] | null | undefined>\n\n/** One expandable child row under a category (e.g. a single MCP tool / memory file). */\nexport interface ContextItem {\n /** Stable-ish key for rendering (tool name, file path). */\n id: string\n /** Display label. */\n label: string\n /** Token count for this item. */\n tokens: number\n /** True when `tokens` came from the heuristic rather than an exact count. */\n estimated: boolean\n}\n\n/** One category row in the breakdown. */\nexport interface ContextCategory {\n id: ContextCategoryId\n /** Display label (e.g. \"Tool definitions\"). */\n label: string\n /** Token count for the category. */\n tokens: number\n /** True when `tokens` came from the heuristic rather than an exact count. */\n estimated: boolean\n /**\n * True for buckets that are NOT counted against the live window total\n * (deferred tools, free space, autocompact buffer) — the UI renders these\n * dimmed / below a divider, mirroring Claude Code's layout.\n */\n deferred?: boolean\n /** Optional expandable child rows (per MCP tool, per memory file, ...). */\n items?: ContextItem[]\n}\n\n/** The shaped, render-ready breakdown. */\nexport interface ContextBreakdown {\n /** Model id the snapshot was built for. */\n modelId: string\n /**\n * Real last-turn context size (provider-reported `input + cacheRead +\n * cacheCreation`) — the SAME number the footer/orb shows. The non-deferred\n * categories always sum to this.\n */\n used: number\n /** Effective window (raw - output reserve), the bar's denominator. */\n effectiveWindow: number\n /** `used / effectiveWindow`, clamped to [0, 1]. */\n fraction: number\n /** Live categories (sum to `used`) followed by deferred + free-space buckets. */\n categories: ContextCategory[]\n /** True when ANY live category is estimated (drives the panel's footnote). */\n hasEstimates: boolean\n /**\n * Exact last-turn usage split (cache read / write / fresh input / output),\n * when the host supplied it. Always exact — rendered as an informational\n * section below the categories. Absent when no usage is available yet.\n */\n usage?: ContextUsageSplit\n /** Extension-owned context panel sections. */\n extensionSections?: readonly ContextExtensionSection[]\n /**\n * Names of currently-active skills. Informational — an activated skill's body\n * lands in the conversation (tool-results), so its tokens are already counted\n * under Conversation; this just surfaces \"which skills are loaded\". Empty/absent\n * when none are active.\n */\n activeSkills?: string[]\n}\n\n/** Host-supplied options for `agent.getContextBreakdown`. */\nexport interface ContextBreakdownOptions {\n /**\n * Effective context window (`rawWindow - outputReserve`) for the active\n * model, resolved by the host via the chat model registry. Drives free-space\n * + fraction. When omitted, defaults to `used` (no headroom shown).\n */\n effectiveWindow?: number\n /**\n * Active model id — used for the exact `countTokens` call + the header label.\n * Only consulted on the PRE-RUN path (no snapshot yet); once a run has fired,\n * the snapshot's own model wins. When omitted, the provider default is used.\n */\n model?: string\n /** Tokens reserved for output (autocompact buffer). Defaults to 0. */\n autocompactBuffer?: number\n /**\n * Auto-compact threshold as a fraction of the window (0–1), when enabled.\n * When set, the \"Autocompact buffer\" bucket represents the ENTIRE tail from\n * the threshold to the end of the window (`(1 − threshold) × window`) rather\n * than just the reserved-output tokens — so the bar/row show exactly where\n * compaction fires. When omitted, `autocompactBuffer` is used as-is.\n */\n compactThreshold?: number\n /** Abort signal forwarded to the provider's `countTokens`. */\n signal?: AbortSignal\n}\n\n/** Tools grouped by MCP server, for the expandable per-server rows. */\nexport interface ContextMcpGroup {\n server: string\n /** Wire tool specs for this server (their JSON size is measured). */\n tools: { name: string, json: string }[]\n}\n\n/**\n * Exact per-segment counts from a provider's `countTokens`, when available.\n * Each field is the count of the cumulative payload up to and including that\n * segment, so categories are derived by differencing (see\n * {@link buildContextBreakdown}). Any subset may be present; missing fields\n * fall back to the heuristic for that category.\n */\nexport interface ContextExactCounts {\n /** Tokens for system prompt alone. */\n system?: number\n /** Tokens for system + tools (so tools = systemAndTools - system). */\n systemAndTools?: number\n /** Tokens for the full payload (system + tools + messages) = the real total. */\n full?: number\n}\n\n/**\n * Raw inputs captured at agent prompt-assembly time. Everything here is what\n * actually went on (or was withheld from) the wire for the last run.\n */\nexport interface ContextSnapshot {\n modelId: string\n /** Full rendered system prompt (marker-stripped, as sent). */\n system: string\n /**\n * Host-composed base system BEFORE the agent appended skills / searchable /\n * MCP-instructions catalogs. Includes the rules block. The \"System prompt\"\n * (base doctrine) bucket = base − rules − env-noise. Defaults to `system`\n * when not provided (back-compat).\n */\n baseSystem?: string\n /** Rendered AGENTS.md/CLAUDE.md rules block (a substring of `baseSystem`). */\n rulesBlock?: string\n /** Source files behind the rules block, for per-file rows. */\n rulesFiles?: { path: string, source: string }[]\n /** Wire tool specs currently disclosed to the model, as JSON strings. */\n toolsJson: string[]\n /** Lazy tool entries NOT currently disclosed (deferred), as JSON strings. */\n deferredToolsJson: string[]\n /** MCP tools grouped by server (disclosed ones). */\n mcpGroups: ContextMcpGroup[]\n /** Deferred (undisclosed) MCP tools grouped by server. */\n deferredMcpGroups: ContextMcpGroup[]\n /** Rendered MCP instructions block, if any. */\n mcpInstructions?: string\n /** Rendered skills catalog text, if any. */\n skillsCatalog?: string\n /** Searchable / subagent (spawn) tool definitions text, if any. */\n subagentDefs?: string\n /** Memory / rules files in context. */\n /** Real last-turn total (input + cacheRead + cacheCreation). */\n used: number\n /** Effective window (raw - output reserve). */\n effectiveWindow: number\n /** Tokens reserved for output (autocompact buffer). */\n autocompactBuffer: number\n /** Exact per-segment counts from the provider, when available. */\n exact?: ContextExactCounts\n /** Exact last-turn usage split, when available (passed straight through). */\n usage?: ContextUsageSplit\n /** Extension-owned context panel sections. */\n extensionSections?: readonly ContextExtensionSection[]\n /** Names of currently-active skills (passed straight through). */\n activeSkills?: string[]\n}\n\nconst LABELS: Record<ContextCategoryId, string> = {\n systemPrompt: 'System prompt',\n rules: 'Rules (AGENTS.md)',\n tools: 'Tool definitions',\n mcpTools: 'MCP tools',\n mcpInstructions: 'MCP instructions',\n skills: 'Skills',\n subagentDefs: 'Subagent definitions',\n conversation: 'Conversation',\n mcpToolsDeferred: 'MCP tools (deferred)',\n toolsDeferred: 'System tools (deferred)',\n autocompactBuffer: 'Autocompact buffer',\n freeSpace: 'Free space',\n}\n\n/** Sum the heuristic token sizes of a list of strings. */\nfunction sumEstimate(parts: readonly string[]): number {\n let total = 0\n for (const p of parts)\n total += estimateTokens(p)\n return total\n}\n\nfunction mcpGroupTokens(group: ContextMcpGroup): { total: number, items: ContextItem[] } {\n const items: ContextItem[] = group.tools.map(t => ({\n id: `${group.server}:${t.name}`,\n label: t.name,\n tokens: estimateTokens(t.json),\n estimated: true,\n }))\n let total = 0\n for (const it of items)\n total += it.tokens\n return { total, items }\n}\n\n/**\n * Shape a {@link ContextSnapshot} into a render-ready {@link ContextBreakdown}.\n *\n * Live categories (system prompt, tools, MCP tools/instructions, skills,\n * memory, subagent defs, conversation) sum to `snapshot.used` — `conversation`\n * absorbs the remainder so the bar matches the provider-reported total exactly.\n * Deferred + autocompact + free-space buckets follow.\n *\n * When `snapshot.exact` carries provider counts, the matching categories use\n * them (system, tools) and drop their `estimated` flag; everything else stays\n * on the heuristic.\n */\nexport function buildContextBreakdown(snapshot: ContextSnapshot): ContextBreakdown {\n const exact = snapshot.exact\n const systemExact = typeof exact?.system === 'number'\n\n // --- System-embedded sub-sections (heuristic) ----------------------------\n // The exact `count_tokens` system number covers the WHOLE system text. The\n // appended catalogs (rules / skills / MCP instructions / subagent defs) all\n // live inside it, so we size each heuristically and let the \"System prompt\"\n // (base doctrine) bucket absorb the remainder — keeping the system group's\n // sum exact while still attributing each section.\n const rulesTokens = snapshot.rulesBlock ? estimateTokens(snapshot.rulesBlock) : 0\n const rulesItems: ContextItem[] = (snapshot.rulesFiles ?? []).map(f => ({\n id: f.path,\n label: f.path,\n // Per-file size isn't separable from the rendered block; show the path only\n // (tokens 0, not estimated — there's nothing to estimate) so the row lists\n // sources without implying a per-file count or a `~` marker.\n tokens: 0,\n estimated: false,\n }))\n const skillsTokens = snapshot.skillsCatalog ? estimateTokens(snapshot.skillsCatalog) : 0\n const mcpInstructionsTokens = snapshot.mcpInstructions ? estimateTokens(snapshot.mcpInstructions) : 0\n const subagentTokens = snapshot.subagentDefs ? estimateTokens(snapshot.subagentDefs) : 0\n const embeddedSubsections = rulesTokens + skillsTokens + mcpInstructionsTokens + subagentTokens\n\n // System TOTAL (whole system text) — exact when available.\n const systemTotal = systemExact ? exact!.system! : estimateTokens(snapshot.system)\n // Base doctrine = system total minus the attributed sub-sections.\n const baseSystemTokens = Math.max(0, systemTotal - embeddedSubsections)\n\n // --- Native tool definitions (non-MCP, disclosed) ------------------------\n // When exact `systemAndTools` is available it covers BOTH native + MCP tools,\n // so we split that combined number proportionally by heuristic weight rather\n // than claiming exactness per sub-bucket.\n const nativeToolsEst = sumEstimate(snapshot.toolsJson)\n const mcpDisclosed = snapshot.mcpGroups.map(mcpGroupTokens)\n const mcpToolsEst = mcpDisclosed.reduce((a, g) => a + g.total, 0)\n\n let nativeToolsTokens = nativeToolsEst\n let mcpToolsTokens = mcpToolsEst\n let toolsEstimated = true\n if (systemExact && typeof exact?.systemAndTools === 'number') {\n const exactToolsTotal = Math.max(0, exact.systemAndTools - exact.system!)\n const estTotal = nativeToolsEst + mcpToolsEst\n if (estTotal > 0) {\n nativeToolsTokens = Math.round(exactToolsTotal * (nativeToolsEst / estTotal))\n mcpToolsTokens = exactToolsTotal - nativeToolsTokens\n }\n else {\n nativeToolsTokens = exactToolsTotal\n mcpToolsTokens = 0\n }\n toolsEstimated = false\n }\n\n // --- Live categories -----------------------------------------------------\n // Detailed split. The system-embedded sub-sections (rules / skills / MCP\n // instructions / subagent defs) are heuristic; the \"System prompt\" base\n // bucket carries the exact remainder so the whole system group still sums to\n // the exact system count. Tools/MCP-tools/conversation are split as before.\n const live: ContextCategory[] = []\n const push = (id: ContextCategoryId, tokens: number, estimated: boolean, items?: ContextItem[]) => {\n if (tokens <= 0 && !items?.length)\n return\n live.push({ id, label: LABELS[id], tokens: Math.max(0, tokens), estimated, ...(items?.length ? { items } : {}) })\n }\n\n // System group: base doctrine (exact remainder) + attributed sub-sections.\n push('systemPrompt', baseSystemTokens, !systemExact)\n push('rules', rulesTokens, true, rulesItems)\n push('skills', skillsTokens, true)\n push('mcpInstructions', mcpInstructionsTokens, true)\n push('subagentDefs', subagentTokens, true)\n // Tools.\n push('tools', nativeToolsTokens, toolsEstimated)\n push('mcpTools', mcpToolsTokens, toolsEstimated, mcpDisclosed.flatMap(g => g.items))\n\n // --- Conversation = remainder (keeps the bar summing to `used`) ----------\n const accountedStatic = live.reduce((a, c) => a + c.tokens, 0)\n const conversationTokens = Math.max(0, snapshot.used - accountedStatic)\n // Exact when both system AND tools were exactly counted — then the remainder\n // (used − exactSystem − exactTools) is itself exact, since `used` is the\n // provider-reported total.\n const conversationEstimated = !(systemExact && !toolsEstimated)\n push('conversation', conversationTokens, conversationEstimated)\n\n // --- Deferred buckets (not counted against `used`) -----------------------\n const deferred: ContextCategory[] = []\n const deferredNativeTools = sumEstimate(snapshot.deferredToolsJson)\n if (deferredNativeTools > 0) {\n deferred.push({ id: 'toolsDeferred', label: LABELS.toolsDeferred, tokens: deferredNativeTools, estimated: true, deferred: true })\n }\n const deferredMcp = snapshot.deferredMcpGroups.map(mcpGroupTokens)\n const deferredMcpTotal = deferredMcp.reduce((a, g) => a + g.total, 0)\n if (deferredMcpTotal > 0) {\n deferred.push({\n id: 'mcpToolsDeferred',\n label: LABELS.mcpToolsDeferred,\n tokens: deferredMcpTotal,\n estimated: true,\n deferred: true,\n items: deferredMcp.flatMap(g => g.items),\n })\n }\n\n // --- Autocompact buffer + free space (exact) -----------------------------\n if (snapshot.autocompactBuffer > 0) {\n deferred.push({ id: 'autocompactBuffer', label: LABELS.autocompactBuffer, tokens: snapshot.autocompactBuffer, estimated: false, deferred: true })\n }\n const free = Math.max(0, snapshot.effectiveWindow - snapshot.used)\n deferred.push({ id: 'freeSpace', label: LABELS.freeSpace, tokens: free, estimated: false, deferred: true })\n\n const categories = [...live, ...deferred]\n const hasEstimates = live.some(c => c.estimated && c.tokens > 0)\n const fraction = snapshot.effectiveWindow > 0\n ? Math.max(0, Math.min(1, snapshot.used / snapshot.effectiveWindow))\n : 0\n\n return {\n modelId: snapshot.modelId,\n used: snapshot.used,\n effectiveWindow: snapshot.effectiveWindow,\n fraction,\n categories,\n hasEstimates,\n ...(snapshot.usage ? { usage: snapshot.usage } : {}),\n ...(snapshot.extensionSections?.length ? { extensionSections: snapshot.extensionSections } : {}),\n ...(snapshot.activeSkills?.length ? { activeSkills: snapshot.activeSkills } : {}),\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"context-breakdown-Dzo25N45.js","names":[],"sources":["../src/chat/context-breakdown.ts"],"sourcesContent":["/**\n * Context-usage breakdown — pure transforms.\n *\n * Turns a {@link ContextSnapshot} (the raw pieces the agent assembled for a\n * run: system prompt, wire tools, deferred tools, MCP tools/instructions,\n * skills catalog, ...) plus the real last-turn token total into a categorized\n * {@link ContextBreakdown} the TUI panel / GUI popover render.\n *\n * No React, no node — a browser-context renderer imports these via\n * `zidane/chat/pure`. The snapshot is captured main-side (TUI direct, GUI in\n * `main/chat`) because building it reads provider/agent state; only the pure\n * shaping lives here.\n *\n * Accuracy: per-category counts are estimates when no provider `countTokens`\n * is available — the heuristic ({@link estimateTokens}, ~4 bytes/token) sizes\n * each static segment and the bar is reconciled so it always sums to the real\n * last-turn total (`conversation = realTotal - sum(static)`). When a provider\n * exposes exact counts (Anthropic, OpenAI), the caller passes them in via\n * {@link ContextSnapshot.exact} and the corresponding categories drop their\n * `estimated` flag. Exact buckets (free space, autocompact buffer) are never\n * estimated.\n */\n\nimport type { SessionTurn } from '../types'\nimport { estimateTokens } from '../compact/utils'\n\n/**\n * Stable identifiers for each breakdown category. Drives swatch color +\n * ordering in both UIs. `*Deferred` buckets are tools/MCP that exist but are\n * NOT currently on the wire (lazy disclosure); `autocompactBuffer` is the\n * reserved output headroom; `freeSpace` is the remaining window.\n */\nexport type ContextCategoryId\n = | 'systemPrompt'\n | 'rules'\n | 'tools'\n | 'mcpTools'\n | 'mcpInstructions'\n | 'skills'\n | 'subagentDefs'\n | 'conversation'\n | 'mcpToolsDeferred'\n | 'toolsDeferred'\n | 'autocompactBuffer'\n | 'freeSpace'\n\n/**\n * Exact last-turn token usage split (provider-reported — always exact). Drives\n * the cache breakdown shown below the categories. `cacheRead + cacheCreation +\n * input` equals the live context total (`used`).\n */\nexport interface ContextUsageSplit {\n /** Fresh, uncached input tokens this turn. */\n input: number\n /** Tokens served from the prompt cache (the cached prefix). */\n cacheRead: number\n /** Tokens written to the cache this turn. */\n cacheCreation: number\n /** Assistant output tokens (not part of the input window; informational). */\n output: number\n}\n\n/** One display row contributed by an extension to the context panel. */\nexport interface ContextExtensionRow {\n label: string\n value: string\n}\n\n/** Extension-owned context panel section. Core treats rows as opaque display data. */\nexport interface ContextExtensionSection {\n id: string\n label: string\n /** Optional section-title foreground color. UIs fall back to their theme accent. */\n accentColor?: string\n rows: readonly ContextExtensionRow[]\n}\n\n/** Data exposed to extension context-breakdown contributors. */\nexport interface ContextBreakdownContributionContext {\n turns: readonly SessionTurn[]\n}\n\n/** Extension hook for adding opaque rows to the context panel. */\nexport type ContextBreakdownContributor\n = (ctx: ContextBreakdownContributionContext) =>\n | ContextExtensionSection\n | readonly ContextExtensionSection[]\n | null\n | undefined\n | Promise<ContextExtensionSection | readonly ContextExtensionSection[] | null | undefined>\n\n/** One expandable child row under a category (e.g. a single MCP tool / memory file). */\nexport interface ContextItem {\n /** Stable-ish key for rendering (tool name, file path). */\n id: string\n /** Display label. */\n label: string\n /** Token count for this item. */\n tokens: number\n /** True when `tokens` came from the heuristic rather than an exact count. */\n estimated: boolean\n}\n\n/** One category row in the breakdown. */\nexport interface ContextCategory {\n id: ContextCategoryId\n /** Display label (e.g. \"Tool definitions\"). */\n label: string\n /** Token count for the category. */\n tokens: number\n /** True when `tokens` came from the heuristic rather than an exact count. */\n estimated: boolean\n /**\n * True for buckets that are NOT counted against the live window total\n * (deferred tools, free space, autocompact buffer) — the UI renders these\n * dimmed / below a divider, mirroring Claude Code's layout.\n */\n deferred?: boolean\n /** Optional expandable child rows (per MCP tool, per memory file, ...). */\n items?: ContextItem[]\n}\n\n/** The shaped, render-ready breakdown. */\nexport interface ContextBreakdown {\n /** Model id the snapshot was built for. */\n modelId: string\n /**\n * Real last-turn context size (provider-reported `input + cacheRead +\n * cacheCreation`) — the SAME number the footer/orb shows. The non-deferred\n * categories always sum to this.\n */\n used: number\n /** Effective window (raw - output reserve), the bar's denominator. */\n effectiveWindow: number\n /** `used / effectiveWindow`, clamped to [0, 1]. */\n fraction: number\n /** Live categories (sum to `used`) followed by deferred + free-space buckets. */\n categories: ContextCategory[]\n /** True when ANY live category is estimated (drives the panel's footnote). */\n hasEstimates: boolean\n /**\n * Exact last-turn usage split (cache read / write / fresh input / output),\n * when the host supplied it. Always exact — rendered as an informational\n * section below the categories. Absent when no usage is available yet.\n */\n usage?: ContextUsageSplit\n /** Extension-owned context panel sections. */\n extensionSections?: readonly ContextExtensionSection[]\n /**\n * Names of currently-active skills. Informational — an activated skill's body\n * lands in the conversation (tool-results), so its tokens are already counted\n * under Conversation; this just surfaces \"which skills are loaded\". Empty/absent\n * when none are active.\n */\n activeSkills?: string[]\n}\n\n/** Host-supplied options for `agent.getContextBreakdown`. */\nexport interface ContextBreakdownOptions {\n /**\n * Effective context window (`rawWindow - outputReserve`) for the active\n * model, resolved by the host via the chat model registry. Drives free-space\n * + fraction. When omitted, defaults to `used` (no headroom shown).\n */\n effectiveWindow?: number\n /**\n * Active model id — used for the exact `countTokens` call + the header label.\n * Only consulted on the PRE-RUN path (no snapshot yet); once a run has fired,\n * the snapshot's own model wins. When omitted, the provider default is used.\n */\n model?: string\n /** Tokens reserved for output (autocompact buffer). Defaults to 0. */\n autocompactBuffer?: number\n /**\n * Auto-compact threshold as a fraction of the window (0–1), when enabled.\n * When set, the \"Autocompact buffer\" bucket represents the ENTIRE tail from\n * the threshold to the end of the window (`(1 − threshold) × window`) rather\n * than just the reserved-output tokens — so the bar/row show exactly where\n * compaction fires. When omitted, `autocompactBuffer` is used as-is.\n */\n compactThreshold?: number\n /** Abort signal forwarded to the provider's `countTokens`. */\n signal?: AbortSignal\n}\n\n/** Tools grouped by MCP server, for the expandable per-server rows. */\nexport interface ContextMcpGroup {\n server: string\n /** Wire tool specs for this server (their JSON size is measured). */\n tools: { name: string, json: string }[]\n}\n\n/**\n * Exact per-segment counts from a provider's `countTokens`, when available.\n * Each field is the count of the cumulative payload up to and including that\n * segment, so categories are derived by differencing (see\n * {@link buildContextBreakdown}). Any subset may be present; missing fields\n * fall back to the heuristic for that category.\n */\nexport interface ContextExactCounts {\n /** Tokens for system prompt alone. */\n system?: number\n /** Tokens for system + tools (so tools = systemAndTools - system). */\n systemAndTools?: number\n /** Tokens for the full payload (system + tools + messages) = the real total. */\n full?: number\n}\n\n/**\n * Raw inputs captured at agent prompt-assembly time. Everything here is what\n * actually went on (or was withheld from) the wire for the last run.\n */\nexport interface ContextSnapshot {\n modelId: string\n /** Full rendered system prompt (marker-stripped, as sent). */\n system: string\n /**\n * Host-composed base system BEFORE the agent appended skills / searchable /\n * MCP-instructions catalogs. Includes the rules block. The \"System prompt\"\n * (base doctrine) bucket = base − rules − env-noise. Defaults to `system`\n * when not provided (back-compat).\n */\n baseSystem?: string\n /** Rendered AGENTS.md/CLAUDE.md rules block (a substring of `baseSystem`). */\n rulesBlock?: string\n /** Source files behind the rules block, for per-file rows. */\n rulesFiles?: { path: string, source: string }[]\n /** Wire tool specs currently disclosed to the model, as JSON strings. */\n toolsJson: string[]\n /** Lazy tool entries NOT currently disclosed (deferred), as JSON strings. */\n deferredToolsJson: string[]\n /** MCP tools grouped by server (disclosed ones). */\n mcpGroups: ContextMcpGroup[]\n /** Deferred (undisclosed) MCP tools grouped by server. */\n deferredMcpGroups: ContextMcpGroup[]\n /** Rendered MCP instructions block, if any. */\n mcpInstructions?: string\n /** Rendered skills catalog text, if any. */\n skillsCatalog?: string\n /** Searchable / subagent (spawn) tool definitions text, if any. */\n subagentDefs?: string\n /** Memory / rules files in context. */\n /** Real last-turn total (input + cacheRead + cacheCreation). */\n used: number\n /** Effective window (raw - output reserve). */\n effectiveWindow: number\n /** Tokens reserved for output (autocompact buffer). */\n autocompactBuffer: number\n /** Exact per-segment counts from the provider, when available. */\n exact?: ContextExactCounts\n /** Exact last-turn usage split, when available (passed straight through). */\n usage?: ContextUsageSplit\n /** Extension-owned context panel sections. */\n extensionSections?: readonly ContextExtensionSection[]\n /** Names of currently-active skills (passed straight through). */\n activeSkills?: string[]\n}\n\nconst LABELS: Record<ContextCategoryId, string> = {\n systemPrompt: 'System prompt',\n rules: 'Rules (AGENTS.md)',\n tools: 'Tool definitions',\n mcpTools: 'MCP tools',\n mcpInstructions: 'MCP instructions',\n skills: 'Skills',\n subagentDefs: 'Subagent definitions',\n conversation: 'Conversation',\n mcpToolsDeferred: 'MCP tools (deferred)',\n toolsDeferred: 'System tools (deferred)',\n autocompactBuffer: 'Autocompact buffer',\n freeSpace: 'Free space',\n}\n\n/** Sum the heuristic token sizes of a list of strings. */\nfunction sumEstimate(parts: readonly string[]): number {\n let total = 0\n for (const p of parts)\n total += estimateTokens(p)\n return total\n}\n\nfunction mcpGroupTokens(group: ContextMcpGroup): { total: number, items: ContextItem[] } {\n const items: ContextItem[] = group.tools.map(t => ({\n id: `${group.server}:${t.name}`,\n label: t.name,\n tokens: estimateTokens(t.json),\n estimated: true,\n }))\n let total = 0\n for (const it of items)\n total += it.tokens\n return { total, items }\n}\n\n/**\n * Shape a {@link ContextSnapshot} into a render-ready {@link ContextBreakdown}.\n *\n * Live categories (system prompt, tools, MCP tools/instructions, skills,\n * memory, subagent defs, conversation) sum to `snapshot.used` — `conversation`\n * absorbs the remainder so the bar matches the provider-reported total exactly.\n * Deferred + autocompact + free-space buckets follow.\n *\n * When `snapshot.exact` carries provider counts, the matching categories use\n * them (system, tools) and drop their `estimated` flag; everything else stays\n * on the heuristic.\n */\nexport function buildContextBreakdown(snapshot: ContextSnapshot): ContextBreakdown {\n const exact = snapshot.exact\n const systemExact = typeof exact?.system === 'number'\n\n // --- System-embedded sub-sections (heuristic) ----------------------------\n // The exact `count_tokens` system number covers the WHOLE system text. The\n // appended catalogs (rules / skills / MCP instructions / subagent defs) all\n // live inside it, so we size each heuristically and let the \"System prompt\"\n // (base doctrine) bucket absorb the remainder — keeping the system group's\n // sum exact while still attributing each section.\n const rulesTokens = snapshot.rulesBlock ? estimateTokens(snapshot.rulesBlock) : 0\n const rulesItems: ContextItem[] = (snapshot.rulesFiles ?? []).map(f => ({\n id: f.path,\n label: f.path,\n // Per-file size isn't separable from the rendered block; show the path only\n // (tokens 0, not estimated — there's nothing to estimate) so the row lists\n // sources without implying a per-file count or a `~` marker.\n tokens: 0,\n estimated: false,\n }))\n const skillsTokens = snapshot.skillsCatalog ? estimateTokens(snapshot.skillsCatalog) : 0\n const mcpInstructionsTokens = snapshot.mcpInstructions ? estimateTokens(snapshot.mcpInstructions) : 0\n const subagentTokens = snapshot.subagentDefs ? estimateTokens(snapshot.subagentDefs) : 0\n const embeddedSubsections = rulesTokens + skillsTokens + mcpInstructionsTokens + subagentTokens\n\n // System TOTAL (whole system text) — exact when available.\n const systemTotal = systemExact ? exact!.system! : estimateTokens(snapshot.system)\n // Base doctrine = system total minus the attributed sub-sections.\n const baseSystemTokens = Math.max(0, systemTotal - embeddedSubsections)\n\n // --- Native tool definitions (non-MCP, disclosed) ------------------------\n // When exact `systemAndTools` is available it covers BOTH native + MCP tools,\n // so we split that combined number proportionally by heuristic weight rather\n // than claiming exactness per sub-bucket.\n const nativeToolsEst = sumEstimate(snapshot.toolsJson)\n const mcpDisclosed = snapshot.mcpGroups.map(mcpGroupTokens)\n const mcpToolsEst = mcpDisclosed.reduce((a, g) => a + g.total, 0)\n\n let nativeToolsTokens = nativeToolsEst\n let mcpToolsTokens = mcpToolsEst\n let toolsEstimated = true\n if (systemExact && typeof exact?.systemAndTools === 'number') {\n const exactToolsTotal = Math.max(0, exact.systemAndTools - exact.system!)\n const estTotal = nativeToolsEst + mcpToolsEst\n if (estTotal > 0) {\n nativeToolsTokens = Math.round(exactToolsTotal * (nativeToolsEst / estTotal))\n mcpToolsTokens = exactToolsTotal - nativeToolsTokens\n }\n else {\n nativeToolsTokens = exactToolsTotal\n mcpToolsTokens = 0\n }\n toolsEstimated = false\n }\n\n // --- Live categories -----------------------------------------------------\n // Detailed split. The system-embedded sub-sections (rules / skills / MCP\n // instructions / subagent defs) are heuristic; the \"System prompt\" base\n // bucket carries the exact remainder so the whole system group still sums to\n // the exact system count. Tools/MCP-tools/conversation are split as before.\n const live: ContextCategory[] = []\n const push = (id: ContextCategoryId, tokens: number, estimated: boolean, items?: ContextItem[]) => {\n if (tokens <= 0 && !items?.length)\n return\n live.push({ id, label: LABELS[id], tokens: Math.max(0, tokens), estimated, ...(items?.length ? { items } : {}) })\n }\n\n // System group: base doctrine (exact remainder) + attributed sub-sections.\n push('systemPrompt', baseSystemTokens, !systemExact)\n push('rules', rulesTokens, true, rulesItems)\n push('skills', skillsTokens, true)\n push('mcpInstructions', mcpInstructionsTokens, true)\n push('subagentDefs', subagentTokens, true)\n // Tools.\n push('tools', nativeToolsTokens, toolsEstimated)\n push('mcpTools', mcpToolsTokens, toolsEstimated, mcpDisclosed.flatMap(g => g.items))\n\n // --- Conversation = remainder (keeps the bar summing to `used`) ----------\n const accountedStatic = live.reduce((a, c) => a + c.tokens, 0)\n const conversationTokens = Math.max(0, snapshot.used - accountedStatic)\n // Exact when both system AND tools were exactly counted — then the remainder\n // (used − exactSystem − exactTools) is itself exact, since `used` is the\n // provider-reported total.\n const conversationEstimated = !(systemExact && !toolsEstimated)\n push('conversation', conversationTokens, conversationEstimated)\n\n // --- Deferred buckets (not counted against `used`) -----------------------\n const deferred: ContextCategory[] = []\n const deferredNativeTools = sumEstimate(snapshot.deferredToolsJson)\n if (deferredNativeTools > 0) {\n deferred.push({ id: 'toolsDeferred', label: LABELS.toolsDeferred, tokens: deferredNativeTools, estimated: true, deferred: true })\n }\n const deferredMcp = snapshot.deferredMcpGroups.map(mcpGroupTokens)\n const deferredMcpTotal = deferredMcp.reduce((a, g) => a + g.total, 0)\n if (deferredMcpTotal > 0) {\n deferred.push({\n id: 'mcpToolsDeferred',\n label: LABELS.mcpToolsDeferred,\n tokens: deferredMcpTotal,\n estimated: true,\n deferred: true,\n items: deferredMcp.flatMap(g => g.items),\n })\n }\n\n // --- Autocompact buffer + free space (exact) -----------------------------\n if (snapshot.autocompactBuffer > 0) {\n deferred.push({ id: 'autocompactBuffer', label: LABELS.autocompactBuffer, tokens: snapshot.autocompactBuffer, estimated: false, deferred: true })\n }\n const free = Math.max(0, snapshot.effectiveWindow - snapshot.used)\n deferred.push({ id: 'freeSpace', label: LABELS.freeSpace, tokens: free, estimated: false, deferred: true })\n\n const categories = [...live, ...deferred]\n const hasEstimates = live.some(c => c.estimated && c.tokens > 0)\n const fraction = snapshot.effectiveWindow > 0\n ? Math.max(0, Math.min(1, snapshot.used / snapshot.effectiveWindow))\n : 0\n\n return {\n modelId: snapshot.modelId,\n used: snapshot.used,\n effectiveWindow: snapshot.effectiveWindow,\n fraction,\n categories,\n hasEstimates,\n ...(snapshot.usage ? { usage: snapshot.usage } : {}),\n ...(snapshot.extensionSections?.length ? { extensionSections: snapshot.extensionSections } : {}),\n ...(snapshot.activeSkills?.length ? { activeSkills: snapshot.activeSkills } : {}),\n }\n}\n"],"mappings":";;AAkQA,MAAM,SAA4C;CAChD,cAAc;CACd,OAAO;CACP,OAAO;CACP,UAAU;CACV,iBAAiB;CACjB,QAAQ;CACR,cAAc;CACd,cAAc;CACd,kBAAkB;CAClB,eAAe;CACf,mBAAmB;CACnB,WAAW;AACb;;AAGA,SAAS,YAAY,OAAkC;CACrD,IAAI,QAAQ;CACZ,KAAK,MAAM,KAAK,OACd,SAAS,eAAe,CAAC;CAC3B,OAAO;AACT;AAEA,SAAS,eAAe,OAAiE;CACvF,MAAM,QAAuB,MAAM,MAAM,KAAI,OAAM;EACjD,IAAI,GAAG,MAAM,OAAO,GAAG,EAAE;EACzB,OAAO,EAAE;EACT,QAAQ,eAAe,EAAE,IAAI;EAC7B,WAAW;CACb,EAAE;CACF,IAAI,QAAQ;CACZ,KAAK,MAAM,MAAM,OACf,SAAS,GAAG;CACd,OAAO;EAAE;EAAO;CAAM;AACxB;;;;;;;;;;;;;AAcA,SAAgB,sBAAsB,UAA6C;CACjF,MAAM,QAAQ,SAAS;CACvB,MAAM,cAAc,OAAO,OAAO,WAAW;CAQ7C,MAAM,cAAc,SAAS,aAAa,eAAe,SAAS,UAAU,IAAI;CAChF,MAAM,cAA6B,SAAS,cAAc,CAAC,EAAA,CAAG,KAAI,OAAM;EACtE,IAAI,EAAE;EACN,OAAO,EAAE;EAIT,QAAQ;EACR,WAAW;CACb,EAAE;CACF,MAAM,eAAe,SAAS,gBAAgB,eAAe,SAAS,aAAa,IAAI;CACvF,MAAM,wBAAwB,SAAS,kBAAkB,eAAe,SAAS,eAAe,IAAI;CACpG,MAAM,iBAAiB,SAAS,eAAe,eAAe,SAAS,YAAY,IAAI;CACvF,MAAM,sBAAsB,cAAc,eAAe,wBAAwB;CAGjF,MAAM,cAAc,cAAc,MAAO,SAAU,eAAe,SAAS,MAAM;CAEjF,MAAM,mBAAmB,KAAK,IAAI,GAAG,cAAc,mBAAmB;CAMtE,MAAM,iBAAiB,YAAY,SAAS,SAAS;CACrD,MAAM,eAAe,SAAS,UAAU,IAAI,cAAc;CAC1D,MAAM,cAAc,aAAa,QAAQ,GAAG,MAAM,IAAI,EAAE,OAAO,CAAC;CAEhE,IAAI,oBAAoB;CACxB,IAAI,iBAAiB;CACrB,IAAI,iBAAiB;CACrB,IAAI,eAAe,OAAO,OAAO,mBAAmB,UAAU;EAC5D,MAAM,kBAAkB,KAAK,IAAI,GAAG,MAAM,iBAAiB,MAAM,MAAO;EACxE,MAAM,WAAW,iBAAiB;EAClC,IAAI,WAAW,GAAG;GAChB,oBAAoB,KAAK,MAAM,mBAAmB,iBAAiB,SAAS;GAC5E,iBAAiB,kBAAkB;EACrC,OACK;GACH,oBAAoB;GACpB,iBAAiB;EACnB;EACA,iBAAiB;CACnB;CAOA,MAAM,OAA0B,CAAC;CACjC,MAAM,QAAQ,IAAuB,QAAgB,WAAoB,UAA0B;EACjG,IAAI,UAAU,KAAK,CAAC,OAAO,QACzB;EACF,KAAK,KAAK;GAAE;GAAI,OAAO,OAAO;GAAK,QAAQ,KAAK,IAAI,GAAG,MAAM;GAAG;GAAW,GAAI,OAAO,SAAS,EAAE,MAAM,IAAI,CAAC;EAAG,CAAC;CAClH;CAGA,KAAK,gBAAgB,kBAAkB,CAAC,WAAW;CACnD,KAAK,SAAS,aAAa,MAAM,UAAU;CAC3C,KAAK,UAAU,cAAc,IAAI;CACjC,KAAK,mBAAmB,uBAAuB,IAAI;CACnD,KAAK,gBAAgB,gBAAgB,IAAI;CAEzC,KAAK,SAAS,mBAAmB,cAAc;CAC/C,KAAK,YAAY,gBAAgB,gBAAgB,aAAa,SAAQ,MAAK,EAAE,KAAK,CAAC;CAGnF,MAAM,kBAAkB,KAAK,QAAQ,GAAG,MAAM,IAAI,EAAE,QAAQ,CAAC;CAM7D,KAAK,gBALsB,KAAK,IAAI,GAAG,SAAS,OAAO,eAKjB,GAAG,EADT,eAAe,CAAC,eACc;CAG9D,MAAM,WAA8B,CAAC;CACrC,MAAM,sBAAsB,YAAY,SAAS,iBAAiB;CAClE,IAAI,sBAAsB,GACxB,SAAS,KAAK;EAAE,IAAI;EAAiB,OAAO,OAAO;EAAe,QAAQ;EAAqB,WAAW;EAAM,UAAU;CAAK,CAAC;CAElI,MAAM,cAAc,SAAS,kBAAkB,IAAI,cAAc;CACjE,MAAM,mBAAmB,YAAY,QAAQ,GAAG,MAAM,IAAI,EAAE,OAAO,CAAC;CACpE,IAAI,mBAAmB,GACrB,SAAS,KAAK;EACZ,IAAI;EACJ,OAAO,OAAO;EACd,QAAQ;EACR,WAAW;EACX,UAAU;EACV,OAAO,YAAY,SAAQ,MAAK,EAAE,KAAK;CACzC,CAAC;CAIH,IAAI,SAAS,oBAAoB,GAC/B,SAAS,KAAK;EAAE,IAAI;EAAqB,OAAO,OAAO;EAAmB,QAAQ,SAAS;EAAmB,WAAW;EAAO,UAAU;CAAK,CAAC;CAElJ,MAAM,OAAO,KAAK,IAAI,GAAG,SAAS,kBAAkB,SAAS,IAAI;CACjE,SAAS,KAAK;EAAE,IAAI;EAAa,OAAO,OAAO;EAAW,QAAQ;EAAM,WAAW;EAAO,UAAU;CAAK,CAAC;CAE1G,MAAM,aAAa,CAAC,GAAG,MAAM,GAAG,QAAQ;CACxC,MAAM,eAAe,KAAK,MAAK,MAAK,EAAE,aAAa,EAAE,SAAS,CAAC;CAC/D,MAAM,WAAW,SAAS,kBAAkB,IACxC,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,SAAS,OAAO,SAAS,eAAe,CAAC,IACjE;CAEJ,OAAO;EACL,SAAS,SAAS;EAClB,MAAM,SAAS;EACf,iBAAiB,SAAS;EAC1B;EACA;EACA;EACA,GAAI,SAAS,QAAQ,EAAE,OAAO,SAAS,MAAM,IAAI,CAAC;EAClD,GAAI,SAAS,mBAAmB,SAAS,EAAE,mBAAmB,SAAS,kBAAkB,IAAI,CAAC;EAC9F,GAAI,SAAS,cAAc,SAAS,EAAE,cAAc,SAAS,aAAa,IAAI,CAAC;CACjF;AACF"}
@@ -1,4 +1,4 @@
1
- import { Ll as Logger } from "../index-B8UtiZy7.js";
1
+ import { Ul as Logger } from "../index-C6Il4kBD.js";
2
2
  import { s as ExecResult } from "../types-BDccavwj.js";
3
3
  import { t as SandboxProvider } from "../sandbox-CgjG2VvQ.js";
4
4
  import { Buffer } from "node:buffer";
@@ -16,6 +16,8 @@ interface DaytonaExecuteResponse {
16
16
  }
17
17
  interface DaytonaFileEntry {
18
18
  name: string;
19
+ /** Present in the Daytona SDK's FileInfo; used for the `/` dir suffix. */
20
+ isDir?: boolean;
19
21
  }
20
22
  interface DaytonaSandbox {
21
23
  id: string;
@@ -1 +1 @@
1
- {"version":3,"file":"daytona.d.ts","names":[],"sources":["../../src/contexts/daytona.ts"],"mappings":";;;;;;UA6BU,sBAAA;EACR,QAAA;EACA,MAAA;EACA,MAAA;EACA,MAAA;EACA,MAAA;EACA,SAAA;IAAc,MAAA;EAAA;AAAA;AAAA,UAGN,gBAAA;EACR,IAAI;AAAA;AAAA,UAGI,cAAA;EACR,EAAA;EACA,KAAA;EACA,OAAA;IACE,cAAA,GAAiB,OAAA,UAAiB,GAAA,WAAc,GAAA,GAAM,MAAA,kBAAwB,OAAA,cAAqB,OAAA,CAAQ,sBAAA;EAAA;EAE7G,EAAA;IACE,YAAA,GAAe,IAAA,aAAiB,OAAA,CAAQ,UAAA,GAAa,WAAA;MAAgB,QAAA,GAAW,QAAA,GAAW,cAAA;IAAA;IAC3F,UAAA,GAAa,OAAA,EAAS,UAAA,GAAa,MAAA,EAAQ,IAAA,aAAiB,OAAA;IAC5D,SAAA,GAAY,IAAA,aAAiB,OAAA,CAAQ,gBAAA;EAAA;EAEvC,UAAA,SAAmB,OAAA;EACnB,KAAA,IAAS,OAAA,cAAqB,OAAA;EAC9B,MAAA,GAAS,OAAA,cAAqB,OAAA;EAC9B,IAAA,IAAQ,OAAA,cAAqB,OAAA;EAC7B,OAAA,SAAgB,OAAA;AAAA;AAAA,UASR,gBAAA;EACR,GAAA;EACA,MAAA;EACA,IAAA;EACA,GAAA;EAAA,CACC,GAAA;AAAA;AAAA,UAGO,mBAAA;EACR,QAAA;EACA,QAAA;EACA,KAAA;EACA,SAAA,GAAY,gBAAA;EACZ,OAAA,GAAU,MAAM;EAAA,CACf,GAAA;AAAA;AAAA,KAcS,gBAAA;AAAA,UAEK,sBAAA;EA9C8B;EAgD7C,MAAA;EA/CE;EAiDF,MAAA;EAjD+B;EAmD/B,MAAA;EAjDA;;;;;;EAwDA,SAAA;EAtD8B;;;;;;;AAEP;AAAA;;;;;;;;EAqEvB,WAAA;EAvDY;EAyDZ,QAAA;EAtDQ;EAwDR,QAAA;;EAEA,KAAA;EAzDA;EA2DA,SAAA,GAAY,gBAAA;EAzDZ;EA2DA,GAAA;EA1DY;EA4DZ,GAAA,GAAM,MAAA;EA3DI;EA6DV,MAAA,GAAS,MAAA;EA5DG;EA8DZ,uBAAA;EAhDU;EAkDV,OAAA;IAAY,IAAA;IAAc,OAAA;EAAA;EAhDX;;;;;;;;;EA0Df,SAAA,GAAY,gBAAA;AAAA;AAAA,UAUG,uBAAA;EAvDf;EAyDA,cAAA;EAtCA;EAwCA,gBAAA,SAAyB,OAAA;EApCzB;EAsCA,GAAA;EApCY;EAsCZ,KAAA,IAAS,EAAA,aAAe,OAAO;AAAA;AAAA,iBAGjB,iBAAA,CACd,SAAA,EAAW,MAAA,8BACX,WAAA,EAAa,MAAA,+BACZ,MAAA;AAAA,iBAKa,0BAAA,CAA2B,QAAA,EAAU,sBAAA,GAAyB,UAAU;AAAA,iBAmDxE,0BAAA,CACd,aAAA,EAAe,MAAA,mBACf,OAAA,EAAS,sBAAA,EACT,OAAA,EAAS,MAAA,+BACR,mBAAA;AAAA,iBA8BmB,mBAAA,CACpB,OAAA,EAAS,cAAA,EACT,MAAA,EAAQ,MAAA,EACR,IAAA,EAAM,uBAAA,GACL,OAAA;AAAA,iBA2BmB,iBAAA,CACpB,OAAA,EAAS,cAAA,EACT,MAAA,EAAQ,MAAA,EACR,IAAA;EAAQ,IAAA;EAAc,OAAA;EAAiB,GAAA;EAAc,OAAA,GAAU,MAAA;AAAA,IAC9D,OAAA;AAAA,iBA4Ba,qBAAA,CAAsB,OAAA,GAAS,sBAAA,GAA8B,eAAe"}
1
+ {"version":3,"file":"daytona.d.ts","names":[],"sources":["../../src/contexts/daytona.ts"],"mappings":";;;;;;UA6BU,sBAAA;EACR,QAAA;EACA,MAAA;EACA,MAAA;EACA,MAAA;EACA,MAAA;EACA,SAAA;IAAc,MAAA;EAAA;AAAA;AAAA,UAGN,gBAAA;EACR,IAAA;EAYkC;EAVlC,KAAK;AAAA;AAAA,UAGG,cAAA;EACR,EAAA;EACA,KAAA;EACA,OAAA;IACE,cAAA,GAAiB,OAAA,UAAiB,GAAA,WAAc,GAAA,GAAM,MAAA,kBAAwB,OAAA,cAAqB,OAAA,CAAQ,sBAAA;EAAA;EAE7G,EAAA;IACE,YAAA,GAAe,IAAA,aAAiB,OAAA,CAAQ,UAAA,GAAa,WAAA;MAAgB,QAAA,GAAW,QAAA,GAAW,cAAA;IAAA;IAC3F,UAAA,GAAa,OAAA,EAAS,UAAA,GAAa,MAAA,EAAQ,IAAA,aAAiB,OAAA;IAC5D,SAAA,GAAY,IAAA,aAAiB,OAAA,CAAQ,gBAAA;EAAA;EAEvC,UAAA,SAAmB,OAAA;EACnB,KAAA,IAAS,OAAA,cAAqB,OAAA;EAC9B,MAAA,GAAS,OAAA,cAAqB,OAAA;EAC9B,IAAA,IAAQ,OAAA,cAAqB,OAAA;EAC7B,OAAA,SAAgB,OAAA;AAAA;AAAA,UASR,gBAAA;EACR,GAAA;EACA,MAAA;EACA,IAAA;EACA,GAAA;EAAA,CACC,GAAA;AAAA;AAAA,UAGO,mBAAA;EACR,QAAA;EACA,QAAA;EACA,KAAA;EACA,SAAA,GAAY,gBAAA;EACZ,OAAA,GAAU,MAAM;EAAA,CACf,GAAA;AAAA;AAAA,KAcS,gBAAA;AAAA,UAEK,sBAAA;EA7Cb;EA+CF,MAAA;EA/C+B;EAiD/B,MAAA;EA/CA;EAiDA,MAAA;EAhDA;;;;;;EAuDA,SAAA;EArDQ;;;;;AACe;AAAA;;;;;;;;;;EAqEvB,WAAA;EApDQ;EAsDR,QAAA;;EAEA,QAAA;EAvDA;EAyDA,KAAA;EAvDA;EAyDA,SAAA,GAAY,gBAAA;EAxDA;EA0DZ,GAAA;EAzDU;EA2DV,GAAA,GAAM,MAAA;EA1DM;EA4DZ,MAAA,GAAS,MAAA;EA9CC;EAgDV,uBAAA;;EAEA,OAAA;IAAY,IAAA;IAAc,OAAA;EAAA;;;;;;;;;;EAU1B,SAAA,GAAY,gBAAA;AAAA;AAAA,UAUG,uBAAA;EApCf;EAsCA,cAAA;EAlCA;EAoCA,gBAAA,SAAyB,OAAA;EAlCb;EAoCZ,GAAA;EAhCA;EAkCA,KAAA,IAAS,EAAA,aAAe,OAAO;AAAA;AAAA,iBAGjB,iBAAA,CACd,SAAA,EAAW,MAAA,8BACX,WAAA,EAAa,MAAA,+BACZ,MAAA;AAAA,iBAKa,0BAAA,CAA2B,QAAA,EAAU,sBAAA,GAAyB,UAAU;AAAA,iBAmDxE,0BAAA,CACd,aAAA,EAAe,MAAA,mBACf,OAAA,EAAS,sBAAA,EACT,OAAA,EAAS,MAAA,+BACR,mBAAA;AAAA,iBA8BmB,mBAAA,CACpB,OAAA,EAAS,cAAA,EACT,MAAA,EAAQ,MAAA,EACR,IAAA,EAAM,uBAAA,GACL,OAAA;AAAA,iBA2BmB,iBAAA,CACpB,OAAA,EAAS,cAAA,EACT,MAAA,EAAQ,MAAA,EACR,IAAA;EAAQ,IAAA;EAAc,OAAA;EAAiB,GAAA;EAAc,OAAA,GAAU,MAAA;AAAA,IAC9D,OAAA;AAAA,iBA4Ba,qBAAA,CAAsB,OAAA,GAAS,sBAAA,GAA8B,eAAe"}
@@ -51,7 +51,7 @@ function takeString(value) {
51
51
  return typeof value === "string" && value.length > 0 ? value : void 0;
52
52
  }
53
53
  function resolveDaytonaCreateParams(sandboxConfig, options, envVars) {
54
- const reserved = new Set([
54
+ const reserved = /* @__PURE__ */ new Set([
55
55
  "provider",
56
56
  "apiKey",
57
57
  "apiUrl",
@@ -262,7 +262,7 @@ function createDaytonaProvider(options = {}) {
262
262
  await get(sandboxId).sandbox.fs.uploadFile(Buffer.from(content), path);
263
263
  },
264
264
  async listFiles(sandboxId, path) {
265
- return (await get(sandboxId).sandbox.fs.listFiles(path)).map((e) => e.name);
265
+ return (await get(sandboxId).sandbox.fs.listFiles(path)).map((e) => e.isDir ? `${e.name}/` : e.name);
266
266
  },
267
267
  async destroy(sandboxId) {
268
268
  const entry = live.get(sandboxId);
@@ -1 +1 @@
1
- {"version":3,"file":"daytona.js","names":[],"sources":["../../src/contexts/daytona.ts"],"sourcesContent":["/**\n * Daytona sandbox provider.\n *\n * Implements {@link SandboxProvider} on top of Daytona sandboxes. Pair it with\n * {@link createSandboxContext} to run an agent's shell + filesystem tools\n * inside a Daytona sandbox:\n *\n * ```ts\n * import { createSandboxContext } from 'zidane/contexts'\n * import { createDaytonaProvider } from 'zidane/contexts/daytona'\n *\n * const execution = createSandboxContext(createDaytonaProvider())\n * ```\n *\n * Requires `@daytona/sdk` as an optional peer dependency:\n * `bun add @daytona/sdk`.\n */\n\nimport type { Logger } from '../logger'\nimport type { SandboxProvider } from './sandbox'\nimport type { ExecResult, SpawnConfig } from './types'\nimport { Buffer } from 'node:buffer'\nimport { consoleSink, createLogger } from '../logger'\n\n// ---------------------------------------------------------------------------\n// Minimal structural views of the `@daytona/sdk` surface we touch. Declared\n// locally so zidane typechecks without the optional peer dependency installed.\n// ---------------------------------------------------------------------------\n\ninterface DaytonaExecuteResponse {\n exitCode?: number | null\n result?: string | null\n output?: string | null\n stdout?: string | null\n stderr?: string | null\n artifacts?: { stdout?: string | null }\n}\n\ninterface DaytonaFileEntry {\n name: string\n}\n\ninterface DaytonaSandbox {\n id: string\n state?: string\n process: {\n executeCommand: (command: string, cwd?: string, env?: Record<string, string>, timeout?: number) => Promise<DaytonaExecuteResponse>\n }\n fs: {\n downloadFile: (path: string) => Promise<Uint8Array | ArrayBuffer | { toString: (encoding?: BufferEncoding) => string }>\n uploadFile: (content: Uint8Array | Buffer, path: string) => Promise<unknown>\n listFiles: (path: string) => Promise<DaytonaFileEntry[]>\n }\n getWorkDir?: () => Promise<string>\n start?: (timeout?: number) => Promise<unknown>\n delete: (timeout?: number) => Promise<unknown>\n stop?: (timeout?: number) => Promise<unknown>\n archive?: () => Promise<unknown>\n}\n\ninterface DaytonaClientConfig {\n apiKey?: string\n apiUrl?: string\n target?: string\n}\n\ninterface DaytonaResources {\n cpu?: number\n memory?: number\n disk?: number\n gpu?: number\n [key: string]: unknown\n}\n\ninterface DaytonaCreateParams {\n language?: string\n snapshot?: string\n image?: unknown\n resources?: DaytonaResources\n envVars?: Record<string, string>\n [key: string]: unknown\n}\n\ninterface DaytonaClient {\n create: (params?: DaytonaCreateParams) => Promise<DaytonaSandbox>\n get: (sandboxIdOrName: string) => Promise<DaytonaSandbox>\n}\n\ntype DaytonaConstructor = new (config?: DaytonaClientConfig) => DaytonaClient\n\n// ---------------------------------------------------------------------------\n// Provider options\n// ---------------------------------------------------------------------------\n\nexport type DaytonaOnDestroy = 'delete' | 'kill' | 'stop' | 'pause' | 'archive' | 'leave'\n\nexport interface DaytonaProviderOptions {\n /** Daytona API key. Falls back to the `DAYTONA_API_KEY` env var when omitted. */\n apiKey?: string\n /** Daytona API URL. Falls back to `DAYTONA_API_URL`, then Daytona's hosted default. */\n apiUrl?: string\n /** Daytona target / region. Falls back to `DAYTONA_TARGET`, then the org default. */\n target?: string\n /**\n * Attach to a pre-existing sandbox by id or name instead of creating a fresh\n * one. By default the provider treats attached sandboxes as externally owned\n * and leaves them running on destroy/failure (set {@link ownAttached} to take\n * over the lifecycle).\n */\n sandboxId?: string\n /**\n * Take ownership of an attached ({@link sandboxId}) sandbox's lifecycle, so\n * `destroy()` applies {@link onDestroy} to it (stop / archive / delete)\n * instead of leaving it running.\n *\n * The intended caller is a durable host that *owns* the sandbox across\n * invocations: mint it once, journal its id, re-attach via `sandboxId` on\n * every replay/resume, and clean it up at the end of the logical run. Without\n * this an attached sandbox can never be stopped/deleted through the provider —\n * a leak for host-owned sandboxes. No effect when creating a fresh sandbox\n * (those are always owned). Spawn-time failures (readiness timeout, pregame\n * error) still never delete a pre-existing attached sandbox — only sandboxes\n * this provider *created* are auto-cleaned on spawn failure.\n *\n * Default: `false`.\n */\n ownAttached?: boolean\n /** Daytona snapshot used when creating a fresh sandbox. */\n snapshot?: string\n /** Runtime used for Daytona's code tooling. Shell execution works either way. */\n language?: string\n /** Daytona image/declarative builder input used when creating a fresh sandbox. */\n image?: unknown\n /** Daytona sandbox resources. */\n resources?: DaytonaResources\n /** Default working directory, applied when a spawn doesn't set `SpawnConfig.cwd`. */\n cwd?: string\n /** Environment variables baked into the sandbox and re-sent on every command. */\n env?: Record<string, string>\n /** Logger for provider-level lifecycle lines. */\n logger?: Logger\n /** Total deadline, in seconds, for the post-create readiness probe. Defaults to `60`. */\n readinessTimeoutSeconds?: number\n /** Setup script to upload and run once the sandbox is ready. */\n pregame?: { name: string, content: string }\n /**\n * What to do with an owned sandbox on destroy:\n *\n * - `'delete'` / `'kill'` (default) — call `sandbox.delete()`.\n * - `'stop'` / `'pause'` — call `sandbox.stop()` so Daytona preserves state\n * according to the runner's stop semantics.\n * - `'archive'` — stop, then archive for longer-term persistence.\n * - `'leave'` — make no API call.\n */\n onDestroy?: DaytonaOnDestroy\n}\n\n/** Per-probe timeout for the readiness `true` command. */\nconst READINESS_PROBE_TIMEOUT_SECONDS = 5\n/** Pause between readiness probes while the sandbox is still warming up. */\nconst READINESS_BACKOFF_MS = 250\n/** Setup scripts commonly install dependencies, so give them a generous cap. */\nconst PREGAME_TIMEOUT_SECONDS = 30 * 60\n\nexport interface DaytonaReadinessOptions {\n /** Total deadline in seconds. `<= 0` disables the probe entirely. */\n timeoutSeconds: number\n /** Optional cleanup for owned sandboxes that never become ready. */\n cleanupOnTimeout?: () => Promise<void>\n /** Clock source — injectable for deterministic tests. */\n now?: () => number\n /** Sleep between probes — injectable for deterministic tests. */\n sleep?: (ms: number) => Promise<void>\n}\n\nexport function resolveDaytonaEnv(\n optionEnv: Record<string, string> | undefined,\n perSpawnEnv: Record<string, string> | undefined,\n): Record<string, string> | undefined {\n const merged = { ...optionEnv, ...perSpawnEnv }\n return Object.keys(merged).length > 0 ? merged : undefined\n}\n\nexport function normalizeDaytonaExecResult(response: DaytonaExecuteResponse): ExecResult {\n return {\n stdout: response.stdout ?? response.artifacts?.stdout ?? response.result ?? response.output ?? '',\n stderr: response.stderr ?? '',\n exitCode: typeof response.exitCode === 'number' ? response.exitCode : 0,\n }\n}\n\nfunction normalizeDaytonaExecError(error: unknown): ExecResult {\n const err = error as { exitCode?: unknown, stdout?: unknown, stderr?: unknown, result?: unknown, output?: unknown, message?: unknown }\n if (typeof err.exitCode === 'number') {\n return {\n stdout: typeof err.stdout === 'string'\n ? err.stdout\n : typeof err.result === 'string'\n ? err.result\n : typeof err.output === 'string'\n ? err.output\n : '',\n stderr: typeof err.stderr === 'string' ? err.stderr : typeof err.message === 'string' ? err.message : '',\n exitCode: err.exitCode,\n }\n }\n return { stdout: '', stderr: error instanceof Error ? error.message : String(error), exitCode: 124 }\n}\n\nfunction bufferToString(value: Uint8Array | ArrayBuffer | { toString: (encoding?: BufferEncoding) => string }): string {\n if (Buffer.isBuffer(value))\n return value.toString('utf8')\n if (value instanceof ArrayBuffer)\n return new TextDecoder().decode(new Uint8Array(value))\n if (value instanceof Uint8Array)\n return new TextDecoder().decode(value)\n return value.toString('utf8')\n}\n\nfunction toClientConfig(apiKey?: string, apiUrl?: string, target?: string): DaytonaClientConfig | undefined {\n const config: DaytonaClientConfig = {}\n if (apiKey !== undefined)\n config.apiKey = apiKey\n if (apiUrl !== undefined)\n config.apiUrl = apiUrl\n if (target !== undefined)\n config.target = target\n return Object.keys(config).length > 0 ? config : undefined\n}\n\nfunction takeString(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined\n}\n\nexport function resolveDaytonaCreateParams(\n sandboxConfig: Record<string, unknown>,\n options: DaytonaProviderOptions,\n envVars: Record<string, string> | undefined,\n): DaytonaCreateParams | undefined {\n const reserved = new Set(['provider', 'apiKey', 'apiUrl', 'target', 'sandboxId'])\n const params: DaytonaCreateParams = {}\n\n for (const [key, value] of Object.entries(sandboxConfig)) {\n if (!reserved.has(key) && value !== undefined)\n params[key] = value\n }\n\n const language = takeString(sandboxConfig.language) ?? options.language\n const snapshot = takeString(sandboxConfig.snapshot) ?? options.snapshot\n const image = sandboxConfig.image ?? options.image\n const resources = (sandboxConfig.resources as DaytonaResources | undefined) ?? options.resources\n const configEnvVars = sandboxConfig.envVars as Record<string, string> | undefined\n const mergedEnvVars = resolveDaytonaEnv(envVars, configEnvVars)\n\n if (language !== undefined)\n params.language = language\n if (snapshot !== undefined)\n params.snapshot = snapshot\n if (image !== undefined)\n params.image = image\n if (resources !== undefined)\n params.resources = resources\n if (mergedEnvVars !== undefined)\n params.envVars = mergedEnvVars\n\n return Object.keys(params).length > 0 ? params : undefined\n}\n\nexport async function waitForDaytonaReady(\n sandbox: DaytonaSandbox,\n logger: Logger,\n opts: DaytonaReadinessOptions,\n): Promise<void> {\n if (opts.timeoutSeconds <= 0)\n return\n\n const now = opts.now ?? Date.now\n const sleep = opts.sleep ?? (ms => new Promise<void>(resolve => setTimeout(resolve, ms)))\n const start = now()\n const deadline = start + opts.timeoutSeconds * 1000\n\n logger.info('waiting for Daytona sandbox to initialize', { sandboxId: sandbox.id })\n\n while (true) {\n try {\n await sandbox.process.executeCommand('true', undefined, undefined, READINESS_PROBE_TIMEOUT_SECONDS)\n logger.info('Daytona sandbox ready', { sandboxId: sandbox.id, elapsedMs: now() - start })\n return\n }\n catch {\n if (now() >= deadline) {\n await opts.cleanupOnTimeout?.().catch(() => {})\n throw new Error(`Daytona sandbox ${sandbox.id} did not become ready within ${opts.timeoutSeconds}s`)\n }\n await sleep(READINESS_BACKOFF_MS)\n }\n }\n}\n\nexport async function runDaytonaPregame(\n sandbox: DaytonaSandbox,\n logger: Logger,\n opts: { name: string, content: string, cwd?: string, envVars?: Record<string, string> },\n): Promise<void> {\n const path = `/tmp/${opts.name}`\n logger.info('running Daytona pregame script', { sandboxId: sandbox.id, path })\n await sandbox.fs.uploadFile(Buffer.from(opts.content), path)\n\n const quoted = JSON.stringify(path)\n let result: ExecResult\n try {\n result = normalizeDaytonaExecResult(\n await sandbox.process.executeCommand(`chmod +x ${quoted} && ${quoted}`, opts.cwd, opts.envVars, PREGAME_TIMEOUT_SECONDS),\n )\n }\n catch (err) {\n result = normalizeDaytonaExecError(err)\n }\n\n if (result.exitCode !== 0) {\n logger.error('Daytona pregame script failed', { sandboxId: sandbox.id, exitCode: result.exitCode, stdout: result.stdout, stderr: result.stderr })\n throw new Error(`Daytona pregame script ${opts.name} failed with exit code ${result.exitCode}`)\n }\n\n logger.info('Daytona pregame ready', { sandboxId: sandbox.id, stdout: result.stdout, stderr: result.stderr })\n}\n\n// ---------------------------------------------------------------------------\n// Provider\n// ---------------------------------------------------------------------------\n\nexport function createDaytonaProvider(options: DaytonaProviderOptions = {}): SandboxProvider {\n const live = new Map<string, { sandbox: DaytonaSandbox, env?: Record<string, string>, owned: boolean }>()\n const logger = options.logger ?? createLogger(consoleSink())\n const readinessTimeoutSeconds = options.readinessTimeoutSeconds ?? 60\n\n async function loadSdk(): Promise<DaytonaConstructor> {\n try {\n const mod = await import('@daytona/sdk') as { Daytona: DaytonaConstructor }\n return mod.Daytona\n }\n catch {\n throw new Error('@daytona/sdk is required for the Daytona sandbox provider. Install it with: bun add @daytona/sdk')\n }\n }\n\n function get(sandboxId: string): { sandbox: DaytonaSandbox, env?: Record<string, string>, owned: boolean } {\n const entry = live.get(sandboxId)\n if (!entry)\n throw new Error(`Daytona sandbox ${sandboxId} is not tracked by this provider`)\n return entry\n }\n\n async function deleteOwnedSandbox(sandbox: DaytonaSandbox): Promise<void> {\n try {\n await sandbox.delete()\n }\n catch {\n // Already gone — nothing to clean up.\n }\n }\n\n async function stopOwnedSandbox(sandbox: DaytonaSandbox): Promise<void> {\n if (!sandbox.stop) {\n logger.error('cannot stop Daytona sandbox: this @daytona/sdk install exposes no stop() method; leaving it running', { sandboxId: sandbox.id })\n return\n }\n try {\n await sandbox.stop()\n logger.info('stopped Daytona sandbox; reconnect later via the sandboxId option (CLI: --sandbox)', { sandboxId: sandbox.id })\n }\n catch (err) {\n logger.error('failed to stop Daytona sandbox; it will run until Daytona lifecycle policy stops it', { sandboxId: sandbox.id, error: err instanceof Error ? err.message : String(err) })\n }\n }\n\n async function archiveOwnedSandbox(sandbox: DaytonaSandbox): Promise<void> {\n if (!sandbox.stop || !sandbox.archive) {\n logger.error('cannot archive Daytona sandbox: this @daytona/sdk install exposes no stop() and archive() pair; leaving it running', { sandboxId: sandbox.id })\n return\n }\n try {\n await sandbox.stop()\n await sandbox.archive()\n logger.info('archived Daytona sandbox; reconnect later via the sandboxId option (CLI: --sandbox)', { sandboxId: sandbox.id })\n }\n catch (err) {\n logger.error('failed to archive Daytona sandbox; it will run until Daytona lifecycle policy stops it', { sandboxId: sandbox.id, error: err instanceof Error ? err.message : String(err) })\n }\n }\n\n async function startAttachedSandboxIfNeeded(sandbox: DaytonaSandbox): Promise<void> {\n if (sandbox.state !== 'stopped' && sandbox.state !== 'archived')\n return\n if (!sandbox.start) {\n logger.error('cannot start Daytona sandbox before attach readiness probe: this @daytona/sdk install exposes no start() method', { sandboxId: sandbox.id, state: sandbox.state })\n return\n }\n logger.info('starting Daytona sandbox before attach readiness probe', { sandboxId: sandbox.id, state: sandbox.state })\n await sandbox.start(readinessTimeoutSeconds > 0 ? readinessTimeoutSeconds : undefined)\n }\n\n return {\n name: 'daytona',\n\n async spawn(config: SpawnConfig = {}): Promise<{ id: string, cwd: string }> {\n const Daytona = await loadSdk()\n const sb: Record<string, unknown> = config.sandbox ?? {}\n\n const apiKey = takeString(sb.apiKey) ?? options.apiKey\n const apiUrl = takeString(sb.apiUrl) ?? options.apiUrl\n const target = takeString(sb.target) ?? options.target\n const connectId = takeString(sb.sandboxId) ?? options.sandboxId\n const envVars = resolveDaytonaEnv(options.env, config.env)\n\n const daytona = new Daytona(toClientConfig(apiKey, apiUrl, target))\n const attached = connectId !== undefined\n // Lifecycle ownership drives `destroy()` (onDestroy). A created sandbox is\n // always owned; an attached one is owned only when the host opts in via\n // `ownAttached`. Spawn-FAILURE cleanup is separate (see below) and keyed\n // on `!attached` so a pre-existing sandbox is never auto-deleted.\n const owned = !attached || options.ownAttached === true\n const sandbox = attached\n ? await daytona.get(connectId)\n : await daytona.create(resolveDaytonaCreateParams(sb, options, envVars))\n\n if (attached)\n await startAttachedSandboxIfNeeded(sandbox)\n\n live.set(sandbox.id, { sandbox, env: envVars, owned })\n\n try {\n await waitForDaytonaReady(sandbox, logger, {\n timeoutSeconds: readinessTimeoutSeconds,\n // Only auto-delete a sandbox we CREATED on readiness failure — never a\n // pre-existing attached one, even when we own its lifecycle.\n ...(!attached ? { cleanupOnTimeout: () => deleteOwnedSandbox(sandbox) } : {}),\n })\n }\n catch (err) {\n live.delete(sandbox.id)\n throw err\n }\n\n const requestedCwd = config.cwd ?? options.cwd\n let cwd: string\n if (requestedCwd) {\n cwd = requestedCwd\n await sandbox.process.executeCommand(`mkdir -p ${JSON.stringify(cwd)}`).catch(() => {})\n }\n else if (sandbox.getWorkDir) {\n cwd = await sandbox.getWorkDir().catch(() => '')\n if (!cwd)\n cwd = await sandbox.process.executeCommand('pwd').then(r => normalizeDaytonaExecResult(r).stdout.trim()).catch(() => '') || '/home/daytona'\n }\n else {\n cwd = await sandbox.process.executeCommand('pwd').then(r => normalizeDaytonaExecResult(r).stdout.trim()).catch(() => '') || '/home/daytona'\n }\n\n if (options.pregame) {\n try {\n await runDaytonaPregame(sandbox, logger, { name: options.pregame.name, content: options.pregame.content, cwd, envVars })\n }\n catch (err) {\n // Spawn-failure cleanup: only delete a sandbox we created, never a\n // pre-existing attached one (the host owns its prior state).\n if (!attached)\n await deleteOwnedSandbox(sandbox)\n live.delete(sandbox.id)\n throw err\n }\n }\n\n return { id: sandbox.id, cwd }\n },\n\n async exec(sandboxId, command, opts): Promise<ExecResult> {\n const { sandbox, env } = get(sandboxId)\n const timeoutSeconds = opts?.timeout ?? 30\n try {\n return normalizeDaytonaExecResult(\n await sandbox.process.executeCommand(\n command,\n opts?.cwd,\n resolveDaytonaEnv(env, opts?.env),\n timeoutSeconds > 0 ? timeoutSeconds : undefined,\n ),\n )\n }\n catch (err) {\n return normalizeDaytonaExecError(err)\n }\n },\n\n async readFile(sandboxId, path): Promise<string> {\n return bufferToString(await get(sandboxId).sandbox.fs.downloadFile(path))\n },\n\n async writeFile(sandboxId, path, content): Promise<void> {\n await get(sandboxId).sandbox.fs.uploadFile(Buffer.from(content), path)\n },\n\n async listFiles(sandboxId, path): Promise<string[]> {\n const entries = await get(sandboxId).sandbox.fs.listFiles(path)\n return entries.map(e => e.name)\n },\n\n async destroy(sandboxId): Promise<void> {\n const entry = live.get(sandboxId)\n if (!entry)\n return\n if (entry.owned) {\n const action = options.onDestroy ?? 'delete'\n if (action === 'stop' || action === 'pause')\n await stopOwnedSandbox(entry.sandbox)\n else if (action === 'archive')\n await archiveOwnedSandbox(entry.sandbox)\n else if (action === 'delete' || action === 'kill')\n await deleteOwnedSandbox(entry.sandbox)\n }\n live.delete(sandboxId)\n },\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"daytona.js","names":[],"sources":["../../src/contexts/daytona.ts"],"sourcesContent":["/**\n * Daytona sandbox provider.\n *\n * Implements {@link SandboxProvider} on top of Daytona sandboxes. Pair it with\n * {@link createSandboxContext} to run an agent's shell + filesystem tools\n * inside a Daytona sandbox:\n *\n * ```ts\n * import { createSandboxContext } from 'zidane/contexts'\n * import { createDaytonaProvider } from 'zidane/contexts/daytona'\n *\n * const execution = createSandboxContext(createDaytonaProvider())\n * ```\n *\n * Requires `@daytona/sdk` as an optional peer dependency:\n * `bun add @daytona/sdk`.\n */\n\nimport type { Logger } from '../logger'\nimport type { SandboxProvider } from './sandbox'\nimport type { ExecResult, SpawnConfig } from './types'\nimport { Buffer } from 'node:buffer'\nimport { consoleSink, createLogger } from '../logger'\n\n// ---------------------------------------------------------------------------\n// Minimal structural views of the `@daytona/sdk` surface we touch. Declared\n// locally so zidane typechecks without the optional peer dependency installed.\n// ---------------------------------------------------------------------------\n\ninterface DaytonaExecuteResponse {\n exitCode?: number | null\n result?: string | null\n output?: string | null\n stdout?: string | null\n stderr?: string | null\n artifacts?: { stdout?: string | null }\n}\n\ninterface DaytonaFileEntry {\n name: string\n /** Present in the Daytona SDK's FileInfo; used for the `/` dir suffix. */\n isDir?: boolean\n}\n\ninterface DaytonaSandbox {\n id: string\n state?: string\n process: {\n executeCommand: (command: string, cwd?: string, env?: Record<string, string>, timeout?: number) => Promise<DaytonaExecuteResponse>\n }\n fs: {\n downloadFile: (path: string) => Promise<Uint8Array | ArrayBuffer | { toString: (encoding?: BufferEncoding) => string }>\n uploadFile: (content: Uint8Array | Buffer, path: string) => Promise<unknown>\n listFiles: (path: string) => Promise<DaytonaFileEntry[]>\n }\n getWorkDir?: () => Promise<string>\n start?: (timeout?: number) => Promise<unknown>\n delete: (timeout?: number) => Promise<unknown>\n stop?: (timeout?: number) => Promise<unknown>\n archive?: () => Promise<unknown>\n}\n\ninterface DaytonaClientConfig {\n apiKey?: string\n apiUrl?: string\n target?: string\n}\n\ninterface DaytonaResources {\n cpu?: number\n memory?: number\n disk?: number\n gpu?: number\n [key: string]: unknown\n}\n\ninterface DaytonaCreateParams {\n language?: string\n snapshot?: string\n image?: unknown\n resources?: DaytonaResources\n envVars?: Record<string, string>\n [key: string]: unknown\n}\n\ninterface DaytonaClient {\n create: (params?: DaytonaCreateParams) => Promise<DaytonaSandbox>\n get: (sandboxIdOrName: string) => Promise<DaytonaSandbox>\n}\n\ntype DaytonaConstructor = new (config?: DaytonaClientConfig) => DaytonaClient\n\n// ---------------------------------------------------------------------------\n// Provider options\n// ---------------------------------------------------------------------------\n\nexport type DaytonaOnDestroy = 'delete' | 'kill' | 'stop' | 'pause' | 'archive' | 'leave'\n\nexport interface DaytonaProviderOptions {\n /** Daytona API key. Falls back to the `DAYTONA_API_KEY` env var when omitted. */\n apiKey?: string\n /** Daytona API URL. Falls back to `DAYTONA_API_URL`, then Daytona's hosted default. */\n apiUrl?: string\n /** Daytona target / region. Falls back to `DAYTONA_TARGET`, then the org default. */\n target?: string\n /**\n * Attach to a pre-existing sandbox by id or name instead of creating a fresh\n * one. By default the provider treats attached sandboxes as externally owned\n * and leaves them running on destroy/failure (set {@link ownAttached} to take\n * over the lifecycle).\n */\n sandboxId?: string\n /**\n * Take ownership of an attached ({@link sandboxId}) sandbox's lifecycle, so\n * `destroy()` applies {@link onDestroy} to it (stop / archive / delete)\n * instead of leaving it running.\n *\n * The intended caller is a durable host that *owns* the sandbox across\n * invocations: mint it once, journal its id, re-attach via `sandboxId` on\n * every replay/resume, and clean it up at the end of the logical run. Without\n * this an attached sandbox can never be stopped/deleted through the provider —\n * a leak for host-owned sandboxes. No effect when creating a fresh sandbox\n * (those are always owned). Spawn-time failures (readiness timeout, pregame\n * error) still never delete a pre-existing attached sandbox — only sandboxes\n * this provider *created* are auto-cleaned on spawn failure.\n *\n * Default: `false`.\n */\n ownAttached?: boolean\n /** Daytona snapshot used when creating a fresh sandbox. */\n snapshot?: string\n /** Runtime used for Daytona's code tooling. Shell execution works either way. */\n language?: string\n /** Daytona image/declarative builder input used when creating a fresh sandbox. */\n image?: unknown\n /** Daytona sandbox resources. */\n resources?: DaytonaResources\n /** Default working directory, applied when a spawn doesn't set `SpawnConfig.cwd`. */\n cwd?: string\n /** Environment variables baked into the sandbox and re-sent on every command. */\n env?: Record<string, string>\n /** Logger for provider-level lifecycle lines. */\n logger?: Logger\n /** Total deadline, in seconds, for the post-create readiness probe. Defaults to `60`. */\n readinessTimeoutSeconds?: number\n /** Setup script to upload and run once the sandbox is ready. */\n pregame?: { name: string, content: string }\n /**\n * What to do with an owned sandbox on destroy:\n *\n * - `'delete'` / `'kill'` (default) — call `sandbox.delete()`.\n * - `'stop'` / `'pause'` — call `sandbox.stop()` so Daytona preserves state\n * according to the runner's stop semantics.\n * - `'archive'` — stop, then archive for longer-term persistence.\n * - `'leave'` — make no API call.\n */\n onDestroy?: DaytonaOnDestroy\n}\n\n/** Per-probe timeout for the readiness `true` command. */\nconst READINESS_PROBE_TIMEOUT_SECONDS = 5\n/** Pause between readiness probes while the sandbox is still warming up. */\nconst READINESS_BACKOFF_MS = 250\n/** Setup scripts commonly install dependencies, so give them a generous cap. */\nconst PREGAME_TIMEOUT_SECONDS = 30 * 60\n\nexport interface DaytonaReadinessOptions {\n /** Total deadline in seconds. `<= 0` disables the probe entirely. */\n timeoutSeconds: number\n /** Optional cleanup for owned sandboxes that never become ready. */\n cleanupOnTimeout?: () => Promise<void>\n /** Clock source — injectable for deterministic tests. */\n now?: () => number\n /** Sleep between probes — injectable for deterministic tests. */\n sleep?: (ms: number) => Promise<void>\n}\n\nexport function resolveDaytonaEnv(\n optionEnv: Record<string, string> | undefined,\n perSpawnEnv: Record<string, string> | undefined,\n): Record<string, string> | undefined {\n const merged = { ...optionEnv, ...perSpawnEnv }\n return Object.keys(merged).length > 0 ? merged : undefined\n}\n\nexport function normalizeDaytonaExecResult(response: DaytonaExecuteResponse): ExecResult {\n return {\n stdout: response.stdout ?? response.artifacts?.stdout ?? response.result ?? response.output ?? '',\n stderr: response.stderr ?? '',\n exitCode: typeof response.exitCode === 'number' ? response.exitCode : 0,\n }\n}\n\nfunction normalizeDaytonaExecError(error: unknown): ExecResult {\n const err = error as { exitCode?: unknown, stdout?: unknown, stderr?: unknown, result?: unknown, output?: unknown, message?: unknown }\n if (typeof err.exitCode === 'number') {\n return {\n stdout: typeof err.stdout === 'string'\n ? err.stdout\n : typeof err.result === 'string'\n ? err.result\n : typeof err.output === 'string'\n ? err.output\n : '',\n stderr: typeof err.stderr === 'string' ? err.stderr : typeof err.message === 'string' ? err.message : '',\n exitCode: err.exitCode,\n }\n }\n return { stdout: '', stderr: error instanceof Error ? error.message : String(error), exitCode: 124 }\n}\n\nfunction bufferToString(value: Uint8Array | ArrayBuffer | { toString: (encoding?: BufferEncoding) => string }): string {\n if (Buffer.isBuffer(value))\n return value.toString('utf8')\n if (value instanceof ArrayBuffer)\n return new TextDecoder().decode(new Uint8Array(value))\n if (value instanceof Uint8Array)\n return new TextDecoder().decode(value)\n return value.toString('utf8')\n}\n\nfunction toClientConfig(apiKey?: string, apiUrl?: string, target?: string): DaytonaClientConfig | undefined {\n const config: DaytonaClientConfig = {}\n if (apiKey !== undefined)\n config.apiKey = apiKey\n if (apiUrl !== undefined)\n config.apiUrl = apiUrl\n if (target !== undefined)\n config.target = target\n return Object.keys(config).length > 0 ? config : undefined\n}\n\nfunction takeString(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined\n}\n\nexport function resolveDaytonaCreateParams(\n sandboxConfig: Record<string, unknown>,\n options: DaytonaProviderOptions,\n envVars: Record<string, string> | undefined,\n): DaytonaCreateParams | undefined {\n const reserved = new Set(['provider', 'apiKey', 'apiUrl', 'target', 'sandboxId'])\n const params: DaytonaCreateParams = {}\n\n for (const [key, value] of Object.entries(sandboxConfig)) {\n if (!reserved.has(key) && value !== undefined)\n params[key] = value\n }\n\n const language = takeString(sandboxConfig.language) ?? options.language\n const snapshot = takeString(sandboxConfig.snapshot) ?? options.snapshot\n const image = sandboxConfig.image ?? options.image\n const resources = (sandboxConfig.resources as DaytonaResources | undefined) ?? options.resources\n const configEnvVars = sandboxConfig.envVars as Record<string, string> | undefined\n const mergedEnvVars = resolveDaytonaEnv(envVars, configEnvVars)\n\n if (language !== undefined)\n params.language = language\n if (snapshot !== undefined)\n params.snapshot = snapshot\n if (image !== undefined)\n params.image = image\n if (resources !== undefined)\n params.resources = resources\n if (mergedEnvVars !== undefined)\n params.envVars = mergedEnvVars\n\n return Object.keys(params).length > 0 ? params : undefined\n}\n\nexport async function waitForDaytonaReady(\n sandbox: DaytonaSandbox,\n logger: Logger,\n opts: DaytonaReadinessOptions,\n): Promise<void> {\n if (opts.timeoutSeconds <= 0)\n return\n\n const now = opts.now ?? Date.now\n const sleep = opts.sleep ?? (ms => new Promise<void>(resolve => setTimeout(resolve, ms)))\n const start = now()\n const deadline = start + opts.timeoutSeconds * 1000\n\n logger.info('waiting for Daytona sandbox to initialize', { sandboxId: sandbox.id })\n\n while (true) {\n try {\n await sandbox.process.executeCommand('true', undefined, undefined, READINESS_PROBE_TIMEOUT_SECONDS)\n logger.info('Daytona sandbox ready', { sandboxId: sandbox.id, elapsedMs: now() - start })\n return\n }\n catch {\n if (now() >= deadline) {\n await opts.cleanupOnTimeout?.().catch(() => {})\n throw new Error(`Daytona sandbox ${sandbox.id} did not become ready within ${opts.timeoutSeconds}s`)\n }\n await sleep(READINESS_BACKOFF_MS)\n }\n }\n}\n\nexport async function runDaytonaPregame(\n sandbox: DaytonaSandbox,\n logger: Logger,\n opts: { name: string, content: string, cwd?: string, envVars?: Record<string, string> },\n): Promise<void> {\n const path = `/tmp/${opts.name}`\n logger.info('running Daytona pregame script', { sandboxId: sandbox.id, path })\n await sandbox.fs.uploadFile(Buffer.from(opts.content), path)\n\n const quoted = JSON.stringify(path)\n let result: ExecResult\n try {\n result = normalizeDaytonaExecResult(\n await sandbox.process.executeCommand(`chmod +x ${quoted} && ${quoted}`, opts.cwd, opts.envVars, PREGAME_TIMEOUT_SECONDS),\n )\n }\n catch (err) {\n result = normalizeDaytonaExecError(err)\n }\n\n if (result.exitCode !== 0) {\n logger.error('Daytona pregame script failed', { sandboxId: sandbox.id, exitCode: result.exitCode, stdout: result.stdout, stderr: result.stderr })\n throw new Error(`Daytona pregame script ${opts.name} failed with exit code ${result.exitCode}`)\n }\n\n logger.info('Daytona pregame ready', { sandboxId: sandbox.id, stdout: result.stdout, stderr: result.stderr })\n}\n\n// ---------------------------------------------------------------------------\n// Provider\n// ---------------------------------------------------------------------------\n\nexport function createDaytonaProvider(options: DaytonaProviderOptions = {}): SandboxProvider {\n const live = new Map<string, { sandbox: DaytonaSandbox, env?: Record<string, string>, owned: boolean }>()\n const logger = options.logger ?? createLogger(consoleSink())\n const readinessTimeoutSeconds = options.readinessTimeoutSeconds ?? 60\n\n async function loadSdk(): Promise<DaytonaConstructor> {\n try {\n const mod = await import('@daytona/sdk') as { Daytona: DaytonaConstructor }\n return mod.Daytona\n }\n catch {\n throw new Error('@daytona/sdk is required for the Daytona sandbox provider. Install it with: bun add @daytona/sdk')\n }\n }\n\n function get(sandboxId: string): { sandbox: DaytonaSandbox, env?: Record<string, string>, owned: boolean } {\n const entry = live.get(sandboxId)\n if (!entry)\n throw new Error(`Daytona sandbox ${sandboxId} is not tracked by this provider`)\n return entry\n }\n\n async function deleteOwnedSandbox(sandbox: DaytonaSandbox): Promise<void> {\n try {\n await sandbox.delete()\n }\n catch {\n // Already gone — nothing to clean up.\n }\n }\n\n async function stopOwnedSandbox(sandbox: DaytonaSandbox): Promise<void> {\n if (!sandbox.stop) {\n logger.error('cannot stop Daytona sandbox: this @daytona/sdk install exposes no stop() method; leaving it running', { sandboxId: sandbox.id })\n return\n }\n try {\n await sandbox.stop()\n logger.info('stopped Daytona sandbox; reconnect later via the sandboxId option (CLI: --sandbox)', { sandboxId: sandbox.id })\n }\n catch (err) {\n logger.error('failed to stop Daytona sandbox; it will run until Daytona lifecycle policy stops it', { sandboxId: sandbox.id, error: err instanceof Error ? err.message : String(err) })\n }\n }\n\n async function archiveOwnedSandbox(sandbox: DaytonaSandbox): Promise<void> {\n if (!sandbox.stop || !sandbox.archive) {\n logger.error('cannot archive Daytona sandbox: this @daytona/sdk install exposes no stop() and archive() pair; leaving it running', { sandboxId: sandbox.id })\n return\n }\n try {\n await sandbox.stop()\n await sandbox.archive()\n logger.info('archived Daytona sandbox; reconnect later via the sandboxId option (CLI: --sandbox)', { sandboxId: sandbox.id })\n }\n catch (err) {\n logger.error('failed to archive Daytona sandbox; it will run until Daytona lifecycle policy stops it', { sandboxId: sandbox.id, error: err instanceof Error ? err.message : String(err) })\n }\n }\n\n async function startAttachedSandboxIfNeeded(sandbox: DaytonaSandbox): Promise<void> {\n if (sandbox.state !== 'stopped' && sandbox.state !== 'archived')\n return\n if (!sandbox.start) {\n logger.error('cannot start Daytona sandbox before attach readiness probe: this @daytona/sdk install exposes no start() method', { sandboxId: sandbox.id, state: sandbox.state })\n return\n }\n logger.info('starting Daytona sandbox before attach readiness probe', { sandboxId: sandbox.id, state: sandbox.state })\n await sandbox.start(readinessTimeoutSeconds > 0 ? readinessTimeoutSeconds : undefined)\n }\n\n return {\n name: 'daytona',\n\n async spawn(config: SpawnConfig = {}): Promise<{ id: string, cwd: string }> {\n const Daytona = await loadSdk()\n const sb: Record<string, unknown> = config.sandbox ?? {}\n\n const apiKey = takeString(sb.apiKey) ?? options.apiKey\n const apiUrl = takeString(sb.apiUrl) ?? options.apiUrl\n const target = takeString(sb.target) ?? options.target\n const connectId = takeString(sb.sandboxId) ?? options.sandboxId\n const envVars = resolveDaytonaEnv(options.env, config.env)\n\n const daytona = new Daytona(toClientConfig(apiKey, apiUrl, target))\n const attached = connectId !== undefined\n // Lifecycle ownership drives `destroy()` (onDestroy). A created sandbox is\n // always owned; an attached one is owned only when the host opts in via\n // `ownAttached`. Spawn-FAILURE cleanup is separate (see below) and keyed\n // on `!attached` so a pre-existing sandbox is never auto-deleted.\n const owned = !attached || options.ownAttached === true\n const sandbox = attached\n ? await daytona.get(connectId)\n : await daytona.create(resolveDaytonaCreateParams(sb, options, envVars))\n\n if (attached)\n await startAttachedSandboxIfNeeded(sandbox)\n\n live.set(sandbox.id, { sandbox, env: envVars, owned })\n\n try {\n await waitForDaytonaReady(sandbox, logger, {\n timeoutSeconds: readinessTimeoutSeconds,\n // Only auto-delete a sandbox we CREATED on readiness failure — never a\n // pre-existing attached one, even when we own its lifecycle.\n ...(!attached ? { cleanupOnTimeout: () => deleteOwnedSandbox(sandbox) } : {}),\n })\n }\n catch (err) {\n live.delete(sandbox.id)\n throw err\n }\n\n const requestedCwd = config.cwd ?? options.cwd\n let cwd: string\n if (requestedCwd) {\n cwd = requestedCwd\n await sandbox.process.executeCommand(`mkdir -p ${JSON.stringify(cwd)}`).catch(() => {})\n }\n else if (sandbox.getWorkDir) {\n cwd = await sandbox.getWorkDir().catch(() => '')\n if (!cwd)\n cwd = await sandbox.process.executeCommand('pwd').then(r => normalizeDaytonaExecResult(r).stdout.trim()).catch(() => '') || '/home/daytona'\n }\n else {\n cwd = await sandbox.process.executeCommand('pwd').then(r => normalizeDaytonaExecResult(r).stdout.trim()).catch(() => '') || '/home/daytona'\n }\n\n if (options.pregame) {\n try {\n await runDaytonaPregame(sandbox, logger, { name: options.pregame.name, content: options.pregame.content, cwd, envVars })\n }\n catch (err) {\n // Spawn-failure cleanup: only delete a sandbox we created, never a\n // pre-existing attached one (the host owns its prior state).\n if (!attached)\n await deleteOwnedSandbox(sandbox)\n live.delete(sandbox.id)\n throw err\n }\n }\n\n return { id: sandbox.id, cwd }\n },\n\n async exec(sandboxId, command, opts): Promise<ExecResult> {\n const { sandbox, env } = get(sandboxId)\n const timeoutSeconds = opts?.timeout ?? 30\n try {\n return normalizeDaytonaExecResult(\n await sandbox.process.executeCommand(\n command,\n opts?.cwd,\n resolveDaytonaEnv(env, opts?.env),\n timeoutSeconds > 0 ? timeoutSeconds : undefined,\n ),\n )\n }\n catch (err) {\n return normalizeDaytonaExecError(err)\n }\n },\n\n async readFile(sandboxId, path): Promise<string> {\n return bufferToString(await get(sandboxId).sandbox.fs.downloadFile(path))\n },\n\n async writeFile(sandboxId, path, content): Promise<void> {\n await get(sandboxId).sandbox.fs.uploadFile(Buffer.from(content), path)\n },\n\n async listFiles(sandboxId, path): Promise<string[]> {\n const entries = await get(sandboxId).sandbox.fs.listFiles(path)\n // Trailing `/` on directories — parity with process/docker contexts.\n return entries.map(e => (e.isDir ? `${e.name}/` : e.name))\n },\n\n async destroy(sandboxId): Promise<void> {\n const entry = live.get(sandboxId)\n if (!entry)\n return\n if (entry.owned) {\n const action = options.onDestroy ?? 'delete'\n if (action === 'stop' || action === 'pause')\n await stopOwnedSandbox(entry.sandbox)\n else if (action === 'archive')\n await archiveOwnedSandbox(entry.sandbox)\n else if (action === 'delete' || action === 'kill')\n await deleteOwnedSandbox(entry.sandbox)\n }\n live.delete(sandboxId)\n },\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"docker.d.ts","names":[],"sources":["../../src/contexts/docker.ts"],"mappings":";;;;;;;;;;cAwBa,oBAAA;AAAA,iBAmBG,mBAAA,CAAoB,MAAA,GAAS,WAAA,GAAc,gBAAgB;;iBAgZ3D,cAAA,CAAe,MAAA,EAAQ,WAAW,CAAC,WAAA"}
1
+ {"version":3,"file":"docker.d.ts","names":[],"sources":["../../src/contexts/docker.ts"],"mappings":";;;;;;;;;;cAwBa,oBAAA;AAAA,iBAmBG,mBAAA,CAAoB,MAAA,GAAS,WAAA,GAAc,gBAAgB;;iBAqZ3D,cAAA,CAAe,MAAA,EAAQ,WAAW,CAAC,WAAA"}
@@ -275,8 +275,8 @@ function createDockerContext(config) {
275
275
  },
276
276
  async listFiles(handle, path, options) {
277
277
  if (options?.glob) return globFilesViaShell(path, (command) => ctx.exec(handle, command), options?.limit ?? 1e3);
278
- const result = await ctx.exec(handle, `ls -1 ${alwaysQuote(path)}`);
279
- if (result.exitCode !== 0) return [];
278
+ const result = await ctx.exec(handle, `ls -1p ${alwaysQuote(path)}`);
279
+ if (result.exitCode !== 0) throw new Error(result.stderr.trim() || `ls failed with exit ${result.exitCode}`);
280
280
  return result.stdout.trim().split("\n").filter(Boolean);
281
281
  },
282
282
  async getMappedPort(handle, containerPort) {
@@ -1 +1 @@
1
- {"version":3,"file":"docker.js","names":[],"sources":["../../src/contexts/docker.ts"],"sourcesContent":["/**\n * Docker execution context.\n *\n * Runs tools inside a Docker container via dockerode.\n * Full isolation with configurable resource limits.\n *\n * Requires `dockerode` as an optional peer dependency.\n */\n\nimport type { CappedSlot } from './output-cap'\nimport type { ContextCapabilities, ExecResult, ExecutionContext, ExecutionHandle, ListFilesEntry, SpawnConfig } from './types'\nimport { Buffer } from 'node:buffer'\nimport { alwaysQuote } from '../tools/shell-quote'\nimport { fileSizeViaExec } from './exec-file-size'\nimport { globFilesViaShell } from './glob'\nimport { appendCapped, DEFAULT_MAX_OUTPUT_BUFFER, outputExceededMessage } from './output-cap'\n\n/**\n * `readFile` transport cap: headroom above the whole-read tool ceiling\n * (256MB in tools/whole-read-guard.ts) so tool policy — not the docker\n * transport — decides read limits, keeping refusal behavior consistent\n * across contexts. Still bounded: a probe-failure path can't buffer\n * more than this into host memory.\n */\nexport const READ_FILE_MAX_BUFFER = 512 * 1024 * 1024\n\nconst SINGLE_QUOTE_RE = /'/g\n\n/**\n * dockerode surfaces HTTP errors with a `statusCode`. 304 = container\n * already stopped, 404 = already gone — both are fine during teardown.\n */\nfunction isGoneOrAlreadyStopped(err: unknown): boolean {\n const code = (err as { statusCode?: number } | null)?.statusCode\n return code === 304 || code === 404\n}\n\ninterface ContainerRef {\n handle: ExecutionHandle\n container: any\n docker: any\n}\n\nexport function createDockerContext(config?: SpawnConfig): ExecutionContext {\n let counter = 0\n const containers = new Map<string, ContainerRef>()\n const defaultImage = config?.image ?? 'oven/bun:latest'\n const defaultCwd = config?.cwd ?? '/workspace'\n const defaultEnv = config?.env\n const defaultLimits = config?.limits\n const defaultMounts = config?.mounts ?? []\n const defaultName = config?.name\n\n async function getDockerode() {\n try {\n const Dockerode = (await import('dockerode')).default\n return new Dockerode()\n }\n catch {\n throw new Error('dockerode is required for Docker execution context. Install it with: bun add dockerode')\n }\n }\n\n /**\n * The exec transport with an explicit output cap. The public `exec`\n * uses the shared 10MB default; `readFile` passes headroom above the\n * whole-read tool ceiling so docker's EFFECTIVE read limit matches\n * every other context (the fileSize probe refuses oversized files\n * before this ever runs — the transport cap is the safety net for\n * probe-failure paths, not the primary bound).\n */\n async function execCapped(handle: ExecutionHandle, command: string, options: { cwd?: string, env?: Record<string, string>, timeout?: number } | undefined, maxBuffer: number): Promise<ExecResult> {\n const ref = containers.get(handle.id)\n if (!ref)\n throw new Error(`Container ${handle.id} not found`)\n\n const execCwd = options?.cwd ?? handle.cwd\n const env = options?.env\n ? Object.entries(options.env).map(([k, v]) => `${k}=${v}`)\n : []\n\n const timeout = options?.timeout ?? defaultLimits?.timeout ?? 30\n\n const exec = await ref.container.exec({\n // Wrap with coreutils `timeout` so a runaway command is killed\n // server-side too — the host-side timer below only detaches the\n // stream; without this the process would keep running in the\n // container until destroy(). Exec-array form avoids re-quoting the\n // command. `timeout` exits 124 on expiry, matching the host\n // sentinel. (Default image oven/bun is Debian-based → coreutils.)\n Cmd: ['timeout', String(timeout), 'sh', '-c', command],\n WorkingDir: execCwd,\n Env: env,\n AttachStdout: true,\n AttachStderr: true,\n })\n\n const stream = await exec.start({ Detach: false })\n\n return new Promise<ExecResult>((resolve) => {\n // Byte-capped accumulation via the shared exec-transport primitive\n // (contexts/output-cap.ts) — the docker `readFile` is `cat` through\n // this very path, so an uncapped sink would buffer a container's\n // multi-GB file straight into HOST memory. On overflow the stream\n // is destroyed and the exec resolves exit 143 with a message\n // (mirroring the process context), so `readFile` THROWS instead of\n // silently returning truncated content — silent truncation would\n // corrupt `edit`'s read-modify-write into data loss.\n const stdoutSlot: CappedSlot = { text: '', bytes: 0 }\n const stderrSlot: CappedSlot = { text: '', bytes: 0 }\n let resolved = false\n\n const overflow = (): void => {\n if (resolved)\n return\n resolved = true\n try {\n stream.destroy?.()\n }\n catch { /* swallow */ }\n resolve({\n stdout: stdoutSlot.text,\n stderr: `${stderrSlot.text ? `${stderrSlot.text}\\n` : ''}${outputExceededMessage(maxBuffer, 'stream detached (server-side timeout reaps the process)')}`,\n exitCode: 143,\n })\n }\n\n const cappedWrite = (slot: CappedSlot) => (chunk: Buffer | string): boolean => {\n const buf = typeof chunk === 'string' ? Buffer.from(chunk, 'utf-8') : chunk\n if (appendCapped(slot, buf, maxBuffer) !== 'ok')\n overflow()\n return true\n }\n\n // Docker's exec stream is a multiplexed protocol — a single byte\n // stream that interleaves stdout/stderr frames. `demuxStream`\n // splits the two onto separate writable sinks. Without it, every\n // byte landed on `stdout` and `stderr` was permanently empty.\n const stdoutSink = {\n write: cappedWrite(stdoutSlot),\n end() {},\n on() {},\n once() {},\n emit() { return true },\n }\n const stderrSink = {\n write: cappedWrite(stderrSlot),\n end() {},\n on() {},\n once() {},\n emit() { return true },\n }\n try {\n ref.docker.modem.demuxStream(stream, stdoutSink, stderrSink)\n }\n catch {\n // Older / different transports may not be multiplexed (TTY mode);\n // fall back to a raw stdout reader so we don't lose output entirely.\n // Same cap: TTY mode is still a host-memory sink.\n stream.on('data', (chunk: Buffer) => {\n cappedWrite(stdoutSlot)(chunk)\n })\n }\n\n // Host-side fallback timer. The server-side `timeout` wrapper is\n // the primary kill mechanism; give it a grace second so the\n // stream-end path (which carries the real exit code) usually wins.\n const timer = setTimeout(() => {\n if (resolved)\n return\n resolved = true\n // Best-effort: detach the stream so we stop accumulating bytes.\n // The server-side `timeout` wrapper kills the process itself.\n try { stream.destroy?.() }\n catch { /* swallow */ }\n resolve({ stdout: stdoutSlot.text, stderr: stderrSlot.text ? `${stderrSlot.text}\\n[timeout]` : '[timeout]', exitCode: 124 })\n }, timeout * 1000 + 1000)\n\n stream.on('end', async () => {\n if (resolved)\n return\n resolved = true\n clearTimeout(timer)\n let exitCode: number\n let inspectNote = ''\n try {\n const inspect = await exec.inspect()\n // `ExitCode: null` means Docker hasn't recorded an exit yet —\n // the outcome is unknown, not success. Use a sentinel so\n // callers don't mistake it for a clean exit.\n if (inspect.ExitCode == null) {\n exitCode = -1\n inspectNote = '[exit code unavailable: exec still marked running by daemon]'\n }\n else {\n exitCode = inspect.ExitCode\n }\n }\n catch (err) {\n exitCode = -1\n inspectNote = `[exit code unavailable: exec inspect failed: ${(err as Error)?.message ?? String(err)}]`\n }\n resolve({\n stdout: stdoutSlot.text,\n stderr: inspectNote ? (stderrSlot.text ? `${stderrSlot.text}\\n${inspectNote}` : inspectNote) : stderrSlot.text,\n exitCode,\n })\n })\n\n stream.on('error', (err: Error) => {\n if (resolved)\n return\n resolved = true\n clearTimeout(timer)\n resolve({ stdout: stdoutSlot.text, stderr: stderrSlot.text ? `${stderrSlot.text}\\n${err.message}` : err.message, exitCode: 1 })\n })\n })\n }\n\n const ctx: ExecutionContext = {\n type: 'docker',\n\n capabilities: {\n shell: true,\n filesystem: true,\n glob: true,\n network: true,\n gpu: false,\n } satisfies ContextCapabilities,\n\n async spawn(overrides?: SpawnConfig): Promise<ExecutionHandle> {\n const docker = await getDockerode()\n const id = `docker-${++counter}`\n const image = overrides?.image ?? defaultImage\n const cwd = overrides?.cwd ?? defaultCwd\n const mounts = [...defaultMounts, ...(overrides?.mounts ?? [])]\n const name = overrides?.name ?? defaultName\n\n // Pull image if not available\n try {\n await docker.getImage(image).inspect()\n }\n catch {\n await new Promise<void>((resolve, reject) => {\n docker.pull(image, (err: Error | null, stream: NodeJS.ReadableStream) => {\n if (err)\n return reject(err)\n docker.modem.followProgress(stream, (err2: Error | null) => {\n err2 ? reject(err2) : resolve()\n })\n })\n })\n }\n\n const limits = { ...defaultLimits, ...overrides?.limits }\n const hostConfig: Record<string, unknown> = {}\n\n if (limits?.memory) {\n hostConfig.Memory = limits.memory * 1024 * 1024\n }\n if (limits?.cpu) {\n hostConfig.NanoCpus = Number.parseFloat(limits.cpu) * 1e9\n }\n\n // Opt-in hardening (all OFF by default — see ContextHardening).\n const hardening = { ...config?.hardening, ...overrides?.hardening }\n if (hardening.dropAllCapabilities)\n hostConfig.CapDrop = ['ALL']\n if (hardening.noNewPrivileges)\n hostConfig.SecurityOpt = ['no-new-privileges']\n if (hardening.readonlyRootfs)\n hostConfig.ReadonlyRootfs = true\n if (hardening.pidsLimit != null)\n hostConfig.PidsLimit = hardening.pidsLimit\n\n const env = { ...defaultEnv, ...overrides?.env }\n\n const bindSpecs = buildBindSpecs(mounts)\n if (bindSpecs.length > 0) {\n hostConfig.Binds = bindSpecs\n }\n\n const portSpec = overrides?.ports ?? config?.ports\n const exposedPorts: Record<string, Record<string, never>> = {}\n const portBindings: Record<string, Array<{ HostPort: string }>> = {}\n\n if (portSpec?.length) {\n for (const p of portSpec) {\n const key = `${p.container}/${p.proto ?? 'tcp'}`\n exposedPorts[key] = {}\n portBindings[key] = [{ HostPort: p.host == null ? '' : String(p.host) }]\n }\n hostConfig.PortBindings = portBindings\n }\n\n const user = overrides?.user ?? config?.user\n const network = overrides?.network ?? config?.network\n if (network) {\n hostConfig.NetworkMode = network\n }\n\n const labels = { ...config?.labels, ...overrides?.labels }\n\n const container = await docker.createContainer({\n // Suffix with a short random token so a crashed run's leftover container\n // (same prefix) never blocks a fresh one with HTTP 409 name conflict.\n ...(name ? { name: `${name}-${counter}-${Math.random().toString(36).slice(2, 8)}` } : {}),\n Image: image,\n Cmd: ['sleep', 'infinity'],\n WorkingDir: cwd,\n Env: Object.entries(env).map(([k, v]) => `${k}=${v}`),\n HostConfig: hostConfig,\n ...(Object.keys(exposedPorts).length ? { ExposedPorts: exposedPorts } : {}),\n ...(user ? { User: user } : {}),\n ...(Object.keys(labels).length ? { Labels: labels } : {}),\n })\n\n try {\n await container.start()\n }\n catch (err) {\n // The container was created but never started — remove it so a\n // failed spawn doesn't leak a stopped container on the daemon.\n await container.remove({ force: true }).catch(() => {})\n throw err\n }\n\n const handle: ExecutionHandle = { id, type: 'docker', cwd }\n containers.set(id, { handle, container, docker })\n\n return handle\n },\n\n async exec(handle: ExecutionHandle, command: string, options?: { cwd?: string, env?: Record<string, string>, timeout?: number }): Promise<ExecResult> {\n return execCapped(handle, command, options, DEFAULT_MAX_OUTPUT_BUFFER)\n },\n\n async fileSize(handle: ExecutionHandle, path: string): Promise<number | null> {\n // Shared exec-probed capability (see exec-file-size.ts) — the same\n // implementation as the sandbox contexts, for a CONSISTENT\n // whole-read experience: tools refuse >ceiling files with the same\n // message everywhere, instead of docker surfacing a transport\n // error at a different threshold.\n return fileSizeViaExec(command => execCapped(handle, command, undefined, DEFAULT_MAX_OUTPUT_BUFFER), path)\n },\n\n // Paths are model-supplied — single-quote them (`alwaysQuote`) rather\n // than JSON.stringify: `$(…)` and backticks still expand inside the\n // double quotes JSON produces, which was a command-injection vector.\n async readFile(handle: ExecutionHandle, path: string): Promise<string> {\n // Transport headroom ABOVE the whole-read tool ceiling (256MB,\n // enforced via the fileSize probe BEFORE this runs): without it,\n // docker's effective read limit would be the 10MB exec default —\n // inconsistent with every other context and surfaced as a\n // confusing transport error instead of the uniform refusal.\n const result = await execCapped(handle, `cat ${alwaysQuote(path)}`, undefined, READ_FILE_MAX_BUFFER)\n if (result.exitCode !== 0)\n throw new Error(`Failed to read file: ${result.stderr}`)\n return result.stdout\n },\n\n async writeFile(handle: ExecutionHandle, path: string, content: string): Promise<void> {\n const escaped = content.replace(SINGLE_QUOTE_RE, String.raw`'\\''`)\n const result = await ctx.exec(handle, `mkdir -p \"$(dirname ${alwaysQuote(path)})\" && printf '%s' '${escaped}' > ${alwaysQuote(path)}`)\n if (result.exitCode !== 0)\n throw new Error(`Failed to write file: ${result.stderr}`)\n },\n\n async listFiles(handle: ExecutionHandle, path: string, options): Promise<ListFilesEntry[]> {\n if (options?.glob)\n return globFilesViaShell(path, command => ctx.exec(handle, command), options?.limit ?? 1000)\n const result = await ctx.exec(handle, `ls -1 ${alwaysQuote(path)}`)\n if (result.exitCode !== 0)\n return []\n return result.stdout.trim().split('\\n').filter(Boolean)\n },\n\n async getMappedPort(handle: ExecutionHandle, containerPort: number): Promise<number | null> {\n const ref = containers.get(handle.id)\n if (!ref)\n return null\n\n const info = await ref.container.inspect()\n const bindings = info?.NetworkSettings?.Ports ?? {}\n\n for (const proto of ['tcp', 'udp']) {\n const list = bindings[`${containerPort}/${proto}`]\n if (Array.isArray(list) && list.length > 0 && list[0]?.HostPort) {\n return Number.parseInt(list[0].HostPort, 10)\n }\n }\n\n return null\n },\n\n async destroy(handle: ExecutionHandle): Promise<void> {\n const ref = containers.get(handle.id)\n if (!ref)\n return\n\n try {\n try {\n await ref.container.stop({ t: 5 })\n }\n catch {\n // 304 (already stopped) / 404 (already gone) are expected here.\n // Any other stop failure must not prevent the remove({force})\n // below — force removal also works on running containers.\n }\n try {\n await ref.container.remove({ force: true })\n }\n catch (err) {\n if (!isGoneOrAlreadyStopped(err))\n throw err\n }\n }\n finally {\n containers.delete(handle.id)\n }\n },\n }\n\n return ctx\n}\n\n/**\n * Format one {@link ContextMount} as a dockerode `HostConfig.Binds` entry.\n * Supports read-only (`:ro`) and SELinux shared-label (`:z`) options; the two\n * are mutually exclusive (a read-only mount can't also be a shared rw mount).\n */\nfunction formatBindMount(mount: NonNullable<SpawnConfig['mounts']>[number]): string {\n if (!mount.source)\n throw new Error('Docker mount source is required')\n if (!mount.target || !mount.target.startsWith('/'))\n throw new Error(`Docker mount target must be an absolute path: ${mount.target}`)\n if (mount.readonly && mount.shared)\n throw new Error(`Docker mount cannot be both readonly and shared: ${mount.target}`)\n const opts = mount.readonly ? ':ro' : mount.shared ? ':z' : ''\n return `${mount.source}:${mount.target}${opts}`\n}\n\n/** Assemble dockerode `HostConfig.Binds` from the context's `mounts`. */\nexport function buildBindSpecs(mounts: NonNullable<SpawnConfig['mounts']>): string[] {\n return mounts.map(formatBindMount)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"docker.js","names":[],"sources":["../../src/contexts/docker.ts"],"sourcesContent":["/**\n * Docker execution context.\n *\n * Runs tools inside a Docker container via dockerode.\n * Full isolation with configurable resource limits.\n *\n * Requires `dockerode` as an optional peer dependency.\n */\n\nimport type { CappedSlot } from './output-cap'\nimport type { ContextCapabilities, ExecResult, ExecutionContext, ExecutionHandle, ListFilesEntry, SpawnConfig } from './types'\nimport { Buffer } from 'node:buffer'\nimport { alwaysQuote } from '../tools/shell-quote'\nimport { fileSizeViaExec } from './exec-file-size'\nimport { globFilesViaShell } from './glob'\nimport { appendCapped, DEFAULT_MAX_OUTPUT_BUFFER, outputExceededMessage } from './output-cap'\n\n/**\n * `readFile` transport cap: headroom above the whole-read tool ceiling\n * (256MB in tools/whole-read-guard.ts) so tool policy — not the docker\n * transport — decides read limits, keeping refusal behavior consistent\n * across contexts. Still bounded: a probe-failure path can't buffer\n * more than this into host memory.\n */\nexport const READ_FILE_MAX_BUFFER = 512 * 1024 * 1024\n\nconst SINGLE_QUOTE_RE = /'/g\n\n/**\n * dockerode surfaces HTTP errors with a `statusCode`. 304 = container\n * already stopped, 404 = already gone — both are fine during teardown.\n */\nfunction isGoneOrAlreadyStopped(err: unknown): boolean {\n const code = (err as { statusCode?: number } | null)?.statusCode\n return code === 304 || code === 404\n}\n\ninterface ContainerRef {\n handle: ExecutionHandle\n container: any\n docker: any\n}\n\nexport function createDockerContext(config?: SpawnConfig): ExecutionContext {\n let counter = 0\n const containers = new Map<string, ContainerRef>()\n const defaultImage = config?.image ?? 'oven/bun:latest'\n const defaultCwd = config?.cwd ?? '/workspace'\n const defaultEnv = config?.env\n const defaultLimits = config?.limits\n const defaultMounts = config?.mounts ?? []\n const defaultName = config?.name\n\n async function getDockerode() {\n try {\n const Dockerode = (await import('dockerode')).default\n return new Dockerode()\n }\n catch {\n throw new Error('dockerode is required for Docker execution context. Install it with: bun add dockerode')\n }\n }\n\n /**\n * The exec transport with an explicit output cap. The public `exec`\n * uses the shared 10MB default; `readFile` passes headroom above the\n * whole-read tool ceiling so docker's EFFECTIVE read limit matches\n * every other context (the fileSize probe refuses oversized files\n * before this ever runs — the transport cap is the safety net for\n * probe-failure paths, not the primary bound).\n */\n async function execCapped(handle: ExecutionHandle, command: string, options: { cwd?: string, env?: Record<string, string>, timeout?: number } | undefined, maxBuffer: number): Promise<ExecResult> {\n const ref = containers.get(handle.id)\n if (!ref)\n throw new Error(`Container ${handle.id} not found`)\n\n const execCwd = options?.cwd ?? handle.cwd\n const env = options?.env\n ? Object.entries(options.env).map(([k, v]) => `${k}=${v}`)\n : []\n\n const timeout = options?.timeout ?? defaultLimits?.timeout ?? 30\n\n const exec = await ref.container.exec({\n // Wrap with coreutils `timeout` so a runaway command is killed\n // server-side too — the host-side timer below only detaches the\n // stream; without this the process would keep running in the\n // container until destroy(). Exec-array form avoids re-quoting the\n // command. `timeout` exits 124 on expiry, matching the host\n // sentinel. (Default image oven/bun is Debian-based → coreutils.)\n Cmd: ['timeout', String(timeout), 'sh', '-c', command],\n WorkingDir: execCwd,\n Env: env,\n AttachStdout: true,\n AttachStderr: true,\n })\n\n const stream = await exec.start({ Detach: false })\n\n return new Promise<ExecResult>((resolve) => {\n // Byte-capped accumulation via the shared exec-transport primitive\n // (contexts/output-cap.ts) — the docker `readFile` is `cat` through\n // this very path, so an uncapped sink would buffer a container's\n // multi-GB file straight into HOST memory. On overflow the stream\n // is destroyed and the exec resolves exit 143 with a message\n // (mirroring the process context), so `readFile` THROWS instead of\n // silently returning truncated content — silent truncation would\n // corrupt `edit`'s read-modify-write into data loss.\n const stdoutSlot: CappedSlot = { text: '', bytes: 0 }\n const stderrSlot: CappedSlot = { text: '', bytes: 0 }\n let resolved = false\n\n const overflow = (): void => {\n if (resolved)\n return\n resolved = true\n try {\n stream.destroy?.()\n }\n catch { /* swallow */ }\n resolve({\n stdout: stdoutSlot.text,\n stderr: `${stderrSlot.text ? `${stderrSlot.text}\\n` : ''}${outputExceededMessage(maxBuffer, 'stream detached (server-side timeout reaps the process)')}`,\n exitCode: 143,\n })\n }\n\n const cappedWrite = (slot: CappedSlot) => (chunk: Buffer | string): boolean => {\n const buf = typeof chunk === 'string' ? Buffer.from(chunk, 'utf-8') : chunk\n if (appendCapped(slot, buf, maxBuffer) !== 'ok')\n overflow()\n return true\n }\n\n // Docker's exec stream is a multiplexed protocol — a single byte\n // stream that interleaves stdout/stderr frames. `demuxStream`\n // splits the two onto separate writable sinks. Without it, every\n // byte landed on `stdout` and `stderr` was permanently empty.\n const stdoutSink = {\n write: cappedWrite(stdoutSlot),\n end() {},\n on() {},\n once() {},\n emit() { return true },\n }\n const stderrSink = {\n write: cappedWrite(stderrSlot),\n end() {},\n on() {},\n once() {},\n emit() { return true },\n }\n try {\n ref.docker.modem.demuxStream(stream, stdoutSink, stderrSink)\n }\n catch {\n // Older / different transports may not be multiplexed (TTY mode);\n // fall back to a raw stdout reader so we don't lose output entirely.\n // Same cap: TTY mode is still a host-memory sink.\n stream.on('data', (chunk: Buffer) => {\n cappedWrite(stdoutSlot)(chunk)\n })\n }\n\n // Host-side fallback timer. The server-side `timeout` wrapper is\n // the primary kill mechanism; give it a grace second so the\n // stream-end path (which carries the real exit code) usually wins.\n const timer = setTimeout(() => {\n if (resolved)\n return\n resolved = true\n // Best-effort: detach the stream so we stop accumulating bytes.\n // The server-side `timeout` wrapper kills the process itself.\n try { stream.destroy?.() }\n catch { /* swallow */ }\n resolve({ stdout: stdoutSlot.text, stderr: stderrSlot.text ? `${stderrSlot.text}\\n[timeout]` : '[timeout]', exitCode: 124 })\n }, timeout * 1000 + 1000)\n\n stream.on('end', async () => {\n if (resolved)\n return\n resolved = true\n clearTimeout(timer)\n let exitCode: number\n let inspectNote = ''\n try {\n const inspect = await exec.inspect()\n // `ExitCode: null` means Docker hasn't recorded an exit yet —\n // the outcome is unknown, not success. Use a sentinel so\n // callers don't mistake it for a clean exit.\n if (inspect.ExitCode == null) {\n exitCode = -1\n inspectNote = '[exit code unavailable: exec still marked running by daemon]'\n }\n else {\n exitCode = inspect.ExitCode\n }\n }\n catch (err) {\n exitCode = -1\n inspectNote = `[exit code unavailable: exec inspect failed: ${(err as Error)?.message ?? String(err)}]`\n }\n resolve({\n stdout: stdoutSlot.text,\n stderr: inspectNote ? (stderrSlot.text ? `${stderrSlot.text}\\n${inspectNote}` : inspectNote) : stderrSlot.text,\n exitCode,\n })\n })\n\n stream.on('error', (err: Error) => {\n if (resolved)\n return\n resolved = true\n clearTimeout(timer)\n resolve({ stdout: stdoutSlot.text, stderr: stderrSlot.text ? `${stderrSlot.text}\\n${err.message}` : err.message, exitCode: 1 })\n })\n })\n }\n\n const ctx: ExecutionContext = {\n type: 'docker',\n\n capabilities: {\n shell: true,\n filesystem: true,\n glob: true,\n network: true,\n gpu: false,\n } satisfies ContextCapabilities,\n\n async spawn(overrides?: SpawnConfig): Promise<ExecutionHandle> {\n const docker = await getDockerode()\n const id = `docker-${++counter}`\n const image = overrides?.image ?? defaultImage\n const cwd = overrides?.cwd ?? defaultCwd\n const mounts = [...defaultMounts, ...(overrides?.mounts ?? [])]\n const name = overrides?.name ?? defaultName\n\n // Pull image if not available\n try {\n await docker.getImage(image).inspect()\n }\n catch {\n await new Promise<void>((resolve, reject) => {\n docker.pull(image, (err: Error | null, stream: NodeJS.ReadableStream) => {\n if (err)\n return reject(err)\n docker.modem.followProgress(stream, (err2: Error | null) => {\n err2 ? reject(err2) : resolve()\n })\n })\n })\n }\n\n const limits = { ...defaultLimits, ...overrides?.limits }\n const hostConfig: Record<string, unknown> = {}\n\n if (limits?.memory) {\n hostConfig.Memory = limits.memory * 1024 * 1024\n }\n if (limits?.cpu) {\n hostConfig.NanoCpus = Number.parseFloat(limits.cpu) * 1e9\n }\n\n // Opt-in hardening (all OFF by default — see ContextHardening).\n const hardening = { ...config?.hardening, ...overrides?.hardening }\n if (hardening.dropAllCapabilities)\n hostConfig.CapDrop = ['ALL']\n if (hardening.noNewPrivileges)\n hostConfig.SecurityOpt = ['no-new-privileges']\n if (hardening.readonlyRootfs)\n hostConfig.ReadonlyRootfs = true\n if (hardening.pidsLimit != null)\n hostConfig.PidsLimit = hardening.pidsLimit\n\n const env = { ...defaultEnv, ...overrides?.env }\n\n const bindSpecs = buildBindSpecs(mounts)\n if (bindSpecs.length > 0) {\n hostConfig.Binds = bindSpecs\n }\n\n const portSpec = overrides?.ports ?? config?.ports\n const exposedPorts: Record<string, Record<string, never>> = {}\n const portBindings: Record<string, Array<{ HostPort: string }>> = {}\n\n if (portSpec?.length) {\n for (const p of portSpec) {\n const key = `${p.container}/${p.proto ?? 'tcp'}`\n exposedPorts[key] = {}\n portBindings[key] = [{ HostPort: p.host == null ? '' : String(p.host) }]\n }\n hostConfig.PortBindings = portBindings\n }\n\n const user = overrides?.user ?? config?.user\n const network = overrides?.network ?? config?.network\n if (network) {\n hostConfig.NetworkMode = network\n }\n\n const labels = { ...config?.labels, ...overrides?.labels }\n\n const container = await docker.createContainer({\n // Suffix with a short random token so a crashed run's leftover container\n // (same prefix) never blocks a fresh one with HTTP 409 name conflict.\n ...(name ? { name: `${name}-${counter}-${Math.random().toString(36).slice(2, 8)}` } : {}),\n Image: image,\n Cmd: ['sleep', 'infinity'],\n WorkingDir: cwd,\n Env: Object.entries(env).map(([k, v]) => `${k}=${v}`),\n HostConfig: hostConfig,\n ...(Object.keys(exposedPorts).length ? { ExposedPorts: exposedPorts } : {}),\n ...(user ? { User: user } : {}),\n ...(Object.keys(labels).length ? { Labels: labels } : {}),\n })\n\n try {\n await container.start()\n }\n catch (err) {\n // The container was created but never started — remove it so a\n // failed spawn doesn't leak a stopped container on the daemon.\n await container.remove({ force: true }).catch(() => {})\n throw err\n }\n\n const handle: ExecutionHandle = { id, type: 'docker', cwd }\n containers.set(id, { handle, container, docker })\n\n return handle\n },\n\n async exec(handle: ExecutionHandle, command: string, options?: { cwd?: string, env?: Record<string, string>, timeout?: number }): Promise<ExecResult> {\n return execCapped(handle, command, options, DEFAULT_MAX_OUTPUT_BUFFER)\n },\n\n async fileSize(handle: ExecutionHandle, path: string): Promise<number | null> {\n // Shared exec-probed capability (see exec-file-size.ts) — the same\n // implementation as the sandbox contexts, for a CONSISTENT\n // whole-read experience: tools refuse >ceiling files with the same\n // message everywhere, instead of docker surfacing a transport\n // error at a different threshold.\n return fileSizeViaExec(command => execCapped(handle, command, undefined, DEFAULT_MAX_OUTPUT_BUFFER), path)\n },\n\n // Paths are model-supplied — single-quote them (`alwaysQuote`) rather\n // than JSON.stringify: `$(…)` and backticks still expand inside the\n // double quotes JSON produces, which was a command-injection vector.\n async readFile(handle: ExecutionHandle, path: string): Promise<string> {\n // Transport headroom ABOVE the whole-read tool ceiling (256MB,\n // enforced via the fileSize probe BEFORE this runs): without it,\n // docker's effective read limit would be the 10MB exec default —\n // inconsistent with every other context and surfaced as a\n // confusing transport error instead of the uniform refusal.\n const result = await execCapped(handle, `cat ${alwaysQuote(path)}`, undefined, READ_FILE_MAX_BUFFER)\n if (result.exitCode !== 0)\n throw new Error(`Failed to read file: ${result.stderr}`)\n return result.stdout\n },\n\n async writeFile(handle: ExecutionHandle, path: string, content: string): Promise<void> {\n const escaped = content.replace(SINGLE_QUOTE_RE, String.raw`'\\''`)\n const result = await ctx.exec(handle, `mkdir -p \"$(dirname ${alwaysQuote(path)})\" && printf '%s' '${escaped}' > ${alwaysQuote(path)}`)\n if (result.exitCode !== 0)\n throw new Error(`Failed to write file: ${result.stderr}`)\n },\n\n async listFiles(handle: ExecutionHandle, path: string, options): Promise<ListFilesEntry[]> {\n if (options?.glob)\n return globFilesViaShell(path, command => ctx.exec(handle, command), options?.limit ?? 1000)\n // `-p` suffixes directories with `/` — parity with the process\n // context's dirent-aware listing.\n const result = await ctx.exec(handle, `ls -1p ${alwaysQuote(path)}`)\n // Surface `ls` stderr instead of masking failures as an empty dir —\n // the `list_files` tool differentiates \"No such file\" / \"Not a\n // directory\" / \"Permission denied\" from its catch handler.\n if (result.exitCode !== 0)\n throw new Error(result.stderr.trim() || `ls failed with exit ${result.exitCode}`)\n return result.stdout.trim().split('\\n').filter(Boolean)\n },\n\n async getMappedPort(handle: ExecutionHandle, containerPort: number): Promise<number | null> {\n const ref = containers.get(handle.id)\n if (!ref)\n return null\n\n const info = await ref.container.inspect()\n const bindings = info?.NetworkSettings?.Ports ?? {}\n\n for (const proto of ['tcp', 'udp']) {\n const list = bindings[`${containerPort}/${proto}`]\n if (Array.isArray(list) && list.length > 0 && list[0]?.HostPort) {\n return Number.parseInt(list[0].HostPort, 10)\n }\n }\n\n return null\n },\n\n async destroy(handle: ExecutionHandle): Promise<void> {\n const ref = containers.get(handle.id)\n if (!ref)\n return\n\n try {\n try {\n await ref.container.stop({ t: 5 })\n }\n catch {\n // 304 (already stopped) / 404 (already gone) are expected here.\n // Any other stop failure must not prevent the remove({force})\n // below — force removal also works on running containers.\n }\n try {\n await ref.container.remove({ force: true })\n }\n catch (err) {\n if (!isGoneOrAlreadyStopped(err))\n throw err\n }\n }\n finally {\n containers.delete(handle.id)\n }\n },\n }\n\n return ctx\n}\n\n/**\n * Format one {@link ContextMount} as a dockerode `HostConfig.Binds` entry.\n * Supports read-only (`:ro`) and SELinux shared-label (`:z`) options; the two\n * are mutually exclusive (a read-only mount can't also be a shared rw mount).\n */\nfunction formatBindMount(mount: NonNullable<SpawnConfig['mounts']>[number]): string {\n if (!mount.source)\n throw new Error('Docker mount source is required')\n if (!mount.target || !mount.target.startsWith('/'))\n throw new Error(`Docker mount target must be an absolute path: ${mount.target}`)\n if (mount.readonly && mount.shared)\n throw new Error(`Docker mount cannot be both readonly and shared: ${mount.target}`)\n const opts = mount.readonly ? ':ro' : mount.shared ? ':z' : ''\n return `${mount.source}:${mount.target}${opts}`\n}\n\n/** Assemble dockerode `HostConfig.Binds` from the context's `mounts`. */\nexport function buildBindSpecs(mounts: NonNullable<SpawnConfig['mounts']>): string[] {\n return mounts.map(formatBindMount)\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { Ll as Logger } from "../index-B8UtiZy7.js";
1
+ import { Ul as Logger } from "../index-C6Il4kBD.js";
2
2
  import { t as SandboxProvider } from "../sandbox-CgjG2VvQ.js";
3
3
 
4
4
  //#region src/contexts/e2b.d.ts
@@ -9,6 +9,8 @@ interface E2BCommandResult {
9
9
  }
10
10
  interface E2BEntry {
11
11
  name: string;
12
+ /** Present in the E2B SDK's EntryInfo; used for the `/` dir suffix. */
13
+ type?: string;
12
14
  }
13
15
  interface E2BSandbox {
14
16
  sandboxId: string;
@@ -1 +1 @@
1
- {"version":3,"file":"e2b.d.ts","names":[],"sources":["../../src/contexts/e2b.ts"],"mappings":";;;;UAoDU,gBAAA;EACR,MAAA;EACA,MAAA;EACA,QAAA;AAAA;AAAA,UAGQ,QAAA;EACR,IAAI;AAAA;AAAA,UAGI,UAAA;EACR,SAAA;EACA,QAAA;IACE,GAAA,GAAM,GAAA,UAAa,IAAA;MAAS,GAAA;MAAc,IAAA,GAAO,MAAA;MAAwB,SAAA;IAAA,MAAyB,OAAA,CAAQ,gBAAA;EAAA;EAE5G,KAAA;IACE,IAAA,GAAO,IAAA,aAAiB,OAAA;IACxB,KAAA,GAAQ,IAAA,UAAc,IAAA,aAAiB,OAAA;IACvC,IAAA,GAAO,IAAA,aAAiB,OAAA,CAAQ,QAAA;EAAA;EAElC,IAAA,QAAY,OAAA;EAKZ,KAAA,SAAc,OAAA;EACd,SAAA,SAAkB,OAAA;AAAA;AAAA,UA0BH,kBAAA;EAAA;EAEf,MAAA;;;;;;EAMA,MAAA;EAoBA;;;;;EAdA,QAAA;EAuCA;;;;;;AA0BS;AA4BX;EApFE,SAAA;;;;;EAKA,GAAA;EAmFQ;;;;EA9ER,cAAA;EA4EA;;;;;EAtEA,GAAA,GAAM,MAAA;EAuEN;;;AACQ;AA8BV;EAhGE,MAAA,GAAS,MAAM;;;;;;;;EAQf,uBAAA;EAqG+B;AAAA;AAgBjC;;;;;;EA5GE,OAAA;IAAY,IAAA;IAAc,OAAA;EAAA;EA6G1B;;;;;;;AAGQ;AAmCV;;;;;;;;EAlIE,SAAA;AAAA;AAgJF;;;;;;;;;;;;AAAA,iBApHsB,aAAA,CACpB,OAAA,EAAS,UAAA,EACT,MAAA,EAAQ,MAAA,EACR,IAAA;EAAQ,IAAA;EAAc,OAAA;EAAiB,GAAA;EAAc,IAAA,GAAO,MAAA;AAAA,IAC3D,OAAA;AAAA,UA8Bc,mBAAA;EAqG0B;EAnGzC,cAAA;EAmGmE;;AAAe;;;;EA5FlF,aAAA;;EAEA,GAAA;;EAEA,KAAA,IAAS,EAAA,aAAe,OAAO;AAAA;;;;;;;;;;;;;;iBAgBX,eAAA,CACpB,OAAA,EAAS,UAAA,EACT,MAAA,EAAQ,MAAA,EACR,IAAA,EAAM,mBAAA,GACL,OAAA;;;;;;;;iBAmCa,kBAAA,CACd,gBAAA,sBACA,cAAA,sBACA,GAAA,GAAK,MAAM;;;;;;;iBAWG,aAAA,CACd,SAAA,EAAW,MAAA,8BACX,WAAA,EAAa,MAAA,+BACZ,MAAA;;;;;;;;iBAgBa,iBAAA,CAAkB,OAAA,GAAS,kBAAA,GAA0B,eAAe"}
1
+ {"version":3,"file":"e2b.d.ts","names":[],"sources":["../../src/contexts/e2b.ts"],"mappings":";;;;UAoDU,gBAAA;EACR,MAAA;EACA,MAAA;EACA,QAAA;AAAA;AAAA,UAGQ,QAAA;EACR,IAAA;EAUA;EARA,IAAI;AAAA;AAAA,UAGI,UAAA;EACR,SAAA;EACA,QAAA;IACE,GAAA,GAAM,GAAA,UAAa,IAAA;MAAS,GAAA;MAAc,IAAA,GAAO,MAAA;MAAwB,SAAA;IAAA,MAAyB,OAAA,CAAQ,gBAAA;EAAA;EAE5G,KAAA;IACE,IAAA,GAAO,IAAA,aAAiB,OAAA;IACxB,KAAA,GAAQ,IAAA,UAAc,IAAA,aAAiB,OAAA;IACvC,IAAA,GAAO,IAAA,aAAiB,OAAA,CAAQ,QAAA;EAAA;EAElC,IAAA,QAAY,OAAA;EAKZ,KAAA,SAAc,OAAA;EACd,SAAA,SAAkB,OAAA;AAAA;AAAA,UA0BH,kBAAA;;EAEf,MAAA;EAAA;;;;;EAMA,MAAA;EA+BA;;;;;EAzBA,QAAA;EAgDY;;;;AAiBH;AA4BX;;;EApFE,SAAA;EAsFQ;;;;EAjFR,GAAA;EAgFS;;;;EA3ET,cAAA;EA6EsB;;;;;EAvEtB,GAAA,GAAM,MAAA;EAwEE;AAAA;AA8BV;;;EAhGE,MAAA,GAAS,MAAM;EAkGf;;;;;;;EA1FA,uBAAA;EAqHoB;;;;;;;;EA5GpB,OAAA;IAAY,IAAA;IAAc,OAAA;EAAA;EA8G1B;;;;;AAEQ;AAmCV;;;;;;;;;AAGuD;EArIrD,SAAA;AAAA;;;;;;;;;;;;;iBA4BoB,aAAA,CACpB,OAAA,EAAS,UAAA,EACT,MAAA,EAAQ,MAAA,EACR,IAAA;EAAQ,IAAA;EAAc,OAAA;EAAiB,GAAA;EAAc,IAAA,GAAO,MAAA;AAAA,IAC3D,OAAA;AAAA,UA8Bc,mBAAA;EAqGoD;EAnGnE,cAAA;EAmGkF;;;;;;EA5FlF,aAAA;;EAEA,GAAA;;EAEA,KAAA,IAAS,EAAA,aAAe,OAAO;AAAA;;;;;;;;;;;;;;iBAgBX,eAAA,CACpB,OAAA,EAAS,UAAA,EACT,MAAA,EAAQ,MAAA,EACR,IAAA,EAAM,mBAAA,GACL,OAAA;;;;;;;;iBAmCa,kBAAA,CACd,gBAAA,sBACA,cAAA,sBACA,GAAA,GAAK,MAAM;;;;;;;iBAWG,aAAA,CACd,SAAA,EAAW,MAAA,8BACX,WAAA,EAAa,MAAA,+BACZ,MAAA;;;;;;;;iBAgBa,iBAAA,CAAkB,OAAA,GAAS,kBAAA,GAA0B,eAAe"}
@@ -264,7 +264,7 @@ function createE2BProvider(options = {}) {
264
264
  await get(sandboxId).sandbox.files.write(path, content);
265
265
  },
266
266
  async listFiles(sandboxId, path) {
267
- return (await get(sandboxId).sandbox.files.list(path)).map((e) => e.name);
267
+ return (await get(sandboxId).sandbox.files.list(path)).map((e) => e.type === "dir" ? `${e.name}/` : e.name);
268
268
  },
269
269
  async destroy(sandboxId) {
270
270
  const entry = live.get(sandboxId);