dsh-cline-pass 0.2.4 → 0.2.5
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/lib/adapter.js +69 -14
- package/package.json +1 -1
package/lib/adapter.js
CHANGED
|
@@ -249,31 +249,88 @@ function collectImageParts(blocks, images, target = []) {
|
|
|
249
249
|
/**
|
|
250
250
|
* Convert the harness conversation to wire messages.
|
|
251
251
|
*
|
|
252
|
-
* Tool results become standalone `{ role: 'tool' }` messages
|
|
253
|
-
*
|
|
254
|
-
*
|
|
252
|
+
* Tool results become standalone `{ role: 'tool' }` messages, in both harness
|
|
253
|
+
* shapes: the current one, where the result IS a `tool`-role message carrying
|
|
254
|
+
* `toolCallId`, and the one dsh < 0.2.0 produced, a user message wrapping a
|
|
255
|
+
* `tool-result` block. A user message carrying an image switches to the OpenAI
|
|
256
|
+
* content-part array; a text-only one keeps the plain-string form.
|
|
255
257
|
*
|
|
256
258
|
* An image nested in a tool result (read_image, a screenshot tool) has no
|
|
257
259
|
* OpenAI `tool` wire spelling, so it rides a following user message instead —
|
|
258
|
-
* the same shape the shipped adapters use.
|
|
260
|
+
* the same shape the shipped adapters use. Deferring that message until the
|
|
261
|
+
* next non-tool message keeps a parallel group's answers contiguous.
|
|
262
|
+
*
|
|
263
|
+
* `developer` becomes `system`. The harness uses that role for its own
|
|
264
|
+
* system-level notices — the tool-registry change it appends when the tool set
|
|
265
|
+
* moves — and the newer OpenAI spelling is not accepted by every
|
|
266
|
+
* OpenAI-compatible gateway, while `system` is. Sending it as a user turn (what
|
|
267
|
+
* an unhandled role fell through to) also misrepresents it as something the user
|
|
268
|
+
* said.
|
|
269
|
+
*
|
|
270
|
+
* The group guard below is the same rule stated once: an assistant message
|
|
271
|
+
* carrying `tool_calls` must be followed immediately by a `tool` message per id.
|
|
272
|
+
* Any other message that would land inside that run — a notice, a user turn, the
|
|
273
|
+
* image message a tool-result image needs — is held back until the group closes,
|
|
274
|
+
* because a gateway that validates the rule rejects the whole request with
|
|
275
|
+
* "insufficient tool messages following tool_calls message".
|
|
259
276
|
*/
|
|
260
277
|
function serializeMessages(messages, images) {
|
|
261
278
|
const wire = []
|
|
262
279
|
let pendingToolImages = []
|
|
280
|
+
/** Call ids of the most recent assistant tool_calls still awaiting an answer. */
|
|
281
|
+
let awaiting = []
|
|
282
|
+
/** Messages held back so they cannot split the group; emitted in order. */
|
|
283
|
+
let deferred = []
|
|
284
|
+
|
|
285
|
+
const flushDeferred = () => {
|
|
286
|
+
if (deferred.length === 0) return
|
|
287
|
+
for (const held of deferred) wire.push(held)
|
|
288
|
+
deferred = []
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Emit a non-tool message.
|
|
292
|
+
*
|
|
293
|
+
* A message that is not a tool answer cannot go out while a group is open, so
|
|
294
|
+
* it waits. With no group open this is a plain push, which is every ordinary
|
|
295
|
+
* conversation and keeps that output unchanged.
|
|
296
|
+
*/
|
|
297
|
+
const emit = (message) => {
|
|
298
|
+
if (awaiting.length > 0) deferred.push(message)
|
|
299
|
+
else wire.push(message)
|
|
300
|
+
}
|
|
263
301
|
const flushToolImages = () => {
|
|
264
302
|
if (pendingToolImages.length === 0) return
|
|
265
|
-
|
|
303
|
+
const message = { role: 'user', content: [{ type: 'text', text: TOOL_RESULT_IMAGE_TEXT }, ...pendingToolImages] }
|
|
266
304
|
pendingToolImages = []
|
|
305
|
+
emit(message)
|
|
267
306
|
}
|
|
307
|
+
/** A tool answer: it closes its own id, and the group when it was the last. */
|
|
308
|
+
const answer = (callId, content) => {
|
|
309
|
+
wire.push({ role: 'tool', tool_call_id: callId, content: flattenText(content) || '(no output)' })
|
|
310
|
+
collectImageParts(content, images, pendingToolImages)
|
|
311
|
+
awaiting = awaiting.filter((id) => id !== callId)
|
|
312
|
+
if (awaiting.length === 0) flushDeferred()
|
|
313
|
+
}
|
|
314
|
+
|
|
268
315
|
for (const message of messages) {
|
|
269
|
-
if (message.role === 'system') {
|
|
316
|
+
if (message.role === 'system' || message.role === 'developer') {
|
|
270
317
|
flushToolImages()
|
|
271
|
-
|
|
318
|
+
emit({ role: 'system', content: flattenText(message.content) })
|
|
272
319
|
continue
|
|
273
320
|
}
|
|
274
321
|
if (message.role === 'assistant') {
|
|
275
322
|
flushToolImages()
|
|
323
|
+
// A turn boundary. Anything still held belongs before the new turn, and an
|
|
324
|
+
// unanswered group from the previous turn can no longer be answered.
|
|
325
|
+
flushDeferred()
|
|
276
326
|
wire.push(serializeAssistant(message))
|
|
327
|
+
awaiting = (message.content ?? []).filter((block) => block.type === 'tool-call').map((block) => block.id)
|
|
328
|
+
continue
|
|
329
|
+
}
|
|
330
|
+
// A harness tool result: the call identity is on the message. No flush here,
|
|
331
|
+
// so the answers to one assistant message's parallel calls stay adjacent.
|
|
332
|
+
if (message.role === 'tool') {
|
|
333
|
+
answer(message.toolCallId, message.content)
|
|
277
334
|
continue
|
|
278
335
|
}
|
|
279
336
|
const toolResults = (message.content ?? []).filter((block) => block.type === 'tool-result')
|
|
@@ -286,18 +343,16 @@ function serializeMessages(messages, images) {
|
|
|
286
343
|
const text = parts.filter((part) => part.type === 'text').map((part) => part.text).join('')
|
|
287
344
|
if (hasImage || text.length > 0 || toolResults.length === 0) {
|
|
288
345
|
flushToolImages()
|
|
289
|
-
|
|
346
|
+
emit({ role: 'user', content: hasImage ? parts : text })
|
|
290
347
|
}
|
|
291
348
|
for (const result of toolResults) {
|
|
292
|
-
|
|
293
|
-
role: 'tool',
|
|
294
|
-
tool_call_id: result.toolCallId,
|
|
295
|
-
content: flattenText(result.content) || '(no output)',
|
|
296
|
-
})
|
|
297
|
-
collectImageParts(result.content, images, pendingToolImages)
|
|
349
|
+
answer(result.toolCallId, result.content)
|
|
298
350
|
}
|
|
299
351
|
}
|
|
300
352
|
flushToolImages()
|
|
353
|
+
// A group the history never closed leaves nothing to wait for; whatever was
|
|
354
|
+
// held still belongs in the request, just after the answers that do exist.
|
|
355
|
+
flushDeferred()
|
|
301
356
|
return wire
|
|
302
357
|
}
|
|
303
358
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-cline-pass",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"description": "Self-contained Cline Pass provider for dsh: register Cline Pass subscription models on the harness LLM seam, enumerate and validate the upstream channels behind each model, pin or exclude them with failover, and drive an account pool. No proxy process and no external cline-pass-switcher install required.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|