@workbench-kit/adapters 0.0.1-prototype.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +39 -0
- package/src/index.ts +3 -0
- package/src/library.ts +145 -0
- package/src/runtime.ts +97 -0
- package/src/workspace.ts +126 -0
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@workbench-kit/adapters",
|
|
3
|
+
"version": "0.0.1-prototype.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./src/index.ts",
|
|
8
|
+
"./library": "./src/library.ts",
|
|
9
|
+
"./runtime": "./src/runtime.ts",
|
|
10
|
+
"./workspace": "./src/workspace.ts"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"src",
|
|
14
|
+
"!src/**/*.test.ts",
|
|
15
|
+
"!src/**/*.test.tsx",
|
|
16
|
+
"!src/**/*.stories.ts",
|
|
17
|
+
"!src/**/*.stories.tsx"
|
|
18
|
+
],
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@workbench-kit/contracts": "0.0.1-prototype.0",
|
|
21
|
+
"@workbench-kit/runtime": "0.0.1-prototype.0",
|
|
22
|
+
"@workbench-kit/workspace": "0.0.1-prototype.0"
|
|
23
|
+
},
|
|
24
|
+
"description": "Workbench Kit adapters for workspace repositories and runtime transports.",
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public",
|
|
27
|
+
"tag": "prototype",
|
|
28
|
+
"provenance": true
|
|
29
|
+
},
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/NewChoBo/newchobo-ui-package.git",
|
|
33
|
+
"directory": "packages/adapters"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"test": "vitest run src",
|
|
37
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
38
|
+
}
|
|
39
|
+
}
|
package/src/index.ts
ADDED
package/src/library.ts
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import type { LibraryManifest, LibraryProvider } from '@workbench-kit/contracts';
|
|
2
|
+
import {
|
|
3
|
+
normalizeLibraryItemProviderSource,
|
|
4
|
+
parseLibraryManifest,
|
|
5
|
+
parseLibraryManifestText,
|
|
6
|
+
} from '@workbench-kit/contracts';
|
|
7
|
+
|
|
8
|
+
type ManifestTextLoader = () => Promise<string> | string;
|
|
9
|
+
|
|
10
|
+
export interface LibraryManifestProviderOptions {
|
|
11
|
+
displayName: string;
|
|
12
|
+
id: string;
|
|
13
|
+
loadManifestText: ManifestTextLoader;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface StaticLibraryManifestProviderOptions {
|
|
17
|
+
displayName: string;
|
|
18
|
+
id: string;
|
|
19
|
+
manifestText: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ObjectLibraryManifestProviderOptions {
|
|
23
|
+
displayName: string;
|
|
24
|
+
id: string;
|
|
25
|
+
manifest: unknown;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface HttpLibraryManifestProviderOptions {
|
|
29
|
+
displayName: string;
|
|
30
|
+
id: string;
|
|
31
|
+
manifestUrl: string;
|
|
32
|
+
readText?: (url: string) => Promise<string>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function createLibraryManifestProvider({
|
|
36
|
+
displayName,
|
|
37
|
+
id,
|
|
38
|
+
loadManifestText,
|
|
39
|
+
}: LibraryManifestProviderOptions): LibraryProvider {
|
|
40
|
+
return {
|
|
41
|
+
displayName,
|
|
42
|
+
id,
|
|
43
|
+
async listItems() {
|
|
44
|
+
const manifest = parseLibraryManifestSourceText(id, await loadManifestText());
|
|
45
|
+
return manifest.items.map((item) =>
|
|
46
|
+
normalizeLibraryItemProviderSource(item, {
|
|
47
|
+
providerId: id,
|
|
48
|
+
}),
|
|
49
|
+
);
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function createStaticLibraryManifestProvider({
|
|
55
|
+
displayName,
|
|
56
|
+
id,
|
|
57
|
+
manifestText,
|
|
58
|
+
}: StaticLibraryManifestProviderOptions): LibraryProvider {
|
|
59
|
+
return createLibraryManifestProvider({
|
|
60
|
+
displayName,
|
|
61
|
+
id,
|
|
62
|
+
loadManifestText: () => manifestText,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function createLibraryManifestObjectProvider({
|
|
67
|
+
displayName,
|
|
68
|
+
id,
|
|
69
|
+
manifest,
|
|
70
|
+
}: ObjectLibraryManifestProviderOptions): LibraryProvider {
|
|
71
|
+
return {
|
|
72
|
+
displayName,
|
|
73
|
+
id,
|
|
74
|
+
async listItems() {
|
|
75
|
+
const parsedManifest = parseLibraryManifestSourceData(id, manifest);
|
|
76
|
+
return parsedManifest.items.map((item) =>
|
|
77
|
+
normalizeLibraryItemProviderSource(item, {
|
|
78
|
+
providerId: id,
|
|
79
|
+
}),
|
|
80
|
+
);
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function createLibraryManifestUrlProvider({
|
|
86
|
+
displayName,
|
|
87
|
+
id,
|
|
88
|
+
manifestUrl,
|
|
89
|
+
readText,
|
|
90
|
+
}: HttpLibraryManifestProviderOptions): LibraryProvider {
|
|
91
|
+
return createLibraryManifestProvider({
|
|
92
|
+
displayName,
|
|
93
|
+
id,
|
|
94
|
+
loadManifestText: async () => {
|
|
95
|
+
const loader = readText ?? defaultReadText;
|
|
96
|
+
return loader(manifestUrl);
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function parseLibraryManifestSourceText(
|
|
102
|
+
providerId: string,
|
|
103
|
+
rawManifestText: string,
|
|
104
|
+
): LibraryManifest {
|
|
105
|
+
const manifest = parseLibraryManifestText(rawManifestText, `library-manifest:${providerId}`);
|
|
106
|
+
|
|
107
|
+
return normalizeManifestSourceId(manifest, providerId);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function parseLibraryManifestSourceData(
|
|
111
|
+
providerId: string,
|
|
112
|
+
rawManifestData: unknown,
|
|
113
|
+
): LibraryManifest {
|
|
114
|
+
const manifest = parseLibraryManifest(rawManifestData, `library-manifest:${providerId}`);
|
|
115
|
+
|
|
116
|
+
return normalizeManifestSourceId(manifest, providerId);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function normalizeManifestSourceId(manifest: LibraryManifest, providerId: string): LibraryManifest {
|
|
120
|
+
if (manifest.source.sourceId) {
|
|
121
|
+
return manifest;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
...manifest,
|
|
126
|
+
source: {
|
|
127
|
+
...manifest.source,
|
|
128
|
+
sourceId: providerId,
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function defaultReadText(url: string): Promise<string> {
|
|
134
|
+
if (typeof globalThis.fetch !== 'function') {
|
|
135
|
+
throw new Error('fetch is not available in this environment');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const response = await globalThis.fetch(url);
|
|
139
|
+
if (!response.ok) {
|
|
140
|
+
const statusText = response.statusText ? ` ${response.statusText}` : '';
|
|
141
|
+
throw new Error(`Failed to load library manifest from ${url}: ${response.status}${statusText}`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return response.text();
|
|
145
|
+
}
|
package/src/runtime.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { ChatStreamEvent, ChatTransport } from '@workbench-kit/contracts';
|
|
2
|
+
import type { WorkbenchRuntimeEvent, MockWorkbenchRuntime } from '@workbench-kit/runtime';
|
|
3
|
+
|
|
4
|
+
export interface RuntimeChatTransportOptions {
|
|
5
|
+
runtime: MockWorkbenchRuntime;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function createChatTransportFromRuntime({
|
|
9
|
+
runtime,
|
|
10
|
+
}: RuntimeChatTransportOptions): ChatTransport {
|
|
11
|
+
return {
|
|
12
|
+
cancel: () => runtime.cancel(),
|
|
13
|
+
sendMessage: async (message, options) => {
|
|
14
|
+
void options;
|
|
15
|
+
const next = runtime.sendMessage(message);
|
|
16
|
+
if (!next) return undefined;
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
content: next.content,
|
|
20
|
+
createdAt: next.createdAt,
|
|
21
|
+
id: next.id,
|
|
22
|
+
label: next.label,
|
|
23
|
+
source: next.source,
|
|
24
|
+
};
|
|
25
|
+
},
|
|
26
|
+
subscribe: (listener: (event: ChatStreamEvent) => void) =>
|
|
27
|
+
runtime.subscribe((event: WorkbenchRuntimeEvent) => {
|
|
28
|
+
if (event.type === 'message') {
|
|
29
|
+
listener({
|
|
30
|
+
message: {
|
|
31
|
+
content: event.message.content,
|
|
32
|
+
createdAt: event.message.createdAt,
|
|
33
|
+
id: event.message.id,
|
|
34
|
+
label: event.message.label,
|
|
35
|
+
source: event.message.source,
|
|
36
|
+
},
|
|
37
|
+
type: 'message',
|
|
38
|
+
});
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (event.type === 'message-delta') {
|
|
43
|
+
listener({
|
|
44
|
+
delta: event.delta,
|
|
45
|
+
message: {
|
|
46
|
+
content: event.message.content,
|
|
47
|
+
createdAt: event.message.createdAt,
|
|
48
|
+
id: event.message.id,
|
|
49
|
+
label: event.message.label,
|
|
50
|
+
source: event.message.source,
|
|
51
|
+
},
|
|
52
|
+
type: 'message-delta',
|
|
53
|
+
});
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (event.type === 'status') {
|
|
58
|
+
listener({
|
|
59
|
+
previousStatus: event.previousStatus,
|
|
60
|
+
status: event.status,
|
|
61
|
+
type: 'status',
|
|
62
|
+
});
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (event.patch.type === 'delete-file') {
|
|
67
|
+
listener({
|
|
68
|
+
patch: { path: event.patch.path, type: 'delete-file' },
|
|
69
|
+
type: 'workspace-patch',
|
|
70
|
+
});
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
listener({
|
|
75
|
+
patch: {
|
|
76
|
+
content: event.patch.content,
|
|
77
|
+
mimeType: event.patch.mimeType,
|
|
78
|
+
path: event.patch.path,
|
|
79
|
+
source: event.patch.source,
|
|
80
|
+
type: 'write-file',
|
|
81
|
+
updatedAt: event.patch.updatedAt,
|
|
82
|
+
},
|
|
83
|
+
type: 'workspace-patch',
|
|
84
|
+
});
|
|
85
|
+
}),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function emitRuntimeWorkspacePatch({
|
|
90
|
+
runtime,
|
|
91
|
+
patch,
|
|
92
|
+
}: {
|
|
93
|
+
runtime: Pick<MockWorkbenchRuntime, 'emitWorkspacePatch'>;
|
|
94
|
+
patch: Parameters<MockWorkbenchRuntime['emitWorkspacePatch']>[0];
|
|
95
|
+
}) {
|
|
96
|
+
runtime.emitWorkspacePatch(patch);
|
|
97
|
+
}
|
package/src/workspace.ts
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { normalizeWorkspacePath } from '@workbench-kit/workspace';
|
|
2
|
+
import type {
|
|
3
|
+
CreateWorkspaceFileInput,
|
|
4
|
+
WriteWorkspaceFileInput,
|
|
5
|
+
WorkspaceFile,
|
|
6
|
+
} from '@workbench-kit/workspace';
|
|
7
|
+
import type { SaveInput, WorkspaceFileRepository } from '@workbench-kit/contracts';
|
|
8
|
+
|
|
9
|
+
export interface WorkspaceFileRepositoryCallbacks {
|
|
10
|
+
createFile: (file: CreateWorkspaceFileInput) => void;
|
|
11
|
+
deleteFile: (path: string) => void;
|
|
12
|
+
saveFile: (path: string, file: WriteWorkspaceFileInput) => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface WorkspaceFileAdapterState {
|
|
16
|
+
getFiles: () => WorkspaceFile[];
|
|
17
|
+
files: WorkspaceFile[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const cloneFile = (file: WorkspaceFile): WorkspaceFile => ({ ...file });
|
|
21
|
+
|
|
22
|
+
function normalizeInputPath(path: string) {
|
|
23
|
+
return normalizeWorkspacePath(path.trim());
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class InMemoryWorkspaceFileRepository implements WorkspaceFileRepository {
|
|
27
|
+
private readonly files = new Map<string, WorkspaceFile>();
|
|
28
|
+
|
|
29
|
+
private readonly onCreateFile?: (file: CreateWorkspaceFileInput) => void;
|
|
30
|
+
private readonly onDeleteFile?: (path: string) => void;
|
|
31
|
+
private readonly onSaveFile?: (path: string, file: WriteWorkspaceFileInput) => void;
|
|
32
|
+
|
|
33
|
+
constructor(
|
|
34
|
+
files: readonly WorkspaceFile[] = [],
|
|
35
|
+
callbacks: {
|
|
36
|
+
onCreateFile?: (file: CreateWorkspaceFileInput) => void;
|
|
37
|
+
onDeleteFile?: (path: string) => void;
|
|
38
|
+
onSaveFile?: (path: string, file: WriteWorkspaceFileInput) => void;
|
|
39
|
+
} = {},
|
|
40
|
+
) {
|
|
41
|
+
files.forEach((file) => {
|
|
42
|
+
const path = normalizeWorkspacePath(file.path);
|
|
43
|
+
if (!path) return;
|
|
44
|
+
this.files.set(path, { ...file, path });
|
|
45
|
+
});
|
|
46
|
+
this.onCreateFile = callbacks.onCreateFile;
|
|
47
|
+
this.onDeleteFile = callbacks.onDeleteFile;
|
|
48
|
+
this.onSaveFile = callbacks.onSaveFile;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async deleteFile(path: string): Promise<void> {
|
|
52
|
+
const normalizedPath = normalizeInputPath(path);
|
|
53
|
+
if (!normalizedPath) return;
|
|
54
|
+
if (!this.files.has(normalizedPath)) return;
|
|
55
|
+
|
|
56
|
+
this.files.delete(normalizedPath);
|
|
57
|
+
this.onDeleteFile?.(normalizedPath);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async getFile(path: string): Promise<WorkspaceFile | null> {
|
|
61
|
+
const normalizedPath = normalizeInputPath(path);
|
|
62
|
+
if (!normalizedPath) return null;
|
|
63
|
+
|
|
64
|
+
const file = this.files.get(normalizedPath);
|
|
65
|
+
return file ? { ...file } : null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async listFiles(): Promise<readonly WorkspaceFile[]> {
|
|
69
|
+
return [...this.files.values()].map(cloneFile);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async writeFile(input: SaveInput): Promise<WorkspaceFile> {
|
|
73
|
+
const normalizedPath = normalizeInputPath(input.path);
|
|
74
|
+
if (!normalizedPath) {
|
|
75
|
+
throw new Error('Invalid workspace path.');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const current = await this.getFile(normalizedPath);
|
|
79
|
+
if (current && input.expectedUpdatedAt && current.updatedAt !== input.expectedUpdatedAt) {
|
|
80
|
+
throw new Error('Stale file version.');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const next: WorkspaceFile = {
|
|
84
|
+
content: input.content,
|
|
85
|
+
mimeType: input.mimeType ?? current?.mimeType,
|
|
86
|
+
path: normalizedPath,
|
|
87
|
+
source: input.source ?? current?.source,
|
|
88
|
+
updatedAt: input.updatedAt ?? current?.updatedAt,
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
this.files.set(normalizedPath, next);
|
|
92
|
+
|
|
93
|
+
if (current) {
|
|
94
|
+
this.onSaveFile?.(normalizedPath, {
|
|
95
|
+
content: input.content,
|
|
96
|
+
source: input.source,
|
|
97
|
+
updatedAt: input.updatedAt,
|
|
98
|
+
});
|
|
99
|
+
} else {
|
|
100
|
+
this.onCreateFile?.({
|
|
101
|
+
content: input.content,
|
|
102
|
+
mimeType: input.mimeType,
|
|
103
|
+
path: normalizedPath,
|
|
104
|
+
source: input.source,
|
|
105
|
+
updatedAt: input.updatedAt,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return next;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function createWorkspaceFileRepository({
|
|
114
|
+
createFile,
|
|
115
|
+
deleteFile,
|
|
116
|
+
files,
|
|
117
|
+
saveFile,
|
|
118
|
+
}: WorkspaceFileRepositoryCallbacks & {
|
|
119
|
+
files: WorkspaceFile[];
|
|
120
|
+
}): WorkspaceFileRepository {
|
|
121
|
+
return new InMemoryWorkspaceFileRepository(files, {
|
|
122
|
+
onCreateFile: createFile,
|
|
123
|
+
onDeleteFile: deleteFile,
|
|
124
|
+
onSaveFile: (path, file) => saveFile(path, file),
|
|
125
|
+
});
|
|
126
|
+
}
|