c64bridge 1.0.6 → 1.1.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/.github/skills/assembly-program/SKILL.md +37 -0
- package/.github/skills/basic-program/SKILL.md +40 -0
- package/.github/skills/cross-platform-demo/SKILL.md +32 -0
- package/.github/skills/drive-manager/SKILL.md +33 -0
- package/.github/skills/graphics-demo/SKILL.md +30 -0
- package/.github/skills/hello-world/SKILL.md +36 -0
- package/.github/skills/memory-debug/SKILL.md +32 -0
- package/.github/skills/pr-converge/SKILL.md +33 -0
- package/.github/skills/printer-job/SKILL.md +30 -0
- package/.github/skills/sid-music/SKILL.md +35 -0
- package/.github/skills/software-launch/SKILL.md +31 -0
- package/.github/skills/stream-control/SKILL.md +29 -0
- package/.github/skills/system-control/SKILL.md +32 -0
- package/AGENTS.md +28 -7
- package/README.md +70 -15
- package/data/assembly/assembly-spec.md +2 -0
- package/dist/c64Client.js +238 -60
- package/dist/chargen.js +0 -4
- package/dist/config.js +53 -28
- package/dist/device.js +92 -37
- package/dist/diagnostics.js +66 -5
- package/dist/hostPaths.js +128 -0
- package/dist/logger.js +3 -2
- package/dist/loggingHttpClient.js +71 -2
- package/dist/mcp-server.js +416 -291
- package/dist/numericArg.js +55 -0
- package/dist/petscii.js +13 -6
- package/dist/platform.js +12 -3
- package/dist/prompts/registry.js +6 -1
- package/dist/redact.js +166 -0
- package/dist/tools/access.js +66 -0
- package/dist/tools/debug.js +6 -0
- package/dist/tools/developer.js +77 -36
- package/dist/tools/errors.js +54 -3
- package/dist/tools/input.js +25 -8
- package/dist/tools/machineControl.js +113 -21
- package/dist/tools/memory.js +135 -30
- package/dist/tools/meta/artifacts.js +5 -5
- package/dist/tools/meta/background.js +216 -54
- package/dist/tools/meta/compilation.js +6 -5
- package/dist/tools/meta/config.js +20 -10
- package/dist/tools/meta/configInventory.js +25 -2
- package/dist/tools/meta/diagnostics.js +2 -1
- package/dist/tools/meta/filesystem.js +16 -14
- package/dist/tools/meta/graphics.js +48 -20
- package/dist/tools/meta/memory.js +45 -19
- package/dist/tools/meta/program.js +10 -7
- package/dist/tools/meta/screen.js +116 -25
- package/dist/tools/pauseGuard.js +23 -0
- package/dist/tools/pollValidator.js +53 -8
- package/dist/tools/programRunners.js +62 -18
- package/dist/tools/registry/batch.js +17 -11
- package/dist/tools/registry/config.js +9 -2
- package/dist/tools/registry/disk.js +12 -2
- package/dist/tools/registry/drive.js +3 -0
- package/dist/tools/registry/extract.js +3 -0
- package/dist/tools/registry/graphics.js +59 -6
- package/dist/tools/registry/index.js +17 -1
- package/dist/tools/registry/memory.js +11 -1
- package/dist/tools/registry/platform.js +4 -1
- package/dist/tools/registry/printer.js +2 -1
- package/dist/tools/registry/program.js +5 -1
- package/dist/tools/registry/rag.js +1 -0
- package/dist/tools/registry/sound.js +12 -1
- package/dist/tools/registry/stream.js +2 -1
- package/dist/tools/registry/system.js +8 -1
- package/dist/tools/schema.js +7 -7
- package/dist/tools/streaming.js +34 -4
- package/dist/tools/translation/assembler.js +247 -24
- package/dist/tools/translation/basicDetokenizer.js +80 -0
- package/dist/tools/translation/basicTokenizer.js +8 -3
- package/dist/tools/translation/disassembler.js +14 -0
- package/dist/tools/types.js +258 -7
- package/dist/tools/vice.js +5 -16
- package/dist/vicBitmap.js +1 -1
- package/dist/vice/mockServer.js +3 -0
- package/dist/vice/process.js +19 -0
- package/dist/vice/resourcePolicy.js +36 -0
- package/dist/vice/viceClient.js +22 -5
- package/doc/vice/support-matrix.md +17 -11
- package/mcp/schemas/c64_batch.schema.json +2 -1
- package/mcp/schemas/c64_config.schema.json +33 -466
- package/mcp/schemas/c64_debug.schema.json +3 -602
- package/mcp/schemas/c64_disk.schema.json +17 -344
- package/mcp/schemas/c64_drive.schema.json +2 -157
- package/mcp/schemas/c64_extract.schema.json +10 -364
- package/mcp/schemas/c64_graphics.schema.json +19 -426
- package/mcp/schemas/c64_input.schema.json +5 -318
- package/mcp/schemas/c64_memory.schema.json +23 -491
- package/mcp/schemas/c64_printer.schema.json +3 -258
- package/mcp/schemas/c64_program.schema.json +16 -491
- package/mcp/schemas/c64_rag.schema.json +1 -81
- package/mcp/schemas/c64_select_backend.schema.json +2 -36
- package/mcp/schemas/c64_sound.schema.json +20 -822
- package/mcp/schemas/c64_stream.schema.json +4 -75
- package/mcp/schemas/c64_system.schema.json +28 -348
- package/mcp/schemas/c64_vice.schema.json +2 -71
- package/mcp/server.json +1 -1
- package/mcp/tools.json +1990 -6900
- package/mcp.json +21 -1
- package/package.json +19 -8
- package/scripts/mockC64Server.mjs +44 -0
- package/scripts/run-coverage.mjs +27 -1
- package/scripts/smoke-run.sh +3 -2
- package/scripts/start.mjs +23 -1
- package/server.json +53 -3
- package/dist/.tsbuildinfo +0 -1
- package/doc/agent-state-spec.md +0 -243
- package/doc/plans/bun/baseline-metrics.md +0 -16
- package/doc/plans/bun/migration-checklist.md +0 -55
- package/doc/plans/bun/migration.md +0 -83
- package/doc/plans/improve-knowledge/notes.md +0 -43
- package/doc/plans/improve-knowledge/plan.md +0 -121
- package/doc/plans/improve-knowledge/rollout.md +0 -110
- package/doc/plans/meta-tools/README.md +0 -110
- package/doc/plans/meta-tools/analysis.md +0 -272
- package/doc/plans/meta-tools/catalog.md +0 -435
- package/doc/plans/meta-tools/notes.md +0 -192
- package/doc/plans/meta-tools/rollout.md +0 -368
- package/doc/plans/prompts/README.md +0 -96
- package/doc/plans/prompts/user-prompt-ideas.md +0 -147
- package/doc/plans/tool-consolidation/notes.md +0 -28
- package/doc/plans/tool-consolidation/plan.md +0 -169
- package/doc/plans/tool-consolidation/rollout.md +0 -104
- package/doc/plans/vice-backend-switch/plan.md +0 -486
- package/doc/plans/vice-backend-switch-2/plan.md +0 -48
- package/doc/plans/vice-support/notes.md +0 -63
- package/doc/plans/vice-support/plan.md +0 -88
- package/doc/plans/vice-support/rollout.md +0 -99
- package/doc/reviews/audit.md +0 -143
- package/doc/reviews/hardening/01-fable/prompt-handover.md +0 -125
- package/doc/reviews/hardening/01-fable/prompt.md +0 -98
- package/doc/reviews/hardening/01-fable/review-prompt.md +0 -468
- package/doc/reviews/hardening/01-fable/review.md +0 -1234
- package/doc/reviews/vice-support-review.md +0 -185
- package/scripts/compare-rest/rest-compare-2025-11-01T00-25-21-830Z.json +0 -2513
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createOperationDispatcher, defineToolModule, discriminatedUnionSchema, } from "../types.js";
|
|
2
|
-
import { memoryModule, memoryOperationHandlers as groupedMemoryHandlers, disassembleArgsSchema, copyMemoryArgsSchema, fillMemoryArgsSchema, searchMemoryArgsSchema, compareMemoryArgsSchema, saveMemoryArgsSchema, } from "../memory.js";
|
|
2
|
+
import { memoryModule, memoryOperationHandlers as groupedMemoryHandlers, listBasicArgsSchema, disassembleArgsSchema, copyMemoryArgsSchema, fillMemoryArgsSchema, searchMemoryArgsSchema, compareMemoryArgsSchema, saveMemoryArgsSchema, } from "../memory.js";
|
|
3
3
|
import { metaModule } from "../meta/index.js";
|
|
4
4
|
import { buildDescriptorIndex, ensureDescriptor, extendSchemaWithOp, createOperationHandlers, invokeModuleTool, } from "./utils.js";
|
|
5
5
|
const memoryDescriptorIndex = buildDescriptorIndex(memoryModule);
|
|
@@ -25,6 +25,11 @@ const memoryOperations = [
|
|
|
25
25
|
schema: extendSchemaWithOp("wait_for_text", ensureDescriptor(metaDescriptorIndex, "wait_for_screen_text").inputSchema, { description: "Poll the screen until a substring or regex appears, or timeout elapses." }),
|
|
26
26
|
handler: async (rawArgs, ctx) => invokeModuleTool(metaModule, "wait_for_screen_text", rawArgs, ctx),
|
|
27
27
|
},
|
|
28
|
+
{
|
|
29
|
+
op: "list_basic",
|
|
30
|
+
schema: extendSchemaWithOp("list_basic", listBasicArgsSchema.jsonSchema, { description: "List the BASIC program in memory as text: reads the $2B-$2E pointers, walks the line links and detokenises. Works on C64U/U64, U2-family hardware, and VICE." }),
|
|
31
|
+
handler: groupedMemoryHandlers.list_basic,
|
|
32
|
+
},
|
|
28
33
|
{
|
|
29
34
|
op: "disassemble",
|
|
30
35
|
schema: extendSchemaWithOp("disassemble", disassembleArgsSchema.jsonSchema, { description: "Disassemble a memory region into annotated 6502/6510 instructions, including undocumented opcodes with canonical names. Symbol annotations from `.vs` files are applied when available. Works on C64U/U64, U2-family hardware, and VICE." }),
|
|
@@ -77,6 +82,11 @@ export const memoryModuleGroup = defineToolModule({
|
|
|
77
82
|
description: "Memory operations available via the c64_memory tool.",
|
|
78
83
|
variants: memoryOperations.map((operation) => operation.schema),
|
|
79
84
|
}),
|
|
85
|
+
operationEffects: {
|
|
86
|
+
read: "read", write: "write", read_screen: "read", wait_for_text: "read", list_basic: "read", disassemble: "read",
|
|
87
|
+
copy_memory: "write", fill_memory: "write", search_memory: "read", compare_memory: "read",
|
|
88
|
+
save_memory: "write",
|
|
89
|
+
},
|
|
80
90
|
tags: ["memory", "screen", "grouped"],
|
|
81
91
|
examples: [
|
|
82
92
|
{
|
|
@@ -44,6 +44,7 @@ function describeTargetPlatform(target) {
|
|
|
44
44
|
return {
|
|
45
45
|
availableTools: info.tools,
|
|
46
46
|
unavailableTools: info.unsupported_tools,
|
|
47
|
+
unavailableOperations: info.unsupported_operations,
|
|
47
48
|
};
|
|
48
49
|
}
|
|
49
50
|
const selectBackendSchema = operationSchema("select", {
|
|
@@ -77,7 +78,7 @@ const platformOperationHandlers = {
|
|
|
77
78
|
cancelGeneratedSidPlayback(); // A backend switch is the documented cue to stop it rather than let it keep playing on the old backend.
|
|
78
79
|
ctx.client.switchBackend(backend);
|
|
79
80
|
ctx.setPlatform(backend);
|
|
80
|
-
const { availableTools, unavailableTools } = describeTargetPlatform(backend);
|
|
81
|
+
const { availableTools, unavailableTools, unavailableOperations } = describeTargetPlatform(backend);
|
|
81
82
|
const switchBackTarget = availableBackends.find((candidate) => candidate !== backend);
|
|
82
83
|
const data = {
|
|
83
84
|
success: true,
|
|
@@ -85,6 +86,7 @@ const platformOperationHandlers = {
|
|
|
85
86
|
configuredBackends: availableBackends,
|
|
86
87
|
availableTools,
|
|
87
88
|
unavailableTools,
|
|
89
|
+
unavailableOperations,
|
|
88
90
|
usageHint: switchBackTarget
|
|
89
91
|
? `Use c64_select_backend again with backend: "${switchBackTarget}" to switch back.`
|
|
90
92
|
: "No other backend is configured.",
|
|
@@ -111,6 +113,7 @@ export const platformModuleGroup = defineToolModule({
|
|
|
111
113
|
description: "Runtime backend selection operations.",
|
|
112
114
|
variants: [selectBackendSchema],
|
|
113
115
|
}),
|
|
116
|
+
operationEffects: { select: "write" },
|
|
114
117
|
tags: ["platform", "backend", "switch"],
|
|
115
118
|
execute: createOperationDispatcher("c64_select_backend", platformOperationHandlers),
|
|
116
119
|
examples: [
|
|
@@ -133,12 +133,13 @@ export const printerModuleGroup = defineToolModule({
|
|
|
133
133
|
description: "Printer operations available via the c64_printer tool.",
|
|
134
134
|
variants: printerOperations.map((operation) => operation.schema),
|
|
135
135
|
}),
|
|
136
|
+
operationEffects: { print_text: "write", print_bitmap: "write", define_chars: "write" },
|
|
136
137
|
tags: ["printer", "device", "grouped"],
|
|
137
138
|
examples: [
|
|
138
139
|
{
|
|
139
140
|
name: "Print text",
|
|
140
141
|
description: "Generate BASIC for device 4",
|
|
141
|
-
arguments: { op: "print_text", text: "HELLO",
|
|
142
|
+
arguments: { op: "print_text", text: "HELLO", target: "commodore", formFeed: true },
|
|
142
143
|
},
|
|
143
144
|
{
|
|
144
145
|
name: "Print bitmap",
|
|
@@ -68,8 +68,12 @@ export const programModule = defineToolModule({
|
|
|
68
68
|
description: "Program operations available via the c64_program tool.",
|
|
69
69
|
variants: programOperations.map((operation) => operation.schema),
|
|
70
70
|
}),
|
|
71
|
+
operationEffects: {
|
|
72
|
+
load_prg: "write", run_prg: "write", run_crt: "write", upload_run_basic: "write",
|
|
73
|
+
upload_run_asm: "write", batch_run: "write", bundle_run: "write", cross_platform_greeting: "write",
|
|
74
|
+
},
|
|
71
75
|
tags: ["programs", "execution", "grouped"],
|
|
72
|
-
operationPlatforms: {
|
|
76
|
+
operationPlatforms: { run_crt: ["c64u", "u2"] },
|
|
73
77
|
operationToolNames: {
|
|
74
78
|
load_prg: "load_prg",
|
|
75
79
|
run_crt: "run_crt",
|
|
@@ -35,6 +35,7 @@ export const ragModuleGroup = defineToolModule({
|
|
|
35
35
|
description: "RAG operations available via the c64_rag tool.",
|
|
36
36
|
variants: ragOperations.map((operation) => operation.schema),
|
|
37
37
|
}),
|
|
38
|
+
operationEffects: { basic: "read", asm: "read" },
|
|
38
39
|
tags: ["rag", "reference", "grouped"],
|
|
39
40
|
examples: [
|
|
40
41
|
{
|
|
@@ -4,10 +4,12 @@ import { audioModule } from "../audio.js";
|
|
|
4
4
|
import { metaModule } from "../meta/index.js";
|
|
5
5
|
import { literalSchema, numberSchema, objectSchema, optionalSchema, stringSchema, } from "../schema.js";
|
|
6
6
|
import { buildDescriptorIndex, ensureDescriptor, extendSchemaWithOp, createOperationHandlers, invokeModuleTool, } from "./utils.js";
|
|
7
|
-
import { ToolError, toolErrorResult, unknownErrorResult, } from "../errors.js";
|
|
7
|
+
import { ToolError, ToolValidationError, toolErrorResult, unknownErrorResult, } from "../errors.js";
|
|
8
8
|
import { jsonResult } from "../responses.js";
|
|
9
9
|
const audioDescriptorIndex = buildDescriptorIndex(audioModule);
|
|
10
10
|
const metaDescriptorIndex = buildDescriptorIndex(metaModule);
|
|
11
|
+
/** Largest encoded PCM payload one capture_samples response may carry. */
|
|
12
|
+
const MAX_SAMPLE_PAYLOAD_BYTES = 2 * 1024 * 1024;
|
|
11
13
|
const captureSamplesArgsSchema = objectSchema({
|
|
12
14
|
description: "Capture raw stereo PCM samples from the C64 Ultimate audio UDP stream.",
|
|
13
15
|
properties: {
|
|
@@ -41,6 +43,10 @@ const soundOperations = [
|
|
|
41
43
|
});
|
|
42
44
|
const capture = await ctx.client.captureSamples({ count: parsed.count });
|
|
43
45
|
const samplesBuffer = Buffer.from(capture.samples.buffer, capture.samples.byteOffset, capture.samples.byteLength);
|
|
46
|
+
const encodedBytes = parsed.encoding === "hex" ? samplesBuffer.length * 2 : Math.ceil(samplesBuffer.length / 3) * 4;
|
|
47
|
+
if (encodedBytes > MAX_SAMPLE_PAYLOAD_BYTES) {
|
|
48
|
+
throw new ToolValidationError(`Sample response would be ${encodedBytes} bytes, above the ${MAX_SAMPLE_PAYLOAD_BYTES} byte limit; lower count`, { path: "$.count", details: { count: parsed.count, encodedBytes, limitBytes: MAX_SAMPLE_PAYLOAD_BYTES } });
|
|
49
|
+
}
|
|
44
50
|
return jsonResult({
|
|
45
51
|
backend: capture.backend,
|
|
46
52
|
channels: capture.channels,
|
|
@@ -156,6 +162,11 @@ export const soundModuleGroup = defineToolModule({
|
|
|
156
162
|
description: "Sound operations available via the c64_sound tool.",
|
|
157
163
|
variants: soundOperations.map((operation) => operation.schema),
|
|
158
164
|
}),
|
|
165
|
+
operationEffects: {
|
|
166
|
+
capture_samples: "read", set_volume: "write", reset: "write", note_on: "write", note_off: "write",
|
|
167
|
+
silence_all: "write", play_sid_file: "write", play_mod_file: "write", generate: "write",
|
|
168
|
+
compile_play: "write", play_preset: "write", pipeline: "write", analyze: "read", record_analyze: "read",
|
|
169
|
+
},
|
|
159
170
|
tags: ["sid", "audio", "grouped"],
|
|
160
171
|
operationPlatforms: {
|
|
161
172
|
capture_samples: ["c64u"],
|
|
@@ -5,7 +5,7 @@ const streamingDescriptorIndex = buildDescriptorIndex(streamingModule);
|
|
|
5
5
|
const streamOperations = [
|
|
6
6
|
{
|
|
7
7
|
op: "start",
|
|
8
|
-
schema: extendSchemaWithOp("start", ensureDescriptor(streamingDescriptorIndex, "stream_start").inputSchema, { description: "Start an Ultimate streaming session
|
|
8
|
+
schema: extendSchemaWithOp("start", ensureDescriptor(streamingDescriptorIndex, "stream_start").inputSchema, { description: "Start an Ultimate streaming session. The device sends an unencrypted UDP stream to the target (IPv4 address or hostname, optional :port)." }),
|
|
9
9
|
handler: async (rawArgs, ctx) => invokeModuleTool(streamingModule, "stream_start", rawArgs, ctx),
|
|
10
10
|
},
|
|
11
11
|
{
|
|
@@ -34,6 +34,7 @@ export const streamModule = defineToolModule({
|
|
|
34
34
|
description: "Streaming operations available via the c64_stream tool.",
|
|
35
35
|
variants: streamOperations.map((operation) => operation.schema),
|
|
36
36
|
}),
|
|
37
|
+
operationEffects: { start: "write", stop: "write" },
|
|
37
38
|
tags: ["stream", "monitor", "grouped"],
|
|
38
39
|
examples: [
|
|
39
40
|
{
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createOperationDispatcher, defineToolModule, discriminatedUnionSchema, } from "../types.js";
|
|
2
|
+
import { taskOperationEffect } from "../access.js";
|
|
2
3
|
import { machineControlModule } from "../machineControl.js";
|
|
3
4
|
import { metaModule } from "../meta/index.js";
|
|
4
5
|
import { buildDescriptorIndex, ensureDescriptor, extendSchemaWithOp, createOperationHandlers, invokeModuleTool, } from "./utils.js";
|
|
@@ -27,7 +28,7 @@ const systemOperations = [
|
|
|
27
28
|
},
|
|
28
29
|
{
|
|
29
30
|
op: "poweroff",
|
|
30
|
-
schema: extendSchemaWithOp("poweroff", ensureDescriptor(machineDescriptorIndex, "poweroff").inputSchema, { description: "Shut down C64U/U64 or quit VICE. U2-family hardware cannot power off." }),
|
|
31
|
+
schema: extendSchemaWithOp("poweroff", ensureDescriptor(machineDescriptorIndex, "poweroff").inputSchema, { description: "Shut down C64U/U64 or quit VICE. U2-family hardware cannot power off. On C64U/U64 the machine cannot be restarted by software afterwards: power must be restored at the outlet, and it starts by itself only if automatic startup on power restoration is enabled." }),
|
|
31
32
|
handler: async (rawArgs, ctx) => invokeModuleTool(machineControlModule, "poweroff", rawArgs, ctx),
|
|
32
33
|
},
|
|
33
34
|
{
|
|
@@ -92,6 +93,12 @@ export const systemModuleGroup = defineToolModule({
|
|
|
92
93
|
description: "System operations available via the c64_system tool.",
|
|
93
94
|
variants: systemOperations.map((operation) => operation.schema),
|
|
94
95
|
}),
|
|
96
|
+
operationEffects: {
|
|
97
|
+
pause: "write", resume: "write", reset: "write", reboot: "destructive", poweroff: "destructive",
|
|
98
|
+
power_cycle: "destructive", menu: "write", read_menu_screen: "read", start_task: "write",
|
|
99
|
+
stop_task: "write", stop_all_tasks: "write", list_tasks: "read", performance_report: "read",
|
|
100
|
+
},
|
|
101
|
+
effectOf: (op, args) => (op === "start_task" ? taskOperationEffect(args.operation) : undefined),
|
|
95
102
|
tags: ["system", "control", "grouped"],
|
|
96
103
|
operationPlatforms: { poweroff: ["c64u", "vice"], pause: ["c64u", "u2"], resume: ["c64u", "u2"], menu: ["c64u", "u2"], read_menu_screen: ["c64u", "u2"] },
|
|
97
104
|
operationToolNames: { poweroff: "poweroff", pause: "pause", resume: "resume", menu: "menu_button", read_menu_screen: "read_menu_screen" },
|
package/dist/tools/schema.js
CHANGED
|
@@ -41,10 +41,10 @@ export function stringSchema(options = {}) {
|
|
|
41
41
|
});
|
|
42
42
|
const stringValue = value;
|
|
43
43
|
if (options.minLength !== undefined && stringValue.length < options.minLength) {
|
|
44
|
-
throw new ToolValidationError(`String must have length ≥ ${options.minLength}`, { path, details: { minLength: options.minLength } });
|
|
44
|
+
throw new ToolValidationError(`String must have length ≥ ${options.minLength}`, { path, details: { minLength: options.minLength, received: stringValue.length } });
|
|
45
45
|
}
|
|
46
46
|
if (options.maxLength !== undefined && stringValue.length > options.maxLength) {
|
|
47
|
-
throw new ToolValidationError(`String must have length ≤ ${options.maxLength}`, { path, details: { maxLength: options.maxLength } });
|
|
47
|
+
throw new ToolValidationError(`String must have length ≤ ${options.maxLength}`, { path, details: { maxLength: options.maxLength, received: stringValue.length } });
|
|
48
48
|
}
|
|
49
49
|
if (options.pattern && !options.pattern.test(stringValue)) {
|
|
50
50
|
throw new ToolValidationError("String does not match required pattern", {
|
|
@@ -55,7 +55,7 @@ export function stringSchema(options = {}) {
|
|
|
55
55
|
if (options.enum && !options.enum.includes(stringValue)) {
|
|
56
56
|
throw new ToolValidationError("Value must be one of the allowed options", {
|
|
57
57
|
path,
|
|
58
|
-
details: { allowed: options.enum },
|
|
58
|
+
details: { allowed: options.enum, received: stringValue },
|
|
59
59
|
});
|
|
60
60
|
}
|
|
61
61
|
return stringValue;
|
|
@@ -86,13 +86,13 @@ export function numberSchema(options = {}) {
|
|
|
86
86
|
if (options.minimum !== undefined && numberValue < options.minimum) {
|
|
87
87
|
throw new ToolValidationError("Value is below minimum", {
|
|
88
88
|
path,
|
|
89
|
-
details: { minimum: options.minimum },
|
|
89
|
+
details: { minimum: options.minimum, received: numberValue },
|
|
90
90
|
});
|
|
91
91
|
}
|
|
92
92
|
if (options.maximum !== undefined && numberValue > options.maximum) {
|
|
93
93
|
throw new ToolValidationError("Value is above maximum", {
|
|
94
94
|
path,
|
|
95
|
-
details: { maximum: options.maximum },
|
|
95
|
+
details: { maximum: options.maximum, received: numberValue },
|
|
96
96
|
});
|
|
97
97
|
}
|
|
98
98
|
return numberValue;
|
|
@@ -154,13 +154,13 @@ export function arraySchema(itemSchema, options = {}) {
|
|
|
154
154
|
if (options.minItems !== undefined && result.length < options.minItems) {
|
|
155
155
|
throw new ToolValidationError("Array has too few items", {
|
|
156
156
|
path,
|
|
157
|
-
details: { minItems: options.minItems },
|
|
157
|
+
details: { minItems: options.minItems, received: result.length },
|
|
158
158
|
});
|
|
159
159
|
}
|
|
160
160
|
if (options.maxItems !== undefined && result.length > options.maxItems) {
|
|
161
161
|
throw new ToolValidationError("Array has too many items", {
|
|
162
162
|
path,
|
|
163
|
-
details: { maxItems: options.maxItems },
|
|
163
|
+
details: { maxItems: options.maxItems, received: result.length },
|
|
164
164
|
});
|
|
165
165
|
}
|
|
166
166
|
return result;
|
package/dist/tools/streaming.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { defineToolModule } from "./types.js";
|
|
2
2
|
import { objectSchema, stringSchema } from "./schema.js";
|
|
3
3
|
import { textResult } from "./responses.js";
|
|
4
|
-
import { ToolError, ToolExecutionError, toolErrorResult, unknownErrorResult, } from "./errors.js";
|
|
4
|
+
import { ToolError, ToolExecutionError, ToolValidationError, toolErrorResult, unknownErrorResult, } from "./errors.js";
|
|
5
5
|
const streamIdentifierSchema = stringSchema({
|
|
6
6
|
description: "Stream type to control (video/audio/debug).",
|
|
7
7
|
enum: ["video", "audio", "debug"],
|
|
@@ -11,8 +11,8 @@ const streamStartArgsSchema = objectSchema({
|
|
|
11
11
|
properties: {
|
|
12
12
|
stream: streamIdentifierSchema,
|
|
13
13
|
target: stringSchema({
|
|
14
|
-
description: "Destination
|
|
15
|
-
minLength:
|
|
14
|
+
description: "Destination of the UDP stream: IPv4 address or hostname with an optional :port (1-65535), e.g. 192.168.1.10, 192.168.1.10:11000 or pc.local:11000. The device sends the stream unencrypted over UDP to this address.",
|
|
15
|
+
minLength: 1,
|
|
16
16
|
}),
|
|
17
17
|
},
|
|
18
18
|
required: ["stream", "target"],
|
|
@@ -26,6 +26,35 @@ const streamStopArgsSchema = objectSchema({
|
|
|
26
26
|
required: ["stream"],
|
|
27
27
|
additionalProperties: false,
|
|
28
28
|
});
|
|
29
|
+
const STREAM_TARGET_FORMS = "an IPv4 address or hostname with an optional :port (1-65535), e.g. 192.168.1.10, 192.168.1.10:11000 or pc.local:11000";
|
|
30
|
+
const HOST_LABEL = /^[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?$/;
|
|
31
|
+
export function validateStreamTarget(target) {
|
|
32
|
+
const fail = () => {
|
|
33
|
+
throw new ToolValidationError(`Invalid parameter 'target' (${JSON.stringify(target)}): expected ${STREAM_TARGET_FORMS}.`, { path: "target" });
|
|
34
|
+
};
|
|
35
|
+
const parts = target.split(":");
|
|
36
|
+
if (parts.length > 2)
|
|
37
|
+
fail();
|
|
38
|
+
const [host, port] = parts;
|
|
39
|
+
if (port !== undefined) {
|
|
40
|
+
if (!/^\d{1,5}$/.test(port))
|
|
41
|
+
fail();
|
|
42
|
+
const n = Number(port);
|
|
43
|
+
if (n < 1 || n > 65535)
|
|
44
|
+
fail();
|
|
45
|
+
}
|
|
46
|
+
if (!host || host.length > 253)
|
|
47
|
+
fail();
|
|
48
|
+
const labels = host.split(".");
|
|
49
|
+
if (!labels.every((label) => HOST_LABEL.test(label)))
|
|
50
|
+
fail();
|
|
51
|
+
if (/^[\d.]+$/.test(host)) {
|
|
52
|
+
// Numeric-only: must be a proper dotted IPv4 address.
|
|
53
|
+
const octets = labels;
|
|
54
|
+
if (octets.length !== 4 || !octets.every((o) => /^\d{1,3}$/.test(o) && Number(o) <= 255))
|
|
55
|
+
fail();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
29
58
|
function toRecord(details) {
|
|
30
59
|
if (details && typeof details === "object") {
|
|
31
60
|
return details;
|
|
@@ -59,7 +88,7 @@ export const streamingModule = defineToolModule({
|
|
|
59
88
|
tools: [
|
|
60
89
|
{
|
|
61
90
|
name: "stream_start",
|
|
62
|
-
description: "Start an Ultimate streaming session (video/audio/debug)
|
|
91
|
+
description: "Start an Ultimate streaming session (video/audio/debug) sending an unencrypted UDP stream from the device to an IPv4 address or hostname with an optional :port. See c64://guide/index for usage notes.",
|
|
63
92
|
summary: "Validates stream arguments and forwards the request to firmware, returning status metadata.",
|
|
64
93
|
inputSchema: streamStartArgsSchema.jsonSchema,
|
|
65
94
|
tags: ["stream", "start"],
|
|
@@ -74,6 +103,7 @@ export const streamingModule = defineToolModule({
|
|
|
74
103
|
async execute(args, ctx) {
|
|
75
104
|
try {
|
|
76
105
|
const parsed = streamStartArgsSchema.parse(args ?? {});
|
|
106
|
+
validateStreamTarget(parsed.target);
|
|
77
107
|
ctx.logger.info("Starting Ultimate stream", {
|
|
78
108
|
stream: parsed.stream,
|
|
79
109
|
target: parsed.target,
|