ns-dsh-llm-devin 0.2.0 → 0.3.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/LICENSE +22 -0
- package/README.md +4 -1
- package/dist/adapter.js +5 -115
- package/dist/adapter.js.map +1 -1
- package/dist/messages.d.ts +1 -27
- package/dist/messages.js +2 -124
- package/dist/messages.js.map +1 -1
- package/package.json +4 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ngosangns
|
|
4
|
+
Copyright (c) 2025-2026 Mike O'Brien (pi-provider-kiro)
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -13,4 +13,7 @@ credential setup lives here.
|
|
|
13
13
|
|
|
14
14
|
Streams text, reasoning, and tool calls through `ns-devin-core` and maps
|
|
15
15
|
Devin's failures onto the Harness `LlmError` routing codes. Works with dsh
|
|
16
|
-
0.2 (tool-role messages) and still reads the dsh 0.1 `tool-result` blocks
|
|
16
|
+
0.2 (tool-role messages) and still reads the dsh 0.1 `tool-result` blocks —
|
|
17
|
+
that projection is `ns-bridge-core/dsh`, shared with `ns-dsh-llm-kiro`.
|
|
18
|
+
|
|
19
|
+
Part of [ns-bridge](https://github.com/ngosangns/ns-bridge).
|
package/dist/adapter.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// ABOUTME: The Harness LLM seam over devin-core.
|
|
2
2
|
import { LlmAdapter, LlmError, } from "@deepseek-ai/dsh-llm";
|
|
3
|
+
import { streamToDsh } from "ns-bridge-core/dsh";
|
|
3
4
|
import { fetchDevinModels, getCachedModels, isCacheStale, logger, streamDevin, updateDevinModelsCache, } from "ns-devin-core";
|
|
4
5
|
import { toLlmError } from "./errors.js";
|
|
5
6
|
import { toDevinMessages } from "./messages.js";
|
|
@@ -11,33 +12,6 @@ const EFFORT_NAMES = {
|
|
|
11
12
|
xhigh: "Extra high",
|
|
12
13
|
max: "Maximum",
|
|
13
14
|
};
|
|
14
|
-
/**
|
|
15
|
-
* Project one core usage record onto the harness token buckets.
|
|
16
|
-
*
|
|
17
|
-
* Devin reports real cache counters when the Cascade backend bills them.
|
|
18
|
-
* The session pill treats a missing `cacheReadTokens` as zero, and a turn
|
|
19
|
-
* drops its cache sum unless every step reported both cache fields — and the
|
|
20
|
-
* fields have to add up to `totalTokens` or the harness discards the sample.
|
|
21
|
-
*/
|
|
22
|
-
function toHarnessUsage(usage) {
|
|
23
|
-
const cacheReadTokens = usage.cacheRead ?? 0;
|
|
24
|
-
const cacheWriteTokens = usage.cacheWrite ?? 0;
|
|
25
|
-
const summed = usage.input + cacheReadTokens + cacheWriteTokens + usage.output;
|
|
26
|
-
if (usage.totalTokens !== summed) {
|
|
27
|
-
return {
|
|
28
|
-
inputTokens: usage.input,
|
|
29
|
-
outputTokens: usage.output,
|
|
30
|
-
totalTokens: usage.totalTokens,
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
return {
|
|
34
|
-
inputTokens: usage.input,
|
|
35
|
-
outputTokens: usage.output,
|
|
36
|
-
totalTokens: summed,
|
|
37
|
-
cacheReadTokens,
|
|
38
|
-
cacheWriteTokens,
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
15
|
export class DevinAdapter extends LlmAdapter {
|
|
42
16
|
options;
|
|
43
17
|
constructor(options) {
|
|
@@ -136,8 +110,9 @@ export class DevinAdapter extends LlmAdapter {
|
|
|
136
110
|
description: tool.description,
|
|
137
111
|
parameters: tool.parameters,
|
|
138
112
|
}));
|
|
139
|
-
|
|
140
|
-
|
|
113
|
+
// Event translation (blocks, usage, finish, unclosed-block safety) is
|
|
114
|
+
// shared with every dsh adapter in ns-bridge-core/dsh.
|
|
115
|
+
yield* streamToDsh(streamDevin({
|
|
141
116
|
model,
|
|
142
117
|
messages: projected.messages,
|
|
143
118
|
systemPrompt: system,
|
|
@@ -146,92 +121,7 @@ export class DevinAdapter extends LlmAdapter {
|
|
|
146
121
|
apiKey: credentials.access,
|
|
147
122
|
sessionId: options.sessionId,
|
|
148
123
|
signal: options.signal,
|
|
149
|
-
}))
|
|
150
|
-
switch (event.type) {
|
|
151
|
-
case "text_start":
|
|
152
|
-
openBlocks.add(event.index);
|
|
153
|
-
yield { type: "block-start", index: event.index, blockType: "text" };
|
|
154
|
-
break;
|
|
155
|
-
case "text_delta":
|
|
156
|
-
yield { type: "text-delta", index: event.index, text: event.delta };
|
|
157
|
-
break;
|
|
158
|
-
case "text_end":
|
|
159
|
-
openBlocks.delete(event.index);
|
|
160
|
-
yield { type: "block-end", index: event.index, block: { type: "text", text: event.text } };
|
|
161
|
-
break;
|
|
162
|
-
case "thinking_start":
|
|
163
|
-
openBlocks.add(event.index);
|
|
164
|
-
yield { type: "block-start", index: event.index, blockType: "reasoning" };
|
|
165
|
-
break;
|
|
166
|
-
case "thinking_delta":
|
|
167
|
-
yield { type: "reasoning-delta", index: event.index, text: event.delta };
|
|
168
|
-
break;
|
|
169
|
-
case "thinking_end":
|
|
170
|
-
openBlocks.delete(event.index);
|
|
171
|
-
yield { type: "block-end", index: event.index, block: { type: "reasoning", text: event.thinking } };
|
|
172
|
-
break;
|
|
173
|
-
case "tool_call_start":
|
|
174
|
-
openBlocks.add(event.index);
|
|
175
|
-
yield { type: "block-start", index: event.index, blockType: "tool-call" };
|
|
176
|
-
yield {
|
|
177
|
-
type: "tool-call-delta",
|
|
178
|
-
index: event.index,
|
|
179
|
-
id: event.id,
|
|
180
|
-
name: event.name,
|
|
181
|
-
argumentsDelta: "",
|
|
182
|
-
};
|
|
183
|
-
break;
|
|
184
|
-
case "tool_call_delta":
|
|
185
|
-
yield {
|
|
186
|
-
type: "tool-call-delta",
|
|
187
|
-
index: event.index,
|
|
188
|
-
id: event.id,
|
|
189
|
-
argumentsDelta: event.argumentsDelta,
|
|
190
|
-
};
|
|
191
|
-
break;
|
|
192
|
-
case "tool_call_end":
|
|
193
|
-
openBlocks.delete(event.index);
|
|
194
|
-
yield {
|
|
195
|
-
type: "block-end",
|
|
196
|
-
index: event.index,
|
|
197
|
-
block: {
|
|
198
|
-
type: "tool-call",
|
|
199
|
-
id: event.id,
|
|
200
|
-
name: event.name,
|
|
201
|
-
// The Harness keeps the model's raw JSON and reports invalid
|
|
202
|
-
// arguments back to the model; re-serializing a parsed preview
|
|
203
|
-
// would run a truncated call with auto-closed arguments.
|
|
204
|
-
arguments: event.argumentsJson.trim() ? event.argumentsJson : "{}",
|
|
205
|
-
},
|
|
206
|
-
};
|
|
207
|
-
break;
|
|
208
|
-
case "usage":
|
|
209
|
-
yield { type: "usage", usage: toHarnessUsage(event.usage) };
|
|
210
|
-
break;
|
|
211
|
-
case "done":
|
|
212
|
-
yield {
|
|
213
|
-
type: "finish",
|
|
214
|
-
reason: event.stopReason === "toolUse"
|
|
215
|
-
? { kind: "tool-calls" }
|
|
216
|
-
: event.stopReason === "length"
|
|
217
|
-
? { kind: "max-tokens" }
|
|
218
|
-
: { kind: "stop" },
|
|
219
|
-
// The finish union carries no message field for a successful
|
|
220
|
-
// reason, so a terminal diagnostic rides the replay envelope —
|
|
221
|
-
// the harness stores it on the assembled message's model source.
|
|
222
|
-
...(event.errorMessage ? { replayState: { response: { errorMessage: event.errorMessage } } } : {}),
|
|
223
|
-
};
|
|
224
|
-
break;
|
|
225
|
-
// `start` needs no chunk. `reset` can arrive on a mid-stream-error
|
|
226
|
-
// retry; this adapter cannot un-deliver a block, so it ignores the
|
|
227
|
-
// marker and lets the retried attempt continue on fresh indexes.
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
// A block the core opened but never closed would leave the assembler waiting
|
|
231
|
-
// for content that is not coming. Close them rather than trust it.
|
|
232
|
-
for (const index of openBlocks) {
|
|
233
|
-
yield { type: "block-end", index, block: { type: "text", text: "" } };
|
|
234
|
-
}
|
|
124
|
+
}));
|
|
235
125
|
}
|
|
236
126
|
}
|
|
237
127
|
//# sourceMappingURL=adapter.js.map
|
package/dist/adapter.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter.js","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA,iDAAiD;AAGjD,OAAO,EAEL,UAAU,EACV,QAAQ,
|
|
1
|
+
{"version":3,"file":"adapter.js","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"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"}
|
package/dist/messages.d.ts
CHANGED
|
@@ -1,27 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { type RequestMessage } from "@deepseek-ai/dsh-llm";
|
|
3
|
-
import type { DevinMessage } from "ns-devin-core";
|
|
4
|
-
/**
|
|
5
|
-
* Harness images live in the attachment service, so their bytes are read here
|
|
6
|
-
* rather than carried on the block. A store is optional: without one the images
|
|
7
|
-
* are dropped and the rest of the turn still reaches the model, which is the
|
|
8
|
-
* behaviour a text-only deployment wants.
|
|
9
|
-
*/
|
|
10
|
-
export interface MessageProjectionContext {
|
|
11
|
-
attachments?: AttachmentStore;
|
|
12
|
-
signal?: AbortSignal;
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* Flatten one Harness history into the neutral shape.
|
|
16
|
-
*
|
|
17
|
-
* Tool results arrive as `tool`-role messages (dsh 0.2) or as `tool-result`
|
|
18
|
-
* blocks inside a user message (dsh 0.1); both become neutral `toolResult`
|
|
19
|
-
* messages. The system prompt travels as a `system`-role message rather than a
|
|
20
|
-
* request field, so its text is returned separately for the system slot.
|
|
21
|
-
* `developer` messages only record tool additions/removals, which this route
|
|
22
|
-
* does not read in-history (it declares no `toolUpdate`), so they are skipped.
|
|
23
|
-
*/
|
|
24
|
-
export declare function toDevinMessages(messages: readonly RequestMessage[], context?: MessageProjectionContext): Promise<{
|
|
25
|
-
messages: DevinMessage[];
|
|
26
|
-
system?: string;
|
|
27
|
-
}>;
|
|
1
|
+
export { type MessageProjectionContext, toBridgeMessagesFromDsh as toDevinMessages, } from "ns-bridge-core/dsh";
|
package/dist/messages.js
CHANGED
|
@@ -1,126 +1,4 @@
|
|
|
1
1
|
// ABOUTME: Projects the Harness conversation vocabulary onto the neutral one devin-core reads.
|
|
2
|
-
// ABOUTME:
|
|
3
|
-
|
|
4
|
-
function isLegacyToolResult(block) {
|
|
5
|
-
return block.type === "tool-result";
|
|
6
|
-
}
|
|
7
|
-
async function userContent(blocks, context) {
|
|
8
|
-
const out = [];
|
|
9
|
-
for (const block of blocks) {
|
|
10
|
-
if (block.type === "text")
|
|
11
|
-
out.push({ type: "text", text: block.text });
|
|
12
|
-
else if (block.type === "reasoning")
|
|
13
|
-
out.push({ type: "text", text: block.text });
|
|
14
|
-
else if (block.type === "file") {
|
|
15
|
-
// Files never go to a provider as bytes: the harness projects them to
|
|
16
|
-
// handle text, and a hand-built call that still carries a file block
|
|
17
|
-
// gets the same representation here.
|
|
18
|
-
out.push({
|
|
19
|
-
type: "text",
|
|
20
|
-
text: fileHandleText(block.attachment, context.attachments?.fileHostPath(block.attachment)),
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
else if (block.type === "image") {
|
|
24
|
-
// An offloaded occurrence is a durable decision to send the placeholder
|
|
25
|
-
// naming the image and its read-only path instead of its bytes.
|
|
26
|
-
if (block.offloaded === true) {
|
|
27
|
-
const hostPath = context.attachments?.imageHostPath(block.attachment);
|
|
28
|
-
out.push({
|
|
29
|
-
type: "text",
|
|
30
|
-
text: offloadedImageText(block.attachment, hostPath ? { readonlyPath: hostPath } : undefined),
|
|
31
|
-
});
|
|
32
|
-
continue;
|
|
33
|
-
}
|
|
34
|
-
if (!context.attachments)
|
|
35
|
-
continue;
|
|
36
|
-
const stored = await context.attachments.readImage(block.attachment, context.signal);
|
|
37
|
-
out.push({
|
|
38
|
-
type: "image",
|
|
39
|
-
data: Buffer.from(stored.data).toString("base64"),
|
|
40
|
-
mimeType: block.attachment.mediaType,
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
// `tool-addition` / `tool-removal` belong to developer messages; this route
|
|
44
|
-
// declares no `toolUpdate`, so every request already carries the full tool list.
|
|
45
|
-
}
|
|
46
|
-
return out;
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Flatten one Harness history into the neutral shape.
|
|
50
|
-
*
|
|
51
|
-
* Tool results arrive as `tool`-role messages (dsh 0.2) or as `tool-result`
|
|
52
|
-
* blocks inside a user message (dsh 0.1); both become neutral `toolResult`
|
|
53
|
-
* messages. The system prompt travels as a `system`-role message rather than a
|
|
54
|
-
* request field, so its text is returned separately for the system slot.
|
|
55
|
-
* `developer` messages only record tool additions/removals, which this route
|
|
56
|
-
* does not read in-history (it declares no `toolUpdate`), so they are skipped.
|
|
57
|
-
*/
|
|
58
|
-
export async function toDevinMessages(messages, context = {}) {
|
|
59
|
-
const out = [];
|
|
60
|
-
const systemParts = [];
|
|
61
|
-
// The Harness correlates a result by call id alone; the call's name is only
|
|
62
|
-
// on the assistant block, so it is remembered here for the result.
|
|
63
|
-
const toolNames = new Map();
|
|
64
|
-
for (const message of messages) {
|
|
65
|
-
if (message.role === "system") {
|
|
66
|
-
for (const block of message.content)
|
|
67
|
-
if (block.type === "text")
|
|
68
|
-
systemParts.push(block.text);
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
if (message.role === "developer")
|
|
72
|
-
continue;
|
|
73
|
-
if (message.role === "assistant") {
|
|
74
|
-
const content = [];
|
|
75
|
-
for (const block of message.content) {
|
|
76
|
-
if (block.type === "text")
|
|
77
|
-
content.push({ type: "text", text: block.text });
|
|
78
|
-
else if (block.type === "reasoning")
|
|
79
|
-
content.push({ type: "thinking", thinking: block.text });
|
|
80
|
-
else if (block.type === "tool-call") {
|
|
81
|
-
let args;
|
|
82
|
-
try {
|
|
83
|
-
args = JSON.parse(block.arguments || "{}");
|
|
84
|
-
}
|
|
85
|
-
catch {
|
|
86
|
-
// A stored call whose arguments no longer parse is still part of
|
|
87
|
-
// the exchange the model must see; sending it with empty arguments
|
|
88
|
-
// keeps the call/result pairing, which dropping would break.
|
|
89
|
-
args = {};
|
|
90
|
-
}
|
|
91
|
-
toolNames.set(block.id, block.name);
|
|
92
|
-
content.push({ type: "toolCall", id: block.id, name: block.name, arguments: args });
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
out.push({ role: "assistant", content });
|
|
96
|
-
continue;
|
|
97
|
-
}
|
|
98
|
-
if (message.role === "tool") {
|
|
99
|
-
out.push({
|
|
100
|
-
role: "toolResult",
|
|
101
|
-
toolCallId: message.toolCallId,
|
|
102
|
-
toolName: toolNames.get(message.toolCallId) ?? "tool",
|
|
103
|
-
content: await userContent(message.content, context),
|
|
104
|
-
isError: message.isError === true,
|
|
105
|
-
});
|
|
106
|
-
continue;
|
|
107
|
-
}
|
|
108
|
-
// user (durable or request-only input)
|
|
109
|
-
const blocks = message.content;
|
|
110
|
-
const results = blocks.filter(isLegacyToolResult);
|
|
111
|
-
const plain = blocks.filter((block) => !isLegacyToolResult(block));
|
|
112
|
-
if (plain.length > 0)
|
|
113
|
-
out.push({ role: "user", content: await userContent(plain, context) });
|
|
114
|
-
for (const result of results) {
|
|
115
|
-
out.push({
|
|
116
|
-
role: "toolResult",
|
|
117
|
-
toolCallId: result.toolCallId,
|
|
118
|
-
toolName: toolNames.get(result.toolCallId) ?? "tool",
|
|
119
|
-
content: await userContent(result.content, context),
|
|
120
|
-
isError: result.isError === true,
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
return { messages: out, ...(systemParts.length > 0 ? { system: systemParts.join("\n\n") } : {}) };
|
|
125
|
-
}
|
|
2
|
+
// ABOUTME: The projection is shared by every dsh adapter and lives in ns-bridge-core/dsh.
|
|
3
|
+
export { toBridgeMessagesFromDsh as toDevinMessages, } from "ns-bridge-core/dsh";
|
|
126
4
|
//# sourceMappingURL=messages.js.map
|
package/dist/messages.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"messages.js","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":"AAAA,+FAA+F;AAC/F,
|
|
1
|
+
{"version":3,"file":"messages.js","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":"AAAA,+FAA+F;AAC/F,0FAA0F;AAE1F,OAAO,EAEL,uBAAuB,IAAI,eAAe,GAC3C,MAAM,oBAAoB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ns-dsh-llm-devin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "DeepSeek Harness (dsh) LLM adapter adding Devin (Cognition Cascade) as a model provider",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"license": "MIT",
|
|
16
16
|
"repository": {
|
|
17
17
|
"type": "git",
|
|
18
|
-
"url": "git+https://github.com/ngosangns/ns-
|
|
18
|
+
"url": "git+https://github.com/ngosangns/ns-bridge.git",
|
|
19
19
|
"directory": "packages/dsh-llm-devin"
|
|
20
20
|
},
|
|
21
21
|
"keywords": [
|
|
@@ -35,7 +35,8 @@
|
|
|
35
35
|
],
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@deepseek-ai/schemastery": "^3.18.2",
|
|
38
|
-
"ns-devin-core": "0.
|
|
38
|
+
"ns-devin-core": "0.3.0",
|
|
39
|
+
"ns-bridge-core": "0.2.0"
|
|
39
40
|
},
|
|
40
41
|
"peerDependencies": {
|
|
41
42
|
"@deepseek-ai/cordis": "^4.0.2",
|