@marimo-team/islands 0.25.1-dev26 → 0.25.1-dev28

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 (170) hide show
  1. package/dist/{ErrorBoundary-pWWhtr0s.js → ErrorBoundary-CE6-1WC1.js} +1 -1
  2. package/dist/{code-visibility-Ba_KMLhg.js → code-visibility--u2Q2Zfx.js} +1 -1
  3. package/dist/{common-Cqwp2VSj.js → common-hHL7iKzA.js} +1 -1
  4. package/dist/{data-grid-overlay-editor-CpH8-A59.js → data-grid-overlay-editor-v7VZO5up.js} +1 -1
  5. package/dist/{feedback-button-CJxD9fy8.js → feedback-button-C3Ya1IXo.js} +2 -2
  6. package/dist/{glide-data-editor-cm-NQofk.js → glide-data-editor-BKOLh2Tv.js} +3 -3
  7. package/dist/{globals-BquX8N9R.js → globals-DOJr_y24.js} +1 -1
  8. package/dist/main.js +6 -6
  9. package/dist/{number-overlay-editor-3AkAzj7F.js → number-overlay-editor-DkEFJYK7.js} +1 -1
  10. package/dist/{reveal-component-rC5k5pu3.js → reveal-component-rbEv9ppg.js} +2 -2
  11. package/dist/style.css +1 -1
  12. package/package.json +1 -1
  13. package/src/__mocks__/common.ts +3 -3
  14. package/src/__tests__/CellStatus.test.tsx +1 -1
  15. package/src/__tests__/setup.ts +7 -7
  16. package/src/components/app-config/ai-config.tsx +2 -0
  17. package/src/components/chat/chat-panel.tsx +19 -8
  18. package/src/components/data-table/column-summary/chart-spec-model.tsx +4 -4
  19. package/src/components/dependency-graph/elements.ts +2 -2
  20. package/src/components/editor/actions/__tests__/pair-with-agent.test.tsx +107 -0
  21. package/src/components/editor/actions/pair-with-agent.tsx +119 -0
  22. package/src/components/editor/actions/useNotebookActions.tsx +2 -1
  23. package/src/components/editor/ai/add-cell-with-ai.tsx +2 -0
  24. package/src/components/editor/ai/staged-cell-submission.ts +2 -2
  25. package/src/components/editor/ai/transport/chat-transport.tsx +1 -1
  26. package/src/components/editor/connections/database/as-code.ts +44 -44
  27. package/src/components/editor/connections/storage/as-code.ts +3 -3
  28. package/src/components/editor/file-tree/requesting-tree.tsx +16 -13
  29. package/src/components/editor/navigation/clipboard.ts +2 -2
  30. package/src/components/editor/output/ansi-reduce.ts +13 -13
  31. package/src/components/slides/__tests__/minimap-actions.test.tsx +7 -7
  32. package/src/components/slides/__tests__/minimap.test.ts +12 -12
  33. package/src/components/slides/__tests__/slide-scroll-hint.test.tsx +8 -8
  34. package/src/core/ai/context/__tests__/registry.test.ts +31 -28
  35. package/src/core/ai/context/providers/cell-output.ts +8 -8
  36. package/src/core/ai/context/providers/datasource.ts +10 -7
  37. package/src/core/ai/context/providers/error.ts +7 -7
  38. package/src/core/ai/context/providers/file.ts +8 -8
  39. package/src/core/ai/context/providers/tables.ts +7 -7
  40. package/src/core/ai/context/providers/variable.ts +7 -7
  41. package/src/core/ai/context/registry.ts +18 -18
  42. package/src/core/ai/ids/ids.ts +5 -5
  43. package/src/core/ai/model-registry.ts +12 -9
  44. package/src/core/ai/staged-cells.ts +6 -6
  45. package/src/core/ai/tools/base.ts +6 -6
  46. package/src/core/ai/tools/edit-notebook-tool.ts +6 -6
  47. package/src/core/ai/tools/registry.ts +4 -4
  48. package/src/core/ai/tools/run-cells-tool.ts +7 -7
  49. package/src/core/ai/tools/sample-tool.ts +6 -6
  50. package/src/core/codemirror/cells/debugger-decorations.ts +13 -7
  51. package/src/core/codemirror/cells/line-timing-decorations.ts +9 -9
  52. package/src/core/codemirror/cells/traceback-decorations.ts +7 -4
  53. package/src/core/codemirror/code-lens/extension.ts +13 -13
  54. package/src/core/codemirror/copilot/__tests__/language-server.test.ts +6 -6
  55. package/src/core/codemirror/copilot/__tests__/transport.test.ts +12 -12
  56. package/src/core/codemirror/copilot/language-server.ts +13 -13
  57. package/src/core/codemirror/copilot/transport.ts +1 -1
  58. package/src/core/codemirror/find-replace/search-highlight.ts +5 -5
  59. package/src/core/codemirror/go-to-definition/underline.ts +3 -3
  60. package/src/core/codemirror/keymaps/vim.ts +3 -3
  61. package/src/core/codemirror/language/languages/markdown.ts +8 -8
  62. package/src/core/codemirror/language/languages/python.ts +7 -7
  63. package/src/core/codemirror/language/languages/sql/completion-builder.ts +5 -5
  64. package/src/core/codemirror/language/languages/sql/completion-store.ts +4 -4
  65. package/src/core/codemirror/language/languages/sql/sql.ts +21 -21
  66. package/src/core/codemirror/lsp/__tests__/transports.test.ts +10 -10
  67. package/src/core/codemirror/lsp/federated-lsp.ts +29 -27
  68. package/src/core/codemirror/lsp/notebook-lsp.ts +21 -21
  69. package/src/core/codemirror/misc/__tests__/dnd.test.ts +10 -10
  70. package/src/core/codemirror/misc/__tests__/paste.test.ts +7 -4
  71. package/src/core/codemirror/reactive-references/extension.ts +3 -3
  72. package/src/core/codemirror/rtc/loro/awareness.ts +12 -12
  73. package/src/core/codemirror/rtc/loro/sync.ts +5 -5
  74. package/src/core/codemirror/vim/cursor-visibility.ts +3 -3
  75. package/src/core/dom/ui-element.ts +5 -5
  76. package/src/core/dom/uiregistry.ts +10 -10
  77. package/src/core/hotkeys/hotkeys.ts +9 -9
  78. package/src/core/islands/__tests__/bridge.test.ts +4 -4
  79. package/src/core/islands/__tests__/test-utils.tsx +4 -4
  80. package/src/core/islands/bridge.ts +85 -82
  81. package/src/core/islands/components/web-components.tsx +7 -7
  82. package/src/core/islands/worker/__tests__/controller.test.ts +1 -1
  83. package/src/core/islands/worker/controller.ts +6 -3
  84. package/src/core/islands/worker-factory.ts +2 -2
  85. package/src/core/kernel/RuntimeState.ts +4 -4
  86. package/src/core/lsp/__tests__/transport.test.ts +19 -19
  87. package/src/core/lsp/transport.ts +5 -5
  88. package/src/core/network/CachingRequestRegistry.ts +2 -2
  89. package/src/core/network/DeferredRequestRegistry.ts +4 -4
  90. package/src/core/runtime/runtime.ts +25 -20
  91. package/src/core/static/virtual-file-tracker.ts +5 -5
  92. package/src/core/wasm/__tests__/bridge.test.ts +2 -2
  93. package/src/core/wasm/bridge.ts +130 -126
  94. package/src/core/wasm/router.ts +5 -5
  95. package/src/core/wasm/store.ts +4 -4
  96. package/src/core/wasm/worker/bootstrap.ts +8 -5
  97. package/src/core/wasm/worker/message-buffer.ts +3 -3
  98. package/src/core/websocket/transports/__tests__/ws.test.ts +8 -8
  99. package/src/core/websocket/transports/basic.ts +15 -8
  100. package/src/core/websocket/transports/sse.ts +7 -7
  101. package/src/core/websocket/transports/transport.ts +3 -3
  102. package/src/core/websocket/transports/ws.ts +9 -7
  103. package/src/hooks/__tests__/useOverflowDetection.test.tsx +5 -5
  104. package/src/hooks/__tests__/useResizeObserver.test.tsx +12 -12
  105. package/src/plugins/core/registerReactComponent.tsx +5 -5
  106. package/src/plugins/core/sidebar-element.tsx +2 -2
  107. package/src/plugins/impl/ButtonPlugin.tsx +3 -3
  108. package/src/plugins/impl/CheckboxPlugin.tsx +3 -3
  109. package/src/plugins/impl/CodeEditorPlugin.tsx +3 -3
  110. package/src/plugins/impl/DatePickerPlugin.tsx +3 -3
  111. package/src/plugins/impl/DateRangePlugin.tsx +3 -3
  112. package/src/plugins/impl/DateTimePickerPlugin.tsx +3 -3
  113. package/src/plugins/impl/DictPlugin.tsx +3 -3
  114. package/src/plugins/impl/DropdownPlugin.tsx +3 -3
  115. package/src/plugins/impl/FileUploadPlugin.tsx +3 -3
  116. package/src/plugins/impl/MatrixPlugin.tsx +4 -4
  117. package/src/plugins/impl/MicrophonePlugin.tsx +3 -3
  118. package/src/plugins/impl/MultiselectPlugin.tsx +3 -3
  119. package/src/plugins/impl/NumberPlugin.tsx +3 -3
  120. package/src/plugins/impl/RadioPlugin.tsx +3 -3
  121. package/src/plugins/impl/RangeSliderPlugin.tsx +3 -3
  122. package/src/plugins/impl/RefreshPlugin.tsx +3 -3
  123. package/src/plugins/impl/SliderPlugin.tsx +3 -3
  124. package/src/plugins/impl/SwitchPlugin.tsx +3 -3
  125. package/src/plugins/impl/TabsPlugin.tsx +3 -3
  126. package/src/plugins/impl/TextAreaPlugin.tsx +3 -3
  127. package/src/plugins/impl/TextInputPlugin.tsx +3 -3
  128. package/src/plugins/impl/__tests__/MatrixPlugin.test.tsx +2 -2
  129. package/src/plugins/impl/anywidget/model.ts +10 -10
  130. package/src/plugins/impl/anywidget/registry.ts +12 -9
  131. package/src/plugins/impl/anywidget/runtime.ts +11 -9
  132. package/src/plugins/impl/anywidget/widget-binding.ts +6 -6
  133. package/src/plugins/impl/matplotlib/MatplotlibPlugin.tsx +3 -3
  134. package/src/plugins/impl/matplotlib/__tests__/matplotlib-renderer.test.ts +2 -2
  135. package/src/plugins/impl/matplotlib/matplotlib-renderer.ts +2 -2
  136. package/src/plugins/impl/mpl-interactive/mpl-websocket-shim.ts +10 -10
  137. package/src/plugins/impl/panel/utils.ts +5 -5
  138. package/src/plugins/impl/plotly/PlotlyPlugin.tsx +3 -3
  139. package/src/plugins/impl/vega/VegaPlugin.tsx +3 -3
  140. package/src/plugins/impl/vega/__tests__/use-vega-container-remeasure.test.ts +7 -7
  141. package/src/plugins/layout/AccordionPlugin.tsx +3 -3
  142. package/src/plugins/layout/CalloutPlugin.tsx +3 -3
  143. package/src/plugins/layout/ImageComparisonPlugin.tsx +5 -3
  144. package/src/plugins/layout/JsonOutputPlugin.tsx +3 -3
  145. package/src/plugins/layout/MimeRenderPlugin.tsx +3 -3
  146. package/src/plugins/layout/NavigationMenuPlugin.tsx +3 -3
  147. package/src/plugins/layout/OutlinePlugin.tsx +3 -3
  148. package/src/plugins/layout/ProgressPlugin.tsx +3 -3
  149. package/src/plugins/layout/RoutesPlugin.tsx +3 -3
  150. package/src/plugins/layout/StatPlugin.tsx +3 -3
  151. package/src/plugins/layout/TexPlugin.tsx +3 -3
  152. package/src/plugins/layout/carousel/CarouselPlugin.tsx +4 -4
  153. package/src/plugins/layout/mermaid/MermaidPlugin.tsx +3 -3
  154. package/src/utils/Deferred.ts +6 -6
  155. package/src/utils/async-capture-tracker.ts +10 -7
  156. package/src/utils/errors.ts +5 -5
  157. package/src/utils/id-tree.tsx +83 -80
  158. package/src/utils/lru.ts +1 -1
  159. package/src/utils/multi-map.ts +14 -12
  160. package/src/utils/paths.ts +6 -6
  161. package/src/utils/pluralize.ts +3 -3
  162. package/src/utils/progress.ts +6 -6
  163. package/src/utils/python-poet/poet.ts +14 -14
  164. package/src/utils/routes.ts +4 -2
  165. package/src/utils/semaphore.ts +6 -6
  166. package/src/utils/storage/storage.ts +6 -6
  167. package/src/utils/storage/typed.ts +16 -12
  168. package/src/utils/time.ts +9 -9
  169. package/src/utils/timed-cache.ts +4 -4
  170. package/src/utils/tracer.ts +9 -6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marimo-team/islands",
3
- "version": "0.25.1-dev26",
3
+ "version": "0.25.1-dev28",
4
4
  "main": "dist/main.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -101,9 +101,9 @@ export const SetupMocks = {
101
101
  const unobserve = vi.fn();
102
102
  const disconnect = vi.fn();
103
103
  global.ResizeObserver = class MockResizeObserver {
104
- observe = observe;
105
- unobserve = unobserve;
106
- disconnect = disconnect;
104
+ public observe = observe;
105
+ public unobserve = unobserve;
106
+ public disconnect = disconnect;
107
107
  } as unknown as typeof ResizeObserver;
108
108
  return { observe, unobserve, disconnect };
109
109
  },
@@ -57,7 +57,7 @@ describe("CellStatusComponent", () => {
57
57
 
58
58
  beforeEach(() => {
59
59
  global.Date = class extends Date {
60
- static override now() {
60
+ public static override now() {
61
61
  return mockDate.getTime();
62
62
  }
63
63
  } as typeof Date;
@@ -9,29 +9,29 @@ import "blob-polyfill";
9
9
  // if we need to test ResizeObserver functionality
10
10
  // we can use a library like "resize-observer-polyfill"
11
11
  globalThis.ResizeObserver ??= class {
12
- observe(_target: Element) {
12
+ public observe(_target: Element) {
13
13
  /* noop */
14
14
  }
15
- unobserve(_target: Element) {
15
+ public unobserve(_target: Element) {
16
16
  /* noop */
17
17
  }
18
- disconnect() {
18
+ public disconnect() {
19
19
  /* noop */
20
20
  }
21
21
  } as never;
22
22
 
23
23
  // mock implementation because jsdom doesn't support IntersectionObserver
24
24
  globalThis.IntersectionObserver ??= class {
25
- observe(_target: Element) {
25
+ public observe(_target: Element) {
26
26
  /* noop */
27
27
  }
28
- unobserve(_target: Element) {
28
+ public unobserve(_target: Element) {
29
29
  /* noop */
30
30
  }
31
- disconnect() {
31
+ public disconnect() {
32
32
  /* noop */
33
33
  }
34
- takeRecords() {
34
+ public takeRecords() {
35
35
  return [];
36
36
  }
37
37
  } as never;
@@ -59,6 +59,7 @@ import {
59
59
  listModelsForAiSettings,
60
60
  } from "../ai/ai-utils";
61
61
  import { getTagColour } from "../ai/display-helpers";
62
+ import { PairWithAgentBanner } from "../editor/actions/pair-with-agent";
62
63
  import {
63
64
  Accordion,
64
65
  AccordionContent,
@@ -2005,6 +2006,7 @@ export const AiConfig: React.FC<AiConfigProps> = ({
2005
2006
  {aiEnabled !== false && (
2006
2007
  <>
2007
2008
  <TabsContent value="ai-providers">
2009
+ <PairWithAgentBanner className="mb-4" />
2008
2010
  <AiProvidersConfig
2009
2011
  form={form}
2010
2012
  config={config}
@@ -64,6 +64,10 @@ import { cn } from "@/utils/cn";
64
64
  import { Logger } from "@/utils/Logger";
65
65
  import { AIModelDropdown } from "../ai/ai-model-dropdown";
66
66
  import { useOpenSettingsToTab } from "../app-config/state";
67
+ import {
68
+ PairWithAgentBanner,
69
+ PairWithAgentButton,
70
+ } from "../editor/actions/pair-with-agent";
67
71
  import { PairWithAgentModal } from "../editor/actions/pair-with-agent-modal";
68
72
  import { PromptInput } from "../editor/ai/add-cell-with-ai";
69
73
  import {
@@ -163,6 +167,9 @@ const ChatHeader: React.FC<ChatHeaderProps> = ({
163
167
  </Button>
164
168
  </Tooltip>
165
169
  <div className="flex items-center gap-2">
170
+ <Tooltip content="Connect your agent">
171
+ <PairWithAgentButton className="mr-1" label="Connect" />
172
+ </Tooltip>
166
173
  <MCPStatusIndicator />
167
174
  <Tooltip content="AI Settings">
168
175
  <Button
@@ -493,7 +500,7 @@ const PairWithAgentCallout: React.FC<{
493
500
  onClick={onPairWithAgent}
494
501
  >
495
502
  <SparklesIcon className="h-3.5 w-3.5 shrink-0" />
496
- <span>Work on this notebook with your own agent</span>
503
+ <span>Connect your own agent to this notebook</span>
497
504
  <ArrowRightIcon className="h-3 w-3 shrink-0" />
498
505
  </Button>
499
506
  );
@@ -509,13 +516,17 @@ const ChatPanel = () => {
509
516
  title="Chat with AI"
510
517
  description="No AI provider configured or Chat model not selected"
511
518
  action={
512
- <Button
513
- variant="outline"
514
- size="sm"
515
- onClick={() => handleClick("ai", "ai-providers")}
516
- >
517
- Edit AI settings
518
- </Button>
519
+ <div className="flex flex-col gap-3">
520
+ <Button
521
+ variant="outline"
522
+ size="sm"
523
+ className="self-start"
524
+ onClick={() => handleClick("ai", "ai-providers")}
525
+ >
526
+ Edit AI settings
527
+ </Button>
528
+ <PairWithAgentBanner />
529
+ </div>
519
530
  }
520
531
  icon={<BotMessageSquareIcon />}
521
532
  />
@@ -137,14 +137,14 @@ export class ColumnChartSpecModel<T> {
137
137
  private legacySourceName: "data_0" | "source_0";
138
138
 
139
139
  private readonly fieldTypes: FieldTypes;
140
- readonly stats: Record<ColumnName, Partial<ColumnHeaderStats>>;
141
- readonly binValues: Record<ColumnName, BinValues>;
142
- readonly valueCounts: Record<ColumnName, ValueCounts>;
140
+ public readonly stats: Record<ColumnName, Partial<ColumnHeaderStats>>;
141
+ public readonly binValues: Record<ColumnName, BinValues>;
142
+ public readonly valueCounts: Record<ColumnName, ValueCounts>;
143
143
  private readonly opts: {
144
144
  includeCharts: boolean;
145
145
  };
146
146
 
147
- constructor(
147
+ public constructor(
148
148
  data: T[] | string,
149
149
  fieldTypes: FieldTypes,
150
150
  stats: Record<ColumnName, Partial<ColumnHeaderStats>>,
@@ -225,7 +225,7 @@ export class VerticalElementsBuilder implements ElementsBuilder {
225
225
  };
226
226
  }
227
227
 
228
- createElements(
228
+ public createElements(
229
229
  cellIds: CellId[],
230
230
  cellAtoms: Atom<CellData>[],
231
231
  variables: Variables,
@@ -311,7 +311,7 @@ export class TreeElementsBuilder implements ElementsBuilder {
311
311
  };
312
312
  }
313
313
 
314
- createElements(
314
+ public createElements(
315
315
  cellIds: CellId[],
316
316
  cellAtoms: Atom<CellData>[],
317
317
  variables: Variables,
@@ -0,0 +1,107 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { fireEvent, render, screen } from "@testing-library/react";
4
+ import { Provider } from "jotai";
5
+ import type React from "react";
6
+ import { afterEach, describe, expect, it, vi } from "vitest";
7
+ import { ModalProvider } from "@/components/modal/ImperativeModal";
8
+ import { TooltipProvider } from "@/components/ui/tooltip";
9
+ import { store } from "@/core/state/jotai";
10
+ import { PairWithAgentBanner, PairWithAgentButton } from "../pair-with-agent";
11
+
12
+ const mocks = vi.hoisted(() => ({
13
+ isWasm: vi.fn(() => false),
14
+ }));
15
+
16
+ vi.mock("@/core/wasm/utils", () => ({
17
+ isWasm: mocks.isWasm,
18
+ }));
19
+
20
+ function wrapper({ children }: { children: React.ReactNode }) {
21
+ return (
22
+ <Provider store={store}>
23
+ <TooltipProvider>
24
+ <ModalProvider>{children}</ModalProvider>
25
+ </TooltipProvider>
26
+ </Provider>
27
+ );
28
+ }
29
+
30
+ function stubTokenFetch() {
31
+ vi.stubGlobal(
32
+ "fetch",
33
+ vi.fn().mockResolvedValue(new Response(JSON.stringify({ token: null }))),
34
+ );
35
+ }
36
+
37
+ describe("PairWithAgent", () => {
38
+ afterEach(() => {
39
+ vi.unstubAllGlobals();
40
+ mocks.isWasm.mockReset();
41
+ mocks.isWasm.mockReturnValue(false);
42
+ });
43
+
44
+ it("shows local agent options", () => {
45
+ render(<PairWithAgentBanner />, { wrapper });
46
+
47
+ expect(
48
+ screen.getByRole("group", { name: "Supported agents" }),
49
+ ).toBeVisible();
50
+ expect(screen.getByRole("figure", { name: "Claude Code" })).toBeVisible();
51
+ expect(screen.getByRole("figure", { name: "Codex" })).toBeVisible();
52
+ expect(screen.getByRole("figure", { name: "Cursor" })).toBeVisible();
53
+ expect(screen.getByRole("figure", { name: "Gemini CLI" })).toBeVisible();
54
+ expect(screen.getByRole("figure", { name: "OpenCode" })).toBeVisible();
55
+ expect(
56
+ screen.getByRole("figure", { name: "GitHub Copilot" }),
57
+ ).toBeVisible();
58
+ expect(
59
+ screen.getByRole("figure", { name: "Any local agent" }),
60
+ ).toBeVisible();
61
+ expect(
62
+ screen.getByRole("button", {
63
+ name: "Learn more.",
64
+ }),
65
+ ).toBeVisible();
66
+ });
67
+
68
+ it("opens the pairing dialog from the banner description", () => {
69
+ stubTokenFetch();
70
+ render(<PairWithAgentBanner />, { wrapper });
71
+
72
+ fireEvent.click(
73
+ screen.getByRole("button", {
74
+ name: "Learn more.",
75
+ }),
76
+ );
77
+
78
+ expect(
79
+ screen.getByRole("dialog", { name: "Pair with an agent" }),
80
+ ).toBeVisible();
81
+ });
82
+
83
+ it("opens the pairing dialog and preserves a passed click handler", () => {
84
+ const onClick = vi.fn();
85
+ stubTokenFetch();
86
+ render(<PairWithAgentButton onClick={onClick} />, { wrapper });
87
+
88
+ fireEvent.click(screen.getByRole("button", { name: "Connect your agent" }));
89
+
90
+ expect(onClick).toHaveBeenCalledOnce();
91
+ expect(
92
+ screen.getByRole("dialog", { name: "Pair with an agent" }),
93
+ ).toBeVisible();
94
+ });
95
+
96
+ it("hides pairing controls in WASM", () => {
97
+ mocks.isWasm.mockReturnValue(true);
98
+
99
+ const { container, rerender } = render(<PairWithAgentButton />, {
100
+ wrapper,
101
+ });
102
+ expect(container).toBeEmptyDOMElement();
103
+
104
+ rerender(<PairWithAgentBanner />);
105
+ expect(container).toBeEmptyDOMElement();
106
+ });
107
+ });
@@ -0,0 +1,119 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { BotIcon } from "lucide-react";
4
+ import React from "react";
5
+ import { AiProviderIcon } from "@/components/ai/ai-provider-icon";
6
+ import { useImperativeModal } from "@/components/modal/ImperativeModal";
7
+ import { Button, type ButtonProps } from "@/components/ui/button";
8
+ import { isWasm } from "@/core/wasm/utils";
9
+ import { cn } from "@/utils/cn";
10
+ import { PairWithAgentModal } from "./pair-with-agent-modal";
11
+
12
+ const FEATURED_AGENTS = [
13
+ { id: "claude", label: "Claude Code" },
14
+ { id: "codex", label: "Codex" },
15
+ { id: "cursor", label: "Cursor" },
16
+ { id: "gemini", label: "Gemini CLI" },
17
+ { id: "opencode", label: "OpenCode" },
18
+ { id: "github", label: "GitHub Copilot" },
19
+ { id: "openai-compatible", label: "Any local agent" },
20
+ ] as const satisfies ReadonlyArray<{
21
+ id: React.ComponentProps<typeof AiProviderIcon>["provider"];
22
+ label: string;
23
+ }>;
24
+
25
+ export const usePairWithAgentModal = () => {
26
+ const { openModal, closeModal } = useImperativeModal();
27
+
28
+ return React.useCallback(
29
+ () => openModal(<PairWithAgentModal onClose={closeModal} />),
30
+ [openModal, closeModal],
31
+ );
32
+ };
33
+
34
+ export const PairWithAgentButton: React.FC<
35
+ ButtonProps & { label?: string }
36
+ > = ({ className, label = "Connect your agent", onClick, ...props }) => {
37
+ const openPairWithAgentModal = usePairWithAgentModal();
38
+
39
+ if (isWasm()) {
40
+ return null;
41
+ }
42
+
43
+ return (
44
+ <Button
45
+ type="button"
46
+ variant="link"
47
+ size="xs"
48
+ className={cn("h-auto gap-1 px-0", className)}
49
+ onClick={(event) => {
50
+ onClick?.(event);
51
+ if (!event.defaultPrevented) {
52
+ openPairWithAgentModal();
53
+ }
54
+ }}
55
+ {...props}
56
+ >
57
+ {label}
58
+ </Button>
59
+ );
60
+ };
61
+
62
+ export const PairWithAgentBanner: React.FC<{
63
+ label?: string;
64
+ className?: string;
65
+ }> = ({ label = "Connect your agent", className }) => {
66
+ const openPairWithAgentModal = usePairWithAgentModal();
67
+
68
+ if (isWasm()) {
69
+ return null;
70
+ }
71
+
72
+ return (
73
+ <div
74
+ className={cn(
75
+ "rounded-lg border border-(--blue-6) bg-(--blue-2)/50 p-3",
76
+ className,
77
+ )}
78
+ >
79
+ <div className="flex items-center gap-2 text-sm font-semibold">
80
+ {label}
81
+ <fieldset
82
+ className="m-0 flex min-w-0 -space-x-1.5 border-0 p-0 mb-0.5"
83
+ aria-label="Supported agents"
84
+ >
85
+ {FEATURED_AGENTS.map(({ id, label }) => (
86
+ <figure
87
+ key={id}
88
+ title={label}
89
+ aria-label={label}
90
+ className="flex size-6 items-center justify-center rounded-full border bg-background shadow-xs"
91
+ >
92
+ {id === "openai-compatible" ? (
93
+ <BotIcon className="size-3.5" aria-hidden={true} />
94
+ ) : (
95
+ <AiProviderIcon
96
+ provider={id}
97
+ className="size-3.5"
98
+ aria-hidden={true}
99
+ />
100
+ )}
101
+ </figure>
102
+ ))}
103
+ </fieldset>
104
+ </div>
105
+ <div className="mt-1 text-xs leading-relaxed">
106
+ <p className="inline">
107
+ Use your AI coding agent to pair-program on this notebook.
108
+ </p>
109
+ <button
110
+ type="button"
111
+ onClick={openPairWithAgentModal}
112
+ className="text-link hover:underline ml-1"
113
+ >
114
+ Learn more.
115
+ </button>
116
+ </div>
117
+ </div>
118
+ );
119
+ };
@@ -297,11 +297,12 @@ export function useNotebookActions({
297
297
 
298
298
  {
299
299
  icon: <SparklesIcon size={14} strokeWidth={1.5} />,
300
- label: "Pair with an agent",
300
+ label: "Connect your agent",
301
301
  hidden: isWasm(),
302
302
  handle: async () => {
303
303
  openModal(<PairWithAgentModal onClose={closeModal} />);
304
304
  },
305
+ additionalKeywords: ["connect"],
305
306
  },
306
307
 
307
308
  {
@@ -29,6 +29,7 @@ import useEvent from "react-use-event-hook";
29
29
  import { DefaultChatTransport } from "ai";
30
30
  import { z } from "zod";
31
31
  import { AIModelDropdown } from "@/components/ai/ai-model-dropdown";
32
+ import { PairWithAgentButton } from "@/components/editor/actions/pair-with-agent";
32
33
  import {
33
34
  AddContextButton,
34
35
  AttachFileButton,
@@ -301,6 +302,7 @@ export const AddCellWithAI: React.FC<{
301
302
  showAddCustomModelDocs={true}
302
303
  />
303
304
  {languageDropdown}
305
+ <PairWithAgentButton />
304
306
  </div>
305
307
  <div className="flex flex-row items-center">
306
308
  {files.length > 0 && (
@@ -14,13 +14,13 @@ interface SubmissionAttempt {
14
14
  export class StagedCellSubmissionController {
15
15
  private activeAttempt: SubmissionAttempt | null = null;
16
16
 
17
- cancel() {
17
+ public cancel() {
18
18
  if (this.activeAttempt) {
19
19
  this.activeAttempt.cancelled = true;
20
20
  }
21
21
  }
22
22
 
23
- async run<T>({ prepare, submit, onError }: SubmissionCallbacks<T>) {
23
+ public async run<T>({ prepare, submit, onError }: SubmissionCallbacks<T>) {
24
24
  if (this.activeAttempt !== null) {
25
25
  return;
26
26
  }
@@ -15,7 +15,7 @@ export class StreamingChunkTransport<
15
15
  > extends DefaultChatTransport<UI_MESSAGE> {
16
16
  private onChunkReceived: (chunk: UIMessageChunk) => void;
17
17
 
18
- constructor(
18
+ public constructor(
19
19
  options: HttpChatTransportInitOptions<UI_MESSAGE>,
20
20
  onChunkReceived: (chunk: UIMessageChunk) => void,
21
21
  ) {