@dsh-stack/translator 0.0.0-stage → 0.1.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/README.md +2 -2
- package/lib/index.d.ts +115 -0
- package/lib/index.js +202 -0
- package/package.json +41 -4
package/README.md
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
#
|
|
1
|
+
# translator
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Translation and transformation utilities for Stack representations and protocols.
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical Stack translator service for supported session/event formats.
|
|
3
|
+
*/
|
|
4
|
+
import { Service, type Context } from "@deepseek-ai/cordis";
|
|
5
|
+
export declare const name = "translator";
|
|
6
|
+
export declare const inject: string[];
|
|
7
|
+
export type Format = "opencode" | "claude" | "dsh";
|
|
8
|
+
export interface OpenCodeMessage {
|
|
9
|
+
role: "user" | "assistant" | "system";
|
|
10
|
+
content: string | Array<{
|
|
11
|
+
type: string;
|
|
12
|
+
text?: string;
|
|
13
|
+
[key: string]: unknown;
|
|
14
|
+
}>;
|
|
15
|
+
timestamp?: string;
|
|
16
|
+
[key: string]: unknown;
|
|
17
|
+
}
|
|
18
|
+
export interface ClaudeEntry {
|
|
19
|
+
type: "human" | "assistant" | "tool_use" | "tool_result";
|
|
20
|
+
text?: string;
|
|
21
|
+
content?: string;
|
|
22
|
+
[key: string]: unknown;
|
|
23
|
+
}
|
|
24
|
+
export interface DshEvent {
|
|
25
|
+
type: string;
|
|
26
|
+
seq: number;
|
|
27
|
+
role?: string;
|
|
28
|
+
content?: Array<{
|
|
29
|
+
type: string;
|
|
30
|
+
text?: string;
|
|
31
|
+
[key: string]: unknown;
|
|
32
|
+
}>;
|
|
33
|
+
timestamp?: string;
|
|
34
|
+
[key: string]: unknown;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Detects the format of the given data object.
|
|
38
|
+
*
|
|
39
|
+
* Guarantees: Returns the format name ("opencode", "claude", or "dsh") if the data matches the respective format.
|
|
40
|
+
* Returns null if the data is null, not an object, or doesn't match any known format.
|
|
41
|
+
*
|
|
42
|
+
* Throws: Throws an error if the data is not an object or doesn't have the required "events" array for "dsh" format.
|
|
43
|
+
*/
|
|
44
|
+
export declare function detectFormat(data: unknown): Format | null;
|
|
45
|
+
export type TranslateFn = (input: unknown) => unknown;
|
|
46
|
+
export declare class TranslatorRegistry {
|
|
47
|
+
private readonly converters;
|
|
48
|
+
/**
|
|
49
|
+
* Register a translation function for converting data from one format to another.
|
|
50
|
+
*
|
|
51
|
+
* @param sourceFormat - The source format of the data.
|
|
52
|
+
* @param targetFormat - The target format to which the data should be converted.
|
|
53
|
+
* @param fn - The translation function that performs the conversion.
|
|
54
|
+
*
|
|
55
|
+
* @throws Will throw an error if no converter is registered for the given source and target formats.
|
|
56
|
+
*/
|
|
57
|
+
register(sourceFormat: Format, targetFormat: Format, fn: TranslateFn): void;
|
|
58
|
+
/**
|
|
59
|
+
* Convert data from a source format to a target format using a registered translation function.
|
|
60
|
+
*
|
|
61
|
+
* @param data - The data to be translated.
|
|
62
|
+
* @param sourceFormat - The current format of the data.
|
|
63
|
+
* @param targetFormat - The desired format to convert the data into.
|
|
64
|
+
*
|
|
65
|
+
* @returns The translated data in the target format.
|
|
66
|
+
*
|
|
67
|
+
* @throws Will throw an error if no translation function is registered for the given formats or if the data is invalid.
|
|
68
|
+
*/
|
|
69
|
+
translate(data: unknown, sourceFormat: Format, targetFormat: Format): unknown;
|
|
70
|
+
/**
|
|
71
|
+
* Translates data from a source format to a target format using a registered translation function.
|
|
72
|
+
*
|
|
73
|
+
* @param data - The data to be translated.
|
|
74
|
+
* @param sourceFormat - The current format of the data.
|
|
75
|
+
* @param targetFormat - The desired format to convert the data into.
|
|
76
|
+
*
|
|
77
|
+
* @returns The translated data in the target format.
|
|
78
|
+
*
|
|
79
|
+
* @throws Will throw an error if no translation function is registered for the given formats or if the data is invalid.
|
|
80
|
+
*/
|
|
81
|
+
supportedConversions(): readonly string[];
|
|
82
|
+
}
|
|
83
|
+
export interface Config {
|
|
84
|
+
defaultFormat?: Format;
|
|
85
|
+
}
|
|
86
|
+
export declare class TranslatorService extends Service {
|
|
87
|
+
static inject: string[];
|
|
88
|
+
readonly registry: TranslatorRegistry;
|
|
89
|
+
/** Constructs an instance. */
|
|
90
|
+
constructor(ctx: Context, _config?: Config);
|
|
91
|
+
/**
|
|
92
|
+
* Converts a CLAUDE document to a DSH format.
|
|
93
|
+
*
|
|
94
|
+
* Guarantees: Returns an object with events, each containing a type, seq, role, and content.
|
|
95
|
+
* Must receive a valid CLAUDE document with an 'entries' array.
|
|
96
|
+
* Throws an error if the input is not a valid CLAUDE document.
|
|
97
|
+
*/
|
|
98
|
+
register(sourceFormat: Format, targetFormat: Format, fn: TranslateFn): void;
|
|
99
|
+
/**
|
|
100
|
+
* Converts a CLUDE document to a DSH-compatible format.
|
|
101
|
+
*
|
|
102
|
+
* Guarantees a valid DSH document with events formatted as user or assistant messages.
|
|
103
|
+
* Throws an error if the input is not a valid CLUDE document.
|
|
104
|
+
*/
|
|
105
|
+
translate(data: unknown, sourceFormat: Format, targetFormat: Format): unknown;
|
|
106
|
+
/** supportedConversions implementation. */
|
|
107
|
+
supportedConversions(): readonly string[];
|
|
108
|
+
}
|
|
109
|
+
declare module "@deepseek-ai/cordis" {
|
|
110
|
+
interface Context {
|
|
111
|
+
translators: TranslatorService;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/** apply implementation. */
|
|
115
|
+
export declare function apply(ctx: Context, config?: Config): void;
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical Stack translator service for supported session/event formats.
|
|
3
|
+
*/
|
|
4
|
+
import { Service } from "@deepseek-ai/cordis";
|
|
5
|
+
export const name = "translator";
|
|
6
|
+
export const inject = [];
|
|
7
|
+
/**
|
|
8
|
+
* Detects the format of the given data object.
|
|
9
|
+
*
|
|
10
|
+
* Guarantees: Returns the format name ("opencode", "claude", or "dsh") if the data matches the respective format.
|
|
11
|
+
* Returns null if the data is null, not an object, or doesn't match any known format.
|
|
12
|
+
*
|
|
13
|
+
* Throws: Throws an error if the data is not an object or doesn't have the required "events" array for "dsh" format.
|
|
14
|
+
*/
|
|
15
|
+
export function detectFormat(data) {
|
|
16
|
+
if (data === null || typeof data !== "object")
|
|
17
|
+
return null;
|
|
18
|
+
const obj = data;
|
|
19
|
+
if ("messages" in obj && Array.isArray(obj.messages))
|
|
20
|
+
return "opencode";
|
|
21
|
+
if ("entries" in obj && Array.isArray(obj.entries))
|
|
22
|
+
return "claude";
|
|
23
|
+
if ("events" in obj && Array.isArray(obj.events))
|
|
24
|
+
return "dsh";
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
/** Narrow an unknown value to a dsh document, or throw if it isn't one. */
|
|
28
|
+
function asDshDocument(data) {
|
|
29
|
+
if (!data || typeof data !== "object" || !("events" in data) || !Array.isArray(data.events))
|
|
30
|
+
throw new Error("invalid dsh document");
|
|
31
|
+
return data;
|
|
32
|
+
}
|
|
33
|
+
export class TranslatorRegistry {
|
|
34
|
+
converters = new Map();
|
|
35
|
+
/**
|
|
36
|
+
* Register a translation function for converting data from one format to another.
|
|
37
|
+
*
|
|
38
|
+
* @param sourceFormat - The source format of the data.
|
|
39
|
+
* @param targetFormat - The target format to which the data should be converted.
|
|
40
|
+
* @param fn - The translation function that performs the conversion.
|
|
41
|
+
*
|
|
42
|
+
* @throws Will throw an error if no converter is registered for the given source and target formats.
|
|
43
|
+
*/
|
|
44
|
+
register(sourceFormat, targetFormat, fn) {
|
|
45
|
+
this.converters.set(`${sourceFormat}->${targetFormat}`, fn);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Convert data from a source format to a target format using a registered translation function.
|
|
49
|
+
*
|
|
50
|
+
* @param data - The data to be translated.
|
|
51
|
+
* @param sourceFormat - The current format of the data.
|
|
52
|
+
* @param targetFormat - The desired format to convert the data into.
|
|
53
|
+
*
|
|
54
|
+
* @returns The translated data in the target format.
|
|
55
|
+
*
|
|
56
|
+
* @throws Will throw an error if no translation function is registered for the given formats or if the data is invalid.
|
|
57
|
+
*/
|
|
58
|
+
translate(data, sourceFormat, targetFormat) {
|
|
59
|
+
if (sourceFormat === targetFormat)
|
|
60
|
+
return data;
|
|
61
|
+
const fn = this.converters.get(`${sourceFormat}->${targetFormat}`);
|
|
62
|
+
if (fn === undefined)
|
|
63
|
+
throw new Error(`no converter for ${sourceFormat} -> ${targetFormat}`);
|
|
64
|
+
return fn(data);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Translates data from a source format to a target format using a registered translation function.
|
|
68
|
+
*
|
|
69
|
+
* @param data - The data to be translated.
|
|
70
|
+
* @param sourceFormat - The current format of the data.
|
|
71
|
+
* @param targetFormat - The desired format to convert the data into.
|
|
72
|
+
*
|
|
73
|
+
* @returns The translated data in the target format.
|
|
74
|
+
*
|
|
75
|
+
* @throws Will throw an error if no translation function is registered for the given formats or if the data is invalid.
|
|
76
|
+
*/
|
|
77
|
+
supportedConversions() {
|
|
78
|
+
return [...this.converters.keys()];
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Converts data from a source format to a target format using a registered translation function.
|
|
83
|
+
*
|
|
84
|
+
* @param data - The data to be translated.
|
|
85
|
+
* @param sourceFormat - The current format of the data.
|
|
86
|
+
* @param targetFormat - The desired format to convert the data into.
|
|
87
|
+
*
|
|
88
|
+
* @returns The translated data in the target format.
|
|
89
|
+
* @throws Will throw an error if no translation function is registered for the given formats or if the data is invalid.
|
|
90
|
+
*/
|
|
91
|
+
function opencodeToDsh(data) {
|
|
92
|
+
if (!data || typeof data !== "object" || !("messages" in data) || !Array.isArray(data.messages))
|
|
93
|
+
throw new Error("invalid opencode document");
|
|
94
|
+
const input = data;
|
|
95
|
+
return {
|
|
96
|
+
events: input.messages.map((msg, seq) => ({
|
|
97
|
+
type: msg.role === "user"
|
|
98
|
+
? "user/message"
|
|
99
|
+
: msg.role === "assistant"
|
|
100
|
+
? "assistant/message"
|
|
101
|
+
: "system/message",
|
|
102
|
+
seq,
|
|
103
|
+
role: msg.role,
|
|
104
|
+
content: typeof msg.content === "string" ? [{ type: "text", text: msg.content }] : msg.content,
|
|
105
|
+
timestamp: msg.timestamp,
|
|
106
|
+
})),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/** dshToOpencode implementation. */
|
|
110
|
+
function dshToOpencode(data) {
|
|
111
|
+
const input = asDshDocument(data);
|
|
112
|
+
return {
|
|
113
|
+
messages: input.events
|
|
114
|
+
.filter((event) => event.type.includes("/message"))
|
|
115
|
+
.map((event) => ({
|
|
116
|
+
role: event.role === "system" ? "system" : event.role === "user" ? "user" : "assistant",
|
|
117
|
+
content: event.content ?? [],
|
|
118
|
+
timestamp: event.timestamp,
|
|
119
|
+
})),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
/** claudeToDsh implementation. */
|
|
123
|
+
function claudeToDsh(data) {
|
|
124
|
+
if (!data || typeof data !== "object" || !("entries" in data) || !Array.isArray(data.entries))
|
|
125
|
+
throw new Error("invalid claude document");
|
|
126
|
+
const input = data;
|
|
127
|
+
return {
|
|
128
|
+
events: input.entries.map((entry, seq) => ({
|
|
129
|
+
type: entry.type === "human"
|
|
130
|
+
? "user/message"
|
|
131
|
+
: entry.type === "assistant"
|
|
132
|
+
? "assistant/message"
|
|
133
|
+
: entry.type,
|
|
134
|
+
seq,
|
|
135
|
+
role: entry.type === "human" ? "user" : entry.type === "assistant" ? "assistant" : undefined,
|
|
136
|
+
content: [{ type: "text", text: entry.text ?? entry.content ?? "" }],
|
|
137
|
+
})),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Converts opencode data to DSH format.
|
|
142
|
+
*
|
|
143
|
+
* @param data - The opencode data to be converted.
|
|
144
|
+
* @param sourceFormat - The current format of the data (not used in this function).
|
|
145
|
+
* @param targetFormat - The desired format to convert the data into (not used in this function).
|
|
146
|
+
*
|
|
147
|
+
* @returns The translated data in the DSH format.
|
|
148
|
+
* @throws Will throw an error if the data is invalid or no translation function is registered for the given formats.
|
|
149
|
+
*/
|
|
150
|
+
function dshToClaude(data) {
|
|
151
|
+
const input = asDshDocument(data);
|
|
152
|
+
return {
|
|
153
|
+
entries: input.events
|
|
154
|
+
.filter((event) => event.type.includes("/message"))
|
|
155
|
+
.map((event) => ({
|
|
156
|
+
type: event.role === "user" ? "human" : "assistant",
|
|
157
|
+
text: event.content?.map((chunk) => chunk.text ?? "").join("") ?? "",
|
|
158
|
+
})),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
export class TranslatorService extends Service {
|
|
162
|
+
static inject = [];
|
|
163
|
+
registry = new TranslatorRegistry();
|
|
164
|
+
/** Constructs an instance. */
|
|
165
|
+
constructor(ctx, _config = {}) {
|
|
166
|
+
super(ctx, "translators");
|
|
167
|
+
this.registry.register("opencode", "dsh", opencodeToDsh);
|
|
168
|
+
this.registry.register("dsh", "opencode", dshToOpencode);
|
|
169
|
+
this.registry.register("claude", "dsh", claudeToDsh);
|
|
170
|
+
this.registry.register("dsh", "claude", dshToClaude);
|
|
171
|
+
this.registry.register("opencode", "claude", (data) => dshToClaude(opencodeToDsh(data)));
|
|
172
|
+
this.registry.register("claude", "opencode", (data) => dshToOpencode(claudeToDsh(data)));
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Converts a CLAUDE document to a DSH format.
|
|
176
|
+
*
|
|
177
|
+
* Guarantees: Returns an object with events, each containing a type, seq, role, and content.
|
|
178
|
+
* Must receive a valid CLAUDE document with an 'entries' array.
|
|
179
|
+
* Throws an error if the input is not a valid CLAUDE document.
|
|
180
|
+
*/
|
|
181
|
+
register(sourceFormat, targetFormat, fn) {
|
|
182
|
+
this.registry.register(sourceFormat, targetFormat, fn);
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Converts a CLUDE document to a DSH-compatible format.
|
|
186
|
+
*
|
|
187
|
+
* Guarantees a valid DSH document with events formatted as user or assistant messages.
|
|
188
|
+
* Throws an error if the input is not a valid CLUDE document.
|
|
189
|
+
*/
|
|
190
|
+
translate(data, sourceFormat, targetFormat) {
|
|
191
|
+
return this.registry.translate(data, sourceFormat, targetFormat);
|
|
192
|
+
}
|
|
193
|
+
/** supportedConversions implementation. */
|
|
194
|
+
supportedConversions() {
|
|
195
|
+
return this.registry.supportedConversions();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/** apply implementation. */
|
|
199
|
+
export function apply(ctx, config = {}) {
|
|
200
|
+
ctx.plugin(TranslatorService, config);
|
|
201
|
+
ctx.logger.info(`translator loaded: 6 converters registered`);
|
|
202
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,43 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dsh-stack/translator",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"description": "dsh plugin: translate between provider session/skill/hook formats and dsh native format",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"types": "lib/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./lib/index.d.ts",
|
|
11
|
+
"default": "./lib/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./src/*": "./src/*"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"lib"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc",
|
|
20
|
+
"typecheck": "tsc --noEmit",
|
|
21
|
+
"test": "node check-plugin.mjs"
|
|
22
|
+
},
|
|
23
|
+
"peerDependencies": {
|
|
24
|
+
"@deepseek-ai/cordis": "^4.0.1"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"typescript": "^5.9.0",
|
|
28
|
+
"@types/node": "^22.0.0",
|
|
29
|
+
"@deepseek-ai/cordis": "^4.0.1"
|
|
30
|
+
},
|
|
31
|
+
"stack": {
|
|
32
|
+
"kind": "plugin",
|
|
33
|
+
"id": "stack.agent.translator"
|
|
34
|
+
},
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public"
|
|
37
|
+
},
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "https://github.com/marius-patrik/dsh-stack.git",
|
|
41
|
+
"directory": "plugins/translator"
|
|
42
|
+
}
|
|
43
|
+
}
|