@animalabs/agent-framework 0.28.0 → 0.29.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 +88 -25
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +4 -1
- package/dist/src/agent.js.map +1 -1
- package/dist/src/api/mcp-server-core.d.ts.map +1 -1
- package/dist/src/api/mcp-server-core.js +3 -12
- package/dist/src/api/mcp-server-core.js.map +1 -1
- package/dist/src/api/server.d.ts.map +1 -1
- package/dist/src/api/server.js +7 -3
- package/dist/src/api/server.js.map +1 -1
- package/dist/src/auto-continue.d.ts +2 -0
- package/dist/src/auto-continue.d.ts.map +1 -1
- package/dist/src/auto-continue.js +5 -2
- package/dist/src/auto-continue.js.map +1 -1
- package/dist/src/code-execution/isolate/runner.d.ts +7 -5
- package/dist/src/code-execution/isolate/runner.d.ts.map +1 -1
- package/dist/src/code-execution/isolate/runner.js +7 -5
- package/dist/src/code-execution/isolate/runner.js.map +1 -1
- package/dist/src/code-execution/isolate/scripts.d.ts.map +1 -1
- package/dist/src/code-execution/isolate/scripts.js +3 -2
- package/dist/src/code-execution/isolate/scripts.js.map +1 -1
- package/dist/src/framework.d.ts +164 -36
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +1120 -479
- package/dist/src/framework.js.map +1 -1
- package/dist/src/gate/event-gate.d.ts +34 -10
- package/dist/src/gate/event-gate.d.ts.map +1 -1
- package/dist/src/gate/event-gate.js +118 -48
- package/dist/src/gate/event-gate.js.map +1 -1
- package/dist/src/gate/index.d.ts +1 -1
- package/dist/src/gate/index.d.ts.map +1 -1
- package/dist/src/gate/index.js +1 -1
- package/dist/src/gate/index.js.map +1 -1
- package/dist/src/gate/types.d.ts +12 -2
- package/dist/src/gate/types.d.ts.map +1 -1
- package/dist/src/heartbeat/heartbeat.d.ts +18 -0
- package/dist/src/heartbeat/heartbeat.d.ts.map +1 -1
- package/dist/src/heartbeat/heartbeat.js +34 -8
- package/dist/src/heartbeat/heartbeat.js.map +1 -1
- package/dist/src/held-writes.d.ts +3 -3
- package/dist/src/held-writes.js +3 -3
- package/dist/src/host-presentation.d.ts +170 -0
- package/dist/src/host-presentation.d.ts.map +1 -0
- package/dist/src/host-presentation.js +561 -0
- package/dist/src/host-presentation.js.map +1 -0
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/line-reader.d.ts +4 -0
- package/dist/src/line-reader.d.ts.map +1 -0
- package/dist/src/line-reader.js +59 -0
- package/dist/src/line-reader.js.map +1 -0
- package/dist/src/mcpl/channel-registry.d.ts +34 -6
- package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
- package/dist/src/mcpl/channel-registry.js +131 -53
- package/dist/src/mcpl/channel-registry.js.map +1 -1
- package/dist/src/mcpl/conversation-router.d.ts.map +1 -1
- package/dist/src/mcpl/conversation-router.js +2 -1
- package/dist/src/mcpl/conversation-router.js.map +1 -1
- package/dist/src/mcpl/convert-block.d.ts +1 -0
- package/dist/src/mcpl/convert-block.d.ts.map +1 -1
- package/dist/src/mcpl/convert-block.js +16 -4
- package/dist/src/mcpl/convert-block.js.map +1 -1
- package/dist/src/mcpl/hook-orchestrator.d.ts.map +1 -1
- package/dist/src/mcpl/hook-orchestrator.js +4 -3
- package/dist/src/mcpl/hook-orchestrator.js.map +1 -1
- package/dist/src/mcpl/inbound-source.d.ts +3 -1
- package/dist/src/mcpl/inbound-source.d.ts.map +1 -1
- package/dist/src/mcpl/inbound-source.js +3 -1
- package/dist/src/mcpl/inbound-source.js.map +1 -1
- package/dist/src/mcpl/modern-connection.d.ts.map +1 -1
- package/dist/src/mcpl/modern-connection.js +11 -7
- package/dist/src/mcpl/modern-connection.js.map +1 -1
- package/dist/src/mcpl/protocol-family.d.ts +3 -2
- package/dist/src/mcpl/protocol-family.d.ts.map +1 -1
- package/dist/src/mcpl/protocol-family.js +25 -3
- package/dist/src/mcpl/protocol-family.js.map +1 -1
- package/dist/src/mcpl/push-handler.js +2 -2
- package/dist/src/mcpl/push-handler.js.map +1 -1
- package/dist/src/mcpl/references.d.ts +12 -40
- package/dist/src/mcpl/references.d.ts.map +1 -1
- package/dist/src/mcpl/references.js +29 -23
- package/dist/src/mcpl/references.js.map +1 -1
- package/dist/src/mcpl/server-connection.d.ts.map +1 -1
- package/dist/src/mcpl/server-connection.js +20 -20
- package/dist/src/mcpl/server-connection.js.map +1 -1
- package/dist/src/mcpl/skip-reply-wake.d.ts +25 -0
- package/dist/src/mcpl/skip-reply-wake.d.ts.map +1 -1
- package/dist/src/mcpl/skip-reply-wake.js +25 -0
- package/dist/src/mcpl/skip-reply-wake.js.map +1 -1
- package/dist/src/mcpl/tool-presence.d.ts.map +1 -1
- package/dist/src/mcpl/tool-presence.js +18 -17
- package/dist/src/mcpl/tool-presence.js.map +1 -1
- package/dist/src/mcpl/tool-signature.d.ts.map +1 -1
- package/dist/src/mcpl/tool-signature.js +17 -16
- package/dist/src/mcpl/tool-signature.js.map +1 -1
- package/dist/src/mcpl/toolset-host.d.ts +8 -26
- package/dist/src/mcpl/toolset-host.d.ts.map +1 -1
- package/dist/src/mcpl/toolset-host.js +16 -6
- package/dist/src/mcpl/toolset-host.js.map +1 -1
- package/dist/src/mcpl/toolsets-module.d.ts +14 -25
- package/dist/src/mcpl/toolsets-module.d.ts.map +1 -1
- package/dist/src/mcpl/toolsets-module.js +180 -50
- package/dist/src/mcpl/toolsets-module.js.map +1 -1
- package/dist/src/mcpl/transport.d.ts +1 -1
- package/dist/src/mcpl/transport.d.ts.map +1 -1
- package/dist/src/mcpl/transport.js +16 -16
- package/dist/src/mcpl/transport.js.map +1 -1
- package/dist/src/mcpl/types.d.ts +28 -12
- package/dist/src/mcpl/types.d.ts.map +1 -1
- package/dist/src/mcpl/types.js.map +1 -1
- package/dist/src/module-registry.d.ts +6 -7
- package/dist/src/module-registry.d.ts.map +1 -1
- package/dist/src/module-registry.js +6 -7
- package/dist/src/module-registry.js.map +1 -1
- package/dist/src/modules/api/index.d.ts.map +1 -1
- package/dist/src/modules/api/index.js +4 -2
- package/dist/src/modules/api/index.js.map +1 -1
- package/dist/src/modules/discord/index.d.ts.map +1 -1
- package/dist/src/modules/discord/index.js +33 -25
- package/dist/src/modules/discord/index.js.map +1 -1
- package/dist/src/modules/history/index.js +8 -6
- package/dist/src/modules/history/index.js.map +1 -1
- package/dist/src/modules/sender-name.d.ts +7 -0
- package/dist/src/modules/sender-name.d.ts.map +1 -0
- package/dist/src/modules/sender-name.js +10 -0
- package/dist/src/modules/sender-name.js.map +1 -0
- package/dist/src/modules/workspace/answers.d.ts +5 -1
- package/dist/src/modules/workspace/answers.d.ts.map +1 -1
- package/dist/src/modules/workspace/answers.js +16 -1
- package/dist/src/modules/workspace/answers.js.map +1 -1
- package/dist/src/modules/workspace/direct-write.d.ts.map +1 -1
- package/dist/src/modules/workspace/direct-write.js +6 -4
- package/dist/src/modules/workspace/direct-write.js.map +1 -1
- package/dist/src/modules/workspace/effects.d.ts +4 -2
- package/dist/src/modules/workspace/effects.d.ts.map +1 -1
- package/dist/src/modules/workspace/effects.js +50 -40
- package/dist/src/modules/workspace/effects.js.map +1 -1
- package/dist/src/modules/workspace/index.d.ts +1 -0
- package/dist/src/modules/workspace/index.d.ts.map +1 -1
- package/dist/src/modules/workspace/index.js +66 -36
- package/dist/src/modules/workspace/index.js.map +1 -1
- package/dist/src/modules/workspace/observe.d.ts +51 -20
- package/dist/src/modules/workspace/observe.d.ts.map +1 -1
- package/dist/src/modules/workspace/observe.js +120 -82
- package/dist/src/modules/workspace/observe.js.map +1 -1
- package/dist/src/modules/workspace/reconcile.d.ts +7 -1
- package/dist/src/modules/workspace/reconcile.d.ts.map +1 -1
- package/dist/src/modules/workspace/reconcile.js +133 -37
- package/dist/src/modules/workspace/reconcile.js.map +1 -1
- package/dist/src/notice-tags.d.ts +79 -0
- package/dist/src/notice-tags.d.ts.map +1 -0
- package/dist/src/notice-tags.js +83 -0
- package/dist/src/notice-tags.js.map +1 -0
- package/dist/src/reasoning-loss-notice.d.ts.map +1 -1
- package/dist/src/reasoning-loss-notice.js +5 -3
- package/dist/src/reasoning-loss-notice.js.map +1 -1
- package/dist/src/silent-heartbeat.d.ts +1 -53
- package/dist/src/silent-heartbeat.d.ts.map +1 -1
- package/dist/src/silent-heartbeat.js +56 -3
- package/dist/src/silent-heartbeat.js.map +1 -1
- package/dist/src/supplied-text.d.ts +35 -0
- package/dist/src/supplied-text.d.ts.map +1 -0
- package/dist/src/supplied-text.js +141 -0
- package/dist/src/supplied-text.js.map +1 -0
- package/dist/src/tool-args.d.ts.map +1 -1
- package/dist/src/tool-args.js +9 -8
- package/dist/src/tool-args.js.map +1 -1
- package/dist/src/tool-image-ledger.d.ts.map +1 -1
- package/dist/src/tool-image-ledger.js +2 -1
- package/dist/src/tool-image-ledger.js.map +1 -1
- package/dist/src/tool-presentation.d.ts.map +1 -1
- package/dist/src/tool-presentation.js +5 -4
- package/dist/src/tool-presentation.js.map +1 -1
- package/dist/src/tune-out/coordinator.d.ts +3 -2
- package/dist/src/tune-out/coordinator.d.ts.map +1 -1
- package/dist/src/tune-out/coordinator.js +24 -26
- package/dist/src/tune-out/coordinator.js.map +1 -1
- package/dist/src/types/agent.d.ts +23 -9
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/framework.d.ts +43 -0
- package/dist/src/types/framework.d.ts.map +1 -1
- package/dist/src/types/module.d.ts +22 -11
- package/dist/src/types/module.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/agent.ts +4 -1
- package/src/api/mcp-server-core.ts +3 -14
- package/src/api/server.ts +7 -3
- package/src/auto-continue.ts +8 -2
- package/src/code-execution/isolate/runner.ts +7 -5
- package/src/code-execution/isolate/scripts.ts +3 -2
- package/src/framework.ts +1132 -492
- package/src/gate/event-gate.ts +128 -47
- package/src/gate/index.ts +1 -1
- package/src/gate/types.ts +12 -2
- package/src/heartbeat/heartbeat.ts +48 -9
- package/src/held-writes.ts +3 -3
- package/src/host-presentation.ts +605 -0
- package/src/index.ts +2 -2
- package/src/line-reader.ts +58 -0
- package/src/mcpl/channel-registry.ts +137 -52
- package/src/mcpl/conversation-router.ts +2 -1
- package/src/mcpl/convert-block.ts +17 -4
- package/src/mcpl/hook-orchestrator.ts +4 -3
- package/src/mcpl/inbound-source.ts +3 -1
- package/src/mcpl/modern-connection.ts +11 -7
- package/src/mcpl/protocol-family.ts +24 -3
- package/src/mcpl/push-handler.ts +2 -2
- package/src/mcpl/references.ts +31 -23
- package/src/mcpl/server-connection.ts +20 -18
- package/src/mcpl/skip-reply-wake.ts +35 -0
- package/src/mcpl/tool-presence.ts +18 -17
- package/src/mcpl/tool-signature.ts +17 -16
- package/src/mcpl/toolset-host.ts +18 -6
- package/src/mcpl/toolsets-module.ts +142 -50
- package/src/mcpl/transport.ts +18 -14
- package/src/mcpl/types.ts +29 -12
- package/src/module-registry.ts +6 -7
- package/src/modules/api/index.ts +4 -2
- package/src/modules/discord/index.ts +37 -25
- package/src/modules/history/index.ts +8 -6
- package/src/modules/sender-name.ts +9 -0
- package/src/modules/workspace/answers.ts +20 -2
- package/src/modules/workspace/direct-write.ts +4 -3
- package/src/modules/workspace/effects.ts +50 -40
- package/src/modules/workspace/index.ts +58 -34
- package/src/modules/workspace/observe.ts +139 -88
- package/src/modules/workspace/reconcile.ts +159 -36
- package/src/notice-tags.ts +84 -0
- package/src/reasoning-loss-notice.ts +5 -3
- package/src/silent-heartbeat.ts +4 -3
- package/src/supplied-text.ts +154 -0
- package/src/tool-args.ts +9 -8
- package/src/tool-image-ledger.ts +2 -1
- package/src/tool-presentation.ts +5 -4
- package/src/tune-out/coordinator.ts +25 -27
- package/src/types/agent.ts +23 -9
- package/src/types/framework.ts +44 -0
- package/src/types/module.ts +23 -11
|
@@ -68,7 +68,7 @@ import {
|
|
|
68
68
|
|
|
69
69
|
import { StdioTransport, type McplTransport } from './transport.js';
|
|
70
70
|
import { McplProtocolVersionError, McplRequestError, McplUnreapedLaunchError } from './server-connection.js';
|
|
71
|
-
import { LEGACY_MCP_PROTOCOL_VERSION, MAX_TIMER_MS, MODERN_MCP_PROTOCOL_VERSION, checkServerConfig, describeCredentialError, maskedError, noteDialSent, redactServerText, redactedData, serverTextRedactor, type ServerTextRedactor } from './protocol-family.js';
|
|
71
|
+
import { LEGACY_MCP_PROTOCOL_VERSION, MAX_TIMER_MS, MODERN_MCP_PROTOCOL_VERSION, checkServerConfig, describeCredentialError, logDroppedLine, maskedError, noteDialSent, redactServerText, redactedData, serverTextRedactor, type ServerTextRedactor } from './protocol-family.js';
|
|
72
72
|
import type { McplServerConfig, McpToolDefinition } from './types.js';
|
|
73
73
|
import { boundedForLog } from './errors.js';
|
|
74
74
|
import { logDetail, logValue, logValueRedacted, loggedCode } from './log-value.js';
|
|
@@ -297,8 +297,9 @@ export function malformedLineDiagnostic(
|
|
|
297
297
|
|
|
298
298
|
/**
|
|
299
299
|
* SDK `Transport` over Connectome's stdio spawner. Framing is the same
|
|
300
|
-
* newline-delimited JSON-RPC as legacy. Lines that are not JSON are
|
|
301
|
-
*
|
|
300
|
+
* newline-delimited JSON-RPC as legacy. Lines that are not JSON are dropped,
|
|
301
|
+
* and the first three on a connection that aren't blank are logged by their
|
|
302
|
+
* length, as the legacy engine does. Because this is a custom transport, the
|
|
302
303
|
* SDK runs `server/discover` on this very process instead of starting a
|
|
303
304
|
* throwaway copy, and cancels with `notifications/cancelled`, which is the
|
|
304
305
|
* stdio binding's mechanism.
|
|
@@ -332,14 +333,17 @@ class SpawnerStdioTransport implements Transport {
|
|
|
332
333
|
const line = await this.spawning;
|
|
333
334
|
this.line = line;
|
|
334
335
|
line.on('line', (text: string) => {
|
|
335
|
-
// A line that isn't JSON is
|
|
336
|
-
//
|
|
337
|
-
//
|
|
338
|
-
//
|
|
336
|
+
// A line that isn't JSON is dropped, and the first three on a
|
|
337
|
+
// connection that aren't blank are logged by their length, as the
|
|
338
|
+
// legacy engine does. A line that is JSON but not a JSON-RPC message
|
|
339
|
+
// (`null`, an array, a bare value) is a diagnostic. It must never reach
|
|
340
|
+
// a handler that assumes the shape: thrown from this callback, it would
|
|
341
|
+
// end the host.
|
|
339
342
|
let parsed: unknown;
|
|
340
343
|
try {
|
|
341
344
|
parsed = JSON.parse(text);
|
|
342
345
|
} catch {
|
|
346
|
+
logDroppedLine(this.config.id, text, 'unparsed', line);
|
|
343
347
|
return;
|
|
344
348
|
}
|
|
345
349
|
let message: JSONRPCMessage;
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
* isn't `initialize`), the SDK's stdio probe launches the server twice, and a
|
|
23
23
|
* server's era is a stable fact its configuration can state.
|
|
24
24
|
*/
|
|
25
|
+
import { NOTICE_TAG } from '../notice-tags.js';
|
|
25
26
|
import { DENY_BY_DEFAULT, capabilityPatternMatches } from './capability-grant.js';
|
|
26
27
|
import { DEFAULT_EAGER_FETCH_MAX_BYTES, DEFAULT_MAX_TOTAL_BYTES } from './reference-fetcher.js';
|
|
27
28
|
import { DEFAULT_MAX_INPUT_BYTES } from './tool-lifecycle.js';
|
|
@@ -198,11 +199,11 @@ export function serverTextRedactor(config: Pick<McplServerConfig, 'url' | 'token
|
|
|
198
199
|
|
|
199
200
|
/** What a redaction gives in place of text when the configuration it
|
|
200
201
|
* redacts with can't be read ({@link redactServerText}). */
|
|
201
|
-
export const WITHHELD_TEXT =
|
|
202
|
+
export const WITHHELD_TEXT = `[${NOTICE_TAG.withheld}: the server's configuration couldn't be read to redact this]`;
|
|
202
203
|
|
|
203
204
|
/** What {@link redactedData} gives in place of data nested deeper than it
|
|
204
205
|
* walks. */
|
|
205
|
-
export const TOO_DEEP_TEXT =
|
|
206
|
+
export const TOO_DEEP_TEXT = `[${NOTICE_TAG.withheld}: nested too deep to redact]`;
|
|
206
207
|
|
|
207
208
|
/** How deep {@link redactedData} walks. A server's JSON can nest deeper than
|
|
208
209
|
* a recursive walk's stack holds (10,000 levels overflow it), and the walk
|
|
@@ -528,7 +529,7 @@ export function credentialRedactor(patterns: readonly CredentialPattern[]): Reda
|
|
|
528
529
|
/** Any value as text: a value that can't be printed reads as a placeholder. */
|
|
529
530
|
export function asText(text: unknown): string {
|
|
530
531
|
if (typeof text === 'string') return text;
|
|
531
|
-
try { return String(text); } catch { return
|
|
532
|
+
try { return String(text); } catch { return `[${NOTICE_TAG.unprintableValue}]`; }
|
|
532
533
|
}
|
|
533
534
|
|
|
534
535
|
/** A part of an environment variable's name that ends with a credential word, in the singular or plural, with any digits after it. */
|
|
@@ -869,3 +870,23 @@ export function checkServerConfig(config: McplServerConfig): ServerBinding {
|
|
|
869
870
|
for (const warning of serverConfigWarnings(config)) console.warn(`[mcp] ${warning}`);
|
|
870
871
|
return resolveServerBinding(config);
|
|
871
872
|
}
|
|
873
|
+
|
|
874
|
+
const DROPPED_LINES_LOGGED = 3;
|
|
875
|
+
const droppedLinesLogged = new WeakMap<object, number>();
|
|
876
|
+
|
|
877
|
+
export function logDroppedLine(serverId: string, line: string, reason: 'unparsed' | 'not-object', connection: object): void {
|
|
878
|
+
if (/^[ \t]*$/.test(line)) return;
|
|
879
|
+
const logged = droppedLinesLogged.get(connection) ?? 0;
|
|
880
|
+
if (logged > DROPPED_LINES_LOGGED) return;
|
|
881
|
+
droppedLinesLogged.set(connection, logged + 1);
|
|
882
|
+
if (logged === DROPPED_LINES_LOGGED) {
|
|
883
|
+
console.error(`MCP server "${serverId}" sent another line that isn't a JSON object, so it was dropped; more such lines on this connection are dropped without a log line`);
|
|
884
|
+
return;
|
|
885
|
+
}
|
|
886
|
+
let length = 0;
|
|
887
|
+
for (const _ of line) length++;
|
|
888
|
+
const why = reason === 'not-object' ? 'is JSON but not an object'
|
|
889
|
+
: /^[ \t]*\{/.test(line) ? "starts like a JSON object but doesn't parse"
|
|
890
|
+
: "isn't JSON";
|
|
891
|
+
console.error(`MCP server "${serverId}" sent a line of length ${length} that ${why}, so it was dropped`);
|
|
892
|
+
}
|
package/src/mcpl/push-handler.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import type { ContentBlock } from '@animalabs/membrane';
|
|
11
|
-
import {
|
|
11
|
+
import { convertBlocks } from './convert-block.js';
|
|
12
12
|
|
|
13
13
|
import type {
|
|
14
14
|
PushEventParams,
|
|
@@ -204,7 +204,7 @@ export class PushHandler {
|
|
|
204
204
|
|
|
205
205
|
// 2. Convert content blocks, and refuse content with nothing visible in
|
|
206
206
|
// it — before dedup, so a refused eventId is not burned for a retry.
|
|
207
|
-
const content: ContentBlock[] = params.payload.content
|
|
207
|
+
const content: ContentBlock[] = convertBlocks(params.payload.content, 'attachment on push event', serverId);
|
|
208
208
|
if (!emptyAllowed && isVisiblyEmptyConversion(params.payload.content, content)) {
|
|
209
209
|
const err = new EmptyContentError();
|
|
210
210
|
this.traceEmptyRejection(serverId, params);
|
package/src/mcpl/references.ts
CHANGED
|
@@ -26,6 +26,9 @@
|
|
|
26
26
|
* the registry is what makes a future host-mediated fetcher and the
|
|
27
27
|
* code-execution materialization hook possible.
|
|
28
28
|
*/
|
|
29
|
+
import { cutToFit, suppliedValue } from '../supplied-text.js';
|
|
30
|
+
import { NOTICE_TAG } from '../notice-tags.js';
|
|
31
|
+
|
|
29
32
|
|
|
30
33
|
export const REFERENCE_LIMITS = { uri: 4096, mimeType: 255, name: 255, expiresAt: 64 } as const;
|
|
31
34
|
export const DIGEST_PATTERN = /^sha256:[A-Za-z0-9_-]{43}$/;
|
|
@@ -210,7 +213,7 @@ export function isInlineContradiction(block: unknown): boolean {
|
|
|
210
213
|
}
|
|
211
214
|
|
|
212
215
|
export const INLINE_WITHHELD_TEXT =
|
|
213
|
-
|
|
216
|
+
`[${NOTICE_TAG.contentNotShown}: its inline data arrived marked as a reference too, so this host withheld it.]`;
|
|
214
217
|
|
|
215
218
|
/*
|
|
216
219
|
* What the model-visible lanes (push, channel, context injection and tool
|
|
@@ -220,28 +223,28 @@ export const INLINE_WITHHELD_TEXT =
|
|
|
220
223
|
* agent to do about any of them. The text marker is NON_STRING_TEXT_PLACEHOLDER
|
|
221
224
|
* (visible-content.ts).
|
|
222
225
|
*/
|
|
223
|
-
export const IMAGE_NO_MEDIA_TYPE_TEXT =
|
|
224
|
-
export const IMAGE_NO_DATA_TEXT =
|
|
225
|
-
export const AUDIO_NO_MEDIA_TYPE_TEXT =
|
|
226
|
+
export const IMAGE_NO_MEDIA_TYPE_TEXT = `[${NOTICE_TAG.imageNotShown}: its data arrived without a media type.]`;
|
|
227
|
+
export const IMAGE_NO_DATA_TEXT = `[${NOTICE_TAG.imageNotShown}: it arrived with no usable data or link.]`;
|
|
228
|
+
export const AUDIO_NO_MEDIA_TYPE_TEXT = `[${NOTICE_TAG.audioNotShown}: its data arrived without a media type.]`;
|
|
226
229
|
/** On the push, channel and context-injection lanes, audio shows only as
|
|
227
230
|
* inline data, so a plain link isn't enough either. (A tool result's audio
|
|
228
231
|
* with a link is a reference, as any block with one is there.) */
|
|
229
|
-
export const AUDIO_NO_DATA_TEXT =
|
|
232
|
+
export const AUDIO_NO_DATA_TEXT = `[${NOTICE_TAG.audioNotShown}: it arrived with no usable inline data.]`;
|
|
230
233
|
/** MCP's embedded resource whose contents are neither text nor a blob, or
|
|
231
234
|
* both. */
|
|
232
|
-
export const EMBEDDED_RESOURCE_UNREADABLE_TEXT =
|
|
235
|
+
export const EMBEDDED_RESOURCE_UNREADABLE_TEXT = `[${NOTICE_TAG.resourceNotShown}: its contents arrived as neither text nor data, or as both.]`;
|
|
233
236
|
/** MCP's embedded resource whose blob has no media type, so it can't be
|
|
234
237
|
* shown as an image or audio. */
|
|
235
|
-
export const EMBEDDED_RESOURCE_NO_MEDIA_TYPE_TEXT =
|
|
238
|
+
export const EMBEDDED_RESOURCE_NO_MEDIA_TYPE_TEXT = `[${NOTICE_TAG.resourceNotShown}: its data arrived without a media type.]`;
|
|
236
239
|
|
|
237
240
|
/** MCP's embedded resource whose blob is of a type no lane can show: what
|
|
238
241
|
* it was, by its type and size. Made well formed (a lone UTF-16 surrogate
|
|
239
242
|
* becomes U+FFFD), as a stub is (#695), since it names the block's media
|
|
240
|
-
* type,
|
|
241
|
-
* text. */
|
|
243
|
+
* type, whose lone surrogates referenceTypeLabel leaves as they are: every
|
|
244
|
+
* lane then shows the same text. */
|
|
242
245
|
export function embeddedBinaryText(mimeType: string, blob: string): string {
|
|
243
246
|
const what = [referenceTypeLabel(mimeType), formatSizeBytes(Math.floor(blob.length * 3 / 4))].filter(Boolean).join(', ');
|
|
244
|
-
return `[
|
|
247
|
+
return `[${NOTICE_TAG.resourceNotShown}: this host can't display its data (${what}).]`.toWellFormed();
|
|
245
248
|
}
|
|
246
249
|
/**
|
|
247
250
|
* What the converter (convertBlock, on every lane) shows for a text
|
|
@@ -250,10 +253,10 @@ export function embeddedBinaryText(mimeType: string, blob: string): string {
|
|
|
250
253
|
* sender's, so it never makes content visible (isVisiblyEmptyConversion in
|
|
251
254
|
* visible-content.ts, which re-exports it).
|
|
252
255
|
*/
|
|
253
|
-
export const NON_STRING_TEXT_PLACEHOLDER =
|
|
254
|
-
export const REFERENCE_NO_LINK_TEXT =
|
|
255
|
-
export const REFERENCE_LINK_TOO_LONG_TEXT =
|
|
256
|
-
const UNREADABLE_BLOCK_TEXT =
|
|
256
|
+
export const NON_STRING_TEXT_PLACEHOLDER = `[${NOTICE_TAG.textNotShown}: a text part arrived without readable text.]`;
|
|
257
|
+
export const REFERENCE_NO_LINK_TEXT = `[${NOTICE_TAG.referenceNotShown}: it arrived without a link.]`;
|
|
258
|
+
export const REFERENCE_LINK_TOO_LONG_TEXT = `[${NOTICE_TAG.referenceNotShown}: its link is longer than this host accepts.]`;
|
|
259
|
+
const UNREADABLE_BLOCK_TEXT = `[${NOTICE_TAG.contentNotShown}: a part arrived in a form this host can't read.]`;
|
|
257
260
|
|
|
258
261
|
/** A block of a type the host doesn't show, of no type at all, or no block.
|
|
259
262
|
* The type is the sender's word, so it's shown as a label is: bounded, with
|
|
@@ -261,10 +264,10 @@ const UNREADABLE_BLOCK_TEXT = "[Content not shown: a part arrived in a form this
|
|
|
261
264
|
* converted to one. */
|
|
262
265
|
export function unknownBlockText(block: unknown): string {
|
|
263
266
|
if (!isRecord(block)) return UNREADABLE_BLOCK_TEXT;
|
|
264
|
-
const label = typeof block.type === 'string' ?
|
|
267
|
+
const label = typeof block.type === 'string' ? suppliedLabel(block.type, STUB_FIELD_CHARS) : '';
|
|
265
268
|
return label
|
|
266
|
-
? `[
|
|
267
|
-
:
|
|
269
|
+
? `[${NOTICE_TAG.contentNotShown}: this host can't display "${label}" content.]`
|
|
270
|
+
: `[${NOTICE_TAG.contentNotShown}: a part arrived with no type.]`;
|
|
268
271
|
}
|
|
269
272
|
|
|
270
273
|
/**
|
|
@@ -654,6 +657,11 @@ export function sanitizeLabel(s: string, maxChars: number): string {
|
|
|
654
657
|
return cut + '…';
|
|
655
658
|
}
|
|
656
659
|
|
|
660
|
+
function suppliedLabel(s: string, maxChars: number): string {
|
|
661
|
+
const shown = suppliedValue(s).replace(/[\p{Cc}\p{Bidi_Control}]/gu, '').trim();
|
|
662
|
+
return shown.length <= maxChars ? shown : `${cutToFit(shown, maxChars - 1)}…`;
|
|
663
|
+
}
|
|
664
|
+
|
|
657
665
|
export function formatSizeBytes(n: number): string {
|
|
658
666
|
if (n < 1024) return `${n}B`;
|
|
659
667
|
if (n < 1024 * 1024) return `~${Math.round(n / 1024)}KB`;
|
|
@@ -786,7 +794,7 @@ export class ReferenceRegistry {
|
|
|
786
794
|
return existing;
|
|
787
795
|
}
|
|
788
796
|
}
|
|
789
|
-
const refId =
|
|
797
|
+
const refId = `${NOTICE_TAG.referenceId}_${(++this.counter).toString(36)}_${Math.random().toString(36).slice(2, 2 + REF_ID_SUFFIX_CHARS)}`;
|
|
790
798
|
const record: ReferenceRecord = { refId, serverIds: serverId === undefined ? [] : [serverId], testimony, receivedAt: Date.now() };
|
|
791
799
|
this.records.set(refId, record);
|
|
792
800
|
this.byKey.set(testimony.uri, refId);
|
|
@@ -871,7 +879,7 @@ export interface ScriptLine {
|
|
|
871
879
|
/** A reference's type as its stub shows one, or '' when nothing is left of
|
|
872
880
|
* it once sanitized (the stub then leaves it out). */
|
|
873
881
|
export function referenceTypeLabel(mimeType: string | undefined): string {
|
|
874
|
-
return mimeType ?
|
|
882
|
+
return mimeType ? suppliedLabel(mimeType, STUB_FIELD_CHARS) : '';
|
|
875
883
|
}
|
|
876
884
|
|
|
877
885
|
/**
|
|
@@ -886,7 +894,7 @@ export function referenceTypeLabel(mimeType: string | undefined): string {
|
|
|
886
894
|
* stub, so it's no slot whatever its length.
|
|
887
895
|
*/
|
|
888
896
|
export const REFERENCE_STUB_MAX_CHARS =
|
|
889
|
-
`[
|
|
897
|
+
`[${NOTICE_TAG.referenceId}_${MAX_SAFE.toString(36)}_] `.length + REF_ID_SUFFIX_CHARS
|
|
890
898
|
+ STUB_FIELD_CHARS
|
|
891
899
|
+ ' — '.length + STUB_FIELD_CHARS + ', '.length + `${formatSizeBytes(MAX_SAFE)} claimed`.length
|
|
892
900
|
+ ' — '.length + STUB_FIELD_CHARS
|
|
@@ -1099,7 +1107,7 @@ export function buildReferenceStub(t: ReferenceTestimony, provenance?: string, s
|
|
|
1099
1107
|
const nowMs = Date.now();
|
|
1100
1108
|
const parts: string[] = [];
|
|
1101
1109
|
// A label is shown only when something is left of it once sanitized.
|
|
1102
|
-
const name = t.name ?
|
|
1110
|
+
const name = t.name ? suppliedLabel(t.name, STUB_FIELD_CHARS) : '';
|
|
1103
1111
|
if (name) parts.push(name);
|
|
1104
1112
|
const meta: string[] = [];
|
|
1105
1113
|
const mimeType = referenceTypeLabel(t.mimeType);
|
|
@@ -1130,9 +1138,9 @@ export function buildReferenceStub(t: ReferenceTestimony, provenance?: string, s
|
|
|
1130
1138
|
* server's link shows (StubOptions.unfetchedLinkUri).
|
|
1131
1139
|
*/
|
|
1132
1140
|
function unfetchedLinkText(t: ReferenceTestimony, uri: string): string {
|
|
1133
|
-
const name = t.name ?
|
|
1141
|
+
const name = t.name ? suppliedLabel(t.name, STUB_FIELD_CHARS) : '';
|
|
1134
1142
|
const mimeType = referenceTypeLabel(t.mimeType);
|
|
1135
|
-
return `[
|
|
1143
|
+
return `[${NOTICE_TAG.resourceLink}${name ? ` "${name}"` : ''}: ${uri}${mimeType ? `, ${mimeType}` : ''}; not fetched]`;
|
|
1136
1144
|
}
|
|
1137
1145
|
|
|
1138
1146
|
/**
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
LEGACY_MCP_PROTOCOL_VERSION,
|
|
21
21
|
MODERN_MCP_PROTOCOL_VERSION,
|
|
22
22
|
checkServerConfig,
|
|
23
|
+
logDroppedLine,
|
|
23
24
|
redactServerText,
|
|
24
25
|
redactedData,
|
|
25
26
|
serverTextRedactor,
|
|
@@ -1330,13 +1331,8 @@ export class McplServerConnection extends EventEmitter {
|
|
|
1330
1331
|
reject(closedWhileConnecting());
|
|
1331
1332
|
};
|
|
1332
1333
|
const onLine = (line: string) => {
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
msg = JSON.parse(line) as JsonRpcResponse;
|
|
1336
|
-
} catch {
|
|
1337
|
-
return; // Ignore non-JSON lines (e.g. logback output from Java servers)
|
|
1338
|
-
}
|
|
1339
|
-
if (msg === null || typeof msg !== 'object' || Array.isArray(msg)) return;
|
|
1334
|
+
const msg = messageFromLine(config.id, line, transport) as JsonRpcResponse | undefined;
|
|
1335
|
+
if (!msg) return;
|
|
1340
1336
|
// A request or a notification from the server is never the
|
|
1341
1337
|
// initialize response, whatever its id: a server's own ids can start
|
|
1342
1338
|
// at 0, this handshake's. MCP lets a server ping before
|
|
@@ -2376,17 +2372,8 @@ export class McplServerConnection extends EventEmitter {
|
|
|
2376
2372
|
// passed is the dead process's (afterBoundary).
|
|
2377
2373
|
const epoch = this.transportEpoch;
|
|
2378
2374
|
transport.on('line', (line: string) => {
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
msg = JSON.parse(line);
|
|
2382
|
-
} catch {
|
|
2383
|
-
// Ignore non-JSON lines
|
|
2384
|
-
return;
|
|
2385
|
-
}
|
|
2386
|
-
|
|
2387
|
-
// JSON-RPC messages must be objects. Ignore unrelated stdout/log values
|
|
2388
|
-
// before property access, just as malformed JSON lines are ignored.
|
|
2389
|
-
if (msg === null || typeof msg !== 'object' || Array.isArray(msg)) return;
|
|
2375
|
+
const msg = messageFromLine(this.id, line, transport) as JsonRpcRequest | JsonRpcResponse | undefined;
|
|
2376
|
+
if (!msg) return;
|
|
2390
2377
|
try {
|
|
2391
2378
|
this.dispatchMessage(msg, epoch);
|
|
2392
2379
|
} catch (error) {
|
|
@@ -2829,3 +2816,18 @@ export class McplServerConnection extends EventEmitter {
|
|
|
2829
2816
|
});
|
|
2830
2817
|
}
|
|
2831
2818
|
}
|
|
2819
|
+
|
|
2820
|
+
function messageFromLine(serverId: string, line: string, transport: McplTransport): object | undefined {
|
|
2821
|
+
let value: unknown;
|
|
2822
|
+
try {
|
|
2823
|
+
value = JSON.parse(line);
|
|
2824
|
+
} catch {
|
|
2825
|
+
logDroppedLine(serverId, line, 'unparsed', transport);
|
|
2826
|
+
return undefined;
|
|
2827
|
+
}
|
|
2828
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
2829
|
+
logDroppedLine(serverId, line, 'not-object', transport);
|
|
2830
|
+
return undefined;
|
|
2831
|
+
}
|
|
2832
|
+
return value;
|
|
2833
|
+
}
|
|
@@ -43,6 +43,41 @@ export function readSkipReplyWake(input: unknown): SkipReplyWake {
|
|
|
43
43
|
return { kind: 'wake', seconds: Math.floor(Math.min(SKIP_REPLY_WAKE_MAX_SECONDS, seconds)) };
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
/**
|
|
47
|
+
* A wake the heartbeat has due before a skip_reply's self-wake: its next tick
|
|
48
|
+
* or a reminder, one the agent's rules would let wake it as they stand when
|
|
49
|
+
* the self-wake is armed. `what` names it to the agent ("your heartbeat
|
|
50
|
+
* (…)"); `inSeconds` is how far off it is then.
|
|
51
|
+
*/
|
|
52
|
+
export interface SkipReplyEarlierWake {
|
|
53
|
+
inSeconds: number;
|
|
54
|
+
what: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The note a skip_reply's result carries: that the turn ended, and, when the
|
|
59
|
+
* agent set a wake, when it fires unless something wakes the agent first.
|
|
60
|
+
* When the heartbeat has a wake due sooner, the note adds it, and what it
|
|
61
|
+
* would mean: any turn start cancels a pending self-wake
|
|
62
|
+
* (EventGate.onInferenceStarted), so a timer the agent still wants after the
|
|
63
|
+
* heartbeat's turn is one it sets again then, for the time that was left.
|
|
64
|
+
*
|
|
65
|
+
* It says when the heartbeat is due, not that it will wake the agent: the
|
|
66
|
+
* rules can change before then, a rate limit can have no token left, a
|
|
67
|
+
* gate.js or a host's inference policy can hold the wake back, and a batch or
|
|
68
|
+
* a queued request can wake the agent sooner. Each of those leaves the note
|
|
69
|
+
* true.
|
|
70
|
+
*/
|
|
71
|
+
export function skipReplyNote(selfWakeSeconds: number | undefined, earlier?: SkipReplyEarlierWake): string {
|
|
72
|
+
if (selfWakeSeconds === undefined) return 'Turn ended; nothing sent.';
|
|
73
|
+
const note = `Turn ended; nothing sent. Self-wake in ~${selfWakeSeconds}s unless something wakes you first.`;
|
|
74
|
+
if (earlier === undefined) return note;
|
|
75
|
+
const what = earlier.what.charAt(0).toUpperCase() + earlier.what.slice(1);
|
|
76
|
+
const left = Math.max(1, selfWakeSeconds - earlier.inSeconds);
|
|
77
|
+
return `${note} ${what} is due sooner, in ~${earlier.inSeconds}s; if it wakes you, the self-wake is cancelled, `
|
|
78
|
+
+ `so set it again for the ~${left}s left if you still want it.`;
|
|
79
|
+
}
|
|
80
|
+
|
|
46
81
|
/** The refusal the model reads when its host has no EventGate to set a timer. */
|
|
47
82
|
export const SKIP_REPLY_WAKE_NO_GATE_REFUSAL =
|
|
48
83
|
"Your turn didn't end, and no wake was set: this host can't wake you on a timer, so skip_reply can't take wake_in_seconds here. To end the turn and wait for the next message, call skip_reply without it. Anything you meant to come back to, do now.";
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
* Ported from mcpl-cc-bridge's presence.ts, the reference host.
|
|
27
27
|
*/
|
|
28
28
|
import { objectSignature, renderToolLines, toolDigest, type HintedTool, type Shown } from './tool-signature.js';
|
|
29
|
+
import { nameToTypeBack, quotedValue, suppliedValue } from '../supplied-text.js';
|
|
29
30
|
|
|
30
31
|
export interface ToolsetDecl {
|
|
31
32
|
title?: string;
|
|
@@ -531,7 +532,7 @@ export class ToolPresence {
|
|
|
531
532
|
const endedKeys = new Set<string>();
|
|
532
533
|
for (const e of ended) {
|
|
533
534
|
if (!e.id || !this.toldEnded.has(e.id)) endedKeys.add(keyOf(e.toolset, e.instance));
|
|
534
|
-
const note = `${this.label(keyOf(e.toolset, e.instance))} ended${e.reason ? `: ${e.reason}` : ''}.`;
|
|
535
|
+
const note = `${this.label(keyOf(e.toolset, e.instance))} ended${e.reason ? `: ${suppliedValue(e.reason)}` : ''}.`;
|
|
535
536
|
if (e.id) {
|
|
536
537
|
if (!this.toldEnded.has(e.id)) told.push({ id: e.id, note });
|
|
537
538
|
} else notes.push(note);
|
|
@@ -588,27 +589,27 @@ export class ToolPresence {
|
|
|
588
589
|
/** The marker a key's announcements begin with, `[server/toolset:instance]`, which a reminder looks for. */
|
|
589
590
|
label(key: string): string {
|
|
590
591
|
const { toolset, instance } = splitKey(key);
|
|
591
|
-
return `[${this.server}${toolset ? `/${toolset}` : ''}${instance ? `:${instance}` : ''}]`;
|
|
592
|
+
return `[${this.server}${toolset ? `/${nameToTypeBack(toolset)}` : ''}${instance ? `:${nameToTypeBack(instance)}` : ''}]`;
|
|
592
593
|
}
|
|
593
594
|
|
|
594
595
|
callHint(key: string): string {
|
|
595
596
|
const { instance } = splitKey(key);
|
|
596
|
-
return `${this.names.act} {server:"${this.server}"${instance ? `, instance
|
|
597
|
+
return `${this.names.act} {server:"${this.server}"${instance ? `, instance:${quotedValue(instance)}` : ''}, tool, args}`;
|
|
597
598
|
}
|
|
598
599
|
|
|
599
600
|
closeHint(key: string): string | null {
|
|
600
601
|
const { toolset, instance } = splitKey(key);
|
|
601
602
|
if (!toolset) return null;
|
|
602
|
-
return `${this.names.close} {server:"${this.server}", toolset
|
|
603
|
+
return `${this.names.close} {server:"${this.server}", toolset:${quotedValue(toolset)}${instance ? `, instance:${quotedValue(instance)}` : ''}}`;
|
|
603
604
|
}
|
|
604
605
|
|
|
605
606
|
openHint(toolset: string, instance?: string): string {
|
|
606
|
-
return `${this.names.open} {server:"${this.server}", toolset
|
|
607
|
+
return `${this.names.open} {server:"${this.server}", toolset:${quotedValue(toolset)}${instance ? `, instance:${quotedValue(instance)}` : ''}}`;
|
|
607
608
|
}
|
|
608
609
|
|
|
609
610
|
/** The tool block for one key: its label, how to call, and the visible tools (new or changed ones described). */
|
|
610
611
|
toolBlock(key: string, tools: HintedTool[], opts: { describeAll?: boolean; headed?: boolean; title?: string; seen?: Shown } = {}): string[] {
|
|
611
|
-
const head = opts.headed === false ? '' : `${this.label(key)}${opts.title ? ` ${opts.title}` : ''} `;
|
|
612
|
+
const head = opts.headed === false ? '' : `${this.label(key)}${opts.title ? ` ${suppliedValue(opts.title)}` : ''} `;
|
|
612
613
|
if (!tools.length) return [`${head}no tools available right now.`];
|
|
613
614
|
return [`${head}tools now — ${this.callHint(key)}:`, ...renderToolLines(tools, opts.seen ?? this.shown, { describeAll: opts.describeAll })];
|
|
614
615
|
}
|
|
@@ -627,13 +628,13 @@ export class ToolPresence {
|
|
|
627
628
|
for (const c of cs.changes) {
|
|
628
629
|
const bare = opts.bare === c.key;
|
|
629
630
|
if (c.closed) {
|
|
630
|
-
lines.push(`${this.label(c.key)} closed.${c.gone.length ? ` Now unavailable: ${c.gone.join(', ')}.` : ''}`);
|
|
631
|
+
lines.push(`${this.label(c.key)} closed.${c.gone.length ? ` Now unavailable: ${c.gone.map(suppliedValue).join(', ')}.` : ''}`);
|
|
631
632
|
continue;
|
|
632
633
|
}
|
|
633
634
|
const title = (c.opened || c.retitled) && !bare ? c.title : undefined;
|
|
634
635
|
if (showsBlock(c)) lines.push(...this.toolBlock(c.key, c.now, { headed: !bare, title, seen, describeAll: opts.describeAll }));
|
|
635
|
-
else if (title) lines.push(`${this.label(c.key)} ${title}`);
|
|
636
|
-
if (c.gone.length) lines.push(`now unavailable: ${c.gone.join(', ')}`);
|
|
636
|
+
else if (title) lines.push(`${this.label(c.key)} ${suppliedValue(title)}`);
|
|
637
|
+
if (c.gone.length) lines.push(`now unavailable: ${c.gone.map(suppliedValue).join(', ')}`);
|
|
637
638
|
if (c.opened && !bare) {
|
|
638
639
|
const close = this.closeHint(c.key);
|
|
639
640
|
if (close) lines.push(`${close} closes it.`);
|
|
@@ -646,9 +647,9 @@ export class ToolPresence {
|
|
|
646
647
|
|
|
647
648
|
/** RFC §6.2: a suggestion is shown, never acted on. */
|
|
648
649
|
renderSuggestion(s: Suggestion): string {
|
|
649
|
-
const what = `${s.toolset}${s.instance ? `:${s.instance}` : ''}
|
|
650
|
+
const what = suppliedValue(`${s.toolset}${s.instance ? `:${s.instance}` : ''}`);
|
|
650
651
|
const how = s.action === 'open' ? this.openHint(s.toolset, s.instance) : this.closeHint(keyOf(s.toolset, s.instance));
|
|
651
|
-
return `${this.server} suggests ${s.action === 'open' ? 'opening' : 'closing'} ${what}${s.reason ? `: ${s.reason}` : ''} — ${how} if you want to (nothing was done).`;
|
|
652
|
+
return `${this.server} suggests ${s.action === 'open' ? 'opening' : 'closing'} ${what}${s.reason ? `: ${suppliedValue(s.reason)}` : ''} — ${how} if you want to (nothing was done).`;
|
|
652
653
|
}
|
|
653
654
|
|
|
654
655
|
/** The keys a reminder restates: every open key of this server's view. */
|
|
@@ -674,7 +675,7 @@ export class ToolPresence {
|
|
|
674
675
|
* reach no agent. */
|
|
675
676
|
catalog(opts: { mark?: boolean } = {}): string {
|
|
676
677
|
const lines: string[] = [];
|
|
677
|
-
lines.push(this.surface ? `${this.server} — ${[this.surface.title, this.surface.summary].filter(Boolean).join(': ')}` : this.server);
|
|
678
|
+
lines.push(this.surface ? `${this.server} — ${[this.surface.title, this.surface.summary].filter(Boolean).map(suppliedValue).join(': ')}` : this.server);
|
|
678
679
|
const v = this.view();
|
|
679
680
|
if (v.size) {
|
|
680
681
|
for (const [key, e] of v) {
|
|
@@ -689,7 +690,7 @@ export class ToolPresence {
|
|
|
689
690
|
for (const [name, d] of Object.entries(this.declared)) {
|
|
690
691
|
if (!d.instances && this.isOpen(name)) continue;
|
|
691
692
|
openable.push(
|
|
692
|
-
` ${name}${d.title ? ` — ${d.title}` : ''}${d.summary ? `: ${d.summary}` : ''}` +
|
|
693
|
+
` ${nameToTypeBack(name)}${d.title ? ` — ${suppliedValue(d.title)}` : ''}${d.summary ? `: ${suppliedValue(d.summary)}` : ''}` +
|
|
693
694
|
(d.instances ? ' (several can be open at once; name each with instance)' : '') +
|
|
694
695
|
(d.options ? ` · options ${objectSignature(d.options)}` : ''),
|
|
695
696
|
);
|
|
@@ -697,7 +698,7 @@ export class ToolPresence {
|
|
|
697
698
|
for (const g of this.undeclaredGroups) {
|
|
698
699
|
if (this.groups.has(g)) continue;
|
|
699
700
|
const names = this.allTools.filter((t) => this.toolsetOf(t.name) === g).map((t) => t.name);
|
|
700
|
-
openable.push(` ${g}: ${names.join(', ')}`);
|
|
701
|
+
openable.push(` ${nameToTypeBack(g)}: ${names.map(nameToTypeBack).join(', ')}`);
|
|
701
702
|
}
|
|
702
703
|
if (openable.length) lines.push(`can open — ${this.names.open} {server:"${this.server}", toolset, instance?, options?}:`, ...openable);
|
|
703
704
|
return lines.join('\n');
|
|
@@ -705,12 +706,12 @@ export class ToolPresence {
|
|
|
705
706
|
|
|
706
707
|
/** Where a tool can be called from now, for `look` naming it. */
|
|
707
708
|
toolStatus(tool: string): string {
|
|
708
|
-
if (this.presenceOf(tool) === 'static') return `static — called by name, as ${this.listedName(tool)}`;
|
|
709
|
+
if (this.presenceOf(tool) === 'static') return `static — called by name, as ${nameToTypeBack(this.listedName(tool))}`;
|
|
709
710
|
const where = [...this.view().entries()].filter(([, e]) => e.tools.has(tool)).map(([k]) => k);
|
|
710
711
|
const ts = this.toolsetOf(tool);
|
|
711
712
|
return where.length
|
|
712
|
-
? `available now in ${where.map((k) => this.label(k)).join(', ')} — ${this.names.act} {server:"${this.server}", tool
|
|
713
|
-
: `not available right now${ts ? ` (toolset ${ts})` : ''}`;
|
|
713
|
+
? `available now in ${where.map((k) => this.label(k)).join(', ')} — ${this.names.act} {server:"${this.server}", tool:${quotedValue(tool)}${where.length > 1 ? ', instance' : ''}, args}`
|
|
714
|
+
: `not available right now${ts ? ` (toolset ${nameToTypeBack(ts)})` : ''}`;
|
|
714
715
|
}
|
|
715
716
|
|
|
716
717
|
}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* reference host of RFC-014. Dependency-free.
|
|
11
11
|
*/
|
|
12
12
|
import { createHash } from 'node:crypto';
|
|
13
|
+
import { nameToTypeBack, quotedValue, suppliedValue } from '../supplied-text.js';
|
|
13
14
|
|
|
14
15
|
export type JsonSchema = Record<string, unknown>;
|
|
15
16
|
|
|
@@ -34,7 +35,7 @@ function nestsWithin(value: unknown, depth = 0): boolean {
|
|
|
34
35
|
|
|
35
36
|
/** A schema's literal value (an enum's, a const or a default) as JSON, or … where it's nested too deeply to write. */
|
|
36
37
|
function literal(value: unknown): string {
|
|
37
|
-
return nestsWithin(value) ?
|
|
38
|
+
return nestsWithin(value) ? quotedValue(value) : '…';
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
function typeSig(schema: JsonSchema | undefined, depth = 0): string {
|
|
@@ -80,12 +81,12 @@ function typeSig(schema: JsonSchema | undefined, depth = 0): string {
|
|
|
80
81
|
if (!isObj(props) || Object.keys(props).length === 0) return 'object';
|
|
81
82
|
if (depth >= 3) return '{…}';
|
|
82
83
|
const req = new Set(Array.isArray(schema.required) ? (schema.required as string[]) : []);
|
|
83
|
-
return `{${Object.entries(props).map(([k, v]) => `${k}${req.has(k) ? '' : '?'}: ${typeSig(v, depth + 1)}`).join(', ')}}`;
|
|
84
|
+
return `{${Object.entries(props).map(([k, v]) => `${nameToTypeBack(k)}${req.has(k) ? '' : '?'}: ${typeSig(v, depth + 1)}`).join(', ')}}`;
|
|
84
85
|
}
|
|
85
86
|
case undefined:
|
|
86
87
|
return 'any';
|
|
87
88
|
default:
|
|
88
|
-
return t;
|
|
89
|
+
return suppliedValue(t);
|
|
89
90
|
}
|
|
90
91
|
}
|
|
91
92
|
|
|
@@ -98,9 +99,9 @@ export function signature(tool: HintedTool): string {
|
|
|
98
99
|
const req = new Set(Array.isArray(s.required) ? (s.required as string[]) : []);
|
|
99
100
|
const params = Object.entries(props).map(([k, v]) => {
|
|
100
101
|
const def = isObj(v) && v.default !== undefined ? ` =${literal(v.default)}` : '';
|
|
101
|
-
return `${k}${req.has(k) ? '' : '?'}: ${typeSig(v, 1)}${def}`;
|
|
102
|
+
return `${nameToTypeBack(k)}${req.has(k) ? '' : '?'}: ${typeSig(v, 1)}${def}`;
|
|
102
103
|
});
|
|
103
|
-
return `${tool.name}(${params.join(', ')})`;
|
|
104
|
+
return `${nameToTypeBack(String(tool.name))}(${params.join(', ')})`;
|
|
104
105
|
}
|
|
105
106
|
|
|
106
107
|
/** An options schema as `{size?: 9|13|19 =9, …}` (a toolset's options). */
|
|
@@ -153,7 +154,7 @@ export function renderToolLines(tools: HintedTool[], shown: Shown, opts: { descr
|
|
|
153
154
|
const describe = opts.describeAll || !shown.has(d);
|
|
154
155
|
shown.add(d);
|
|
155
156
|
const sentence = describe ? firstSentence(t.description) : '';
|
|
156
|
-
return `${indent}${signature(t)}${sentence ? ` — ${sentence}` : ''}`;
|
|
157
|
+
return `${indent}${signature(t)}${sentence ? ` — ${suppliedValue(sentence)}` : ''}`;
|
|
157
158
|
});
|
|
158
159
|
}
|
|
159
160
|
|
|
@@ -161,7 +162,7 @@ export function renderToolLines(tools: HintedTool[], shown: Shown, opts: { descr
|
|
|
161
162
|
export function jsonText(value: unknown, space?: number): string {
|
|
162
163
|
if (!nestsWithin(value)) return '(nested too deeply to show)';
|
|
163
164
|
try {
|
|
164
|
-
return JSON.stringify(value
|
|
165
|
+
return JSON.stringify(value) === undefined ? '' : quotedValue(value, space);
|
|
165
166
|
} catch {
|
|
166
167
|
return '(too long to show)';
|
|
167
168
|
}
|
|
@@ -173,8 +174,8 @@ export function renderToolDefinition(tool: HintedTool): string {
|
|
|
173
174
|
const classes = Array.isArray(cls) ? cls.filter((c): c is string => typeof c === 'string') : [];
|
|
174
175
|
return [
|
|
175
176
|
signature(tool),
|
|
176
|
-
typeof tool.description === 'string' ? tool.description : '',
|
|
177
|
-
...(classes.length ? [`class: ${classes.join(', ')}`] : []),
|
|
177
|
+
typeof tool.description === 'string' ? suppliedValue(tool.description) : '',
|
|
178
|
+
...(classes.length ? [`class: ${classes.map(suppliedValue).join(', ')}`] : []),
|
|
178
179
|
'inputSchema:',
|
|
179
180
|
jsonText(tool.inputSchema ?? {}, 2),
|
|
180
181
|
]
|
|
@@ -252,15 +253,15 @@ function check(schema: unknown, value: unknown, at: string, depth: number, budge
|
|
|
252
253
|
const s = schema;
|
|
253
254
|
if (s.type !== undefined) {
|
|
254
255
|
const types = Array.isArray(s.type) ? (s.type as string[]) : [s.type as string];
|
|
255
|
-
if (!types.some((t) => typeOk(t, value))) return `${at}: must be ${types.join(' or ')}`;
|
|
256
|
+
if (!types.some((t) => typeOk(t, value))) return `${at}: must be ${types.map(suppliedValue).join(' or ')}`;
|
|
256
257
|
}
|
|
257
258
|
if (Array.isArray(s.enum) || s.const !== undefined) {
|
|
258
259
|
const own = canonicalJson(value);
|
|
259
260
|
if (Array.isArray(s.enum)) {
|
|
260
261
|
spend(budget, s.enum.length);
|
|
261
|
-
if (!s.enum.some((e) => canonicalJson(e) === own)) return `${at}: must be one of ${s.enum.map((e) =>
|
|
262
|
+
if (!s.enum.some((e) => canonicalJson(e) === own)) return `${at}: must be one of ${s.enum.map((e) => quotedValue(e)).join('|')}`;
|
|
262
263
|
}
|
|
263
|
-
if (s.const !== undefined && canonicalJson(s.const) !== own) return `${at}: must be ${
|
|
264
|
+
if (s.const !== undefined && canonicalJson(s.const) !== own) return `${at}: must be ${quotedValue(s.const)}`;
|
|
264
265
|
}
|
|
265
266
|
if (typeof value === 'string') {
|
|
266
267
|
const n = [...value].length;
|
|
@@ -286,16 +287,16 @@ function check(schema: unknown, value: unknown, at: string, depth: number, budge
|
|
|
286
287
|
if (isObj(value)) {
|
|
287
288
|
const props = isObj(s.properties) ? s.properties : {};
|
|
288
289
|
for (const r of Array.isArray(s.required) ? (s.required as unknown[]) : []) {
|
|
289
|
-
if (typeof r === 'string' && !Object.hasOwn(value, r)) return `${at}.${r}: required`;
|
|
290
|
+
if (typeof r === 'string' && !Object.hasOwn(value, r)) return `${at}.${nameToTypeBack(r)}: required`;
|
|
290
291
|
}
|
|
291
292
|
for (const [k, v] of Object.entries(value)) {
|
|
292
293
|
if (Object.hasOwn(props, k)) {
|
|
293
|
-
const e = check(props[k], v, `${at}.${k}`, depth + 1, budget);
|
|
294
|
+
const e = check(props[k], v, `${at}.${nameToTypeBack(k)}`, depth + 1, budget);
|
|
294
295
|
if (e) return e;
|
|
295
296
|
} else if (s.additionalProperties === false) {
|
|
296
|
-
return `${at}.${k}: not an argument of this tool`;
|
|
297
|
+
return `${at}.${nameToTypeBack(k)}: not an argument of this tool`;
|
|
297
298
|
} else if (isObj(s.additionalProperties)) {
|
|
298
|
-
const e = check(s.additionalProperties, v, `${at}.${k}`, depth + 1, budget);
|
|
299
|
+
const e = check(s.additionalProperties, v, `${at}.${nameToTypeBack(k)}`, depth + 1, budget);
|
|
299
300
|
if (e) return e;
|
|
300
301
|
}
|
|
301
302
|
}
|