@deepseek-ai/dsh-repeat-tool-reminder 0.1.6-alpha.2 → 0.1.7-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/lib/index.js +36 -60
- package/lib/types/index.d.ts +8 -0
- package/package.json +13 -13
package/lib/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import z from "@deepseek-ai/schemastery";
|
|
3
|
-
import { Service } from "@deepseek-ai/cordis";
|
|
3
|
+
import { Context, Service } from "@deepseek-ai/cordis";
|
|
4
4
|
//#region ../../typert/protocol/src/remote-error.ts
|
|
5
5
|
/**
|
|
6
6
|
* One Remote call failure: a real Error carrying its stable code and typed
|
|
@@ -46,7 +46,10 @@ function isTypertRemoteSegment(value) {
|
|
|
46
46
|
}
|
|
47
47
|
const REMOTE_METHOD_DESCRIPTOR = "@deepseek-ai/dsh-typert-protocol/remote-methods";
|
|
48
48
|
/**
|
|
49
|
-
* Bind one visible Service field to a Cordis key and Remote namespace.
|
|
49
|
+
* Bind one visible Service field to a Cordis key and Remote namespace. A
|
|
50
|
+
* service that owns a Cordis Context also gives its tree `ctx.invocation`,
|
|
51
|
+
* `undefined` outside a Remote call, so no `TypertRemoteService` is needed for
|
|
52
|
+
* a Host composition to read it.
|
|
50
53
|
* @param service - owning Service instance, normally `this`.
|
|
51
54
|
* @param serviceKey - exact Cordis service key.
|
|
52
55
|
* @param options - optional distinct wire namespace.
|
|
@@ -56,6 +59,8 @@ function bindTypertRemote(service, serviceKey, options = {}) {
|
|
|
56
59
|
validateName("service key", serviceKey);
|
|
57
60
|
const namespace = options.namespace ?? serviceKey;
|
|
58
61
|
validateName("namespace", namespace);
|
|
62
|
+
const ctx = Reflect.get(service, "ctx");
|
|
63
|
+
if (ctx instanceof Context) provideInvocationAccessor(ctx);
|
|
59
64
|
return Object.freeze({
|
|
60
65
|
service,
|
|
61
66
|
serviceKey,
|
|
@@ -77,6 +82,16 @@ var TypertRemoteService = class extends Service {
|
|
|
77
82
|
this.typertRemote = bindTypertRemote(this, this.name, options);
|
|
78
83
|
}
|
|
79
84
|
};
|
|
85
|
+
/**
|
|
86
|
+
* Make `ctx.invocation` read as `undefined` outside a Remote call instead of the
|
|
87
|
+
* reflect service's "cannot get property" error; a call-derived Context shadows
|
|
88
|
+
* the accessor with its own property. The first Remote Service constructed in a
|
|
89
|
+
* tree registers it on the root, where it outlives any one Service.
|
|
90
|
+
*/
|
|
91
|
+
function provideInvocationAccessor(ctx) {
|
|
92
|
+
if (Object.hasOwn(ctx.root.reflect.props, "invocation")) return;
|
|
93
|
+
ctx.root.accessor("invocation", { get: () => void 0 });
|
|
94
|
+
}
|
|
80
95
|
function Remote(methodExportOrOptions, context) {
|
|
81
96
|
if (typeof methodExportOrOptions === "string") {
|
|
82
97
|
validateName("Remote export name", methodExportOrOptions);
|
|
@@ -230,10 +245,10 @@ function freezeMessage(message) {
|
|
|
230
245
|
* @returns an immutable message with a fresh stable identity.
|
|
231
246
|
*/
|
|
232
247
|
function createMessage(input) {
|
|
233
|
-
return
|
|
248
|
+
return deepFreeze(structuredClone({
|
|
234
249
|
...input,
|
|
235
250
|
id: brandString(randomUUID())
|
|
236
|
-
});
|
|
251
|
+
}));
|
|
237
252
|
}
|
|
238
253
|
/**
|
|
239
254
|
* Create one identified user-role message and freeze it before publication.
|
|
@@ -509,25 +524,23 @@ function textOnlyImageText(ref) {
|
|
|
509
524
|
return `[image omitted because this model accepts text only; attachment sha256:${String(ref.attachmentId).slice(7, 15)}]`;
|
|
510
525
|
}
|
|
511
526
|
/**
|
|
512
|
-
* True when typed model content contains an image block
|
|
513
|
-
*
|
|
514
|
-
*
|
|
515
|
-
* survey), so a consumer cannot silently diverge on nesting depth.
|
|
527
|
+
* True when typed model content contains an image block. This is the one image
|
|
528
|
+
* walk shared by every image policy (capability gating, text-only
|
|
529
|
+
* serialization, compaction survey), so a consumer cannot silently diverge.
|
|
516
530
|
* @param content - typed model content blocks.
|
|
517
|
-
* @returns whether any
|
|
531
|
+
* @returns whether any block is an image.
|
|
518
532
|
*/
|
|
519
533
|
function contentHasImage(content) {
|
|
520
|
-
return content.some((block) => block.type === "image"
|
|
534
|
+
return content.some((block) => block.type === "image");
|
|
521
535
|
}
|
|
522
536
|
/**
|
|
523
|
-
* True when typed model content contains a file block
|
|
524
|
-
* tool-result content on the same recursion every file policy shares.
|
|
537
|
+
* True when typed model content contains a file block.
|
|
525
538
|
* Reads current content on every call without retaining scan results.
|
|
526
539
|
* @param content - typed model content blocks.
|
|
527
|
-
* @returns whether any
|
|
540
|
+
* @returns whether any block is a file.
|
|
528
541
|
*/
|
|
529
542
|
function contentHasFile(content) {
|
|
530
|
-
for (const block of content) if (block.type === "file"
|
|
543
|
+
for (const block of content) if (block.type === "file") return true;
|
|
531
544
|
return false;
|
|
532
545
|
}
|
|
533
546
|
/**
|
|
@@ -544,7 +557,7 @@ function fileHandleText(ref, readonlyPath) {
|
|
|
544
557
|
if (readonlyPath === void 0) return `[${identity} was uploaded, but the current execution environment cannot access a readable path. Report that limitation if its contents are needed; do not claim to have read it.]`;
|
|
545
558
|
return `[${identity}: verbatim read-only copy saved at ${quoted(readonlyPath)}. Read that path with your file tools when its contents are needed; copy it to a writable location before modifying it. When delegating file work, include this saved path in the delegation prompt; only subagents sharing this execution environment can read it.]`;
|
|
546
559
|
}
|
|
547
|
-
/** Replace every file occurrence
|
|
560
|
+
/** Replace every file occurrence with handle text. */
|
|
548
561
|
function replaceFilesWithHandles(blocks, resolvePath) {
|
|
549
562
|
let next;
|
|
550
563
|
for (const [index, block] of blocks.entries()) {
|
|
@@ -556,29 +569,10 @@ function replaceFilesWithHandles(blocks, resolvePath) {
|
|
|
556
569
|
});
|
|
557
570
|
continue;
|
|
558
571
|
}
|
|
559
|
-
if (block.type === "tool-result") {
|
|
560
|
-
const content = replaceFilesWithHandles(block.content, resolvePath);
|
|
561
|
-
if (content !== block.content) {
|
|
562
|
-
next ??= blocks.slice(0, index);
|
|
563
|
-
next.push({
|
|
564
|
-
...block,
|
|
565
|
-
content
|
|
566
|
-
});
|
|
567
|
-
continue;
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
572
|
next?.push(block);
|
|
571
573
|
}
|
|
572
574
|
return next ?? blocks;
|
|
573
575
|
}
|
|
574
|
-
/**
|
|
575
|
-
* Project durable file history into deterministic handle text for every model
|
|
576
|
-
* route. Unlike images, no provider receives file blocks natively, so this
|
|
577
|
-
* projection is unconditional in request assembly.
|
|
578
|
-
* @param messages - complete request history.
|
|
579
|
-
* @param resolvePath - resolve one reference's current execution-world read path.
|
|
580
|
-
* @returns the original list without files, otherwise shallow message copies with handle text.
|
|
581
|
-
*/
|
|
582
576
|
function projectFilesToText(messages, resolvePath) {
|
|
583
577
|
if (!messages.some((message) => contentHasFile(message.content))) return messages;
|
|
584
578
|
return messages.map((message) => {
|
|
@@ -589,7 +583,7 @@ function projectFilesToText(messages, resolvePath) {
|
|
|
589
583
|
};
|
|
590
584
|
});
|
|
591
585
|
}
|
|
592
|
-
/** Replace every image occurrence
|
|
586
|
+
/** Replace every image occurrence for a text-only model. */
|
|
593
587
|
function replaceImagesForTextModel(blocks) {
|
|
594
588
|
let next;
|
|
595
589
|
for (const [index, block] of blocks.entries()) {
|
|
@@ -601,26 +595,10 @@ function replaceImagesForTextModel(blocks) {
|
|
|
601
595
|
});
|
|
602
596
|
continue;
|
|
603
597
|
}
|
|
604
|
-
if (block.type === "tool-result") {
|
|
605
|
-
const content = replaceImagesForTextModel(block.content);
|
|
606
|
-
if (content !== block.content) {
|
|
607
|
-
next ??= blocks.slice(0, index);
|
|
608
|
-
next.push({
|
|
609
|
-
...block,
|
|
610
|
-
content
|
|
611
|
-
});
|
|
612
|
-
continue;
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
598
|
next?.push(block);
|
|
616
599
|
}
|
|
617
600
|
return next ?? blocks;
|
|
618
601
|
}
|
|
619
|
-
/**
|
|
620
|
-
* Project durable image history into deterministic text for an exact text-only model.
|
|
621
|
-
* @param messages - complete request history.
|
|
622
|
-
* @returns the original list without images, otherwise shallow message copies with stable placeholders.
|
|
623
|
-
*/
|
|
624
602
|
function projectImagesForTextModel(messages) {
|
|
625
603
|
if (!messages.some((message) => contentHasImage(message.content))) return messages;
|
|
626
604
|
return messages.map((message) => {
|
|
@@ -1213,8 +1191,9 @@ var LlmError = class extends HarnessError {
|
|
|
1213
1191
|
/** Remove replay state whose historical route is owned by another adapter. */
|
|
1214
1192
|
forAdapter(options, adapter) {
|
|
1215
1193
|
const messages = options.messages.map((message) => {
|
|
1194
|
+
if (message.role !== "assistant") return message;
|
|
1216
1195
|
const source = message.source;
|
|
1217
|
-
if (
|
|
1196
|
+
if (source.replayState === void 0) return message;
|
|
1218
1197
|
if (this.adapters.get(source.provider)?.adapter === adapter) return message;
|
|
1219
1198
|
return freezeMessage({
|
|
1220
1199
|
...message,
|
|
@@ -1374,14 +1353,11 @@ const Config = z.object({
|
|
|
1374
1353
|
argumentsPreviewChars: z.number().default(500)
|
|
1375
1354
|
});
|
|
1376
1355
|
/**
|
|
1377
|
-
* The `{kind:'
|
|
1378
|
-
* the label is load-bearing (an unlabeled context would
|
|
1379
|
-
* prompt in derived history).
|
|
1356
|
+
* The `{kind:'repeat-tool-reminder'}` producer source stamped on every reminder
|
|
1357
|
+
* this guard injects — the label is load-bearing (an unlabeled context would
|
|
1358
|
+
* render as a user prompt in derived history).
|
|
1380
1359
|
*/
|
|
1381
|
-
const
|
|
1382
|
-
kind: "plugin",
|
|
1383
|
-
plugin: "repeat-tool-reminder"
|
|
1384
|
-
};
|
|
1360
|
+
const REMINDER_SOURCE = { kind: "repeat-tool-reminder" };
|
|
1385
1361
|
/**
|
|
1386
1362
|
* The gentle first-threshold reminder. Keyed to `thresholds[0]`, not a literal
|
|
1387
1363
|
* count, so a custom first threshold keeps the gentle-then-detailed escalation.
|
|
@@ -1490,7 +1466,7 @@ function apply(ctx, config) {
|
|
|
1490
1466
|
text: count === thresholds[0] ? GENTLE_REMINDER : detailedReminder(exec.name, count, previewArguments(canonical, argumentsPreviewChars))
|
|
1491
1467
|
}],
|
|
1492
1468
|
source: {
|
|
1493
|
-
...
|
|
1469
|
+
...REMINDER_SOURCE,
|
|
1494
1470
|
form: "notice",
|
|
1495
1471
|
summary: `${exec.name} × ${count}`
|
|
1496
1472
|
}
|
package/lib/types/index.d.ts
CHANGED
|
@@ -7,6 +7,14 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import type { Context } from '@deepseek-ai/cordis';
|
|
9
9
|
import z from '@deepseek-ai/schemastery';
|
|
10
|
+
import type { ContextFormed } from '@deepseek-ai/dsh-llm';
|
|
11
|
+
declare module '@deepseek-ai/dsh-llm' {
|
|
12
|
+
interface MessageSourceMap {
|
|
13
|
+
'repeat-tool-reminder': {
|
|
14
|
+
kind: 'repeat-tool-reminder';
|
|
15
|
+
} & ContextFormed;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
10
18
|
export declare const name = "repeat-tool-reminder";
|
|
11
19
|
/**
|
|
12
20
|
* Plugin config, validated by the same-named schemastery schema plus the
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepseek-ai/dsh-repeat-tool-reminder",
|
|
3
3
|
"description": "Repeat-tool-call guard plugin: advisory reminders when an agent loops on identical tool calls",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.7-alpha.1",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -27,21 +27,21 @@
|
|
|
27
27
|
],
|
|
28
28
|
"license": "MIT",
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@deepseek-ai/schemastery": "^3.18.
|
|
30
|
+
"@deepseek-ai/schemastery": "^3.18.3"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
|
-
"@deepseek-ai/
|
|
34
|
-
"@deepseek-ai/dsh-
|
|
35
|
-
"@deepseek-ai/
|
|
33
|
+
"@deepseek-ai/cordis": "^4.0.3",
|
|
34
|
+
"@deepseek-ai/dsh-agent": "^0.1.7-alpha.1",
|
|
35
|
+
"@deepseek-ai/dsh-tools": "^0.1.7-alpha.1"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"@deepseek-ai/dsh-agent": "^0.1.
|
|
39
|
-
"@deepseek-ai/dsh-agent-loop": "^0.1.
|
|
40
|
-
"@deepseek-ai/dsh-
|
|
41
|
-
"@deepseek-ai/dsh-
|
|
42
|
-
"@deepseek-ai/dsh-
|
|
43
|
-
"@deepseek-ai/dsh-tools": "^0.1.
|
|
44
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
45
|
-
"@deepseek-ai/dsh-session-projection": "^0.1.
|
|
38
|
+
"@deepseek-ai/dsh-agent": "^0.1.7-alpha.1",
|
|
39
|
+
"@deepseek-ai/dsh-agent-loop": "^0.1.7-alpha.1",
|
|
40
|
+
"@deepseek-ai/dsh-llm": "^0.1.7-alpha.1",
|
|
41
|
+
"@deepseek-ai/dsh-session": "^0.1.7-alpha.1",
|
|
42
|
+
"@deepseek-ai/dsh-agent-loop-testkit": "^0.1.7-alpha.1",
|
|
43
|
+
"@deepseek-ai/dsh-tools": "^0.1.7-alpha.1",
|
|
44
|
+
"@deepseek-ai/cordis": "^4.0.3",
|
|
45
|
+
"@deepseek-ai/dsh-session-projection": "^0.1.7-alpha.1"
|
|
46
46
|
}
|
|
47
47
|
}
|