@deepseek-ai/dsh-repeat-tool-reminder 0.1.6-alpha.2 → 0.1.7-alpha.2

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 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 freezeMessage({
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, walking nested
513
- * tool-result content. This is the one recursive image walk shared by every
514
- * image policy (capability gating, text-only serialization, compaction
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 nested block is an image.
531
+ * @returns whether any block is an image.
518
532
  */
519
533
  function contentHasImage(content) {
520
- return content.some((block) => block.type === "image" || block.type === "tool-result" && contentHasImage(block.content));
534
+ return content.some((block) => block.type === "image");
521
535
  }
522
536
  /**
523
- * True when typed model content contains a file block, walking nested
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 nested block is a file.
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" || block.type === "tool-result" && contentHasFile(block.content)) return true;
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, including nested tool results, with handle text. */
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, including nested tool results, for a text-only model. */
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 (message.role !== "assistant" || source.kind !== "model" || source.replayState === void 0) return message;
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:'plugin'}` source stamped on every reminder this guard injects —
1378
- * the label is load-bearing (an unlabeled context would render as a user
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 PLUGIN_SOURCE = {
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
- ...PLUGIN_SOURCE,
1469
+ ...REMINDER_SOURCE,
1494
1470
  form: "notice",
1495
1471
  summary: `${exec.name} × ${count}`
1496
1472
  }
@@ -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.6-alpha.2",
4
+ "version": "0.1.7-alpha.2",
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.2"
30
+ "@deepseek-ai/schemastery": "~3.18.4"
31
31
  },
32
32
  "peerDependencies": {
33
- "@deepseek-ai/dsh-agent": "^0.1.6-alpha.2",
34
- "@deepseek-ai/dsh-tools": "^0.1.6-alpha.2",
35
- "@deepseek-ai/cordis": "^4.0.2"
33
+ "@deepseek-ai/cordis": "~4.0.4",
34
+ "@deepseek-ai/dsh-tools": "0.1.7-alpha.2",
35
+ "@deepseek-ai/dsh-agent": "0.1.7-alpha.2"
36
36
  },
37
37
  "devDependencies": {
38
- "@deepseek-ai/dsh-agent": "^0.1.6-alpha.2",
39
- "@deepseek-ai/dsh-agent-loop": "^0.1.6-alpha.2",
40
- "@deepseek-ai/dsh-agent-loop-testkit": "^0.1.6-alpha.2",
41
- "@deepseek-ai/dsh-llm": "^0.1.6-alpha.2",
42
- "@deepseek-ai/dsh-session": "^0.1.6-alpha.2",
43
- "@deepseek-ai/dsh-tools": "^0.1.6-alpha.2",
44
- "@deepseek-ai/cordis": "^4.0.2",
45
- "@deepseek-ai/dsh-session-projection": "^0.1.6-alpha.2"
38
+ "@deepseek-ai/dsh-agent": "0.1.7-alpha.2",
39
+ "@deepseek-ai/dsh-agent-loop": "0.1.7-alpha.2",
40
+ "@deepseek-ai/dsh-agent-loop-testkit": "0.1.7-alpha.2",
41
+ "@deepseek-ai/dsh-llm": "0.1.7-alpha.2",
42
+ "@deepseek-ai/dsh-session": "0.1.7-alpha.2",
43
+ "@deepseek-ai/dsh-tools": "0.1.7-alpha.2",
44
+ "@deepseek-ai/dsh-session-projection": "0.1.7-alpha.2",
45
+ "@deepseek-ai/cordis": "~4.0.4"
46
46
  }
47
47
  }