dsh-surface-bridge 0.1.0-alpha.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.
- package/LICENSE +21 -0
- package/README.md +132 -0
- package/cordis.patch.yml +9 -0
- package/lib/client.js +310 -0
- package/lib/types/client/SurfaceSelectionDock.d.ts +37 -0
- package/lib/types/client/SurfaceSelectionDock.js +125 -0
- package/lib/types/client/index.d.ts +40 -0
- package/lib/types/client/index.js +41 -0
- package/lib/types/client/locales.d.ts +30 -0
- package/lib/types/client/locales.js +36 -0
- package/lib/types/client/service.d.ts +46 -0
- package/lib/types/client/service.js +89 -0
- package/lib/types/client/transport.d.ts +19 -0
- package/lib/types/client/transport.js +38 -0
- package/lib/types/contract.d.ts +329 -0
- package/lib/types/contract.js +38 -0
- package/lib/types/host/narrow.d.ts +25 -0
- package/lib/types/host/narrow.js +193 -0
- package/lib/types/host/render.d.ts +63 -0
- package/lib/types/host/render.js +228 -0
- package/lib/types/host/routes.d.ts +31 -0
- package/lib/types/host/routes.js +108 -0
- package/lib/types/host/service.d.ts +41 -0
- package/lib/types/host/service.js +93 -0
- package/lib/types/host/store.d.ts +85 -0
- package/lib/types/host/store.js +206 -0
- package/lib/types/index.d.ts +93 -0
- package/lib/types/index.js +132 -0
- package/package.json +88 -0
- package/src/client/SurfaceSelectionDock.module.css +186 -0
- package/src/client/SurfaceSelectionDock.tsx +245 -0
- package/src/client/index.ts +65 -0
- package/src/client/locales.ts +42 -0
- package/src/client/service.ts +110 -0
- package/src/client/transport.ts +39 -0
- package/src/contract.ts +351 -0
- package/src/css-modules.d.ts +10 -0
- package/src/host/narrow.ts +180 -0
- package/src/host/render.ts +226 -0
- package/src/host/routes.ts +117 -0
- package/src/host/service.ts +116 -0
- package/src/host/store.ts +236 -0
- package/src/index.ts +194 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser half of dsh-surface-bridge.
|
|
3
|
+
*
|
|
4
|
+
* Two things live here and nothing else: the `ctx.surfaceBridge` registry a
|
|
5
|
+
* business surface publishes into, and the single `conversation.input.dock` entry
|
|
6
|
+
* that renders every surface's selection. Keeping the chip in this bundle — rather
|
|
7
|
+
* than letting each surface draw its own — is what makes the composer row stable
|
|
8
|
+
* as more surfaces adopt the seam.
|
|
9
|
+
*
|
|
10
|
+
* @module dsh-surface-bridge/client
|
|
11
|
+
*/
|
|
12
|
+
import type { Context as ClientContext } from '@deepseek-ai/cordis';
|
|
13
|
+
import { type SurfaceBridgeService } from './service.ts';
|
|
14
|
+
import { type SurfaceBridgeKey } from './locales.ts';
|
|
15
|
+
declare module '@deepseek-ai/cordis' {
|
|
16
|
+
interface Context {
|
|
17
|
+
/** Browser face of the surface bridge; the seam a business surface publishes into. */
|
|
18
|
+
surfaceBridge: SurfaceBridgeService;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
22
|
+
interface LocaleNamespaceMap {
|
|
23
|
+
/** The selection chip's copy. */
|
|
24
|
+
'surface-bridge': SurfaceBridgeKey;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/** The slot registry, the locale registry, and the composer's overlay slot declaration. */
|
|
28
|
+
export declare const inject: readonly ["slots", "locale"];
|
|
29
|
+
/**
|
|
30
|
+
* Client plugin body: provide the registry, register its copy, and contribute the
|
|
31
|
+
* one chip entry.
|
|
32
|
+
*
|
|
33
|
+
* The seat is `conversation.input.overlay` — inside the composer card, on the
|
|
34
|
+
* shell's top-edge anchor — rather than `conversation.input.dock`, which renders
|
|
35
|
+
* above the card in the same stack as the to-do dock. The overlay seat is what
|
|
36
|
+
* makes the chip a region of the input box instead of one more bar in that stack.
|
|
37
|
+
*
|
|
38
|
+
* @param ctx - Client root context.
|
|
39
|
+
*/
|
|
40
|
+
export declare function apply(ctx: ClientContext): void;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser half of dsh-surface-bridge.
|
|
3
|
+
*
|
|
4
|
+
* Two things live here and nothing else: the `ctx.surfaceBridge` registry a
|
|
5
|
+
* business surface publishes into, and the single `conversation.input.dock` entry
|
|
6
|
+
* that renders every surface's selection. Keeping the chip in this bundle — rather
|
|
7
|
+
* than letting each surface draw its own — is what makes the composer row stable
|
|
8
|
+
* as more surfaces adopt the seam.
|
|
9
|
+
*
|
|
10
|
+
* @module dsh-surface-bridge/client
|
|
11
|
+
*/
|
|
12
|
+
import { SurfaceBridgeRegistry } from "./service.js";
|
|
13
|
+
import { SurfaceSelectionChip } from "./SurfaceSelectionDock.js";
|
|
14
|
+
import { NS, en, zh } from "./locales.js";
|
|
15
|
+
/** The slot registry, the locale registry, and the composer's overlay slot declaration. */
|
|
16
|
+
export const inject = ['slots', 'locale'];
|
|
17
|
+
/** Slot entry id; unique within the chip slot so a re-registration shadows instead of throwing. */
|
|
18
|
+
const CHIP_ENTRY_ID = 'surface-bridge';
|
|
19
|
+
/**
|
|
20
|
+
* Client plugin body: provide the registry, register its copy, and contribute the
|
|
21
|
+
* one chip entry.
|
|
22
|
+
*
|
|
23
|
+
* The seat is `conversation.input.overlay` — inside the composer card, on the
|
|
24
|
+
* shell's top-edge anchor — rather than `conversation.input.dock`, which renders
|
|
25
|
+
* above the card in the same stack as the to-do dock. The overlay seat is what
|
|
26
|
+
* makes the chip a region of the input box instead of one more bar in that stack.
|
|
27
|
+
*
|
|
28
|
+
* @param ctx - Client root context.
|
|
29
|
+
*/
|
|
30
|
+
export function apply(ctx) {
|
|
31
|
+
const bridge = new SurfaceBridgeRegistry();
|
|
32
|
+
ctx.effect(() => ctx.reflect.provide('surfaceBridge', bridge), 'dsh-surface-bridge: client registry');
|
|
33
|
+
ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-surface-bridge: dictionaries');
|
|
34
|
+
ctx.slots.inject('conversation.input.overlay', () => ctx.slots.register({
|
|
35
|
+
name: 'conversation.input.overlay',
|
|
36
|
+
id: CHIP_ENTRY_ID,
|
|
37
|
+
order: 5,
|
|
38
|
+
locale: NS,
|
|
39
|
+
inject: () => ({ bridge }),
|
|
40
|
+
}, SurfaceSelectionChip));
|
|
41
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copy owned by the surface bridge.
|
|
3
|
+
*
|
|
4
|
+
* The bridge renders chrome inside the composer, so every string it shows comes from
|
|
5
|
+
* here and is registered for both shipped locales. The model-facing text is not here:
|
|
6
|
+
* it belongs to the Host renderer, which is not a locale consumer.
|
|
7
|
+
*
|
|
8
|
+
* The chip's visible label is deliberately terse — `画布 · 3 个元素` — because it sits
|
|
9
|
+
* on the input line, not in a panel of its own. The long form ("已选择 3 个元素") is
|
|
10
|
+
* the chip's accessible name and the summary menu's heading, where there is room for
|
|
11
|
+
* it and where it explains rather than labels.
|
|
12
|
+
*
|
|
13
|
+
* @module dsh-surface-bridge/client/locales
|
|
14
|
+
*/
|
|
15
|
+
/** Dictionary namespace owned by this plugin. */
|
|
16
|
+
export declare const NS = "surface-bridge";
|
|
17
|
+
/** Chinese dictionary. */
|
|
18
|
+
export declare const zh: {
|
|
19
|
+
readonly 'chip.aria': "已选择 {count} 个元素,展开查看";
|
|
20
|
+
readonly 'chip.count': "{count} 个元素";
|
|
21
|
+
readonly 'menu.title': "已选择 {count} 个元素";
|
|
22
|
+
readonly 'menu.empty': "(没有元素明细)";
|
|
23
|
+
readonly 'menu.more': "还有 {count} 个元素未列出";
|
|
24
|
+
readonly 'action.reveal': "定位到画布";
|
|
25
|
+
readonly 'action.clear': "清空选择";
|
|
26
|
+
};
|
|
27
|
+
/** English dictionary; key set must match {@link zh} exactly. */
|
|
28
|
+
export declare const en: Record<keyof typeof zh, string>;
|
|
29
|
+
/** Dictionary key union for this namespace. */
|
|
30
|
+
export type SurfaceBridgeKey = keyof typeof zh;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copy owned by the surface bridge.
|
|
3
|
+
*
|
|
4
|
+
* The bridge renders chrome inside the composer, so every string it shows comes from
|
|
5
|
+
* here and is registered for both shipped locales. The model-facing text is not here:
|
|
6
|
+
* it belongs to the Host renderer, which is not a locale consumer.
|
|
7
|
+
*
|
|
8
|
+
* The chip's visible label is deliberately terse — `画布 · 3 个元素` — because it sits
|
|
9
|
+
* on the input line, not in a panel of its own. The long form ("已选择 3 个元素") is
|
|
10
|
+
* the chip's accessible name and the summary menu's heading, where there is room for
|
|
11
|
+
* it and where it explains rather than labels.
|
|
12
|
+
*
|
|
13
|
+
* @module dsh-surface-bridge/client/locales
|
|
14
|
+
*/
|
|
15
|
+
/** Dictionary namespace owned by this plugin. */
|
|
16
|
+
export const NS = 'surface-bridge';
|
|
17
|
+
/** Chinese dictionary. */
|
|
18
|
+
export const zh = {
|
|
19
|
+
'chip.aria': '已选择 {count} 个元素,展开查看',
|
|
20
|
+
'chip.count': '{count} 个元素',
|
|
21
|
+
'menu.title': '已选择 {count} 个元素',
|
|
22
|
+
'menu.empty': '(没有元素明细)',
|
|
23
|
+
'menu.more': '还有 {count} 个元素未列出',
|
|
24
|
+
'action.reveal': '定位到画布',
|
|
25
|
+
'action.clear': '清空选择',
|
|
26
|
+
};
|
|
27
|
+
/** English dictionary; key set must match {@link zh} exactly. */
|
|
28
|
+
export const en = {
|
|
29
|
+
'chip.aria': '{count} elements selected, expand for details',
|
|
30
|
+
'chip.count': '{count} elements',
|
|
31
|
+
'menu.title': '{count} elements selected',
|
|
32
|
+
'menu.empty': '(no element details)',
|
|
33
|
+
'menu.more': '{count} more elements not listed',
|
|
34
|
+
'action.reveal': 'Reveal surface',
|
|
35
|
+
'action.clear': 'Clear selection',
|
|
36
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser half of the bridge: the source registry and the one live selection store.
|
|
3
|
+
*
|
|
4
|
+
* A business surface registers a descriptor once and then publishes its selection
|
|
5
|
+
* whenever the user changes it. The bridge owns everything after that — the chip and
|
|
6
|
+
* the answer to a read.
|
|
7
|
+
*
|
|
8
|
+
* Note what is *not* here: any network. Nothing leaves the page when the user selects
|
|
9
|
+
* something. The registry is the single local answer to "what is selected", read by
|
|
10
|
+
* the surface's own operation loop when the Host asks, which is why a selection only
|
|
11
|
+
* ever reaches a model as part of a message being sent.
|
|
12
|
+
*
|
|
13
|
+
* The vocabulary lives in `../contract.ts` because the Host half, the browser half,
|
|
14
|
+
* and every consumer must spell it the same way.
|
|
15
|
+
*
|
|
16
|
+
* @module dsh-surface-bridge/client/service
|
|
17
|
+
*/
|
|
18
|
+
import type { SurfaceBridgeService, SurfaceSelection, SurfaceSourceDescriptor } from '../contract.ts';
|
|
19
|
+
export type { SurfaceBridgeService, SurfaceIconProps, SurfaceSourceDescriptor } from '../contract.ts';
|
|
20
|
+
/** Default implementation of the bridge's browser-side registry. */
|
|
21
|
+
export declare class SurfaceBridgeRegistry implements SurfaceBridgeService {
|
|
22
|
+
private readonly entries;
|
|
23
|
+
private readonly listeners;
|
|
24
|
+
private revision;
|
|
25
|
+
/** @inheritdoc */
|
|
26
|
+
registerSource(descriptor: SurfaceSourceDescriptor): () => void;
|
|
27
|
+
/** @inheritdoc */
|
|
28
|
+
setVisible(source: string, visible: boolean): void;
|
|
29
|
+
/** @inheritdoc */
|
|
30
|
+
isVisible(source: string): boolean;
|
|
31
|
+
/** @inheritdoc */
|
|
32
|
+
publish(source: string, selection: SurfaceSelection | null): void;
|
|
33
|
+
/** @inheritdoc */
|
|
34
|
+
read(source: string): SurfaceSelection | null;
|
|
35
|
+
/** @inheritdoc */
|
|
36
|
+
active(): readonly {
|
|
37
|
+
descriptor: SurfaceSourceDescriptor;
|
|
38
|
+
selection: SurfaceSelection;
|
|
39
|
+
}[];
|
|
40
|
+
/** @inheritdoc */
|
|
41
|
+
subscribe(listener: () => void): () => void;
|
|
42
|
+
/** @inheritdoc */
|
|
43
|
+
version(): number;
|
|
44
|
+
/** Advance the version and notify every subscriber. */
|
|
45
|
+
private bump;
|
|
46
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser half of the bridge: the source registry and the one live selection store.
|
|
3
|
+
*
|
|
4
|
+
* A business surface registers a descriptor once and then publishes its selection
|
|
5
|
+
* whenever the user changes it. The bridge owns everything after that — the chip and
|
|
6
|
+
* the answer to a read.
|
|
7
|
+
*
|
|
8
|
+
* Note what is *not* here: any network. Nothing leaves the page when the user selects
|
|
9
|
+
* something. The registry is the single local answer to "what is selected", read by
|
|
10
|
+
* the surface's own operation loop when the Host asks, which is why a selection only
|
|
11
|
+
* ever reaches a model as part of a message being sent.
|
|
12
|
+
*
|
|
13
|
+
* The vocabulary lives in `../contract.ts` because the Host half, the browser half,
|
|
14
|
+
* and every consumer must spell it the same way.
|
|
15
|
+
*
|
|
16
|
+
* @module dsh-surface-bridge/client/service
|
|
17
|
+
*/
|
|
18
|
+
/** Default implementation of the bridge's browser-side registry. */
|
|
19
|
+
export class SurfaceBridgeRegistry {
|
|
20
|
+
entries = new Map();
|
|
21
|
+
listeners = new Set();
|
|
22
|
+
revision = 0;
|
|
23
|
+
/** @inheritdoc */
|
|
24
|
+
registerSource(descriptor) {
|
|
25
|
+
this.entries.set(descriptor.id, { descriptor, selection: null, visible: false });
|
|
26
|
+
this.bump();
|
|
27
|
+
return () => {
|
|
28
|
+
this.entries.delete(descriptor.id);
|
|
29
|
+
this.bump();
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/** @inheritdoc */
|
|
33
|
+
setVisible(source, visible) {
|
|
34
|
+
const entry = this.entries.get(source);
|
|
35
|
+
if (entry === undefined || entry.visible === visible)
|
|
36
|
+
return;
|
|
37
|
+
entry.visible = visible;
|
|
38
|
+
this.bump();
|
|
39
|
+
}
|
|
40
|
+
/** @inheritdoc */
|
|
41
|
+
isVisible(source) {
|
|
42
|
+
return this.entries.get(source)?.visible === true;
|
|
43
|
+
}
|
|
44
|
+
/** @inheritdoc */
|
|
45
|
+
publish(source, selection) {
|
|
46
|
+
const entry = this.entries.get(source);
|
|
47
|
+
if (entry === undefined)
|
|
48
|
+
return;
|
|
49
|
+
entry.selection = selection;
|
|
50
|
+
this.bump();
|
|
51
|
+
}
|
|
52
|
+
/** @inheritdoc */
|
|
53
|
+
read(source) {
|
|
54
|
+
return this.entries.get(source)?.selection ?? null;
|
|
55
|
+
}
|
|
56
|
+
/** @inheritdoc */
|
|
57
|
+
active() {
|
|
58
|
+
const out = [];
|
|
59
|
+
for (const entry of this.entries.values()) {
|
|
60
|
+
// A hidden surface offers nothing: the chip must never advertise context the
|
|
61
|
+
// user cannot see, and the read answer draws on the same rule, so an
|
|
62
|
+
// invisible selection can never reach a model either.
|
|
63
|
+
if (entry.selection === null || !entry.visible)
|
|
64
|
+
continue;
|
|
65
|
+
out.push({ descriptor: entry.descriptor, selection: entry.selection });
|
|
66
|
+
}
|
|
67
|
+
return out.sort((left, right) => {
|
|
68
|
+
const order = (left.descriptor.order ?? 0) - (right.descriptor.order ?? 0);
|
|
69
|
+
if (order !== 0)
|
|
70
|
+
return order;
|
|
71
|
+
return left.descriptor.id < right.descriptor.id ? -1 : left.descriptor.id > right.descriptor.id ? 1 : 0;
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/** @inheritdoc */
|
|
75
|
+
subscribe(listener) {
|
|
76
|
+
this.listeners.add(listener);
|
|
77
|
+
return () => { this.listeners.delete(listener); };
|
|
78
|
+
}
|
|
79
|
+
/** @inheritdoc */
|
|
80
|
+
version() {
|
|
81
|
+
return this.revision;
|
|
82
|
+
}
|
|
83
|
+
/** Advance the version and notify every subscriber. */
|
|
84
|
+
bump() {
|
|
85
|
+
this.revision += 1;
|
|
86
|
+
for (const listener of [...this.listeners])
|
|
87
|
+
listener();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-side transport of the bridge.
|
|
3
|
+
*
|
|
4
|
+
* One long poll, held by the Host for up to twenty seconds, that carries both
|
|
5
|
+
* directions: the surface asks for work and reports what happened. A failure —
|
|
6
|
+
* including an abort from a page unload or a Connection generation change — is an
|
|
7
|
+
* ordinary reason to poll again rather than a broken surface.
|
|
8
|
+
*
|
|
9
|
+
* There is deliberately no "here is my selection" call. A selection is read by the
|
|
10
|
+
* Host when a model step needs it, so nothing travels while the user is merely
|
|
11
|
+
* drawing.
|
|
12
|
+
*
|
|
13
|
+
* @module dsh-surface-bridge/client/transport
|
|
14
|
+
*/
|
|
15
|
+
import type { SurfaceOperation, SurfaceOperationResult } from '../contract.ts';
|
|
16
|
+
/** Hold one poll for pending operations. */
|
|
17
|
+
export declare function pollOperations(sessionId: string, holdMs: number, signal: AbortSignal): Promise<readonly SurfaceOperation[]>;
|
|
18
|
+
/** Report one operation's outcome back to the Host. */
|
|
19
|
+
export declare function settleOperation(sessionId: string, result: SurfaceOperationResult, signal?: AbortSignal): Promise<void>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-side transport of the bridge.
|
|
3
|
+
*
|
|
4
|
+
* One long poll, held by the Host for up to twenty seconds, that carries both
|
|
5
|
+
* directions: the surface asks for work and reports what happened. A failure —
|
|
6
|
+
* including an abort from a page unload or a Connection generation change — is an
|
|
7
|
+
* ordinary reason to poll again rather than a broken surface.
|
|
8
|
+
*
|
|
9
|
+
* There is deliberately no "here is my selection" call. A selection is read by the
|
|
10
|
+
* Host when a model step needs it, so nothing travels while the user is merely
|
|
11
|
+
* drawing.
|
|
12
|
+
*
|
|
13
|
+
* @module dsh-surface-bridge/client/transport
|
|
14
|
+
*/
|
|
15
|
+
import { OPS_PATH, SETTLE_PATH } from "../contract.js";
|
|
16
|
+
/** Hold one poll for pending operations. */
|
|
17
|
+
export async function pollOperations(sessionId, holdMs, signal) {
|
|
18
|
+
const url = `${OPS_PATH}?sessionId=${encodeURIComponent(sessionId)}&hold=${String(holdMs)}`;
|
|
19
|
+
const response = await fetch(url, { signal, headers: { accept: 'application/json' } });
|
|
20
|
+
if (!response.ok)
|
|
21
|
+
throw new Error(`operations poll failed: HTTP ${response.status}`);
|
|
22
|
+
const body = await response.json();
|
|
23
|
+
return Array.isArray(body.operations) ? body.operations : [];
|
|
24
|
+
}
|
|
25
|
+
/** Report one operation's outcome back to the Host. */
|
|
26
|
+
export async function settleOperation(sessionId, result, signal) {
|
|
27
|
+
const response = await fetch(SETTLE_PATH, {
|
|
28
|
+
method: 'POST',
|
|
29
|
+
headers: { 'content-type': 'application/json' },
|
|
30
|
+
body: JSON.stringify({ sessionId, result }),
|
|
31
|
+
...(signal === undefined ? {} : { signal }),
|
|
32
|
+
});
|
|
33
|
+
if (!response.ok)
|
|
34
|
+
throw new Error(`operation settle failed: HTTP ${response.status}`);
|
|
35
|
+
const body = await response.json();
|
|
36
|
+
if (body.accepted !== true)
|
|
37
|
+
throw new Error('operation settle was not accepted');
|
|
38
|
+
}
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The surface-bridge contract: the vocabulary a business surface (a right-Sidebar
|
|
3
|
+
* canvas, grid, or notebook) uses to hand its current selection to the composer,
|
|
4
|
+
* and the vocabulary the Host uses to turn that selection into model context.
|
|
5
|
+
*
|
|
6
|
+
* The contract is deliberately surface-agnostic. Nothing here mentions a canvas,
|
|
7
|
+
* a cell, or a table: a source projects its own selection into {@link SurfaceElement}
|
|
8
|
+
* rows, and the bridge owns the envelope (revision, count, bounds, the rendered
|
|
9
|
+
* model text, the optional raster). That split is what lets a second surface adopt
|
|
10
|
+
* the seam without the bridge learning what a shape is.
|
|
11
|
+
*
|
|
12
|
+
* @module dsh-surface-bridge/contract
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Route paths owned by the bridge.
|
|
16
|
+
*
|
|
17
|
+
* Spelled once, in the module both halves already import, because the browser's
|
|
18
|
+
* `fetch` and the Host's route registration are two halves of one wire: a typo in
|
|
19
|
+
* either would 404 at runtime with no compile-time signal.
|
|
20
|
+
*/
|
|
21
|
+
export declare const OPS_PATH = "/api/data-canvas/ops";
|
|
22
|
+
export declare const SETTLE_PATH = "/api/data-canvas/settle";
|
|
23
|
+
/**
|
|
24
|
+
* Reserved operation a Host read uses to ask a live surface what is selected.
|
|
25
|
+
*
|
|
26
|
+
* Its payload carries `consume`: a *consuming* read is one taken to put the selection
|
|
27
|
+
* into a message, and the surface drops its selection once it has answered, so the
|
|
28
|
+
* chip disappears from the composer at exactly the moment the context is spent. A
|
|
29
|
+
* non-consuming read is a peek (a tool asking what is selected) and changes nothing.
|
|
30
|
+
*
|
|
31
|
+
* It carries no source: only the browser knows which surfaces exist and which are
|
|
32
|
+
* on screen, so the Host asks "what has anyone selected?" and gets back a list. The
|
|
33
|
+
* surface answers through the ordinary settle channel with that list in
|
|
34
|
+
* {@link SurfaceOperationResult.value}. One queue item rather than a second channel,
|
|
35
|
+
* so the browser needs no extra listener and a read costs one round trip on a
|
|
36
|
+
* connection that is already parked and idle.
|
|
37
|
+
*/
|
|
38
|
+
export declare const READ_SELECTION_OP = "__surface-read-selection";
|
|
39
|
+
/** Media types an attached raster may use; the set the Host's attachment service admits. */
|
|
40
|
+
export type SurfaceRasterMediaType = 'image/png' | 'image/jpeg' | 'image/webp' | 'image/gif';
|
|
41
|
+
/** How one element connects to others in the same selection. */
|
|
42
|
+
export interface SurfaceElementLinks {
|
|
43
|
+
/** Source-side endpoint id (an arrow's start). */
|
|
44
|
+
readonly from?: string;
|
|
45
|
+
/** Target-side endpoint id (an arrow's end). */
|
|
46
|
+
readonly to?: string;
|
|
47
|
+
/** Container this element is bound into (text inside a shape, label on a line). */
|
|
48
|
+
readonly container?: string;
|
|
49
|
+
/** Elements bound to this one. */
|
|
50
|
+
readonly bound?: readonly string[];
|
|
51
|
+
}
|
|
52
|
+
/** One element of a selection, projected to the facts a model can act on. */
|
|
53
|
+
export interface SurfaceElement {
|
|
54
|
+
/** Stable identity inside the source; the handle every write-back operation addresses. */
|
|
55
|
+
readonly id: string;
|
|
56
|
+
/** Source-defined kind, e.g. `rectangle`, `arrow`, `text`, `image`, `cell`. */
|
|
57
|
+
readonly type: string;
|
|
58
|
+
/** One-line human label shown in the chip's summary list, e.g. `矩形 "下单"`. */
|
|
59
|
+
readonly label: string;
|
|
60
|
+
/** Text the element carries, if any. Absent for purely graphical elements. */
|
|
61
|
+
readonly text?: string;
|
|
62
|
+
/** Left edge in source coordinates. */
|
|
63
|
+
readonly x: number;
|
|
64
|
+
/** Top edge in source coordinates. */
|
|
65
|
+
readonly y: number;
|
|
66
|
+
readonly width: number;
|
|
67
|
+
readonly height: number;
|
|
68
|
+
/** Rotation in degrees, when the source has one. */
|
|
69
|
+
readonly angle?: number;
|
|
70
|
+
/** Presentation facts worth sending, already projected to short names. */
|
|
71
|
+
readonly style?: Readonly<Record<string, string | number>>;
|
|
72
|
+
/**
|
|
73
|
+
* Where a heavy payload lives, never the payload itself. An image element
|
|
74
|
+
* reports its asset handle here and the bytes travel as {@link SurfaceSelection.raster}.
|
|
75
|
+
*/
|
|
76
|
+
readonly asset?: Readonly<Record<string, string>>;
|
|
77
|
+
/** Connection relations to other elements. */
|
|
78
|
+
readonly links?: SurfaceElementLinks;
|
|
79
|
+
}
|
|
80
|
+
/** A rectangle in source coordinates. */
|
|
81
|
+
export interface SurfaceBounds {
|
|
82
|
+
readonly x: number;
|
|
83
|
+
readonly y: number;
|
|
84
|
+
readonly width: number;
|
|
85
|
+
readonly height: number;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* The document a selection was taken from, when the surface is a view of one file.
|
|
89
|
+
*
|
|
90
|
+
* A right-Sidebar surface is usually a view of a project file — a scene, a workbook, a
|
|
91
|
+
* notebook — and the Agent's tools address that file by path. Without this field the
|
|
92
|
+
* context said "the user selected 30 elements" and nothing about *which* document, so a
|
|
93
|
+
* model that wrote to the same-named file in another directory believed it had edited the
|
|
94
|
+
* drawing on screen. It had not. The path travels with the selection so the write-back
|
|
95
|
+
* can name the exact file, and the row the user reads says which document they sent.
|
|
96
|
+
*
|
|
97
|
+
* Optional throughout: a surface that is not a view of a file simply omits it.
|
|
98
|
+
*/
|
|
99
|
+
export interface SurfaceResource {
|
|
100
|
+
/** Absolute path on the Host, when the surface can name one. */
|
|
101
|
+
readonly path?: string;
|
|
102
|
+
/** Short name for the chip and the model header, e.g. `main.excalidraw`. */
|
|
103
|
+
readonly name?: string;
|
|
104
|
+
/**
|
|
105
|
+
* Version of the document this selection was projected from — a file mtime, a
|
|
106
|
+
* revision counter, whatever the surface uses. It is what lets the Agent detect that
|
|
107
|
+
* the file moved on between the selection and the edit.
|
|
108
|
+
*/
|
|
109
|
+
readonly version?: number;
|
|
110
|
+
}
|
|
111
|
+
/** A raster the source attached for one element or for the selection as a whole. */
|
|
112
|
+
export interface SurfaceRaster {
|
|
113
|
+
readonly mediaType: SurfaceRasterMediaType;
|
|
114
|
+
/** Base64 bytes without a data-URL prefix. */
|
|
115
|
+
readonly data: string;
|
|
116
|
+
/** Display name for the resulting attachment. */
|
|
117
|
+
readonly name?: string;
|
|
118
|
+
/**
|
|
119
|
+
* Element this raster belongs to, when it is one element's own bytes rather
|
|
120
|
+
* than a render of the whole selection. The model-facing text lists the same
|
|
121
|
+
* id, so the model can pair each image with the row that locates it.
|
|
122
|
+
*/
|
|
123
|
+
readonly elementId?: string;
|
|
124
|
+
}
|
|
125
|
+
/** Everything one source wants the model to know about its current selection. */
|
|
126
|
+
export interface SurfaceSelection {
|
|
127
|
+
/** Source id; the same string the source registered under. */
|
|
128
|
+
readonly source: string;
|
|
129
|
+
/**
|
|
130
|
+
* Monotonic revision minted by the source. The Host consumes each revision at
|
|
131
|
+
* most once, so a selection cannot leak into an unrelated later message.
|
|
132
|
+
*/
|
|
133
|
+
readonly revision: number;
|
|
134
|
+
/** Element count. May exceed `elements.length` when the source caps the list. */
|
|
135
|
+
readonly count: number;
|
|
136
|
+
/** Human title of the surface, e.g. `Canvas`. Rendered in the chip and the model header. */
|
|
137
|
+
readonly title: string;
|
|
138
|
+
/** One-line chip summary, e.g. `已选择 3 个元素`. */
|
|
139
|
+
readonly summary: string;
|
|
140
|
+
/** Element projections, capped by the source. */
|
|
141
|
+
readonly elements: readonly SurfaceElement[];
|
|
142
|
+
/** True when `elements` is a truncated view of `count`. */
|
|
143
|
+
readonly truncated?: boolean;
|
|
144
|
+
/** The document this selection came from, when the surface is a view of a file. */
|
|
145
|
+
readonly resource?: SurfaceResource;
|
|
146
|
+
/** Bounding box of the whole selection, when the source can compute one. */
|
|
147
|
+
readonly bounds?: SurfaceBounds;
|
|
148
|
+
/**
|
|
149
|
+
* Heavy element payloads the source chose to send as real images instead of
|
|
150
|
+
* text. A source sends at most a few, capped by its own byte budget; each one
|
|
151
|
+
* names the element it belongs to so the text can stay a set of locators.
|
|
152
|
+
*/
|
|
153
|
+
readonly images?: readonly SurfaceRaster[];
|
|
154
|
+
/** Extra facts the source wants in the model text (scene size, per-type counts…). */
|
|
155
|
+
readonly notes?: readonly string[];
|
|
156
|
+
/** What the model may do to this surface; rendered as the available operations. */
|
|
157
|
+
readonly capabilities?: readonly string[];
|
|
158
|
+
}
|
|
159
|
+
/** One selection as the Host holds it, with the arrival time that makes staleness visible. */
|
|
160
|
+
export interface StoredSelection {
|
|
161
|
+
readonly selection: SurfaceSelection;
|
|
162
|
+
/** Host clock at arrival. */
|
|
163
|
+
readonly receivedAt: number;
|
|
164
|
+
}
|
|
165
|
+
/** One write-back operation an Agent asked the surface to perform. */
|
|
166
|
+
export interface SurfaceOperation {
|
|
167
|
+
/** Operation id minted by the Host; echoed back with the result. */
|
|
168
|
+
readonly id: string;
|
|
169
|
+
/** Source the operation targets. */
|
|
170
|
+
readonly source: string;
|
|
171
|
+
/** Source-defined verb, e.g. `update`, `add`, `delete`. */
|
|
172
|
+
readonly op: string;
|
|
173
|
+
/** Source-defined payload. The bridge never interprets it. */
|
|
174
|
+
readonly payload: unknown;
|
|
175
|
+
}
|
|
176
|
+
/** Outcome of one operation, reported by the surface that executed it. */
|
|
177
|
+
export interface SurfaceOperationResult {
|
|
178
|
+
readonly id: string;
|
|
179
|
+
readonly ok: boolean;
|
|
180
|
+
/** Source-reported detail on success (the element's final state, a count, …). */
|
|
181
|
+
readonly detail?: string;
|
|
182
|
+
/** Failure message on `ok: false`. */
|
|
183
|
+
readonly error?: string;
|
|
184
|
+
/**
|
|
185
|
+
* Value carried back for a read ({@link READ_SELECTION_OP}): every on-screen
|
|
186
|
+
* selection, as an array. Empty when nothing is selected or no surface is open.
|
|
187
|
+
*/
|
|
188
|
+
readonly value?: unknown;
|
|
189
|
+
}
|
|
190
|
+
/** Poll response of `GET /api/data-canvas/ops`. */
|
|
191
|
+
export interface OperationsPollResponse {
|
|
192
|
+
readonly operations: readonly SurfaceOperation[];
|
|
193
|
+
}
|
|
194
|
+
/** Reply body of `POST /api/data-canvas/ops/{id}`. */
|
|
195
|
+
export interface OperationSettleResponse {
|
|
196
|
+
readonly accepted: boolean;
|
|
197
|
+
}
|
|
198
|
+
/** Body of a rejected bridge request. */
|
|
199
|
+
export interface BridgeErrorBody {
|
|
200
|
+
readonly ok: false;
|
|
201
|
+
readonly error: string;
|
|
202
|
+
}
|
|
203
|
+
/** One write-back operation as a business plugin's tool states it. */
|
|
204
|
+
export interface SurfaceApplyOperation {
|
|
205
|
+
/** Source-defined verb, e.g. `update`, `add`, `delete`. */
|
|
206
|
+
readonly op: string;
|
|
207
|
+
/** Source-defined payload, passed through the bridge untouched. */
|
|
208
|
+
readonly payload: unknown;
|
|
209
|
+
}
|
|
210
|
+
/** Glyph contract shared with the host shell's icon set. */
|
|
211
|
+
export interface SurfaceIconProps {
|
|
212
|
+
readonly size?: number;
|
|
213
|
+
readonly className?: string;
|
|
214
|
+
}
|
|
215
|
+
/** What a surface declares once, at registration. */
|
|
216
|
+
export interface SurfaceSourceDescriptor {
|
|
217
|
+
/** Stable source id; the same string published selections carry. */
|
|
218
|
+
readonly id: string;
|
|
219
|
+
/** Human label for the chip, e.g. `Canvas`. */
|
|
220
|
+
readonly label: string;
|
|
221
|
+
/** Glyph rendered before the label. */
|
|
222
|
+
readonly icon?: import('react').ComponentType<SurfaceIconProps>;
|
|
223
|
+
/** Lower sorts first when several sources have selections at once. */
|
|
224
|
+
readonly order?: number;
|
|
225
|
+
/**
|
|
226
|
+
* Bring the surface to the front. The chip's reveal action calls this; a source
|
|
227
|
+
* that cannot be revealed omits it and the action is not offered.
|
|
228
|
+
*/
|
|
229
|
+
readonly reveal?: () => void;
|
|
230
|
+
/**
|
|
231
|
+
* Bring one element into view. The chip's summary list calls this when a row is
|
|
232
|
+
* picked, so a user reading "3 elements selected" can see which one they mean.
|
|
233
|
+
*/
|
|
234
|
+
readonly focusElement?: (elementId: string) => void;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* The `ctx.surfaceBridge` face a business surface publishes into.
|
|
238
|
+
*
|
|
239
|
+
* A surface registers once and then calls {@link publish} whenever the user's
|
|
240
|
+
* selection changes. Publishing is local — it drives the chip and answers the Host's
|
|
241
|
+
* read — and nothing about it touches the network.
|
|
242
|
+
*/
|
|
243
|
+
export interface SurfaceBridgeService {
|
|
244
|
+
/** Register one surface. The returned disposer removes it and its selection. */
|
|
245
|
+
registerSource(descriptor: SurfaceSourceDescriptor): () => void;
|
|
246
|
+
/**
|
|
247
|
+
* Tell the bridge whether a surface is on screen for the current Session.
|
|
248
|
+
*
|
|
249
|
+
* A surface that is hidden — the user switched tabs, switched Session, or
|
|
250
|
+
* collapsed the column — must not advertise a selection: the chip would offer
|
|
251
|
+
* to send context the user cannot see or clear. Hidden sources drop out of
|
|
252
|
+
* {@link active}, which both hides the chip and disarms the Host.
|
|
253
|
+
*/
|
|
254
|
+
setVisible(source: string, visible: boolean): void;
|
|
255
|
+
/** Whether a source is currently on screen. */
|
|
256
|
+
isVisible(source: string): boolean;
|
|
257
|
+
/** Publish (or clear, with `null`) one source's selection. */
|
|
258
|
+
publish(source: string, selection: SurfaceSelection | null): void;
|
|
259
|
+
/** Read one source's current selection. */
|
|
260
|
+
read(source: string): SurfaceSelection | null;
|
|
261
|
+
/** Read every source that currently has a selection, in chip order. */
|
|
262
|
+
active(): readonly {
|
|
263
|
+
descriptor: SurfaceSourceDescriptor;
|
|
264
|
+
selection: SurfaceSelection;
|
|
265
|
+
}[];
|
|
266
|
+
/** Subscribe to any change (selection, sync state, roster). */
|
|
267
|
+
subscribe(listener: () => void): () => void;
|
|
268
|
+
/** Snapshot version, for `useSyncExternalStore`. */
|
|
269
|
+
version(): number;
|
|
270
|
+
}
|
|
271
|
+
/** The Host face of the bridge; what a business plugin's tools call. */
|
|
272
|
+
export interface SurfaceBridgeHostFace {
|
|
273
|
+
/**
|
|
274
|
+
* Ask the live surfaces what they have selected right now.
|
|
275
|
+
*
|
|
276
|
+
* Read on demand rather than pushed on every change: nothing travels and nothing
|
|
277
|
+
* is remembered until a model step actually needs it. A surface that is closed,
|
|
278
|
+
* off screen, or has cleared its selection contributes nothing, so an invisible
|
|
279
|
+
* selection can never reach a model.
|
|
280
|
+
*
|
|
281
|
+
* @param sessionId - Session whose composer is in play.
|
|
282
|
+
* @param signal - Aborts the wait when the turn is cancelled.
|
|
283
|
+
* @returns one entry per source that has something selected — or `undefined` when
|
|
284
|
+
* nothing answered at all. The difference matters and must not be flattened: an
|
|
285
|
+
* empty array means "the surface spoke and there is no selection", while
|
|
286
|
+
* `undefined` means "no surface is there to ask".
|
|
287
|
+
*/
|
|
288
|
+
readSelections(sessionId: string, signal?: AbortSignal): Promise<readonly SurfaceSelection[] | undefined>;
|
|
289
|
+
/**
|
|
290
|
+
* Read every on-screen selection **and spend it**: the surface drops each selection
|
|
291
|
+
* it reported, so it is not offered again unless the user selects something else.
|
|
292
|
+
*
|
|
293
|
+
* Used at the step that is about to carry the selection into a message. A peek
|
|
294
|
+
* ({@link readSelections}) leaves the selection in place.
|
|
295
|
+
*
|
|
296
|
+
* @param sessionId - Session whose composer is in play.
|
|
297
|
+
* @param signal - Aborts the wait when the turn is cancelled.
|
|
298
|
+
* @returns the same answer as {@link readSelections}.
|
|
299
|
+
*/
|
|
300
|
+
consumeSelections(sessionId: string, signal?: AbortSignal): Promise<readonly SurfaceSelection[] | undefined>;
|
|
301
|
+
/**
|
|
302
|
+
* Read one named source's current selection.
|
|
303
|
+
*
|
|
304
|
+
* @param sessionId - Session whose composer is in play.
|
|
305
|
+
* @param source - Source id to pick out of the answer.
|
|
306
|
+
* @param signal - Aborts the wait when the turn is cancelled.
|
|
307
|
+
* @returns that source's selection, `null` when it answered with none, or
|
|
308
|
+
* `undefined` when nothing answered.
|
|
309
|
+
*/
|
|
310
|
+
readSelection(sessionId: string, source: string, signal?: AbortSignal): Promise<SurfaceSelection | null | undefined>;
|
|
311
|
+
/**
|
|
312
|
+
* Whether a surface of this Session has polled recently enough to be considered open.
|
|
313
|
+
*
|
|
314
|
+
* @param sessionId - Session to test.
|
|
315
|
+
* @returns whether the surface is live.
|
|
316
|
+
*/
|
|
317
|
+
isSurfaceLive(sessionId: string): boolean;
|
|
318
|
+
/**
|
|
319
|
+
* Run write-back operations on one surface and wait for its report.
|
|
320
|
+
*
|
|
321
|
+
* @param sessionId - Session owning the surface.
|
|
322
|
+
* @param source - Source id.
|
|
323
|
+
* @param operations - Operations to run, in order.
|
|
324
|
+
* @param signal - Aborts the wait when the turn is cancelled.
|
|
325
|
+
* @returns one result per operation, in the same order; an unanswered operation
|
|
326
|
+
* comes back as an explicit failure rather than a silent success.
|
|
327
|
+
*/
|
|
328
|
+
apply(sessionId: string, source: string, operations: readonly SurfaceApplyOperation[], signal?: AbortSignal): Promise<readonly SurfaceOperationResult[]>;
|
|
329
|
+
}
|