@henols/c64-re-tools 2.0.1 → 2.1.0-rc.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/README.md +8 -4
- package/package.json +1 -1
- package/skills/c64-assembler/SKILL.md +24 -9
- package/skills/c64-assembler/scripts/assembler.ts +95 -0
- package/skills/c64-basic/SKILL.md +17 -9
- package/skills/c64-basic/scripts/basic.ts +26 -36
- package/skills/c64-disk/SKILL.md +17 -7
- package/skills/c64-disk/scripts/disk.ts +130 -104
- package/skills/c64-disk/scripts/lib/containers.ts +10 -12
- package/skills/c64-docs/SKILL.md +47 -28
- package/skills/c64-docs/references/style.md +6 -6
- package/skills/c64-docs/scripts/docs.ts +323 -0
- package/skills/c64-docs/scripts/lib/source-map.ts +1 -1
- package/skills/c64-emulator/SKILL.md +11 -7
- package/skills/c64-knowledge/SKILL.md +34 -21
- package/skills/c64-knowledge/scripts/knowledge.ts +597 -718
- package/skills/c64-knowledge/scripts/lib/listing.ts +5 -7
- package/skills/c64-memory-map/SKILL.md +18 -10
- package/skills/c64-memory-map/scripts/memory-map.ts +81 -0
- package/skills/c64-provenance/SKILL.md +82 -26
- package/skills/c64-provenance/scripts/lib/compare.ts +8 -12
- package/skills/c64-provenance/scripts/lib/leftovers.ts +442 -0
- package/skills/c64-provenance/scripts/lib/library.ts +15 -25
- package/skills/c64-provenance/scripts/lib/runtime.ts +12 -37
- package/skills/c64-provenance/scripts/lib/separate.ts +9 -10
- package/skills/c64-provenance/scripts/provenance.ts +222 -252
- package/skills/c64-reverse-engineering/SKILL.md +18 -15
- package/skills/c64-source/SKILL.md +28 -16
- package/skills/c64-source/scripts/lib/loader.ts +52 -77
- package/skills/c64-source/scripts/lib/source.ts +33 -42
- package/skills/c64-source/scripts/{generate.ts → source.ts} +68 -109
- package/skills/c64-static-analysis/SKILL.md +24 -13
- package/skills/c64-static-analysis/scripts/lib/coverage.ts +3 -4
- package/skills/c64-static-analysis/scripts/lib/findings.ts +2 -2
- package/skills/c64-static-analysis/scripts/lib/instructions.ts +1 -1
- package/skills/c64-static-analysis/scripts/lib/macros.ts +6 -8
- package/skills/c64-static-analysis/scripts/{analyze.ts → static-analysis.ts} +58 -118
- package/skills/c64-testing/SKILL.md +94 -19
- package/skills/c64-testing/references/recording-format.md +125 -0
- package/skills/c64-testing/references/scenario-format.md +24 -19
- package/skills/c64-testing/scripts/{diff.ts → lib/diff-files.ts} +18 -69
- package/skills/c64-testing/scripts/lib/diff.ts +5 -3
- package/skills/c64-testing/scripts/lib/play.ts +334 -0
- package/skills/c64-testing/scripts/lib/recording.ts +662 -0
- package/skills/c64-testing/scripts/lib/replay.ts +105 -0
- package/skills/c64-testing/scripts/{run.ts → lib/runs.ts} +36 -59
- package/skills/c64-testing/scripts/lib/scenario.ts +73 -81
- package/skills/c64-testing/scripts/testing.ts +189 -0
- package/skills/c64-unpacker/SKILL.md +42 -25
- package/skills/c64-unpacker/scripts/lib/evidence.ts +3 -4
- package/skills/c64-unpacker/scripts/lib/loads.ts +12 -10
- package/skills/c64-unpacker/scripts/lib/machine.ts +0 -2
- package/skills/c64-unpacker/scripts/lib/stages.ts +14 -19
- package/skills/c64-unpacker/scripts/{unpack.ts → unpacker.ts} +109 -211
- package/src/c64.ts +35 -20
- package/src/cli/main.ts +2 -2
- package/src/errors.ts +38 -0
- package/src/graphics.ts +3 -1
- package/src/host/server.ts +3 -2
- package/src/host/vice/session.ts +4 -4
- package/src/host-client/entry-state.ts +106 -0
- package/src/host-client/image.ts +21 -10
- package/src/host-client/steps.ts +285 -108
- package/src/host-client/transfer.ts +21 -15
- package/src/host-client/vice-session.ts +1 -1
- package/src/knowledge/database.ts +3 -23
- package/src/knowledge/expression.ts +8 -11
- package/src/knowledge/history.ts +2 -2
- package/src/knowledge/import.ts +13 -14
- package/src/knowledge/names.ts +50 -0
- package/src/knowledge/program.ts +11 -10
- package/src/knowledge/read.ts +16 -21
- package/src/knowledge/schema.ts +9 -3
- package/src/knowledge/text.ts +5 -5
- package/src/knowledge/write.ts +93 -137
- package/src/mcp/inputs.ts +105 -0
- package/src/mcp/output.ts +2 -13
- package/src/mcp/server.ts +12 -60
- package/src/mcp/tools/debug.ts +17 -12
- package/src/mcp/tools/execution.ts +5 -32
- package/src/mcp/tools/input.ts +7 -32
- package/src/mcp/tools/media.ts +6 -5
- package/src/mcp/tools/memory.ts +11 -38
- package/src/mcp/tools/video.ts +6 -5
- package/src/native/c64run.ts +162 -0
- package/src/native/discover.ts +8 -0
- package/src/native/dxa.ts +7 -6
- package/src/native/ghidra/analyze.ts +2 -1
- package/src/native/ghidra/index.ts +2 -1
- package/src/native/processes.ts +4 -3
- package/src/native/status.ts +25 -4
- package/src/native/types.ts +3 -7
- package/src/opcodes.ts +13 -13
- package/src/platform.ts +9 -9
- package/src/project.ts +16 -26
- package/src/protocol/messages.ts +3 -21
- package/src/protocol/vice.ts +7 -7
- package/src/script.ts +491 -49
- package/skills/c64-assembler/scripts/assemble.ts +0 -138
- package/skills/c64-docs/scripts/generate.ts +0 -109
- package/skills/c64-docs/scripts/link.ts +0 -239
- package/skills/c64-memory-map/scripts/memmap.ts +0 -92
- package/skills/c64-testing/scripts/checklist.ts +0 -51
- package/skills/c64-testing/scripts/test.ts +0 -65
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// The inputs of the MCP tools that change or wait for the machine. The action
|
|
2
|
+
// steps of the scripts take the same fields, ranges and defaults (host-client/
|
|
3
|
+
// steps.ts, without zod, because the scripts install without packages);
|
|
4
|
+
// inputs.test.ts checks that both take the same inputs.
|
|
5
|
+
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
|
|
8
|
+
import { c64AddressOf } from "../c64.ts";
|
|
9
|
+
import { DEFAULT_TIMEOUT_FRAMES, keyboardBytes, MAX_RASTER_LINE } from "../host-client/steps.ts";
|
|
10
|
+
import { COMPARISONS, CONDITION_REGISTERS, JOYSTICK_DIRECTIONS, MAX_KEYBOARD_BYTES, MAX_MEMORY_WRITE, MAX_TIMEOUT_FRAMES, MEMORY_VIEWS, SPACES } from "../protocol/vice.ts";
|
|
11
|
+
|
|
12
|
+
export const Byte = z.number().int().min(0).max(255);
|
|
13
|
+
|
|
14
|
+
/** Input bytes: hex in either case, two digits per byte, at least one byte. Parses to lowercase. */
|
|
15
|
+
export const HexDataInput = z
|
|
16
|
+
.string()
|
|
17
|
+
.regex(/^(?:[0-9a-fA-F]{2})+$/, "must be hex bytes, two digits per byte, no separators, for example a9008d20d0")
|
|
18
|
+
.transform((value) => value.toLowerCase());
|
|
19
|
+
|
|
20
|
+
export const SpaceInput = z.enum(SPACES).default("c64").describe("c64 is the computer. drive8 is the 1541 disk drive CPU");
|
|
21
|
+
|
|
22
|
+
export const MemoryViewInput = z
|
|
23
|
+
.enum(MEMORY_VIEWS)
|
|
24
|
+
.default("cpu")
|
|
25
|
+
.describe("cpu reads what the CPU sees now (ROM and I/O where banked in). ram reads the RAM underneath (c64 only)");
|
|
26
|
+
|
|
27
|
+
/** A tool address: $ and 1 to 4 hex digits, either case. Parses to a number. */
|
|
28
|
+
export const AddressInput = z
|
|
29
|
+
.string()
|
|
30
|
+
.refine((value) => c64AddressOf(value) !== undefined, {
|
|
31
|
+
message: "must be $ and 1 to 4 hex digits, for example $c000 or $fb. A tool takes no names: get the address of a name with knowledge.ts at <name> of the c64-knowledge skill",
|
|
32
|
+
})
|
|
33
|
+
.transform((value) => c64AddressOf(value)!)
|
|
34
|
+
.describe("C64 address: $ and 1 to 4 hex digits, $0000 to $ffff");
|
|
35
|
+
|
|
36
|
+
const rasterLine = z.number().int().min(0).max(MAX_RASTER_LINE).describe("raster line: PAL 0-311, NTSC 0-262");
|
|
37
|
+
|
|
38
|
+
const condition = z
|
|
39
|
+
.discriminatedUnion("kind", [
|
|
40
|
+
z.object({ kind: z.literal("register"), register: z.enum(CONDITION_REGISTERS), operator: z.enum(COMPARISONS), value: Byte }).strict(),
|
|
41
|
+
z.object({ kind: z.literal("memory"), address: AddressInput, operator: z.enum(COMPARISONS), value: Byte, space: SpaceInput, view: MemoryViewInput }).strict(),
|
|
42
|
+
z.object({ kind: z.literal("raster"), line: rasterLine, cycle: z.number().int().min(0).max(64).optional().describe("cycle in the line: PAL 0-62, NTSC 0-64. True from this cycle on") }).strict(),
|
|
43
|
+
])
|
|
44
|
+
.describe("register: a CPU register compared to a byte. memory: a byte in memory compared to a byte. raster: the raster position");
|
|
45
|
+
|
|
46
|
+
const flags = z.object({ n: z.boolean(), v: z.boolean(), b: z.boolean(), d: z.boolean(), i: z.boolean(), z: z.boolean(), c: z.boolean() });
|
|
47
|
+
|
|
48
|
+
export const TOOL_INPUTS = {
|
|
49
|
+
condition,
|
|
50
|
+
|
|
51
|
+
runTarget: z.discriminatedUnion("kind", [
|
|
52
|
+
z.object({ kind: z.literal("address"), address: AddressInput, space: SpaceInput, condition: condition.optional().describe("stop at the address only when this is true") }).strict(),
|
|
53
|
+
z.object({ kind: z.literal("memory"), address: AddressInput, operator: z.enum(COMPARISONS), value: Byte, space: SpaceInput, view: MemoryViewInput }).strict(),
|
|
54
|
+
z.object({ kind: z.literal("raster"), line: rasterLine, cycle: z.number().int().min(0).max(64).optional().describe("cycle in the line: PAL 0-62, NTSC 0-64. Default 0") }).strict(),
|
|
55
|
+
]),
|
|
56
|
+
|
|
57
|
+
timeoutFrames: z.number().int().min(1).max(MAX_TIMEOUT_FRAMES).default(DEFAULT_TIMEOUT_FRAMES),
|
|
58
|
+
|
|
59
|
+
memoryWrite: z
|
|
60
|
+
.object({
|
|
61
|
+
address: AddressInput,
|
|
62
|
+
data: HexDataInput.describe("bytes as hex, two digits per byte, no separators, for example a9008d20d0"),
|
|
63
|
+
space: SpaceInput,
|
|
64
|
+
view: MemoryViewInput,
|
|
65
|
+
})
|
|
66
|
+
.strict()
|
|
67
|
+
.refine((value) => value.data.length / 2 <= MAX_MEMORY_WRITE, { message: `data has at most ${MAX_MEMORY_WRITE} bytes`, path: ["data"] }),
|
|
68
|
+
|
|
69
|
+
flags,
|
|
70
|
+
|
|
71
|
+
registerValues: z
|
|
72
|
+
.object({
|
|
73
|
+
pc: AddressInput.optional(),
|
|
74
|
+
a: Byte.optional(),
|
|
75
|
+
x: Byte.optional(),
|
|
76
|
+
y: Byte.optional(),
|
|
77
|
+
sp: Byte.optional(),
|
|
78
|
+
flags: flags.partial().strict().optional().describe("any of n, v, b, d, i, z and c. Flags that you do not name keep their value"),
|
|
79
|
+
})
|
|
80
|
+
.strict()
|
|
81
|
+
.refine((value) => Object.entries(value).some(([name, item]) => item !== undefined && !(name === "flags" && Object.keys(item as object).length === 0)), { message: "name at least one register" }),
|
|
82
|
+
|
|
83
|
+
joystick: z
|
|
84
|
+
.object({
|
|
85
|
+
port: z.union([z.literal(1), z.literal(2)]).describe("control port 1 or 2"),
|
|
86
|
+
direction: z.enum(JOYSTICK_DIRECTIONS),
|
|
87
|
+
fire: z.boolean().default(false),
|
|
88
|
+
})
|
|
89
|
+
.strict(),
|
|
90
|
+
|
|
91
|
+
keyboard: z
|
|
92
|
+
.object({
|
|
93
|
+
mode: z.enum(["text", "petscii"]),
|
|
94
|
+
text: z.string().min(1).optional().describe("mode text: the text to type"),
|
|
95
|
+
bytes: z.array(z.number().int().min(1).max(255)).min(1).optional().describe("mode petscii: PETSCII codes to type"),
|
|
96
|
+
})
|
|
97
|
+
.strict()
|
|
98
|
+
.superRefine((value, context) => {
|
|
99
|
+
if (value.mode === "text" && (value.text === undefined || value.bytes !== undefined)) return context.addIssue({ code: "custom", message: "mode text takes text and no bytes" });
|
|
100
|
+
if (value.mode === "petscii" && (value.bytes === undefined || value.text !== undefined)) return context.addIssue({ code: "custom", message: "mode petscii takes bytes and no text" });
|
|
101
|
+
const keys = keyboardBytes(value);
|
|
102
|
+
if (typeof keys === "string") context.addIssue({ code: "custom", message: keys, path: ["text"] });
|
|
103
|
+
else if (keys.length > MAX_KEYBOARD_BYTES) context.addIssue({ code: "custom", message: `the keyboard queue takes at most ${MAX_KEYBOARD_BYTES} keys at once. This input has ${keys.length} keys` });
|
|
104
|
+
}),
|
|
105
|
+
};
|
package/src/mcp/output.ts
CHANGED
|
@@ -3,11 +3,9 @@
|
|
|
3
3
|
// host never sees the path. The project is the folder that `project` names,
|
|
4
4
|
// or the one that the project rules find from the folder the agent started in.
|
|
5
5
|
|
|
6
|
-
import { createHash } from "node:crypto";
|
|
7
|
-
|
|
8
6
|
import { z } from "zod";
|
|
9
7
|
|
|
10
|
-
import { projectFolder,
|
|
8
|
+
import { projectFolder, writeReported, type Written } from "../host-client/transfer.ts";
|
|
11
9
|
|
|
12
10
|
export const OutInput = z.string().min(1).describe("write the result to this file, relative to the project folder, in place of returning it. An existing file is overwritten");
|
|
13
11
|
|
|
@@ -17,13 +15,6 @@ export const ProjectInput = z
|
|
|
17
15
|
.min(1)
|
|
18
16
|
.describe("the project folder, relative to the folder where the agent started. Give it when that folder is not in a project and has projects below it. The paths of this call are relative to the project folder");
|
|
19
17
|
|
|
20
|
-
/** What a result says in place of the bytes it wrote. */
|
|
21
|
-
export interface Written {
|
|
22
|
-
out: string;
|
|
23
|
-
bytes: number;
|
|
24
|
-
sha256: string;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
18
|
/** The fields of a result with out, optional in an output schema that also has the inline form. */
|
|
28
19
|
export const WrittenOutput = {
|
|
29
20
|
out: z.string().optional(),
|
|
@@ -33,7 +24,5 @@ export const WrittenOutput = {
|
|
|
33
24
|
|
|
34
25
|
/** Writes `data` to the file `path` of the project that `project` names (or the project lookup finds), and gives what the result shows in its place. */
|
|
35
26
|
export function writeOut(path: string, data: Uint8Array | string, project: string | undefined): Written {
|
|
36
|
-
|
|
37
|
-
writeProjectFile(path, bytes, { what: "out", root: projectFolder(project) });
|
|
38
|
-
return { out: path, bytes: bytes.length, sha256: createHash("sha256").update(bytes).digest("hex") };
|
|
27
|
+
return writeReported(path, data, { what: "out", root: projectFolder(project) });
|
|
39
28
|
}
|
package/src/mcp/server.ts
CHANGED
|
@@ -7,8 +7,10 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
|
7
7
|
import { CallToolRequestSchema, ListToolsRequestSchema, type CallToolResult, type Tool as McpTool } from "@modelcontextprotocol/sdk/types.js";
|
|
8
8
|
import { z } from "zod";
|
|
9
9
|
|
|
10
|
-
import { formatC64Address
|
|
10
|
+
import { formatC64Address } from "../c64.ts";
|
|
11
|
+
import { Byte, TOOL_INPUTS } from "./inputs.ts";
|
|
11
12
|
import { LostBeforeAnswer, type ViceSessionClient } from "../host-client/vice-session.ts";
|
|
13
|
+
import { isRefusal } from "../errors.ts";
|
|
12
14
|
import { shownMessage, WireFailure, type WireError } from "../protocol/messages.ts";
|
|
13
15
|
import { COMPARISONS, CONDITION_REGISTERS, MEMORY_VIEWS, SPACES, type Condition } from "../protocol/vice.ts";
|
|
14
16
|
import { elapsedMs, trace, type TraceFields } from "../trace.ts";
|
|
@@ -62,35 +64,14 @@ export type SessionSource = () => Promise<ViceSessionApi>;
|
|
|
62
64
|
type JsonObject = Record<string, unknown>;
|
|
63
65
|
|
|
64
66
|
// ---------------------------------------------------------------------------
|
|
65
|
-
// Public schema primitives shared by the tool groups
|
|
66
|
-
|
|
67
|
-
/** Input address: "$" and four hex digits, either case. Parses to a number. */
|
|
68
|
-
export const AddressInput = z
|
|
69
|
-
.string()
|
|
70
|
-
.regex(/^\$[0-9a-fA-F]{4}$/, "must be $ followed by four hex digits, for example $c000")
|
|
71
|
-
.transform((value) => parseC64Address(value))
|
|
72
|
-
.describe("C64 address, $0000 to $ffff");
|
|
67
|
+
// Public schema primitives shared by the tool groups. The inputs that change or
|
|
68
|
+
// wait for the machine are in inputs.ts: the action steps take the same inputs.
|
|
73
69
|
|
|
74
70
|
/** Output address: "$" and four lowercase hex digits. */
|
|
75
71
|
export const AddressOutput = z.string().regex(/^\$[0-9a-f]{4}$/).describe("C64 address, $0000 to $ffff");
|
|
76
72
|
|
|
77
|
-
export const Byte = z.number().int().min(0).max(255);
|
|
78
|
-
|
|
79
73
|
export const HexData = z.string().regex(/^(?:[0-9a-f]{2})*$/).describe("bytes as lowercase hex, two digits per byte, no separators");
|
|
80
74
|
|
|
81
|
-
/** Input bytes: hex in either case, two digits per byte, at least one byte. Parses to lowercase. */
|
|
82
|
-
export const HexDataInput = z
|
|
83
|
-
.string()
|
|
84
|
-
.regex(/^(?:[0-9a-fA-F]{2})+$/, "must be hex bytes, two digits per byte, no separators, for example a9008d20d0")
|
|
85
|
-
.transform((value) => value.toLowerCase());
|
|
86
|
-
|
|
87
|
-
export const SpaceInput = z.enum(SPACES).default("c64").describe("c64 is the computer. drive8 is the 1541 disk drive CPU");
|
|
88
|
-
|
|
89
|
-
export const MemoryViewInput = z
|
|
90
|
-
.enum(MEMORY_VIEWS)
|
|
91
|
-
.default("cpu")
|
|
92
|
-
.describe("cpu reads what the CPU sees now (ROM and I/O where banked in). ram reads the RAM underneath (c64 only)");
|
|
93
|
-
|
|
94
75
|
/** A name the agent gives to something the session keeps, such as a screen baseline or a snapshot. */
|
|
95
76
|
export const TransientName = z
|
|
96
77
|
.string()
|
|
@@ -132,37 +113,6 @@ export interface ToolDefinition<I extends z.ZodObject = z.ZodObject, O extends z
|
|
|
132
113
|
run(input: z.output<I>, session: ViceSessionApi): Promise<z.input<O> | ToolOutput<z.input<O>>>;
|
|
133
114
|
}
|
|
134
115
|
|
|
135
|
-
/** A typed condition: register, memory or raster. No expression language. */
|
|
136
|
-
export const ConditionInput = z
|
|
137
|
-
.discriminatedUnion("kind", [
|
|
138
|
-
z
|
|
139
|
-
.object({
|
|
140
|
-
kind: z.literal("register"),
|
|
141
|
-
register: z.enum(CONDITION_REGISTERS),
|
|
142
|
-
operator: z.enum(COMPARISONS),
|
|
143
|
-
value: Byte,
|
|
144
|
-
})
|
|
145
|
-
.strict(),
|
|
146
|
-
z
|
|
147
|
-
.object({
|
|
148
|
-
kind: z.literal("memory"),
|
|
149
|
-
address: AddressInput,
|
|
150
|
-
operator: z.enum(COMPARISONS),
|
|
151
|
-
value: Byte,
|
|
152
|
-
space: SpaceInput,
|
|
153
|
-
view: MemoryViewInput,
|
|
154
|
-
})
|
|
155
|
-
.strict(),
|
|
156
|
-
z
|
|
157
|
-
.object({
|
|
158
|
-
kind: z.literal("raster"),
|
|
159
|
-
line: z.number().int().min(0).describe("raster line: PAL 0-311, NTSC 0-262"),
|
|
160
|
-
cycle: z.number().int().min(0).optional().describe("cycle in the line: PAL 0-62, NTSC 0-64. True from this cycle on"),
|
|
161
|
-
})
|
|
162
|
-
.strict(),
|
|
163
|
-
])
|
|
164
|
-
.describe("register: a CPU register compared to a byte. memory: a byte in memory compared to a byte. raster: the raster position");
|
|
165
|
-
|
|
166
116
|
/** A condition as a result shows it: the input form, with the memory address as "$xxxx". */
|
|
167
117
|
export const ConditionOutput = z
|
|
168
118
|
.discriminatedUnion("kind", [
|
|
@@ -183,7 +133,7 @@ export function defined<T extends Record<string, unknown>>(value: T): T {
|
|
|
183
133
|
}
|
|
184
134
|
|
|
185
135
|
/** A parsed condition input as the protocol type. */
|
|
186
|
-
export function toCondition(input: z.output<typeof
|
|
136
|
+
export function toCondition(input: z.output<typeof TOOL_INPUTS.condition>): Condition {
|
|
187
137
|
return defined(input) as Condition;
|
|
188
138
|
}
|
|
189
139
|
|
|
@@ -208,8 +158,9 @@ export function toolListing(tool: ToolDefinition): McpTool {
|
|
|
208
158
|
};
|
|
209
159
|
}
|
|
210
160
|
|
|
161
|
+
/** An error in the one form of the toolkit: {"error":{"code","message"}}, as a script prints it. */
|
|
211
162
|
function errorResult(error: WireError): CallToolResult {
|
|
212
|
-
return { isError: true, content: [{ type: "text", text: JSON.stringify({ code: error.code, message: shownMessage(error) }) }] };
|
|
163
|
+
return { isError: true, content: [{ type: "text", text: JSON.stringify({ error: { code: error.code, message: shownMessage(error) } }) }] };
|
|
213
164
|
}
|
|
214
165
|
|
|
215
166
|
/** "address: Invalid string: must match ..." style messages, one sentence per problem. */
|
|
@@ -244,7 +195,7 @@ function outcome(result: CallToolResult): TraceFields {
|
|
|
244
195
|
const first = result.content[0];
|
|
245
196
|
const text = first?.type === "text" ? first.text : "";
|
|
246
197
|
try {
|
|
247
|
-
return { ok: false, ...(JSON.parse(text) as WireError) };
|
|
198
|
+
return { ok: false, ...(JSON.parse(text) as { error: WireError }).error };
|
|
248
199
|
} catch {
|
|
249
200
|
return { ok: false, message: text };
|
|
250
201
|
}
|
|
@@ -282,7 +233,7 @@ async function runCall(
|
|
|
282
233
|
const extra = output instanceof ToolOutput ? output.content : [];
|
|
283
234
|
return { content: [{ type: "text", text: JSON.stringify(result) }, ...extra], structuredContent: result };
|
|
284
235
|
} catch (error) {
|
|
285
|
-
if (error
|
|
236
|
+
if (isRefusal(error)) return errorResult({ code: error.code, message: error.message });
|
|
286
237
|
log(`${name} failed: ${error instanceof Error ? (error.stack ?? error.message) : String(error)}`);
|
|
287
238
|
return errorResult(INTERNAL_FAILURE);
|
|
288
239
|
}
|
|
@@ -299,7 +250,8 @@ function packageVersion(): string {
|
|
|
299
250
|
|
|
300
251
|
const INSTRUCTIONS =
|
|
301
252
|
"These tools control one live Commodore 64 emulator that belongs to this session. " +
|
|
302
|
-
"
|
|
253
|
+
"An address is a dollar sign and 1 to 4 hex digits, for example $c000 or $fb. A result gives four hex digits. " +
|
|
254
|
+
"An error is {\"error\": {\"code\", \"message\"}}. " +
|
|
303
255
|
"Memory data is lowercase hex without separators. " +
|
|
304
256
|
"Other register values and chip values are decimal numbers. " +
|
|
305
257
|
"A read does not change the run state of the machine.";
|
package/src/mcp/tools/debug.ts
CHANGED
|
@@ -8,7 +8,8 @@ import { WireFailure } from "../../protocol/messages.ts";
|
|
|
8
8
|
import { CHECKPOINT_ACTIONS, MAX_BACKTRACE, MAX_HISTORY, MAX_TRACE_FRAMES, MAX_TRACE_HITS, MAX_TRACE_POINTS, TRACE_KINDS, TRACE_ENDS, MAX_MEMMAP_RANGES, MAX_MEMORY_READ, MAX_PROFILE, MAX_WATCH_SIZE, SPACES, TIMING_ACTIONS, WATCH_ACCESS, type Breakpoint, type Watchpoint } from "../../protocol/vice.ts";
|
|
9
9
|
import { readCoverage } from "../../host-client/coverage.ts";
|
|
10
10
|
import { OutInput, ProjectInput, writeOut, WrittenOutput } from "../output.ts";
|
|
11
|
-
import { AddressInput,
|
|
11
|
+
import { AddressInput, Byte, SpaceInput, TOOL_INPUTS } from "../inputs.ts";
|
|
12
|
+
import { AddressOutput, ConditionOutput, defineTool, HexData, requireFields, showCondition, toCondition } from "../server.ts";
|
|
12
13
|
import { spaced } from "./memory.ts";
|
|
13
14
|
|
|
14
15
|
/** No default here: a filled-in default would count as a field the other actions refuse. */
|
|
@@ -23,7 +24,7 @@ const BreakpointOutput = z.object({
|
|
|
23
24
|
enabled: z.boolean(),
|
|
24
25
|
condition: ConditionOutput.optional(),
|
|
25
26
|
});
|
|
26
|
-
const WatchpointOutput = BreakpointOutput.
|
|
27
|
+
const WatchpointOutput = BreakpointOutput.omit({ address: true }).extend({ start: AddressOutput, end: AddressOutput, access: z.enum(WATCH_ACCESS) });
|
|
27
28
|
|
|
28
29
|
function showBreakpoint(point: Breakpoint) {
|
|
29
30
|
const { condition, ...rest } = point;
|
|
@@ -31,8 +32,8 @@ function showBreakpoint(point: Breakpoint) {
|
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
function showWatchpoint(point: Watchpoint) {
|
|
34
|
-
const { condition, ...rest } = point;
|
|
35
|
-
return { ...rest, address: formatC64Address(
|
|
35
|
+
const { condition, address, size, ...rest } = point;
|
|
36
|
+
return { ...rest, start: formatC64Address(address), end: formatC64Address(address + size - 1), ...(condition === undefined ? {} : { condition: showCondition(condition) }) };
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
export const c64Breakpoint = defineTool({
|
|
@@ -49,7 +50,7 @@ export const c64Breakpoint = defineTool({
|
|
|
49
50
|
action: z.enum(CHECKPOINT_ACTIONS),
|
|
50
51
|
address: AddressInput.optional().describe("add only"),
|
|
51
52
|
space: OptionalSpace,
|
|
52
|
-
condition:
|
|
53
|
+
condition: TOOL_INPUTS.condition.optional().describe("add only: stop only when this is true"),
|
|
53
54
|
id: Id.optional().describe("remove, enable and disable only"),
|
|
54
55
|
})
|
|
55
56
|
.strict(),
|
|
@@ -83,18 +84,18 @@ export const c64Watchpoint = defineTool({
|
|
|
83
84
|
title: "C64 watchpoints",
|
|
84
85
|
description:
|
|
85
86
|
"Manage watchpoints. A watchpoint stops the CPU when an instruction reads or writes memory in a range. " +
|
|
86
|
-
`Action add sets one
|
|
87
|
+
`Action add sets one from start to end (at most ${MAX_WATCH_SIZE} bytes, end defaults to start), with access read, write or read-write. ` +
|
|
87
88
|
"With a condition, it stops only when the condition is true. For a write, a memory condition sees the memory after the write. " +
|
|
88
89
|
"add returns the new id. Breakpoints and watchpoints take their ids from one counter, so the ids of one kind can have gaps. " +
|
|
89
90
|
"Actions remove, enable and disable take that id. remove returns removed true and the id. Action list gives all watchpoints. Each watchpoint shows its condition, when it has one.",
|
|
90
91
|
inputSchema: z
|
|
91
92
|
.object({
|
|
92
93
|
action: z.enum(CHECKPOINT_ACTIONS),
|
|
93
|
-
|
|
94
|
-
|
|
94
|
+
start: AddressInput.optional().describe("add only: the first address"),
|
|
95
|
+
end: AddressInput.optional().describe("add only: the last address. Default start"),
|
|
95
96
|
access: z.enum(WATCH_ACCESS).optional().describe("add only"),
|
|
96
97
|
space: OptionalSpace,
|
|
97
|
-
condition:
|
|
98
|
+
condition: TOOL_INPUTS.condition.optional().describe("add only: stop only when this is true"),
|
|
98
99
|
id: Id.optional().describe("remove, enable and disable only"),
|
|
99
100
|
})
|
|
100
101
|
.strict(),
|
|
@@ -112,11 +113,15 @@ export const c64Watchpoint = defineTool({
|
|
|
112
113
|
if (action === "remove") return session.watchpoint({ action, id: fields.id! });
|
|
113
114
|
return showWatchpoint(await session.watchpoint({ action, id: fields.id! }));
|
|
114
115
|
}
|
|
115
|
-
requireFields(action, fields, ["
|
|
116
|
+
requireFields(action, fields, ["start", "end", "access", "space", "condition"], ["start", "access"]);
|
|
117
|
+
const start = fields.start!;
|
|
118
|
+
const end = fields.end ?? start;
|
|
119
|
+
if (end < start) throw new WireFailure("invalid-input", "end must not be before start.");
|
|
120
|
+
if (end - start + 1 > MAX_WATCH_SIZE) throw new WireFailure("invalid-input", `A watchpoint covers at most ${MAX_WATCH_SIZE} bytes. This one covers ${end - start + 1}.`);
|
|
116
121
|
const added = await session.watchpoint({
|
|
117
122
|
action,
|
|
118
|
-
address:
|
|
119
|
-
size:
|
|
123
|
+
address: start,
|
|
124
|
+
size: end - start + 1,
|
|
120
125
|
access: fields.access!,
|
|
121
126
|
space: fields.space ?? "c64",
|
|
122
127
|
...(fields.condition === undefined ? {} : { condition: toCondition(fields.condition) }),
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
|
|
5
5
|
import { formatC64Address } from "../../c64.ts";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
6
|
+
import { EXECUTION_ACTIONS, MAX_EXECUTION_COUNT, MAX_HISTORY, MAX_TIMEOUT_FRAMES, POINT_STOP_REASONS, RUN_STATES, SPACES, STOP_REASONS, type RunTarget } from "../../protocol/vice.ts";
|
|
7
|
+
import { Byte, SpaceInput, TOOL_INPUTS } from "../inputs.ts";
|
|
8
|
+
import { AddressOutput, defineTool, toCondition, ToolOutput } from "../server.ts";
|
|
8
9
|
import { c64Observe } from "./video.ts";
|
|
9
10
|
|
|
10
11
|
export const c64Execution = defineTool({
|
|
@@ -57,34 +58,6 @@ export const c64Execution = defineTool({
|
|
|
57
58
|
},
|
|
58
59
|
});
|
|
59
60
|
|
|
60
|
-
const RunTargetInput = z.discriminatedUnion("kind", [
|
|
61
|
-
z
|
|
62
|
-
.object({
|
|
63
|
-
kind: z.literal("address"),
|
|
64
|
-
address: AddressInput,
|
|
65
|
-
space: SpaceInput,
|
|
66
|
-
condition: ConditionInput.optional().describe("stop at the address only when this is true"),
|
|
67
|
-
})
|
|
68
|
-
.strict(),
|
|
69
|
-
z
|
|
70
|
-
.object({
|
|
71
|
-
kind: z.literal("memory"),
|
|
72
|
-
address: AddressInput,
|
|
73
|
-
operator: z.enum(COMPARISONS),
|
|
74
|
-
value: Byte,
|
|
75
|
-
space: SpaceInput,
|
|
76
|
-
view: MemoryViewInput,
|
|
77
|
-
})
|
|
78
|
-
.strict(),
|
|
79
|
-
z
|
|
80
|
-
.object({
|
|
81
|
-
kind: z.literal("raster"),
|
|
82
|
-
line: z.number().int().min(0).describe("raster line: PAL 0-311, NTSC 0-262"),
|
|
83
|
-
cycle: z.number().int().min(0).optional().describe("cycle in the line: PAL 0-62, NTSC 0-64. Default 0"),
|
|
84
|
-
})
|
|
85
|
-
.strict(),
|
|
86
|
-
]);
|
|
87
|
-
|
|
88
61
|
export const c64RunUntil = defineTool({
|
|
89
62
|
name: "c64_run_until",
|
|
90
63
|
title: "Run the C64 until a target",
|
|
@@ -103,8 +76,8 @@ export const c64RunUntil = defineTool({
|
|
|
103
76
|
"The CPU is always stopped after this tool.",
|
|
104
77
|
inputSchema: z
|
|
105
78
|
.object({
|
|
106
|
-
target:
|
|
107
|
-
timeoutFrames:
|
|
79
|
+
target: TOOL_INPUTS.runTarget,
|
|
80
|
+
timeoutFrames: TOOL_INPUTS.timeoutFrames,
|
|
108
81
|
observe: c64Observe.inputSchema.optional().describe("what to read at the stop, as for c64_observe"),
|
|
109
82
|
history: z.number().int().min(1).max(MAX_HISTORY).optional().describe("the number of instructions before the stop to give"),
|
|
110
83
|
})
|
package/src/mcp/tools/input.ts
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
|
|
5
|
-
import { textToPetscii } from "../../c64.ts";
|
|
6
|
-
import { WireFailure } from "../../protocol/messages.ts";
|
|
7
5
|
import { JOYSTICK_DIRECTIONS, MAX_KEYBOARD_BYTES } from "../../protocol/vice.ts";
|
|
6
|
+
import { keyboardBytes } from "../../host-client/steps.ts";
|
|
7
|
+
import { TOOL_INPUTS } from "../inputs.ts";
|
|
8
8
|
import { defineTool } from "../server.ts";
|
|
9
9
|
|
|
10
10
|
const EMULATOR_FLAW = z.string().optional().describe("a known flaw of the emulator version that affects input, and the version that fixes it");
|
|
@@ -15,35 +15,16 @@ export const c64Keyboard = defineTool({
|
|
|
15
15
|
description:
|
|
16
16
|
`Type up to ${MAX_KEYBOARD_BYTES} keys into the C64 keyboard queue. The C64 reads them while it runs. ` +
|
|
17
17
|
"Only a program that reads keys through the KERNAL sees them. A program that reads the keyboard hardware directly does not see them. Many games do that. " +
|
|
18
|
-
"No
|
|
18
|
+
"No tool presses one key of the keyboard matrix. For one specific key, ask the user to press it in the window, or give a press step in a record file of c64-testing. " +
|
|
19
19
|
'Mode text types text, for example "RUN\\n". Letters of either case type the normal (unshifted) key. A line break types RETURN. ' +
|
|
20
20
|
"Mode petscii types PETSCII codes 1 to 255 in bytes, for example [147] to clear the screen. " +
|
|
21
21
|
"This does not change the run state of the machine. emulatorFlaw in the result names a known flaw of the emulator version that affects input, and the version that fixes it.",
|
|
22
|
-
inputSchema:
|
|
23
|
-
.object({
|
|
24
|
-
mode: z.enum(["text", "petscii"]),
|
|
25
|
-
text: z.string().min(1).optional().describe("mode text: the text to type"),
|
|
26
|
-
bytes: z.array(z.number().int().min(1).max(255)).min(1).optional().describe("mode petscii: PETSCII codes to type"),
|
|
27
|
-
})
|
|
28
|
-
.strict(),
|
|
22
|
+
inputSchema: TOOL_INPUTS.keyboard,
|
|
29
23
|
outputSchema: z.object({ queuedBytes: z.number().int().min(1).max(MAX_KEYBOARD_BYTES), emulatorFlaw: EMULATOR_FLAW }),
|
|
30
24
|
readOnly: false,
|
|
31
25
|
async run(input, session) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
if (input.text === undefined || input.bytes !== undefined) throw new WireFailure("invalid-input", "Mode text takes text and no bytes.");
|
|
35
|
-
try {
|
|
36
|
-
petscii = textToPetscii(input.text);
|
|
37
|
-
} catch (error) {
|
|
38
|
-
throw new WireFailure("invalid-input", `${(error as Error).message}. Use mode petscii for other keys.`);
|
|
39
|
-
}
|
|
40
|
-
} else {
|
|
41
|
-
if (input.bytes === undefined || input.text !== undefined) throw new WireFailure("invalid-input", "Mode petscii takes bytes and no text.");
|
|
42
|
-
petscii = Uint8Array.from(input.bytes);
|
|
43
|
-
}
|
|
44
|
-
if (petscii.length > MAX_KEYBOARD_BYTES) {
|
|
45
|
-
throw new WireFailure("invalid-input", `The keyboard queue takes at most ${MAX_KEYBOARD_BYTES} keys at once. This input has ${petscii.length} keys.`);
|
|
46
|
-
}
|
|
26
|
+
// The schema checked the keys: keyboardBytes gives bytes here.
|
|
27
|
+
const petscii = keyboardBytes(input) as Uint8Array;
|
|
47
28
|
return session.keyboard(petscii);
|
|
48
29
|
},
|
|
49
30
|
});
|
|
@@ -55,13 +36,7 @@ export const c64Joystick = defineTool({
|
|
|
55
36
|
"Set the joystick in control port 1 or 2: a direction (or center) and the fire button. " +
|
|
56
37
|
"The C64 sees this state until you change it. To release the joystick, set direction center and fire false. " +
|
|
57
38
|
"Most games read port 2. This does not change the run state of the machine. emulatorFlaw in the result names a known flaw of the emulator version that affects input, and the version that fixes it.",
|
|
58
|
-
inputSchema:
|
|
59
|
-
.object({
|
|
60
|
-
port: z.union([z.literal(1), z.literal(2)]).describe("control port 1 or 2"),
|
|
61
|
-
direction: z.enum(JOYSTICK_DIRECTIONS),
|
|
62
|
-
fire: z.boolean().default(false),
|
|
63
|
-
})
|
|
64
|
-
.strict(),
|
|
39
|
+
inputSchema: TOOL_INPUTS.joystick,
|
|
65
40
|
outputSchema: z.object({ port: z.union([z.literal(1), z.literal(2)]), direction: z.enum(JOYSTICK_DIRECTIONS), fire: z.boolean(), emulatorFlaw: EMULATOR_FLAW }),
|
|
66
41
|
readOnly: false,
|
|
67
42
|
async run(input, session) {
|
package/src/mcp/tools/media.ts
CHANGED
|
@@ -8,7 +8,8 @@ import { formatC64Address } from "../../c64.ts";
|
|
|
8
8
|
import { projectFolder, readProjectFile } from "../../host-client/transfer.ts";
|
|
9
9
|
import { AUTOSTART_TYPES, DISK_TYPES, MAX_SNAPSHOTS, RUN_STATES } from "../../protocol/vice.ts";
|
|
10
10
|
import { OutInput, ProjectInput, writeOut, WrittenOutput } from "../output.ts";
|
|
11
|
-
import { AddressInput
|
|
11
|
+
import { AddressInput } from "../inputs.ts";
|
|
12
|
+
import { AddressOutput, defineTool, requireFields, TransientName } from "../server.ts";
|
|
12
13
|
|
|
13
14
|
const ProjectPath = z.string().min(1).describe("path relative to the project folder, for example build/game.prg");
|
|
14
15
|
|
|
@@ -91,11 +92,11 @@ export const c64Snapshot = defineTool({
|
|
|
91
92
|
"With out, save also writes it to that project file as a snapshot file (.vsf), so a later session can use it. " +
|
|
92
93
|
"Action restore puts the machine back in that state and stops the CPU. Give name for a snapshot of this session, or file for a .vsf in the project. " +
|
|
93
94
|
"A file restores only in the emulator version and with the machine settings that saved it. The c64_timing stopwatch keeps its reading. " +
|
|
94
|
-
`Action list gives the names, and action
|
|
95
|
+
`Action list gives the names, and action remove removes one. A session keeps at most ${MAX_SNAPSHOTS} snapshots until it ends.`,
|
|
95
96
|
inputSchema: z
|
|
96
97
|
.object({
|
|
97
|
-
action: z.enum(["save", "restore", "list", "
|
|
98
|
-
name: TransientName.optional().describe("save and
|
|
98
|
+
action: z.enum(["save", "restore", "list", "remove"]),
|
|
99
|
+
name: TransientName.optional().describe("save and remove, and restore without file: 1 to 64 letters, digits, dots, underscores or hyphens"),
|
|
99
100
|
out: OutInput.optional().describe("save only: also write the snapshot to this project file, for example snapshots/room0.vsf. An existing file is overwritten"),
|
|
100
101
|
file: ProjectPath.optional().describe("restore only: a .vsf file in the project, in place of name"),
|
|
101
102
|
project: ProjectInput.optional().describe("save with out, and restore with file: the project folder, relative to the folder where the agent started. Give it when that folder is not in a project and has projects below it"),
|
|
@@ -107,7 +108,7 @@ export const c64Snapshot = defineTool({
|
|
|
107
108
|
restored: z.boolean().optional(),
|
|
108
109
|
state: z.enum(RUN_STATES).optional(),
|
|
109
110
|
snapshots: z.array(z.string()).optional(),
|
|
110
|
-
|
|
111
|
+
removed: z.boolean().optional(),
|
|
111
112
|
...WrittenOutput,
|
|
112
113
|
}),
|
|
113
114
|
readOnly: false,
|
package/src/mcp/tools/memory.ts
CHANGED
|
@@ -7,7 +7,8 @@ import { formatC64Address } from "../../c64.ts";
|
|
|
7
7
|
import { WireFailure } from "../../protocol/messages.ts";
|
|
8
8
|
import { MAX_COMPARE_DIFFERENCES, MAX_COMPARE_SIZE, MAX_DISASSEMBLE, MAX_MEMORY_READ, MAX_MEMORY_WRITE, MAX_SEARCH_PATTERN, MAX_SEARCH_RESULTS, type RegisterValues } from "../../protocol/vice.ts";
|
|
9
9
|
import { OutInput, ProjectInput, writeOut, WrittenOutput } from "../output.ts";
|
|
10
|
-
import { AddressInput,
|
|
10
|
+
import { AddressInput, Byte, MemoryViewInput, SpaceInput, TOOL_INPUTS } from "../inputs.ts";
|
|
11
|
+
import { AddressOutput, defineTool, HexData, requireFields } from "../server.ts";
|
|
11
12
|
|
|
12
13
|
/** With out, a read or a disassembly can be larger than one answer of the session: it is read in parts. */
|
|
13
14
|
const MAX_MEMORY_READ_OUT = 0x10000;
|
|
@@ -61,14 +62,7 @@ export const c64MemoryWrite = defineTool({
|
|
|
61
62
|
"The range must not go past $ffff. With view cpu, the write goes where the CPU writes now. " +
|
|
62
63
|
"A write to a ROM address goes to the RAM under it, and a write to I/O goes to the chip. " +
|
|
63
64
|
"With view ram, the write goes to RAM (c64 only).",
|
|
64
|
-
inputSchema:
|
|
65
|
-
.object({
|
|
66
|
-
address: AddressInput,
|
|
67
|
-
data: HexDataInput.describe("bytes as hex, two digits per byte, no separators, for example a9008d20d0"),
|
|
68
|
-
space: SpaceInput,
|
|
69
|
-
view: MemoryViewInput,
|
|
70
|
-
})
|
|
71
|
-
.strict(),
|
|
65
|
+
inputSchema: TOOL_INPUTS.memoryWrite,
|
|
72
66
|
outputSchema: z.object({ address: AddressOutput, bytesWritten: z.number().int().min(1).max(MAX_MEMORY_WRITE) }),
|
|
73
67
|
readOnly: false,
|
|
74
68
|
async run(input, session) {
|
|
@@ -77,47 +71,26 @@ export const c64MemoryWrite = defineTool({
|
|
|
77
71
|
},
|
|
78
72
|
});
|
|
79
73
|
|
|
80
|
-
const Flags = z.object({
|
|
81
|
-
n: z.boolean(),
|
|
82
|
-
v: z.boolean(),
|
|
83
|
-
b: z.boolean(),
|
|
84
|
-
d: z.boolean(),
|
|
85
|
-
i: z.boolean(),
|
|
86
|
-
z: z.boolean(),
|
|
87
|
-
c: z.boolean(),
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
const RegisterValuesInput = z
|
|
91
|
-
.object({
|
|
92
|
-
pc: AddressInput.optional(),
|
|
93
|
-
a: Byte.optional(),
|
|
94
|
-
x: Byte.optional(),
|
|
95
|
-
y: Byte.optional(),
|
|
96
|
-
sp: Byte.optional(),
|
|
97
|
-
flags: Flags.partial().strict().optional().describe("any of n, v, b, d, i, z and c. Flags that you do not name keep their value"),
|
|
98
|
-
})
|
|
99
|
-
.strict();
|
|
100
|
-
|
|
101
74
|
export const c64Registers = defineTool({
|
|
102
75
|
name: "c64_registers",
|
|
103
76
|
title: "C64 CPU registers",
|
|
104
77
|
description:
|
|
105
|
-
"
|
|
106
|
-
"Action
|
|
107
|
-
"Action
|
|
78
|
+
"Read or write the CPU registers: pc, a, x, y, sp and the status flags n, v, b, d, i, z and c. " +
|
|
79
|
+
"Action read reads them. This does not change the run state of the machine. " +
|
|
80
|
+
"Action write writes the registers that values names and returns all registers. Stop the CPU before you use write. " +
|
|
108
81
|
"Use space drive8 for the CPU of the 1541 disk drive.",
|
|
109
82
|
inputSchema: z
|
|
110
83
|
.object({
|
|
111
|
-
action: z.enum(["
|
|
84
|
+
action: z.enum(["read", "write"]),
|
|
112
85
|
space: SpaceInput,
|
|
113
|
-
values:
|
|
86
|
+
values: TOOL_INPUTS.registerValues.optional().describe("write only: the registers to write"),
|
|
114
87
|
})
|
|
115
88
|
.strict(),
|
|
116
|
-
outputSchema: z.object({ pc: AddressOutput, a: Byte, x: Byte, y: Byte, sp: Byte, flags:
|
|
89
|
+
outputSchema: z.object({ pc: AddressOutput, a: Byte, x: Byte, y: Byte, sp: Byte, flags: TOOL_INPUTS.flags }),
|
|
117
90
|
readOnly: false,
|
|
118
91
|
async run(input, session) {
|
|
119
92
|
let registers;
|
|
120
|
-
if (input.action === "
|
|
93
|
+
if (input.action === "read") {
|
|
121
94
|
requireFields(input.action, { values: input.values }, []);
|
|
122
95
|
registers = await session.registersGet(input.space);
|
|
123
96
|
} else {
|
|
@@ -130,7 +103,7 @@ export const c64Registers = defineTool({
|
|
|
130
103
|
const flags = Object.fromEntries(Object.entries(input.values?.flags ?? {}).filter(([, value]) => value !== undefined));
|
|
131
104
|
if (Object.keys(flags).length > 0) values.flags = flags;
|
|
132
105
|
if (Object.keys(values).length === 0) {
|
|
133
|
-
throw new WireFailure("invalid-input", "values must name at least one register for action
|
|
106
|
+
throw new WireFailure("invalid-input", "values must name at least one register for action write.");
|
|
134
107
|
}
|
|
135
108
|
registers = await session.registersSet(input.space, values);
|
|
136
109
|
}
|
package/src/mcp/tools/video.ts
CHANGED
|
@@ -7,7 +7,8 @@ import { formatC64Address, formatSprite, formatVicii } from "../../c64.ts";
|
|
|
7
7
|
import { WireFailure } from "../../protocol/messages.ts";
|
|
8
8
|
import { CIA_SELECTIONS, MAX_MASK_RECTANGLES, MAX_MEMORY_READ, MAX_OBSERVE_BYTES, MAX_OBSERVE_RANGES, SPACES, VICII_MODES, type ObserveParams } from "../../protocol/vice.ts";
|
|
9
9
|
import { OutInput, ProjectInput, writeOut, WrittenOutput } from "../output.ts";
|
|
10
|
-
import { AddressInput,
|
|
10
|
+
import { AddressInput, Byte, MemoryViewInput, SpaceInput } from "../inputs.ts";
|
|
11
|
+
import { AddressOutput, defineTool, HexData, requireFields, ToolOutput, TransientName } from "../server.ts";
|
|
11
12
|
|
|
12
13
|
const Color = z.number().int().min(0).max(15);
|
|
13
14
|
const NO_CHANGE = "This does not change the run state of the machine.";
|
|
@@ -138,13 +139,13 @@ export const c64Screen = defineTool({
|
|
|
138
139
|
"A mask that covers the whole frame is invalid input. comparedPixels is the number of pixels outside the mask. " +
|
|
139
140
|
"match is true when the part of compared pixels that differ is at most maxMismatchRatio (default 0). bounds holds every different pixel. " +
|
|
140
141
|
"With includeDiff, compare also returns an image with the different pixels in magenta. " +
|
|
141
|
-
"Action list gives the baseline names. Action
|
|
142
|
+
"Action list gives the baseline names. Action remove removes a baseline. Baselines stay until the session ends. " +
|
|
142
143
|
"The emulator draws frames while the CPU runs. After the CPU stops, the frame stays the same. " +
|
|
143
144
|
"This does not change the run state of the machine.",
|
|
144
145
|
inputSchema: z
|
|
145
146
|
.object({
|
|
146
|
-
action: z.enum(["capture", "compare", "list", "
|
|
147
|
-
baseline: TransientName.optional().describe("capture: keep the frame under this name. compare and
|
|
147
|
+
action: z.enum(["capture", "compare", "list", "remove"]),
|
|
148
|
+
baseline: TransientName.optional().describe("capture: keep the frame under this name. compare and remove: the baseline to use"),
|
|
148
149
|
maxMismatchRatio: z.number().min(0).max(1).optional().describe("compare only: 0 to 1, default 0"),
|
|
149
150
|
mask: z.array(Rectangle).max(MAX_MASK_RECTANGLES).optional().describe("compare only: rectangles to ignore, in frame pixels"),
|
|
150
151
|
includeDiff: z.boolean().optional().describe("compare only: also return a difference image"),
|
|
@@ -162,7 +163,7 @@ export const c64Screen = defineTool({
|
|
|
162
163
|
mismatchRatio: z.number().min(0).max(1).optional(),
|
|
163
164
|
bounds: Rectangle.optional(),
|
|
164
165
|
baselines: z.array(z.string()).optional(),
|
|
165
|
-
|
|
166
|
+
removed: z.boolean().optional(),
|
|
166
167
|
...WrittenOutput,
|
|
167
168
|
}),
|
|
168
169
|
readOnly: false,
|