Drive Wire Desk from your own code
Everything the web page does is available over HTTP: post two or more captured frames as hex, get
back either a named field map with a Kaitai spec, a Wireshark dissector and a Python parser, or a
read on which regions of the frame are compressed, encoded, obfuscated or genuinely encrypted. The
natural use is a CI job that re-runs the map whenever a firmware build changes the wire format and
fails the build when a field moves, or a script that walks a folder of saved captures overnight and
writes one protocol.ksy per device instead of one per afternoon of staring at hex.
The browser and the API send the same input object. The one thing the browser does that you do not have to reproduce is the free local prescan — see prescan, honestly below.
Base URL and the envelope
Every endpoint lives under https://api.skillsafe.ai/v1/app-api and every response uses
the same envelope, so one helper covers the whole API:
{ "ok": true, "data": { ... } }
{ "ok": false, "error": { "code": "...", "message": "...", "status": 402, "details": { ... } } }
Send your token as Authorization: Bearer aut_… and nothing else. There is
no app-slug header: an app-user token already binds the pair {app, subject}, so the
token alone says which app you are calling and who you are. The only call that names the app in its
body is the guest mint, and the only call that names the app in a header is none of them.
Error codes
| code | status | what to do |
|---|---|---|
unauthorized | 401 | The token is missing, malformed or expired. Get a new one from the token page. |
payment_required | 402 | The balance is below min_credits. Call /estimate for the lane you are about to run, then top up. |
forbidden | 403 | The token is valid but not for this app, or a guest token tried a metered run. Sign in for a personal token, or ask the owner to sponsor usage. |
not_found | 404 | Unknown job id, or the app slug in the guest mint does not exist. |
conflict | 409 | The same Idempotency-Key was replayed with a different body. Change the key or send the original input. |
validation_error | 422 | The input object is missing a required field — frames is the usual one — or a field is the wrong type. A body that is not valid JSON at all comes back as a 400. |
rate_limited | 429 | Too many requests. Back off and retry; do not tight-loop a poll. |
internal | 5xx | A server-side failure, reported as server_error on a plain 500. Retry with the SAME Idempotency-Key so you are not billed twice. |
Start with task: it picks the lane
task is the first field to decide and the only one that changes the shape of the
answer. It is the lane router. Two lanes read the same frames and return the same envelope, then
diverge completely in the body — a caller that reads fields[] out of an
armor reply finds an empty array, and a caller that reads regions[] out of
a map reply finds the same. Send it explicitly on every call; the reply echoes it back
in task, and that echo is what you branch on.
task | the question it answers | the body it adds | posture values |
|---|---|---|---|
map |
What do these bytes mean? Framing, endianness, and every field at its offset with a width, a type, a role, the observed values and the evidence that decided it — plus the checksum algorithm, the byte ranges that could not be named, and the next capture that would settle them. | framing, endianness, fields[], checksum, unresolved[], next_captures[] |
decoded, partial, opaque |
armor |
Which parts are deliberately hard, and what kind of hard? Per-region entropy readings, container signatures and block alignment decide compressed versus encoded versus cheaply obfuscated versus genuinely encrypted — with a confirmation test for each technique, the blockers, and the pivot when frame-staring cannot win. | regions[], techniques[], blockers[], pivots[] |
transparent, packed, obfuscated, encrypted, mixed |
The lanes are complementary, not alternatives, and the usual order is map first: it
produces unresolved[], and unresolved[] is exactly what the armour lane is
for. Carry it across in handoff and the second run starts where the first stopped
rather than re-deriving the header. Two lanes over one capture are two metered runs.
Worked example: task: "map"
Two frames off an RS-485 sensor bus. The request, trimmed to the fields that matter:
{
"task": "map",
"frames": "# frame 1 [tx] poll reply, bay A idle (19 bytes)\n0000: aa 55 11 21 00 0b 42 41 59 41 01 41 ac 00 00 12\n0010: d4 19 98\n# frame 2 [tx] poll reply, bay A heating (19 bytes)\n0000: aa 55 12 21 00 0b 42 41 59 41 05 41 b6 00 00 12\n0010: 6b 07 2b",
"frame_count": 2,
"transport": "serial",
"goal": "parse",
"context": "Two frames off an RS-485 bus between an HVAC controller and a bay sensor, 19200 8N1, the same bay polled a minute apart. The vendor documents nothing."
}
The reply, with the long strings cut for reading. Nothing is optional; every key is present:
{
"task": "map",
"title": "AA-55 framed sensor telemetry, 2 frames",
"posture": "decoded",
"confidence": "medium",
"verdict": "A length-prefixed AA-55 frame whose CRC-16/MODBUS trailer verifies in both frames, so every byte of the 19-byte frame is accounted for.",
"exec_summary": "...",
"framing": {
"style": "length-prefixed",
"evidence": "u16 big-endian at offset 4 equals 11 in both frames, which is the byte count between offset 6 and the CRC field at offset 17"
},
"endianness": "big",
"fields": [
{"id": "F-001", "offset": 0, "width": 2, "name": "sync_word", "type": "bytes", "endian": "n/a", "role": "magic", "observed": "aa 55 in both frames", "evidence": "prescan MAGIC-PREFIX", "confidence": "high"},
{"id": "F-002", "offset": 2, "width": 1, "name": "sequence", "type": "u8", "endian": "n/a", "role": "sequence", "observed": "0x11 then 0x12", "evidence": "prescan SEQUENCE-FIELD: steps by 1", "confidence": "high"},
{"id": "F-003", "offset": 3, "width": 1, "name": "msg_type", "type": "u8", "endian": "n/a", "role": "type", "observed": "0x21 in both frames", "evidence": "constant across frames of equal length", "confidence": "medium"},
{"id": "F-004", "offset": 4, "width": 2, "name": "payload_len","type": "u16", "endian": "big", "role": "length", "observed": "0x000b = 11", "evidence": "prescan LENGTH-FIELD", "confidence": "high"},
{"id": "F-005", "offset": 6, "width": 4, "name": "bay_id", "type": "ascii", "endian": "n/a", "role": "address", "observed": "\"BAYA\"", "evidence": "prescan ASCII-RUNS at offset 6", "confidence": "high"},
{"id": "F-006", "offset": 10, "width": 1, "name": "status", "type": "bitfield", "endian": "n/a", "role": "flags", "observed": "0x01 idle, 0x05 heating", "evidence": "prescan BITFIELD: only bits 0 and 2 ever move", "confidence": "medium"},
{"id": "F-007", "offset": 11, "width": 4, "name": "temp_c", "type": "f32", "endian": "big", "role": "payload", "observed": "21.5 then 22.75", "evidence": "prescan float32 candidate; the u32 reading is 1101529088 and means nothing", "confidence": "medium"},
{"id": "F-008", "offset": 15, "width": 2, "name": "humidity_pct_x100", "type": "u16", "endian": "big", "role": "payload", "observed": "0x12d4 = 4820", "evidence": "48.20% read as hundredths is the only plausible physical scale", "confidence": "low"},
{"id": "F-009", "offset": 17, "width": 2, "name": "crc", "type": "u16", "endian": "little", "role": "checksum", "observed": "19 98 then 07 2b", "evidence": "prescan CHECKSUM-VERIFIED", "confidence": "high"}
],
"checksum": {
"algorithm": "crc16-modbus",
"offset": 17, "width": 2, "endian": "little",
"range": "bytes 0 through 16 inclusive",
"verified": "prescan-confirmed",
"note": ""
},
"unresolved": [],
"next_captures": [
{"action": "Poll the same bay twice with only the setpoint changed", "resolves": "whether offset 15 is humidity or the setpoint echo"}
],
"assumptions": ["..."], "open_questions": ["..."],
"coverage_check": [{"id": "MAGIC-PREFIX", "addressed": true, "note": "..."}],
"artifacts": [
{"name": "protocol.ksy", "language": "yaml", "content": "meta:\n id: aa55_sensor\n endian: be\n..."},
{"name": "dissector.lua", "language": "lua", "content": "local p = Proto(\"aa55\", \"AA-55 sensor bus\")\n..."},
{"name": "parse.py", "language": "python", "content": "import struct\n..."}
],
"next_steps": ["..."],
"summary": "..."
}
Worked example: task: "armor"
The same request object with "task": "armor" — nothing else changes — over a capture
whose body is block-aligned and high-entropy. The envelope keys are identical; the body is not:
{
"task": "armor",
"title": "WP-framed application protocol, 64-byte aligned body",
"posture": "encrypted",
"confidence": "medium",
"verdict": "The body from offset 8 onward is high-entropy with no container signature and every frame length is a multiple of 16, so it is a block cipher and no field mapping inside it is possible without key material.",
"exec_summary": "...",
"regions": [
{
"id": "R-001", "offset": 8, "width": 64,
"character": "high-entropy",
"technique": "AES-CBC or another 16-byte block cipher",
"assessment": "Two to four sentences on what this region is and what it would take to read it.",
"evidence": "prescan HIGH-ENTROPY-REGION: 7.81 bits/byte over offsets 8-71; prescan BLOCK-ALIGNED: every frame length is a multiple of 16",
"confidence": "medium"
}
],
"techniques": [
{
"name": "Fixed-key XOR over the body",
"indicators": ["byte-wise difference between the two frames repeats with period 4"],
"how_to_confirm": "XOR the two frames together from offset 8 and look for a repeating 4-byte pattern",
"effort": "low"
}
],
"blockers": [
{"blocker": "Per-frame IV with no reuse", "why": "Nothing can be recovered from ciphertext alone.", "workaround": "Instrument the client before encryption."}
],
"pivots": [
{"pivot": "The client binary", "rationale": "The key has to exist in the process that built these frames.", "first_move": "Search the binary for the constant 4-byte prefix seen at offset 8."}
],
"assumptions": ["..."], "open_questions": ["..."],
"coverage_check": [{"id": "BLOCK-ALIGNED", "addressed": true, "note": "..."}],
"artifacts": [
{"name": "confirm.md", "language": "markdown", "content": "# Confirmation plan\n1. ..."},
{"name": "probe.py", "language": "python", "content": "import sys\n..."}
],
"next_steps": ["..."],
"summary": "..."
}
The input object
The input object is exactly what the app's own form submits, field for field. It is the body of
/estimate, /run and /run-stream — the object itself, not
wrapped in anything.
| field | type | meaning |
|---|---|---|
task | string, required | map or armor. The lane router, described above. An unrecognised value is not rejected — the model answers the closest lane and names the lane it chose in title — so send one of the two literals and branch on the task the reply echoes. |
frames | string, required | The capture itself: one labelled block per frame, each block a header line followed by offset: hh hh hh … lines. The browser normalises whatever was pasted — xxd, hexdump -C, od, a Wireshark hex stream, a C byte array, a backslash-x escaped string — into that one shape, and the shape is what the run receives. Two frames is the working minimum; one frame has nothing to diff against. Lowercase hex pairs, decimal offsets in the citations, hex offsets in the gutter. The browser clips the capture to about 6,000 characters of hex on frame boundaries, and an over-long capture is better cut by you than by the budget. |
frame_count | number | How many frames the capture really has, which may be more than the number of blocks in frames when the middle of a long capture was dropped to fit. It changes how much the answer is allowed to claim: three frames cannot establish a 16-bit enum, and the reply is expected to say low rather than guess. Send the honest count. |
transport | string | Where it was captured: serial, tcp, udp, usb, can, bluetooth, file or unknown. This is not decoration — it changes what framing is even possible. A UDP datagram is self-delimiting and needs no length field; a serial stream cannot be parsed without one, so on serial the absence of a length field is a finding rather than a shrug. It also decides which DissectorTable the generated Lua registers on: tcp.port, udp.port or wtap_encap. |
goal | string | parse (build a working parser), document (write the protocol down), interop (talk to the device) or debug (find out why a frame is being rejected). Emphasis, not exclusivity — the artifacts are produced either way, but debug pushes the answer toward the validation fields and document toward the prose. |
context | string, optional | What the device is, what action produced each frame, what is already known and what has already been ruled out. This is the highest-value optional field in the object: "frame 1 is idle, frame 2 is after the heater started" is what turns an offset that varies into a temperature. An explicit statement — that it is a Modbus variant, that the device is a known model, that the capture is one side of a TLS session — is planned around rather than argued with. Clipped at about 4,000 characters. |
prescan | object, optional for an API caller | The browser's deterministic measurements over the same bytes. Shape and honest advice below. |
handoff | object, optional | Present when this run continues the other lane's result: {from, posture, unresolved: [{offset, width, why}], fields_named: [{offset, width, name, role}]}. from is map or armor. When it is present the run starts at those regions instead of re-deriving the header, and it will say which lane got it wrong if it disagrees. This is how you chain the two lanes in a script: run map, take its unresolved[] and fields[], post them back as handoff on the armor run. |
retry_note | string, optional | Send only on a retry, when a previous reply failed to parse. The instruction is obeyed exactly — "the previous reply was not valid JSON; return the same field map with shorter evidence strings and only the Kaitai artifact" is the sort of thing that works. Bump the attempt suffix on the Idempotency-Key when you add it, because the body has changed. |
prescan, honestly
prescan is optional for an API caller. In the browser it is computed
for free before the run by a local frame scanner: it diffs every frame offset by offset, tries
thirteen checksum algorithms at every plausible field position and range, tests every offset, width
and endianness against two length-field conventions, walks six TLV shapes from eight start offsets,
measures Shannon entropy in a sliding window and scans for container signatures. None of that is
something you have to reproduce. Omit the key entirely, or send
{"facts": {"resources": [], "flags": []}}, and the run still works — the model reads
frames either way. What you lose is the reconciliation, not the answer: a caller who
omits prescan simply gets a reply with an empty coverage_check and nothing
to hold the answer against.
What makes it worth sending is that contract. Every id you send in
facts.flags must come back exactly once in coverage_check, which
turns a fact your own tooling already established into something the reply is measured by. An entry
with addressed: false and a real reason in note is a correct answer — the
prescan is deliberately eager and some of its flags do not matter for a given protocol — and it is
a different thing from silence. A flag that never appears at all is a failed run. Ids you did not
send should not appear either.
This is the whole sub-shape, exactly as the browser sends it:
{
"facts": {
"resources": [{"id": "FRAMES", "label": "2 frames read, 38 bytes total, lengths 19"}],
"flags": [{"id": "MAGIC-PREFIX", "label": "every frame starts with the same 2 bytes: aa 55"}]
},
"checksums": [ // up to 3, verified arithmetically over EVERY frame
{"algorithm": "crc16-modbus", "width": 2, "endian": "little", "position": "last 2 bytes",
"offset": 17, "covers": "bytes 0 .. 16 (from the start of the frame)",
"rangeStart": 0, "rangeEndExclusive": 17, "frames": 2}
],
"length_fields": [ // up to 4, in confidence order; [0] is the one to trust
{"offset": 4, "width": 2, "endian": "big", "basis": "bytes after this field",
"adjust": 2, "formula": "value == bytes remaining after this field - 2"}
],
"counters": [ // up to 3
{"offset": 2, "width": 1, "endian": "n/a", "step": 1, "wrapped": false, "values": [17, 18]}
],
"variance": [ // up to 64 head offsets, ascending
{"offset": 0, "kind": "constant", "distinct": 1, "values": ["aa", "aa"]},
{"offset": 2, "kind": "counter", "distinct": 2, "values": ["11", "12"]}
],
"tail_variance": [ // up to 8 offsets counted from the END; offset -1 is the last byte
{"offset": -1, "kind": "varying", "distinct": 2, "values": ["98", "2b"]}
],
"entropy": {
"whole_frame_bits": [3.62, 3.71], // one Shannon reading per frame, bits per byte
"high_regions": [ // up to 12 merged windows at or above the high-entropy threshold
{"frame": 1, "offset": 8, "width": 64, "bits": 7.81}
]
},
"magics": [{"frame": 1, "offset": 8, "id": "gzip", "label": "gzip stream"}],
"ascii": [{"offset": 6, "length": 4, "text": "BAYA", "frames": [1, 2]}],
"tlv": {"start": 4, "type_width": 1, "length_width": 2, "endian": "big", "elements": 3},
"alignment": {"lengths": [19, 19], "fixedLength": true, "multiples": [], "largestMultiple": 1,
"trailingZeroRun": 0},
"bitfields": [{"offset": 10, "mask": 4, "bits": ["bit 2"], "values": [1, 5]}],
"timestamps": [{"offset": 12, "endian": "big", "values": [1755820800], "spreadSeconds": 0,
"iso": "2025-08-22T00:00:00Z"}],
"floats": [{"offset": 11, "endian": "big", "values": [21.5, 22.75], "distinct": 2}],
"claimed_offsets": {"0": "magic prefix", "1": "magic prefix", "4": "length field",
"5": "length field", "17": "checksum field", "18": "checksum field"}
}
tlv is null when no TLV walk consumed a frame exactly.
variance[].kind is one of constant, low-variance,
counter, varying or random.
claimed_offsets maps a byte position to the stronger explanation it already has, and it
is why a length field's low byte is not also reported as a two-bit flags field.
checksums, length_fields and counters are arithmetic that held
for every frame, not inference — the reply may add to them and may disagree in writing, but a reply
that quietly contradicts them is contradicted back on the page.
These are the flag ids the scanner raises, and therefore the ids you can raise by hand:
| id | fires when |
|---|---|
MAGIC-PREFIX | Every frame starts with the same one or more bytes — a sync word, a version, or a fixed header field. It anchors the whole map. |
NO-MAGIC | No two frames share even their first byte, so either these are not the same message type or the frame really does open with a varying field. |
COMMON-SUFFIX | Every frame ends with the same bytes — a terminator, a constant footer, or padding. |
FIXED-LENGTH | Every frame is exactly the same size, so framing is by size alone and no length field is needed. |
LENGTH-FIELD | An offset, width and endianness reproduces the frame length in every frame. The stream is parseable from this field alone. |
NO-LENGTH-FIELD | Variable-length frames and no offset reproduces the length, so framing must be by delimiter or by the transport. |
CHECKSUM-VERIFIED | One of the thirteen algorithms reproduces the field in every frame. The strongest single fact in the scan. |
NO-CHECKSUM-FOUND | None of the thirteen reproduces any trailing or header field: either a MAC, a CRC variant not tried, or none at all. |
SEQUENCE-FIELD | An offset steps monotonically across the frames in order, wrap-around included. |
TLV-FRAMING | A type-length-value walk consumes every frame exactly. |
ASCII-RUNS | A run of printable bytes sits at the same offset in every frame — a device id, a command name, a version string. |
BITFIELD | An offset where only a few bit positions ever change: flags, not a number. |
TIMESTAMP | Four bytes read as a Unix epoch land in a plausible window with a plausible spread. |
HIGH-ENTROPY-REGION | A merged window at or near 8 bits per byte: encrypted, compressed, or already random. The armour lane's starting point. |
CONTAINER-MAGIC | A recognised container signature inside a frame — gzip, zlib, zstd, LZ4, bzip2, xz, zip, PNG, JPEG, DER, CBOR, MessagePack, a TLS record. This makes the posture packed, not encrypted. |
BLOCK-ALIGNED | Every frame length is a multiple of 8 or 16, which with high entropy and no signature points at a block cipher. |
TRAILING-ZEROS | Every frame ends in a run of zero bytes: padding, a reserved tail, or a fixed-size buffer sent short. |
TOO-FEW-FRAMES | Fewer frames than the scan needs to distinguish a counter from a coincidence. Expect low confidence and a populated next_captures. |
UNREAD-BLOCKS | Part of the paste did not decode as bytes at all, so the capture the run saw is smaller than the capture you pasted. |
Resource ids — the measurements rather than the conclusions — are FRAMES,
FORMATS, DIRECTIONS, VARIANCE, LENGTH-ALTS,
FLOAT-CANDIDATES and ASCII-NOISE. Resources are context and are not
reconciled; only flags are.
The output contract
The answer arrives as one JSON object, as a string, at output.output on the finished
job. Both lanes return the same twelve envelope keys; the body underneath differs. Every key listed
here is always present in a well-formed reply — "" or [] rather than
omitted — and the normaliser fills in the defaults noted below when the model drifts.
The shared envelope — identical in both lanes
| key | type | meaning |
|---|---|---|
task | enum | map or armor. The lane that was answered. Branch on this, not on what you asked for. |
title | string | A short name for the capture, e.g. "AA-55 framed sensor telemetry, 2 frames". Empty becomes Untitled capture. |
posture | enum | The one-word state. The value set differs per lane — see the enum table. An unrecognised value normalizes to partial on map and to mixed on armor. This is the value a CI gate should branch on. |
confidence | enum | high, medium or low, for the answer as a whole. Unrecognised normalizes to low, which is the safe direction. Two frames rarely justify high. |
verdict | string | ONE sentence naming the single thing that decides the posture. Required. An empty verdict fails the parse outright in both lanes. |
exec_summary | string | Two to five sentences someone can act on without reading the tables. |
assumptions | string[] | What had to be assumed because the capture is silent on it — the bus speed, whether a frame is a request or a reply, whether the two frames are the same message type. Blank entries are dropped. |
open_questions | string[] | What would be asked before anyone writes code against this. Also where a paste that reads as an attack on a third party's service gets said out loud. |
coverage_check | object[] | {id, addressed, note}. One entry per prescan.facts.flags id, exactly once, and no ids you did not send. addressed is coerced to a boolean; addressed: false means deliberately set aside, with the reason in note. An entry with no id is dropped. Empty when you sent no prescan. |
artifacts | object[] | {name, language, content} — the take-away files, complete and pasteable, never a sketch with ... in it. A missing name becomes artifact-1, artifact-2, …; an unrecognised language becomes text; an entry with empty content is dropped entirely, so count what you got rather than assuming three files came back. |
next_steps | string[] | Ordered, concrete, one line each. |
summary | string | One closing paragraph. |
The map body
| key | type | meaning |
|---|---|---|
framing | object | {style, evidence}. style is the framing convention; evidence is the sentence that settles it, and it is expected to quote an offset and a width — "u16 big-endian at offset 4 equals the byte count from offset 6 to the CRC in both frames". An unrecognised style becomes unknown. |
endianness | enum | The protocol's overall byte order, or mixed when a header field and a payload field disagree. Unrecognised becomes unknown. |
fields | object[] | {id, offset, width, name, type, endian, role, observed, evidence, confidence}. offset is decimal from the start of the frame and width is in bytes. Entries are sorted by offset ascending, then by width. An entry whose offset is not a non-negative integer, or whose width is not a positive integer, is dropped. A missing id becomes F-001, F-002, …; a missing name becomes unnamed. At least one surviving entry is required — a map reply with none fails the parse. |
checksum | object or null | {algorithm, offset, width, endian, range, verified, note}. Present only when algorithm is a non-empty string, or when verified is exactly none; otherwise the key is null and there is no integrity field to speak of. range is prose — "bytes 0 through 16 inclusive". verified defaults to asserted, which is the honest default: it means nobody has proved it. |
unresolved | object[] | {offset, width, why, next_step} — every byte range that could not be assigned, with the reason and the single next move that would resolve it. An entry with an empty why is dropped. This array is the input to the armour lane: it is what handoff.unresolved carries. |
next_captures | object[] | {action, resolves}. The most valuable part of a partial map — "poll the same bay twice with only the setpoint changed" resolves more than any amount of staring at two frames. An entry with an empty action is dropped. |
The armor body
| key | type | meaning |
|---|---|---|
regions | object[] | {id, offset, width, character, technique, assessment, evidence, confidence}, sorted by offset ascending. character is the classification; technique names what is doing it; assessment is two to four sentences on what the region is and what it would take to read it; evidence is expected to quote the measured entropy figure. An entry is dropped unless offset is a non-negative integer, width is positive and assessment is non-empty — so a region with no assessment silently disappears. A missing id becomes R-001, R-002, … |
techniques | object[] | {name, indicators, how_to_confirm, effort}. indicators is a string array of what points at it; how_to_confirm is a test a person can run against the bytes they already hold. An entry with an empty name is dropped. |
blockers | object[] | {blocker, why, workaround} — what genuinely cannot be got past from the frames alone, such as a per-frame nonce with no reuse. An entry with an empty blocker is dropped. |
pivots | object[] | {pivot, rationale, first_move} — where the work moves to when the bytes are exhausted: the client binary, the key provisioning path, the firmware image. An entry with an empty pivot is dropped. |
At least one regions[] entry or one techniques[] entry must
survive — an armor reply with neither fails the parse. Both empty is the one
shape that cannot be rendered: a lane that found nothing to say about concealment should return a
transparent posture with a region classified structured, not an empty body.
The enums
| field | values | fallback |
|---|---|---|
posture on map | decoded, partial, opaque | partial. decoded means every byte of the shortest frame is assigned to a defensible field; opaque means the framing itself is unresolved or most of the frame is high-entropy. |
posture on armor | transparent, packed, obfuscated, encrypted, mixed | mixed. packed is recoverable with a known decoder and is the right answer whenever a container signature was found, however random the bytes look. obfuscated is cheap and reversible — XOR, byte-swap, a substitution table. encrypted means field mapping inside the region is impossible without key material. |
confidence, fields[].confidence, regions[].confidence | high, medium, low | low |
framing.style | fixed, length-prefixed, delimited, tlv, unknown | unknown |
endianness, fields[].endian, checksum.endian | big, little, mixed, n/a, unknown | unknown for endianness; n/a for the per-field and checksum values, which is what a single byte or a byte string should carry anyway. |
fields[].type | u8, u16, u32, u64, i8, i16, i32, f32, f64, bytes, ascii, bitfield, bcd | bytes. Note there is no i64 and no u128; anything wider than eight bytes is bytes. |
fields[].role | magic, version, type, length, sequence, address, flags, timestamp, payload, checksum, padding, reserved, unknown | unknown |
checksum.verified | prescan-confirmed, asserted, none | asserted. prescan-confirmed is only legitimate for an algorithm your prescan actually verified; asserting it otherwise is a disagreement the page prints. |
regions[].character | high-entropy, compressed, encoded, structured, constant-blob, padding | structured |
techniques[].effort | low, medium, high | medium |
artifacts[].language | yaml, lua, python, markdown, text | text. The map lane ships protocol.ksy (yaml), dissector.lua (lua) and parse.py (python); the armor lane ships confirm.md (markdown) and probe.py (python). |
Enum matching is case-insensitive and trims whitespace, so "Length-Prefixed" is
accepted; anything else falls back rather than erroring. The four hard failures are the only ones
that abort a parse: a reply that is not a JSON object at all, an empty verdict, a
map reply with no usable fields[] entry, and an armor reply
with neither a usable regions[] entry nor a techniques[] entry. Everything
else degrades. Separately, the web page grounds the reply against the bytes — a field past the end
of the shortest frame, two fields claiming the same byte, a checksum that contradicts the
brute-force, high confidence on an opaque frame — and shows each disagreement rather
than refusing the answer. An API caller who wants that check has to run it, and the field list is
arrives sorted by offset precisely so that the overlap pass is a single loop.
1. Get a token
The easiest route is the token page: it shows the token this browser already holds, with Copy token and Copy shell export buttons, and a sign-in button for a personal token. Nothing on that page needs a developer tool — it reads the same storage the app itself uses and prints the token for you.
Failing that, mint a guest token: POST /v1/app-api/guest with the body
{"slug": "wire-desk"} and no Authorization header at all. It comes back as
{"token", "guest_id"}. A guest token can call /me and
/estimate; mapping a capture is metered, so /run needs a
personal token from signing in, and a guest gets a 403 forbidden there
unless the owner has sponsored usage.
# The token page is the shortest path. It shows the token this browser holds and
# hands you a ready-made shell export:
#
# https://wire-desk.skillsafe.ai/tokens.html
# export SKILLSAFE_TOKEN="aut_..."
#
# To mint a guest token from the command line instead. The slug goes in the BODY;
# there is no app-slug header anywhere in this API.
curl -sS -X POST "https://api.skillsafe.ai/v1/app-api/guest" \
-H "Content-Type: application/json" \
-d '{"slug": "wire-desk"}'
# {"ok":true,"data":{"token":"aut_...","guest_id":"gst_..."}}
#
# A guest token is enough for /me and /estimate. Mapping a capture is metered and
# needs a personal token from signing in.
# Open https://wire-desk.skillsafe.ai/tokens.html and press "Copy token", or mint
# a guest token here. A guest token can call /me and /estimate but cannot start a
# metered run. The slug goes in the body; there is no app-slug header.
import json, urllib.request
req = urllib.request.Request(
"https://api.skillsafe.ai/v1/app-api/guest",
data=json.dumps({"slug": "wire-desk"}).encode(),
method="POST")
req.add_header("Content-Type", "application/json")
with urllib.request.urlopen(req) as r:
data = json.load(r)["data"]
TOKEN = data["token"]
print(data["guest_id"])
// Open https://wire-desk.skillsafe.ai/tokens.html and press "Copy token", or mint
// a guest token here. A guest token can call /me and /estimate but cannot start a
// metered run. The slug goes in the body; there is no app-slug header.
const res = await fetch("https://api.skillsafe.ai/v1/app-api/guest", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ slug: "wire-desk" }),
});
const { token, guest_id } = (await res.json()).data;
console.log(token, guest_id);
// Open https://wire-desk.skillsafe.ai/tokens.html and press "Copy token", or mint
// a guest token here. A guest token can call /me and /estimate but cannot start a
// metered run. The slug goes in the body; there is no app-slug header.
guestReq, _ := http.NewRequest(http.MethodPost,
"https://api.skillsafe.ai/v1/app-api/guest",
bytes.NewReader([]byte(`{"slug": "wire-desk"}`)))
guestReq.Header.Set("Content-Type", "application/json")
guestRes, err := http.DefaultClient.Do(guestReq)
if err != nil {
panic(err)
}
defer guestRes.Body.Close()
var guest struct {
Data struct {
Token string `json:"token"`
GuestID string `json:"guest_id"`
} `json:"data"`
}
_ = json.NewDecoder(guestRes.Body).Decode(&guest)
fmt.Println(guest.Data.Token, guest.Data.GuestID)
// Open https://wire-desk.skillsafe.ai/tokens.html and press "Copy token", or mint
// a guest token here. A guest token can call /me and /estimate but cannot start a
// metered run. The slug goes in the body; there is no app-slug header.
var http = HttpClient.newHttpClient();
var guestReq = HttpRequest.newBuilder(URI.create("https://api.skillsafe.ai/v1/app-api/guest"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString("{\"slug\": \"wire-desk\"}"))
.build();
HttpResponse<String> guest = http.send(guestReq, HttpResponse.BodyHandlers.ofString());
System.out.println(guest.body()); // {"ok":true,"data":{"token":"aut_...","guest_id":"gst_..."}}
# Open https://wire-desk.skillsafe.ai/tokens.html and press "Copy token", or mint
# a guest token here. A guest token can call /me and /estimate but cannot start a
# metered run. The slug goes in the body; there is no app-slug header.
require "json"
require "net/http"
require "uri"
uri = URI("https://api.skillsafe.ai/v1/app-api/guest")
req = Net::HTTP::Post.new(uri)
req["Content-Type"] = "application/json"
req.body = JSON.generate({ "slug" => "wire-desk" })
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
data = JSON.parse(res.body)["data"]
TOKEN = data["token"]
puts data["guest_id"]
<?php
// Open https://wire-desk.skillsafe.ai/tokens.html and press "Copy token", or mint
// a guest token here. A guest token can call /me and /estimate but cannot start a
// metered run. The slug goes in the body; there is no app-slug header.
$ch = curl_init("https://api.skillsafe.ai/v1/app-api/guest");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(["slug" => "wire-desk"]));
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Content-Type: application/json"]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$guest = json_decode(curl_exec($ch), true);
curl_close($ch);
echo $guest["data"]["token"], " ", $guest["data"]["guest_id"], PHP_EOL;
// Open https://wire-desk.skillsafe.ai/tokens.html and press "Copy token", or mint
// a guest token here. A guest token can call /me and /estimate but cannot start a
// metered run. The slug goes in the body; there is no app-slug header.
using var http = new HttpClient();
var guestReq = new HttpRequestMessage(HttpMethod.Post,
"https://api.skillsafe.ai/v1/app-api/guest")
{
Content = new StringContent("{\"slug\": \"wire-desk\"}", Encoding.UTF8, "application/json"),
};
var guestRes = await http.SendAsync(guestReq);
var guest = await guestRes.Content.ReadFromJsonAsync<JsonElement>();
var data = guest.GetProperty("data");
Console.WriteLine($"{data.GetProperty("token")} {data.GetProperty("guest_id")}");
2. A tiny client
One helper that adds the bearer token, unwraps data and raises on error.
There is no slug to thread through and no second header to remember: the token is the whole of the
request identity, so the helper is genuinely this short.
# Every call is the same three things: the base URL, your bearer token, and a JSON
# body. Keep the token in a shell variable.
BASE="https://api.skillsafe.ai/v1/app-api"
TOKEN="$SKILLSAFE_TOKEN" # from https://wire-desk.skillsafe.ai/tokens.html
call() { # call <path> [json-body]
if [ -n "$2" ]; then
curl -sS -X POST "$BASE/$1" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "$2"
else
curl -sS "$BASE/$1" -H "Authorization: Bearer $TOKEN"
fi
}
# jq is optional; python3 -c is used below so the samples need nothing installed.
import json, os, urllib.error, urllib.request
BASE = "https://api.skillsafe.ai/v1/app-api"
TOKEN = os.environ.get("SKILLSAFE_TOKEN", "YOUR_TOKEN") # from /tokens.html
def call(path, body=None):
"""Returns the unwrapped `data`, or raises with the API error code."""
data = json.dumps(body).encode() if body is not None else None
req = urllib.request.Request(
f"{BASE}/{path}", data=data, method="POST" if body is not None else "GET")
req.add_header("Authorization", f"Bearer {TOKEN}")
if body is not None:
req.add_header("Content-Type", "application/json")
try:
with urllib.request.urlopen(req) as r:
payload = json.load(r)
except urllib.error.HTTPError as e:
payload = json.load(e)
if not payload.get("ok"):
err = payload.get("error", {})
raise RuntimeError(f"{err.get('code')}: {err.get('message')}")
return payload["data"]
const BASE = "https://api.skillsafe.ai/v1/app-api";
const TOKEN = "YOUR_TOKEN"; // from https://wire-desk.skillsafe.ai/tokens.html
async function call(path, body) {
const res = await fetch(`${BASE}/${path}`, {
method: body ? "POST" : "GET",
headers: {
Authorization: `Bearer ${TOKEN}`,
...(body ? { "Content-Type": "application/json" } : {}),
},
body: body ? JSON.stringify(body) : undefined,
});
const payload = await res.json();
if (!payload.ok) throw new Error(`${payload.error.code}: ${payload.error.message}`);
return payload.data;
}
package main
import (
"bufio"
"bytes"
"crypto/sha256"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"strings"
"time"
)
const base = "https://api.skillsafe.ai/v1/app-api"
var token = os.Getenv("SKILLSAFE_TOKEN") // from /tokens.html
type envelope struct {
OK bool `json:"ok"`
Data json.RawMessage `json:"data"`
Error struct {
Code string `json:"code"`
Message string `json:"message"`
} `json:"error"`
}
func call(path string, body any) (json.RawMessage, error) {
method := http.MethodGet
var rdr io.Reader
if body != nil {
method = http.MethodPost
b, _ := json.Marshal(body)
rdr = bytes.NewReader(b)
}
req, _ := http.NewRequest(method, base+"/"+path, rdr)
req.Header.Set("Authorization", "Bearer "+token)
if body != nil {
req.Header.Set("Content-Type", "application/json")
}
res, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
var env envelope
if err := json.NewDecoder(res.Body).Decode(&env); err != nil {
return nil, err
}
if !env.OK {
return nil, fmt.Errorf("%s: %s", env.Error.Code, env.Error.Message)
}
return env.Data, nil
}
import java.net.URI;
import java.net.http.*;
public class WireDesk {
static final String BASE = "https://api.skillsafe.ai/v1/app-api";
static final String TOKEN = System.getenv().getOrDefault("SKILLSAFE_TOKEN", "YOUR_TOKEN");
static final HttpClient HTTP = HttpClient.newHttpClient();
static String call(String path, String jsonBody) throws Exception {
HttpRequest.Builder b = HttpRequest.newBuilder(URI.create(BASE + "/" + path))
.header("Authorization", "Bearer " + TOKEN);
if (jsonBody != null) {
b.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(jsonBody));
} else {
b.GET();
}
HttpResponse<String> res = HTTP.send(b.build(), HttpResponse.BodyHandlers.ofString());
// The envelope is always {"ok":true,"data":...} or {"ok":false,"error":...}.
return res.body();
}
}
require "json"
require "net/http"
require "uri"
BASE = "https://api.skillsafe.ai/v1/app-api"
TOKEN = ENV.fetch("SKILLSAFE_TOKEN", "YOUR_TOKEN") # from /tokens.html
def call(path, body = nil)
uri = URI("#{BASE}/#{path}")
req = body ? Net::HTTP::Post.new(uri) : Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer #{TOKEN}"
if body
req["Content-Type"] = "application/json"
req.body = JSON.generate(body)
end
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
payload = JSON.parse(res.body)
raise "#{payload['error']['code']}: #{payload['error']['message']}" unless payload["ok"]
payload["data"]
end
<?php
const BASE = "https://api.skillsafe.ai/v1/app-api";
define("TOKEN", getenv("SKILLSAFE_TOKEN") ?: "YOUR_TOKEN"); // from /tokens.html
function call(string $path, ?array $body = null) {
$ch = curl_init(BASE . "/" . $path);
$headers = ["Authorization: Bearer " . TOKEN];
if ($body !== null) {
$headers[] = "Content-Type: application/json";
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
}
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$payload = json_decode(curl_exec($ch), true);
curl_close($ch);
if (empty($payload["ok"])) {
throw new RuntimeException($payload["error"]["code"] . ": " . $payload["error"]["message"]);
}
return $payload["data"];
}
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
static class WireDesk
{
const string Base = "https://api.skillsafe.ai/v1/app-api";
static readonly string Token =
Environment.GetEnvironmentVariable("SKILLSAFE_TOKEN") ?? "YOUR_TOKEN";
static readonly HttpClient Http = new();
public static async Task<JsonElement> Call(string path, object? body = null)
{
var req = new HttpRequestMessage(
body is null ? HttpMethod.Get : HttpMethod.Post, $"{Base}/{path}");
req.Headers.Add("Authorization", $"Bearer {Token}");
if (body is not null) req.Content = JsonContent.Create(body);
var res = await Http.SendAsync(req);
var payload = await res.Content.ReadFromJsonAsync<JsonElement>();
if (!payload.GetProperty("ok").GetBoolean())
{
var e = payload.GetProperty("error");
throw new Exception($"{e.GetProperty("code")}: {e.GetProperty("message")}");
}
return payload.GetProperty("data");
}
}
3. Check the session and the balance
GET /me returns {"subject_type", "subject_id", "credits"}.
subject_type is user or guest — a guest can price
a run but cannot start one — subject_id identifies the subject the token is bound to,
and credits is the wallet balance. Compare it against min_credits from the
next step before you run, so a shortfall surfaces as your own clear message rather than as a 402
halfway through a batch of captures.
call me
# {"ok":true,"data":{"subject_type":"user","subject_id":"usr_...","credits":51234}}
#
# subject_type "guest" means /run will come back 403 unless the app sponsors usage.
me = call("me")
print(me["subject_type"], me["subject_id"], me["credits"])
if me["subject_type"] != "user":
print("guest token: /me and /estimate only, /run will be 403 without a sponsor")
const me = await call("me");
console.log(me.subject_type, me.subject_id, me.credits);
if (me.subject_type !== "user") {
console.warn("guest token: /me and /estimate only");
}
raw, err := call("me", nil)
if err != nil {
panic(err)
}
var me struct {
SubjectType string `json:"subject_type"`
SubjectID string `json:"subject_id"`
Credits int `json:"credits"`
}
_ = json.Unmarshal(raw, &me)
fmt.Println(me.SubjectType, me.SubjectID, me.Credits)
System.out.println(call("me", null));
// {"ok":true,"data":{"subject_type":"user","subject_id":"usr_...","credits":51234}}
// A "guest" subject_type can price a run but cannot start one.
me = call("me")
puts "#{me['subject_type']} #{me['subject_id']} #{me['credits']}"
<?php
$me = call("me");
echo $me["subject_type"], " ", $me["subject_id"], " ", $me["credits"], PHP_EOL;
var me = await WireDesk.Call("me");
Console.WriteLine(me.GetProperty("subject_type").GetString());
Console.WriteLine(me.GetProperty("credits").GetInt32());
4. Price the run — free
POST /estimate takes the same JSON the run would submit and returns
{"hold_credits", "min_credits", "model", "model_alias", "markup_bps", "sponsor_enabled"}.
It creates no job and charges nothing, and a guest token may call it.
hold_credits is what gets reserved, not what the run costs: it prices the full
output cap, so the charged_credits on the settled job is usually a fraction of it.
Budget against hold_credits, report against charged_credits.
min_credits is the balance you must clear to start at all, and
sponsor_enabled says whether the app is covering the run for you.
The hold differs per lane, because the two prompts and the two output caps are
different sizes — a map run that has to emit a Kaitai spec, a Lua dissector and a Python
parser reserves more than an armor run that emits a confirmation plan and a probe
script. So estimate the lane you are about to run: pricing map and then running
armor on the strength of that number is how a batch job walks into a 402 on its fourth
capture. The web app keeps one estimate per lane for exactly this reason.
The worked capture below is two frames of an AA-55 length-prefixed sensor protocol, and it is the same capture in every language and every step that follows.
# The capture: two frames, 19 bytes each, one labelled block per frame.
FRAMES='# frame 1 [tx] poll reply, bay A idle (19 bytes)\n0000: aa 55 11 21 00 0b 42 41 59 41 01 41 ac 00 00 12\n0010: d4 19 98\n# frame 2 [tx] poll reply, bay A heating (19 bytes)\n0000: aa 55 12 21 00 0b 42 41 59 41 05 41 b6 00 00 12\n0010: 6b 07 2b'
# task comes first because it picks the lane. prescan is OPTIONAL - these four
# flags are sent only so the reply is held to reconciling them in coverage_check.
INPUT='{"task":"map","frames":"'"$FRAMES"'","frame_count":2,"transport":"serial","goal":"parse","context":"Two frames off an RS-485 bus between an HVAC controller and a bay sensor, 19200 8N1. Frame 1 is idle, frame 2 is the same bay a minute later with the heater running. The vendor documents nothing.","prescan":{"facts":{"resources":[{"id":"FRAMES","label":"2 frames read, 38 bytes total, lengths 19"}],"flags":[{"id":"MAGIC-PREFIX","label":"every frame starts with aa 55"},{"id":"LENGTH-FIELD","label":"u16 big-endian at offset 4 verifies in both frames"},{"id":"CHECKSUM-VERIFIED","label":"crc16-modbus over bytes 0..16, little-endian at offset 17"},{"id":"TOO-FEW-FRAMES","label":"only 2 frames"}]}}}'
call estimate "$INPUT"
# {"ok":true,"data":{"model":"gpt-5.6-terra","model_alias":"gpt-terra",
# "markup_bps":1000,"hold_credits":3120,"min_credits":410,"sponsor_enabled":false}}
#
# estimate is FREE: no job, no charge, guests allowed. hold_credits is what gets
# RESERVED; the charged_credits on the settled job is normally much lower. Re-run
# this with "task":"armor" before an armour run - the hold is not the same.
# The capture: two frames, 19 bytes each, one labelled block per frame.
FRAMES = "\n".join([
"# frame 1 [tx] poll reply, bay A idle (19 bytes)",
"0000: aa 55 11 21 00 0b 42 41 59 41 01 41 ac 00 00 12",
"0010: d4 19 98",
"# frame 2 [tx] poll reply, bay A heating (19 bytes)",
"0000: aa 55 12 21 00 0b 42 41 59 41 05 41 b6 00 00 12",
"0010: 6b 07 2b",
])
INPUT = {
"task": "map", # the lane router: "map" or "armor"
"frames": FRAMES,
"frame_count": 2,
"transport": "serial",
"goal": "parse",
"context": (
"Two frames off an RS-485 bus between an HVAC controller and a bay sensor, "
"19200 8N1. Frame 1 is idle, frame 2 is the same bay a minute later with the "
"heater running. The vendor documents nothing."
),
# prescan is OPTIONAL for an API caller. Sent here only so the reply is held to
# reconciling these four flag ids, exactly once each, in coverage_check.
"prescan": {
"facts": {
"resources": [{"id": "FRAMES", "label": "2 frames read, 38 bytes total, lengths 19"}],
"flags": [
{"id": "MAGIC-PREFIX", "label": "every frame starts with aa 55"},
{"id": "LENGTH-FIELD", "label": "u16 big-endian at offset 4 verifies in both frames"},
{"id": "CHECKSUM-VERIFIED", "label": "crc16-modbus over bytes 0..16, little-endian at offset 17"},
{"id": "TOO-FEW-FRAMES", "label": "only 2 frames"},
],
}
},
}
est = call("estimate", INPUT)
print(est["model"], est["model_alias"], est["markup_bps"])
print(est["hold_credits"], est["min_credits"], est["sponsor_enabled"])
# Free: no job is created and nothing is charged. Estimate the lane you are about
# to run - the hold for {"task": "armor"} is a different number.
// The capture: two frames, 19 bytes each, one labelled block per frame.
const FRAMES = [
"# frame 1 [tx] poll reply, bay A idle (19 bytes)",
"0000: aa 55 11 21 00 0b 42 41 59 41 01 41 ac 00 00 12",
"0010: d4 19 98",
"# frame 2 [tx] poll reply, bay A heating (19 bytes)",
"0000: aa 55 12 21 00 0b 42 41 59 41 05 41 b6 00 00 12",
"0010: 6b 07 2b",
].join("\n");
const INPUT = {
task: "map", // the lane router: "map" or "armor"
frames: FRAMES,
frame_count: 2,
transport: "serial",
goal: "parse",
context:
"Two frames off an RS-485 bus between an HVAC controller and a bay sensor, 19200 8N1. " +
"Frame 1 is idle, frame 2 is the same bay a minute later with the heater running. " +
"The vendor documents nothing.",
// prescan is OPTIONAL. Sent here only so the reply is held to reconciling these
// four flag ids, exactly once each, in coverage_check.
prescan: {
facts: {
resources: [{ id: "FRAMES", label: "2 frames read, 38 bytes total, lengths 19" }],
flags: [
{ id: "MAGIC-PREFIX", label: "every frame starts with aa 55" },
{ id: "LENGTH-FIELD", label: "u16 big-endian at offset 4 verifies in both frames" },
{ id: "CHECKSUM-VERIFIED", label: "crc16-modbus over bytes 0..16, little-endian at offset 17" },
{ id: "TOO-FEW-FRAMES", label: "only 2 frames" },
],
},
},
};
const est = await call("estimate", INPUT);
console.log(est.model, est.model_alias, est.markup_bps);
console.log(est.hold_credits, est.min_credits, est.sponsor_enabled);
// Free: no job, no charge, guests allowed. hold_credits is a reservation against
// the output cap, and it differs per lane - re-estimate before an armour run.
// The capture: two frames, 19 bytes each, one labelled block per frame.
frames := strings.Join([]string{
"# frame 1 [tx] poll reply, bay A idle (19 bytes)",
"0000: aa 55 11 21 00 0b 42 41 59 41 01 41 ac 00 00 12",
"0010: d4 19 98",
"# frame 2 [tx] poll reply, bay A heating (19 bytes)",
"0000: aa 55 12 21 00 0b 42 41 59 41 05 41 b6 00 00 12",
"0010: 6b 07 2b",
}, "\n")
input := map[string]any{
"task": "map", // the lane router: "map" or "armor"
"frames": frames,
"frame_count": 2,
"transport": "serial",
"goal": "parse",
"context": "Two frames off an RS-485 bus between an HVAC controller and a bay sensor, " +
"19200 8N1. Frame 1 is idle, frame 2 is the same bay a minute later with the heater " +
"running. The vendor documents nothing.",
// prescan is OPTIONAL. Sent so the reply must reconcile these ids in coverage_check.
"prescan": map[string]any{
"facts": map[string]any{
"resources": []any{
map[string]string{"id": "FRAMES", "label": "2 frames read, 38 bytes total, lengths 19"},
},
"flags": []any{
map[string]string{"id": "MAGIC-PREFIX", "label": "every frame starts with aa 55"},
map[string]string{"id": "LENGTH-FIELD", "label": "u16 big-endian at offset 4 verifies in both frames"},
map[string]string{"id": "CHECKSUM-VERIFIED", "label": "crc16-modbus over bytes 0..16, little-endian at offset 17"},
map[string]string{"id": "TOO-FEW-FRAMES", "label": "only 2 frames"},
},
},
},
}
raw, err := call("estimate", input)
if err != nil {
panic(err)
}
fmt.Println(string(raw)) // free: no job, no charge; the hold is a per-lane reservation
// The capture: two frames, 19 bytes each, one labelled block per frame.
String frames = String.join("\\n",
"# frame 1 [tx] poll reply, bay A idle (19 bytes)",
"0000: aa 55 11 21 00 0b 42 41 59 41 01 41 ac 00 00 12",
"0010: d4 19 98",
"# frame 2 [tx] poll reply, bay A heating (19 bytes)",
"0000: aa 55 12 21 00 0b 42 41 59 41 05 41 b6 00 00 12",
"0010: 6b 07 2b");
// Note the doubled backslash: the JSON body needs a literal \n inside the string.
String input = """
{
"task": "map",
"frames": "FRAMES_PLACEHOLDER",
"frame_count": 2,
"transport": "serial",
"goal": "parse",
"context": "Two frames off an RS-485 bus between an HVAC controller and a bay sensor, 19200 8N1. Frame 1 is idle, frame 2 is the same bay a minute later with the heater running. The vendor documents nothing.",
"prescan": {
"facts": {
"resources": [
{ "id": "FRAMES", "label": "2 frames read, 38 bytes total, lengths 19" }
],
"flags": [
{ "id": "MAGIC-PREFIX", "label": "every frame starts with aa 55" },
{ "id": "LENGTH-FIELD", "label": "u16 big-endian at offset 4 verifies in both frames" },
{ "id": "CHECKSUM-VERIFIED", "label": "crc16-modbus over bytes 0..16, little-endian at offset 17" },
{ "id": "TOO-FEW-FRAMES", "label": "only 2 frames" }
]
}
}
}
""".replace("FRAMES_PLACEHOLDER", frames);
System.out.println(call("estimate", input));
// estimate is free: no job is created and nothing is charged. The data object
// carries model, model_alias, markup_bps, hold_credits, min_credits and
// sponsor_enabled. hold_credits is a per-lane reservation against the output cap,
// so re-estimate with "task": "armor" before running the armour lane.
# The capture: two frames, 19 bytes each, one labelled block per frame.
frames = [
"# frame 1 [tx] poll reply, bay A idle (19 bytes)",
"0000: aa 55 11 21 00 0b 42 41 59 41 01 41 ac 00 00 12",
"0010: d4 19 98",
"# frame 2 [tx] poll reply, bay A heating (19 bytes)",
"0000: aa 55 12 21 00 0b 42 41 59 41 05 41 b6 00 00 12",
"0010: 6b 07 2b"
].join("\n")
input = {
"task" => "map", # the lane router: "map" or "armor"
"frames" => frames,
"frame_count" => 2,
"transport" => "serial",
"goal" => "parse",
"context" => "Two frames off an RS-485 bus between an HVAC controller and a bay " \
"sensor, 19200 8N1. Frame 1 is idle, frame 2 is the same bay a minute " \
"later with the heater running. The vendor documents nothing.",
# prescan is OPTIONAL. Sent so the reply must reconcile these ids in coverage_check.
"prescan" => {
"facts" => {
"resources" => [{ "id" => "FRAMES", "label" => "2 frames read, 38 bytes total, lengths 19" }],
"flags" => [
{ "id" => "MAGIC-PREFIX", "label" => "every frame starts with aa 55" },
{ "id" => "LENGTH-FIELD", "label" => "u16 big-endian at offset 4 verifies in both frames" },
{ "id" => "CHECKSUM-VERIFIED", "label" => "crc16-modbus over bytes 0..16, little-endian at offset 17" },
{ "id" => "TOO-FEW-FRAMES", "label" => "only 2 frames" }
]
}
}
}
est = call("estimate", input)
puts "#{est['model']} hold=#{est['hold_credits']} min=#{est['min_credits']}"
# Free: no job is created and nothing is charged. The hold differs per lane.
<?php
// The capture: two frames, 19 bytes each, one labelled block per frame.
$frames = implode("\n", [
"# frame 1 [tx] poll reply, bay A idle (19 bytes)",
"0000: aa 55 11 21 00 0b 42 41 59 41 01 41 ac 00 00 12",
"0010: d4 19 98",
"# frame 2 [tx] poll reply, bay A heating (19 bytes)",
"0000: aa 55 12 21 00 0b 42 41 59 41 05 41 b6 00 00 12",
"0010: 6b 07 2b",
]);
$input = [
"task" => "map", // the lane router: "map" or "armor"
"frames" => $frames,
"frame_count" => 2,
"transport" => "serial",
"goal" => "parse",
"context" => "Two frames off an RS-485 bus between an HVAC controller and a bay " .
"sensor, 19200 8N1. Frame 1 is idle, frame 2 is the same bay a minute " .
"later with the heater running. The vendor documents nothing.",
// prescan is OPTIONAL. Sent so the reply must reconcile these ids in coverage_check.
"prescan" => [
"facts" => [
"resources" => [["id" => "FRAMES", "label" => "2 frames read, 38 bytes total, lengths 19"]],
"flags" => [
["id" => "MAGIC-PREFIX", "label" => "every frame starts with aa 55"],
["id" => "LENGTH-FIELD", "label" => "u16 big-endian at offset 4 verifies in both frames"],
["id" => "CHECKSUM-VERIFIED", "label" => "crc16-modbus over bytes 0..16, little-endian at offset 17"],
["id" => "TOO-FEW-FRAMES", "label" => "only 2 frames"],
],
],
],
];
$est = call("estimate", $input);
echo $est["model"], " ", $est["hold_credits"], " ", $est["min_credits"], PHP_EOL;
// Free: no job is created and nothing is charged. The hold differs per lane.
// The capture: two frames, 19 bytes each, one labelled block per frame.
var frames = string.Join("\n", new[]
{
"# frame 1 [tx] poll reply, bay A idle (19 bytes)",
"0000: aa 55 11 21 00 0b 42 41 59 41 01 41 ac 00 00 12",
"0010: d4 19 98",
"# frame 2 [tx] poll reply, bay A heating (19 bytes)",
"0000: aa 55 12 21 00 0b 42 41 59 41 05 41 b6 00 00 12",
"0010: 6b 07 2b",
});
var input = new
{
task = "map", // the lane router: "map" or "armor"
frames,
frame_count = 2,
transport = "serial",
goal = "parse",
context = "Two frames off an RS-485 bus between an HVAC controller and a bay sensor, " +
"19200 8N1. Frame 1 is idle, frame 2 is the same bay a minute later with the " +
"heater running. The vendor documents nothing.",
// prescan is OPTIONAL. Sent so the reply must reconcile these ids in coverage_check.
prescan = new
{
facts = new
{
resources = new[] { new { id = "FRAMES", label = "2 frames read, 38 bytes total, lengths 19" } },
flags = new[]
{
new { id = "MAGIC-PREFIX", label = "every frame starts with aa 55" },
new { id = "LENGTH-FIELD", label = "u16 big-endian at offset 4 verifies in both frames" },
new { id = "CHECKSUM-VERIFIED", label = "crc16-modbus over bytes 0..16, little-endian at offset 17" },
new { id = "TOO-FEW-FRAMES", label = "only 2 frames" },
},
},
},
};
var est = await WireDesk.Call("estimate", input);
Console.WriteLine(est.GetProperty("hold_credits").GetInt32());
Console.WriteLine(est.GetProperty("min_credits").GetInt32());
Console.WriteLine(est.GetProperty("sponsor_enabled").GetBoolean());
// Free: no job is created and nothing is charged. The hold differs per lane.
5. Run it, then poll
POST /run takes the input object itself as the body. Do not wrap it in
an input key: a wrapped body still returns 200, and the model never sees
task, so you get a plausible answer for the wrong lane and no error to tell you. The
call returns 202 {"job_id", "model", "model_alias"}; poll
GET /jobs/{job_id} until status is succeeded or
failed. The answer JSON is the string at data.output.output, and the
terminal job also carries price_credits, charged_credits — the real cost —
created_at and completed_at.
Always send an Idempotency-Key, and put the lane in it. A replayed key
returns {"job_id", "deduped": true} instead of billing a second run, which is what makes
a CI retry safe after a network blip. The web app derives it as
wire-desk:<lane>:<hash>:a<attempt>, and the lane segment is not
decoration: map and armor over one capture are two distinct runs, and a key
that omits the lane makes the second one dedupe into the first and hand back the first lane's answer.
Replaying a key with a different body is a 409 conflict, so bump the attempt
suffix whenever the input actually changed — which includes adding a retry_note.
# The key carries the LANE. Two lanes over one capture must not share a key.
LANE="map"
KEY="wire-desk:$LANE:$(printf '%s' "$INPUT" | shasum -a 256 | cut -c1-16):a1"
# The body is $INPUT itself - not {"input": ...}.
JOB=$(curl -sS -X POST "$BASE/run" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $KEY" \
-d "$INPUT" | python3 -c 'import sys,json;print(json.load(sys.stdin)["data"]["job_id"])')
# Poll until the job reaches a terminal status.
while :; do
OUT=$(call "jobs/$JOB")
STATUS=$(printf '%s' "$OUT" | python3 -c 'import sys,json;print(json.load(sys.stdin)["data"]["status"])')
[ "$STATUS" = "succeeded" ] && break
[ "$STATUS" = "failed" ] && echo "$OUT" && exit 1
sleep 2
done
# The terminal job looks like this:
# {"ok":true,"data":{"status":"succeeded","model":"gpt-5.6-terra",
# "output":{"output":"{\"task\":\"map\",\"title\":\"AA-55 framed sensor telemetry, 2 frames\", ...}"},
# "price_credits":3120,"charged_credits":742,
# "created_at":"2026-08-23T09:14:02Z","completed_at":"2026-08-23T09:14:37Z"}}
printf '%s' "$OUT" | python3 -c 'import sys,json;print(json.load(sys.stdin)["data"]["output"]["output"])'
import hashlib, time
# The key carries the LANE: two lanes over one capture are two distinct runs and
# must never share a key, or the second dedupes into the first.
digest = hashlib.sha256(json.dumps(INPUT, sort_keys=True).encode()).hexdigest()[:16]
key = f"wire-desk:{INPUT['task']}:{digest}:a1"
# The body is INPUT itself - never {"input": INPUT}.
req = urllib.request.Request(f"{BASE}/run", data=json.dumps(INPUT).encode(), method="POST")
req.add_header("Authorization", f"Bearer {TOKEN}")
req.add_header("Content-Type", "application/json")
req.add_header("Idempotency-Key", key)
with urllib.request.urlopen(req) as r:
started = json.load(r)["data"]
if started.get("deduped"):
print("replayed an earlier run, nothing charged")
job_id = started["job_id"]
while True:
job = call(f"jobs/{job_id}")
if job["status"] == "succeeded":
break
if job["status"] == "failed":
raise RuntimeError(job.get("error"))
time.sleep(2)
answer = json.loads(job["output"]["output"])
print(answer["task"], answer["posture"], answer["confidence"])
print(answer["verdict"])
print("charged", job.get("charged_credits"), "of a", job.get("price_credits"), "hold")
import { createHash } from "node:crypto";
// The key carries the LANE: two lanes over one capture are two distinct runs and
// must never share a key, or the second dedupes into the first.
const digest = createHash("sha256").update(JSON.stringify(INPUT)).digest("hex").slice(0, 16);
const key = `wire-desk:${INPUT.task}:${digest}:a1`;
// The body is INPUT itself - never { input: INPUT }.
const started = await fetch(`${BASE}/run`, {
method: "POST",
headers: {
Authorization: `Bearer ${TOKEN}`,
"Content-Type": "application/json",
"Idempotency-Key": key,
},
body: JSON.stringify(INPUT),
}).then((r) => r.json());
if (started.data.deduped) console.log("replayed an earlier run, nothing charged");
let job = started.data;
while (job.status !== "succeeded" && job.status !== "failed") {
await new Promise((r) => setTimeout(r, 2000));
job = await call(`jobs/${job.job_id}`);
}
if (job.status === "failed") throw new Error(JSON.stringify(job.error));
const answer = JSON.parse(job.output.output);
console.log(answer.task, answer.posture, answer.confidence, "-", answer.verdict);
console.log("charged", job.charged_credits, "of a", job.price_credits, "hold");
// The key carries the LANE: two lanes over one capture are two distinct runs and
// must never share a key, or the second dedupes into the first.
body, _ := json.Marshal(input)
sum := sha256.Sum256(body)
key := fmt.Sprintf("wire-desk:%s:%x:a1", input["task"], sum[:8])
// The body is `input` itself - never {"input": ...}.
req, _ := http.NewRequest(http.MethodPost, base+"/run", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", key)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
var started struct {
Data struct {
JobID string `json:"job_id"`
Deduped bool `json:"deduped"`
} `json:"data"`
}
_ = json.NewDecoder(res.Body).Decode(&started)
for {
raw, err := call("jobs/"+started.Data.JobID, nil)
if err != nil {
panic(err)
}
var job struct {
Status string `json:"status"`
Output struct {
Output string `json:"output"`
} `json:"output"`
PriceCredits int `json:"price_credits"`
ChargedCredits int `json:"charged_credits"`
}
_ = json.Unmarshal(raw, &job)
if job.Status == "succeeded" {
fmt.Println(job.Output.Output) // the answer JSON, as a string
fmt.Println(job.ChargedCredits, "charged of a", job.PriceCredits, "hold")
break
}
if job.Status == "failed" {
panic("run failed")
}
time.Sleep(2 * time.Second)
}
// The key carries the LANE: two lanes over one capture are two distinct runs and
// must never share a key, or the second dedupes into the first.
var digest = java.security.MessageDigest.getInstance("SHA-256")
.digest(input.getBytes(java.nio.charset.StandardCharsets.UTF_8));
var key = "wire-desk:map:"
+ java.util.HexFormat.of().formatHex(digest).substring(0, 16) + ":a1";
// The body is `input` itself - never {"input": ...}.
var start = HttpRequest.newBuilder(URI.create(BASE + "/run"))
.header("Authorization", "Bearer " + TOKEN)
.header("Content-Type", "application/json")
.header("Idempotency-Key", key)
.POST(HttpRequest.BodyPublishers.ofString(input))
.build();
String started = HTTP.send(start, HttpResponse.BodyHandlers.ofString()).body();
// {"ok":true,"data":{"job_id":"job_...","model":"gpt-5.6-terra","model_alias":"gpt-terra"}}
// A replay of the same key comes back as {"job_id":"...","deduped":true}.
//
// Parse job_id out of `started`, then poll GET jobs/{job_id} every two seconds
// until status is "succeeded" or "failed". The answer JSON is data.output.output;
// the terminal job also carries price_credits and charged_credits.
System.out.println(started);
require "digest"
# The key carries the LANE: two lanes over one capture are two distinct runs and
# must never share a key, or the second dedupes into the first.
digest = Digest::SHA256.hexdigest(JSON.generate(input))[0, 16]
key = "wire-desk:#{input['task']}:#{digest}:a1"
# The body is `input` itself - never { "input" => input }.
uri = URI("#{BASE}/run")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{TOKEN}"
req["Content-Type"] = "application/json"
req["Idempotency-Key"] = key
req.body = JSON.generate(input)
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
started = JSON.parse(res.body)["data"]
puts "replayed, nothing charged" if started["deduped"]
job = nil
loop do
job = call("jobs/#{started['job_id']}")
break if %w[succeeded failed].include?(job["status"])
sleep 2
end
abort "run failed: #{job['error']}" if job["status"] == "failed"
answer = JSON.parse(job["output"]["output"])
puts "#{answer['task']} #{answer['posture']} - #{answer['verdict']}"
puts "charged #{job['charged_credits']} of a #{job['price_credits']} hold"
<?php
// The key carries the LANE: two lanes over one capture are two distinct runs and
// must never share a key, or the second dedupes into the first.
$key = "wire-desk:" . $input["task"] . ":" .
substr(hash("sha256", json_encode($input)), 0, 16) . ":a1";
// The body is $input itself - never ["input" => $input].
$ch = curl_init(BASE . "/run");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($input));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer " . TOKEN,
"Content-Type: application/json",
"Idempotency-Key: " . $key,
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$started = json_decode(curl_exec($ch), true)["data"];
curl_close($ch);
do {
sleep(2);
$job = call("jobs/" . $started["job_id"]);
} while (!in_array($job["status"], ["succeeded", "failed"], true));
if ($job["status"] === "failed") {
throw new RuntimeException("run failed");
}
$answer = json_decode($job["output"]["output"], true);
echo $answer["task"], " ", $answer["posture"], " - ", $answer["verdict"], PHP_EOL;
echo "charged ", $job["charged_credits"], " of a ", $job["price_credits"], " hold", PHP_EOL;
using System.Security.Cryptography;
// The key carries the LANE: two lanes over one capture are two distinct runs and
// must never share a key, or the second dedupes into the first.
var bodyJson = JsonSerializer.Serialize(input);
var digest = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(bodyJson)))
.ToLowerInvariant()[..16];
var key = $"wire-desk:{input.task}:{digest}:a1";
// The body is `input` itself - never new { input }.
var runReq = new HttpRequestMessage(HttpMethod.Post, "https://api.skillsafe.ai/v1/app-api/run")
{
Content = new StringContent(bodyJson, Encoding.UTF8, "application/json"),
};
runReq.Headers.Add("Authorization", $"Bearer {Environment.GetEnvironmentVariable("SKILLSAFE_TOKEN")}");
runReq.Headers.Add("Idempotency-Key", key);
using var client = new HttpClient();
var startedRes = await client.SendAsync(runReq);
var started = (await startedRes.Content.ReadFromJsonAsync<JsonElement>()).GetProperty("data");
var jobId = started.GetProperty("job_id").GetString();
JsonElement job;
while (true)
{
job = await WireDesk.Call($"jobs/{jobId}");
var status = job.GetProperty("status").GetString();
if (status is "succeeded" or "failed") break;
await Task.Delay(2000);
}
var answerJson = job.GetProperty("output").GetProperty("output").GetString();
Console.WriteLine(answerJson);
Console.WriteLine(job.GetProperty("charged_credits").GetInt32());
6. Or stream it
POST /run-stream takes the same body and the same optional
Idempotency-Key, and replies with text/event-stream. The sequence is a
single job event carrying the job id and the model, then a run of delta
events each carrying {"text"}, then exactly one terminator: done with the
finished job — job_id, status, charged_credits and
output — or error with the usual {"code", "message"}.
Concatenating every delta.text in order gives you the same string that
output.output would have held.
Two things to expect. The stream is a JSON object being generated left to right, so a partial buffer
is not parseable and there is no point trying until the terminator arrives — the app uses the growing
buffer only to show progress. And an idempotent replay may not stream at all: when
the key has been seen before there is nothing left to generate, so the response can come back as
plain application/json with the finished job in data. Check the
Content-Type before you start parsing events.
# -N disables buffering so the deltas arrive as they are produced.
curl -N -sS -X POST "$BASE/run-stream" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: text/event-stream" \
-H "Idempotency-Key: $KEY" \
-d "$INPUT"
# event: job
# data: {"job_id":"job_...","model":"gpt-5.6-terra","model_alias":"gpt-terra"}
#
# event: delta
# data: {"text":"{\"task\":\"map\",\"title\":\"AA-55 framed"}
#
# event: delta
# data: {"text":" sensor telemetry, 2 frames\",\"posture\":\"decoded\","}
#
# event: done
# data: {"job_id":"job_...","status":"succeeded","charged_credits":742,
# "output":{"output":"{\"task\":\"map\", ...}"}}
#
# An `error` event replaces `done` on failure and carries {"code","message"}.
import json, urllib.request
req = urllib.request.Request(f"{BASE}/run-stream", data=json.dumps(INPUT).encode(), method="POST")
req.add_header("Authorization", f"Bearer {TOKEN}")
req.add_header("Content-Type", "application/json")
req.add_header("Accept", "text/event-stream")
req.add_header("Idempotency-Key", key)
buf, done, event = "", None, "message"
with urllib.request.urlopen(req) as r:
# An idempotent replay has nothing left to generate and answers as plain JSON.
if "text/event-stream" not in r.headers.get("Content-Type", ""):
done = json.load(r)["data"]
else:
for raw in r:
line = raw.decode().rstrip("\n")
if line.startswith("event:"):
event = line[6:].strip()
elif line.startswith("data:"):
data = json.loads(line[5:].strip())
if event == "delta":
buf += data.get("text", "")
elif event == "job":
print("job", data["job_id"], data["model"])
elif event == "done":
done = data
elif event == "error":
raise RuntimeError(f"{data.get('code')}: {data.get('message')}")
full = (done or {}).get("output", {}).get("output") or buf
answer = json.loads(full[full.index("{"):full.rindex("}") + 1])
print(answer["task"], answer["posture"], answer["verdict"])
const res = await fetch(`${BASE}/run-stream`, {
method: "POST",
headers: {
Authorization: `Bearer ${TOKEN}`,
"Content-Type": "application/json",
Accept: "text/event-stream",
"Idempotency-Key": key,
},
body: JSON.stringify(INPUT),
});
// An idempotent replay has nothing left to generate and answers as plain JSON.
if (!(res.headers.get("content-type") || "").includes("text/event-stream")) {
const done = (await res.json()).data;
console.log(done.output.output);
} else {
const reader = res.body.getReader();
const dec = new TextDecoder();
let buffer = "";
let text = "";
let done = null;
for (;;) {
const chunk = await reader.read();
if (chunk.done) break;
buffer += dec.decode(chunk.value, { stream: true });
let i;
while ((i = buffer.indexOf("\n\n")) >= 0) {
const frame = buffer.slice(0, i);
buffer = buffer.slice(i + 2);
let event = "message";
let payload = "";
for (const line of frame.split("\n")) {
if (line.startsWith("event:")) event = line.slice(6).trim();
else if (line.startsWith("data:")) payload += line.slice(5).trim();
}
if (!payload) continue;
const data = JSON.parse(payload);
if (event === "delta") text += data.text || "";
else if (event === "job") console.log("job", data.job_id, data.model);
else if (event === "done") done = data;
else if (event === "error") throw new Error(`${data.code}: ${data.message}`);
}
}
const full = done?.output?.output ?? text;
const answer = JSON.parse(full.slice(full.indexOf("{"), full.lastIndexOf("}") + 1));
console.log(answer.task, answer.posture, answer.verdict, done?.charged_credits);
}
streamBody, _ := json.Marshal(input)
sreq, _ := http.NewRequest(http.MethodPost, base+"/run-stream", bytes.NewReader(streamBody))
sreq.Header.Set("Authorization", "Bearer "+token)
sreq.Header.Set("Content-Type", "application/json")
sreq.Header.Set("Accept", "text/event-stream")
sreq.Header.Set("Idempotency-Key", key)
sres, err := http.DefaultClient.Do(sreq)
if err != nil {
panic(err)
}
defer sres.Body.Close()
// An idempotent replay has nothing left to generate and answers as plain JSON.
if !strings.Contains(sres.Header.Get("Content-Type"), "text/event-stream") {
b, _ := io.ReadAll(sres.Body)
fmt.Println(string(b))
return
}
var text strings.Builder
event := "message"
scanner := bufio.NewScanner(sres.Body)
scanner.Buffer(make([]byte, 0, 64*1024), 1024*1024)
for scanner.Scan() {
line := scanner.Text()
switch {
case strings.HasPrefix(line, "event:"):
event = strings.TrimSpace(line[6:])
case strings.HasPrefix(line, "data:"):
payload := strings.TrimSpace(line[5:])
switch event {
case "delta":
var d struct{ Text string `json:"text"` }
_ = json.Unmarshal([]byte(payload), &d)
text.WriteString(d.Text)
case "job":
fmt.Println("job:", payload)
case "done":
fmt.Println("done:", payload)
case "error":
panic("stream error: " + payload)
}
}
}
fmt.Println(text.Len(), "characters of answer JSON")
var streamReq = HttpRequest.newBuilder(URI.create(BASE + "/run-stream"))
.header("Authorization", "Bearer " + TOKEN)
.header("Content-Type", "application/json")
.header("Accept", "text/event-stream")
.header("Idempotency-Key", key)
.POST(HttpRequest.BodyPublishers.ofString(input))
.build();
var stream = HTTP.send(streamReq, HttpResponse.BodyHandlers.ofLines());
var text = new StringBuilder();
var event = new java.util.concurrent.atomic.AtomicReference<String>("message");
stream.body().forEach(line -> {
if (line.startsWith("event:")) {
event.set(line.substring(6).trim());
} else if (line.startsWith("data:")) {
String payload = line.substring(5).trim();
switch (event.get()) {
case "delta" -> text.append(payload); // {"text":"..."} - unwrap with your JSON library
case "job" -> System.out.println("job: " + payload);
case "done" -> System.out.println("done: " + payload);
case "error" -> throw new RuntimeException("stream error: " + payload);
default -> { }
}
}
});
// Concatenating every delta.text in order gives the same string that
// output.output would have held. An idempotent replay answers as plain
// application/json instead of a stream, so check the Content-Type first.
System.out.println(text.length());
uri = URI("#{BASE}/run-stream")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{TOKEN}"
req["Content-Type"] = "application/json"
req["Accept"] = "text/event-stream"
req["Idempotency-Key"] = key
req.body = JSON.generate(input)
text = ""
event = "message"
done = nil
Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(req) do |res|
# An idempotent replay has nothing left to generate and answers as plain JSON.
unless res["Content-Type"].to_s.include?("text/event-stream")
done = JSON.parse(res.body)["data"]
next
end
buffer = ""
res.read_body do |chunk|
buffer << chunk
while (i = buffer.index("\n\n"))
frame = buffer.slice!(0, i + 2)
frame.each_line do |line|
line = line.chomp
if line.start_with?("event:")
event = line[6..].strip
elsif line.start_with?("data:")
data = JSON.parse(line[5..].strip)
case event
when "delta" then text << data.fetch("text", "")
when "job" then puts "job #{data['job_id']} #{data['model']}"
when "done" then done = data
when "error" then raise "#{data['code']}: #{data['message']}"
end
end
end
end
end
end
end
full = done&.dig("output", "output") || text
answer = JSON.parse(full[full.index("{")..full.rindex("}")])
puts "#{answer['task']} #{answer['posture']} - #{answer['verdict']}"
<?php
$text = "";
$event = "message";
$buffer = "";
$ch = curl_init(BASE . "/run-stream");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($input));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer " . TOKEN,
"Content-Type: application/json",
"Accept: text/event-stream",
"Idempotency-Key: " . $key,
]);
curl_setopt($ch, CURLOPT_WRITEFUNCTION, function ($ch, $chunk) use (&$text, &$event, &$buffer) {
$buffer .= $chunk;
while (($i = strpos($buffer, "\n\n")) !== false) {
$frame = substr($buffer, 0, $i);
$buffer = substr($buffer, $i + 2);
foreach (explode("\n", $frame) as $line) {
if (str_starts_with($line, "event:")) {
$event = trim(substr($line, 6));
} elseif (str_starts_with($line, "data:")) {
$data = json_decode(trim(substr($line, 5)), true);
if ($event === "delta") {
$text .= $data["text"] ?? "";
} elseif ($event === "error") {
throw new RuntimeException($data["code"] . ": " . $data["message"]);
}
}
}
}
return strlen($chunk);
});
curl_exec($ch);
curl_close($ch);
$body = substr($text, strpos($text, "{"), strrpos($text, "}") - strpos($text, "{") + 1);
$answer = json_decode($body, true);
echo $answer["task"], " ", $answer["posture"], " - ", $answer["verdict"], PHP_EOL;
var streamReq = new HttpRequestMessage(HttpMethod.Post,
"https://api.skillsafe.ai/v1/app-api/run-stream")
{
Content = new StringContent(bodyJson, Encoding.UTF8, "application/json"),
};
streamReq.Headers.Add("Authorization", $"Bearer {Environment.GetEnvironmentVariable("SKILLSAFE_TOKEN")}");
streamReq.Headers.Add("Accept", "text/event-stream");
streamReq.Headers.Add("Idempotency-Key", key);
var streamRes = await client.SendAsync(streamReq, HttpCompletionOption.ResponseHeadersRead);
var text = new StringBuilder();
var evt = "message";
// An idempotent replay has nothing left to generate and answers as plain JSON.
if (streamRes.Content.Headers.ContentType?.MediaType != "text/event-stream")
{
Console.WriteLine(await streamRes.Content.ReadAsStringAsync());
}
else
{
using var reader = new StreamReader(await streamRes.Content.ReadAsStreamAsync());
string? line;
while ((line = await reader.ReadLineAsync()) is not null)
{
if (line.StartsWith("event:")) { evt = line[6..].Trim(); continue; }
if (!line.StartsWith("data:")) continue;
var data = JsonSerializer.Deserialize<JsonElement>(line[5..].Trim());
if (evt == "delta") text.Append(data.GetProperty("text").GetString());
else if (evt == "job") Console.WriteLine($"job {data.GetProperty("job_id")}");
else if (evt == "done") Console.WriteLine($"charged {data.GetProperty("charged_credits")}");
else if (evt == "error") throw new Exception(data.GetProperty("message").GetString());
}
var full = text.ToString();
var answerJson = full[full.IndexOf('{')..(full.LastIndexOf('}') + 1)];
Console.WriteLine(answerJson.Length);
}
7. Parse the reply and pull the artifacts out
output.output is a string, and the model is asked for bare JSON with no prose and no
fences — but a caller that assumes that will fail on the run where it wrapped the object in
```json. The web app's reader is four steps and it is worth copying exactly: trim, strip
a leading fence and a trailing one, slice from the first { to the
last }, then parse. Only then branch on task and read the lane
body.
Three checks are worth running before you trust the object. Every
prescan.facts.flags id you sent must appear exactly once in
coverage_check, and no id you did not send may appear at all. On map, the
fields arrive sorted by offset, so one pass tells you whether any two overlap or any field runs past
the shortest frame — both are answers the page would contradict out loud. And a
posture of decoded with an unresolved[] that is not empty is a
self-contradiction worth failing on rather than rendering.
Then write artifacts[] to disk. Remember that an artifact with empty
content was dropped by the normaliser, so check what you actually got: the
map lane should hand back three files and the armor lane two.
# $OUT holds the terminal job envelope from step 5.
printf '%s' "$OUT" | python3 -c '
import json, re, sys
job = json.load(sys.stdin)["data"]
text = job["output"]["output"].strip()
text = re.sub(r"^```[a-z]*\s*", "", text, flags=re.I)
text = re.sub(r"```\s*$", "", text)
answer = json.loads(text[text.index("{"):text.rindex("}") + 1])
print(answer["task"], answer["posture"], answer["confidence"])
print(answer["verdict"])
if answer["task"] == "map":
print(answer["framing"]["style"], answer["endianness"])
for f in answer["fields"]:
print(" %3d +%-2d %-9s %-8s %s" %
(f["offset"], f["width"], f["role"], f["type"], f["name"]))
ck = answer.get("checksum")
if ck:
print(" checksum", ck["algorithm"], "at", ck["offset"], ck["verified"])
for u in answer["unresolved"]:
print(" unresolved", u["offset"], u["width"], u["why"])
else:
for r in answer["regions"]:
print(" %3d +%-3d %-14s %s" %
(r["offset"], r["width"], r["character"], r["technique"]))
for t in answer["techniques"]:
print(" technique", t["effort"], t["name"], "->", t["how_to_confirm"])
for b in answer["blockers"]:
print(" blocker", b["blocker"])
# Every artifact to its own file: protocol.ksy, dissector.lua, parse.py on the map
# lane; confirm.md and probe.py on the armour lane.
for a in answer["artifacts"]:
with open(a["name"], "w") as fh:
fh.write(a["content"])
print("wrote", a["name"], a["language"], len(a["content"]), "bytes")
'
import pathlib, re
def read_answer(text):
"""The web app's reader, exactly: trim, strip fences, first { to last }."""
t = text.strip()
t = re.sub(r"^```[a-z]*\s*", "", t, flags=re.I)
t = re.sub(r"```\s*$", "", t)
i, j = t.find("{"), t.rfind("}")
if i < 0 or j <= i:
raise ValueError("no JSON object found - retry with a retry_note")
return json.loads(t[i:j + 1])
answer = read_answer(job["output"]["output"])
# 1. Every prescan flag id comes back exactly once, and nothing else does.
sent = [f["id"] for f in INPUT["prescan"]["facts"]["flags"]]
seen = [c["id"] for c in answer["coverage_check"]]
missing = [i for i in sent if seen.count(i) != 1]
extra = [i for i in seen if i not in sent]
if missing or extra:
raise SystemExit(f"coverage_check drift: missing={missing} extra={extra}")
# 2. The lane body, and the invariants the page would check.
if answer["task"] == "map":
print(answer["framing"]["style"], answer["framing"]["evidence"])
print("endianness", answer["endianness"])
end = 0
for f in answer["fields"]:
if f["offset"] < end:
raise SystemExit(f"fields overlap at offset {f['offset']}")
end = f["offset"] + f["width"]
print(f" {f['offset']:3} +{f['width']:<2} {f['role']:9} {f['type']:8} "
f"{f['name']:22} {f['confidence']:6} {f['observed']}")
if answer["posture"] == "decoded" and answer["unresolved"]:
raise SystemExit("posture says decoded but unresolved[] is not empty")
ck = answer.get("checksum")
if ck:
print(" checksum", ck["algorithm"], "at", ck["offset"], ck["endian"], ck["verified"])
for c in answer["next_captures"]:
print(" next:", c["action"], "->", c["resolves"])
else:
for r in answer["regions"]:
print(f" {r['offset']:3} +{r['width']:<3} {r['character']:14} {r['technique']}")
print(" ", r["evidence"])
for t in answer["techniques"]:
print(f" [{t['effort']}] {t['name']}: {t['how_to_confirm']}")
for b in answer["blockers"]:
print(" blocked:", b["blocker"], "-", b["why"])
for p in answer["pivots"]:
print(" pivot:", p["pivot"], "-", p["first_move"])
# 3. The artifacts, each to its own file. An artifact with empty content was
# dropped by the normalizer, so count what arrived rather than assuming.
out = pathlib.Path("wire-desk-out")
out.mkdir(exist_ok=True)
for a in answer["artifacts"]:
(out / a["name"]).write_text(a["content"], encoding="utf-8")
print("wrote", a["name"], f"({a['language']}, {len(a['content'])} bytes)")
expected = 3 if answer["task"] == "map" else 2
if len(answer["artifacts"]) < expected:
print(f"note: only {len(answer['artifacts'])} of {expected} artifacts came back")
import { mkdirSync, writeFileSync } from "node:fs";
import { join } from "node:path";
function readAnswer(text) {
// The web app's reader, exactly: trim, strip fences, first { to last }.
let t = String(text ?? "").trim();
t = t.replace(/^```[a-z]*\s*/i, "").replace(/```\s*$/, "");
const i = t.indexOf("{");
const j = t.lastIndexOf("}");
if (i < 0 || j <= i) throw new Error("no JSON object found - retry with a retry_note");
return JSON.parse(t.slice(i, j + 1));
}
const answer = readAnswer(job.output.output);
// 1. Every prescan flag id comes back exactly once, and nothing else does.
const sent = INPUT.prescan.facts.flags.map((f) => f.id);
const seen = answer.coverage_check.map((c) => c.id);
const missing = sent.filter((id) => seen.filter((s) => s === id).length !== 1);
const extra = seen.filter((id) => !sent.includes(id));
if (missing.length || extra.length) {
throw new Error(`coverage_check drift: missing=${missing} extra=${extra}`);
}
// 2. The lane body, and the invariants the page would check.
if (answer.task === "map") {
console.log(answer.framing.style, "-", answer.framing.evidence);
let end = 0;
for (const f of answer.fields) {
if (f.offset < end) throw new Error(`fields overlap at offset ${f.offset}`);
end = f.offset + f.width;
console.log(` ${String(f.offset).padStart(3)} +${f.width} ${f.role} ${f.type} ${f.name}`);
}
if (answer.posture === "decoded" && answer.unresolved.length) {
throw new Error("posture says decoded but unresolved[] is not empty");
}
if (answer.checksum) {
console.log("checksum", answer.checksum.algorithm, answer.checksum.verified);
}
} else {
for (const r of answer.regions) {
console.log(` ${r.offset} +${r.width} ${r.character} ${r.technique}`);
console.log(" ", r.evidence);
}
for (const t of answer.techniques) console.log(`[${t.effort}] ${t.name}: ${t.how_to_confirm}`);
for (const b of answer.blockers) console.log("blocked:", b.blocker, "-", b.why);
for (const p of answer.pivots) console.log("pivot:", p.pivot, "-", p.first_move);
}
// 3. The artifacts, each to its own file. Empty-content entries were dropped.
mkdirSync("wire-desk-out", { recursive: true });
for (const a of answer.artifacts) {
writeFileSync(join("wire-desk-out", a.name), a.content, "utf8");
console.log("wrote", a.name, `(${a.language}, ${a.content.length} bytes)`);
}
const expected = answer.task === "map" ? 3 : 2;
if (answer.artifacts.length < expected) {
console.warn(`only ${answer.artifacts.length} of ${expected} artifacts came back`);
}
type field struct {
ID string `json:"id"`
Offset int `json:"offset"`
Width int `json:"width"`
Name string `json:"name"`
Type string `json:"type"`
Endian string `json:"endian"`
Role string `json:"role"`
Observed string `json:"observed"`
Evidence string `json:"evidence"`
Confidence string `json:"confidence"`
}
type region struct {
ID string `json:"id"`
Offset int `json:"offset"`
Width int `json:"width"`
Character string `json:"character"`
Technique string `json:"technique"`
Assessment string `json:"assessment"`
Evidence string `json:"evidence"`
Confidence string `json:"confidence"`
}
type answer struct {
Task string `json:"task"`
Title string `json:"title"`
Posture string `json:"posture"`
Confidence string `json:"confidence"`
Verdict string `json:"verdict"`
Framing struct {
Style string `json:"style"`
Evidence string `json:"evidence"`
} `json:"framing"`
Endianness string `json:"endianness"`
Fields []field `json:"fields"`
Unresolved []struct {
Offset int `json:"offset"`
Width int `json:"width"`
Why string `json:"why"`
} `json:"unresolved"`
Regions []region `json:"regions"`
Techniques []struct {
Name string `json:"name"`
Effort string `json:"effort"`
HowToConfirm string `json:"how_to_confirm"`
} `json:"techniques"`
CoverageCheck []struct {
ID string `json:"id"`
Addressed bool `json:"addressed"`
Note string `json:"note"`
} `json:"coverage_check"`
Artifacts []struct {
Name string `json:"name"`
Language string `json:"language"`
Content string `json:"content"`
} `json:"artifacts"`
}
// The web app's reader: trim, strip fences, first { to last }.
func readAnswer(text string) (answer, error) {
var a answer
t := strings.TrimSpace(text)
t = strings.TrimPrefix(t, "```json")
t = strings.TrimPrefix(t, "```")
t = strings.TrimSuffix(strings.TrimSpace(t), "```")
i, j := strings.Index(t, "{"), strings.LastIndex(t, "}")
if i < 0 || j <= i {
return a, fmt.Errorf("no JSON object found - retry with a retry_note")
}
return a, json.Unmarshal([]byte(t[i:j+1]), &a)
}
a, err := readAnswer(job.Output.Output)
if err != nil {
panic(err)
}
// Every prescan flag id comes back exactly once.
count := map[string]int{}
for _, c := range a.CoverageCheck {
count[c.ID]++
}
for _, id := range []string{"MAGIC-PREFIX", "LENGTH-FIELD", "CHECKSUM-VERIFIED", "TOO-FEW-FRAMES"} {
if count[id] != 1 {
panic("unreconciled prescan flag: " + id)
}
}
if a.Task == "map" {
end := 0
for _, f := range a.Fields {
if f.Offset < end {
panic(fmt.Sprintf("fields overlap at offset %d", f.Offset))
}
end = f.Offset + f.Width
fmt.Printf(" %3d +%-2d %-9s %-8s %s\n", f.Offset, f.Width, f.Role, f.Type, f.Name)
}
if a.Posture == "decoded" && len(a.Unresolved) > 0 {
panic("posture says decoded but unresolved[] is not empty")
}
} else {
for _, r := range a.Regions {
fmt.Printf(" %3d +%-3d %-14s %s\n", r.Offset, r.Width, r.Character, r.Technique)
}
}
_ = os.MkdirAll("wire-desk-out", 0o755)
for _, art := range a.Artifacts {
if err := os.WriteFile("wire-desk-out/"+art.Name, []byte(art.Content), 0o644); err != nil {
panic(err)
}
fmt.Println("wrote", art.Name, art.Language, len(art.Content), "bytes")
}
// The reader is four steps, and they matter in this order:
//
// 1. trim the string at data.output.output;
// 2. strip a leading ``` or ```json fence and a trailing ```;
// 3. slice from the FIRST '{' to the LAST '}';
// 4. parse with your JSON library, then branch on the "task" field.
//
// Then the three checks:
//
// - every prescan.facts.flags id appears exactly once in coverage_check, and no
// id you did not send appears at all;
// - on "map", fields[] arrives sorted by offset, so a single pass catches an
// overlap or a field running past the shortest frame;
// - posture "decoded" with a non-empty unresolved[] contradicts itself.
//
// Finally write artifacts[]: {name, language, content} - protocol.ksy,
// dissector.lua and parse.py on the map lane, confirm.md and probe.py on armour.
// An artifact whose content was empty is dropped by the normalizer, so count.
String raw = /* data.output.output */ call("jobs/" + jobId, null);
String t = raw.strip().replaceFirst("(?i)^```[a-z]*\\s*", "").replaceFirst("```\\s*$", "");
String body = t.substring(t.indexOf('{'), t.lastIndexOf('}') + 1);
System.out.println(body.length() + " bytes of answer JSON");
java.nio.file.Files.createDirectories(java.nio.file.Path.of("wire-desk-out"));
// for (var a : answer.artifacts)
// Files.writeString(Path.of("wire-desk-out", a.name), a.content);
require "fileutils"
def read_answer(text)
# The web app's reader, exactly: trim, strip fences, first { to last }.
t = text.to_s.strip
t = t.sub(/\A```[a-z]*\s*/i, "").sub(/```\s*\z/, "")
i = t.index("{")
j = t.rindex("}")
raise "no JSON object found - retry with a retry_note" if i.nil? || j.nil? || j <= i
JSON.parse(t[i..j])
end
answer = read_answer(job["output"]["output"])
# 1. Every prescan flag id comes back exactly once, and nothing else does.
sent = input["prescan"]["facts"]["flags"].map { |f| f["id"] }
seen = answer["coverage_check"].map { |c| c["id"] }
missing = sent.reject { |id| seen.count(id) == 1 }
extra = seen - sent
raise "coverage_check drift: #{missing} / #{extra}" unless missing.empty? && extra.empty?
# 2. The lane body.
if answer["task"] == "map"
puts "#{answer['framing']['style']} - #{answer['framing']['evidence']}"
finish = 0
answer["fields"].each do |f|
raise "fields overlap at offset #{f['offset']}" if f["offset"] < finish
finish = f["offset"] + f["width"]
puts format(" %3d +%-2d %-9s %-8s %s", f["offset"], f["width"], f["role"], f["type"], f["name"])
end
raise "decoded but unresolved[] is not empty" if
answer["posture"] == "decoded" && !answer["unresolved"].empty?
ck = answer["checksum"]
puts " checksum #{ck['algorithm']} #{ck['verified']}" if ck
else
answer["regions"].each do |r|
puts format(" %3d +%-3d %-14s %s", r["offset"], r["width"], r["character"], r["technique"])
end
answer["techniques"].each { |t| puts " [#{t['effort']}] #{t['name']}: #{t['how_to_confirm']}" }
answer["pivots"].each { |p| puts " pivot: #{p['pivot']} - #{p['first_move']}" }
end
# 3. The artifacts, each to its own file.
FileUtils.mkdir_p("wire-desk-out")
answer["artifacts"].each do |a|
File.write(File.join("wire-desk-out", a["name"]), a["content"])
puts "wrote #{a['name']} (#{a['language']}, #{a['content'].bytesize} bytes)"
end
expected = answer["task"] == "map" ? 3 : 2
warn "only #{answer['artifacts'].size} of #{expected} artifacts came back" if
answer["artifacts"].size < expected
<?php
function read_answer(string $text): array {
// The web app's reader, exactly: trim, strip fences, first { to last }.
$t = trim($text);
$t = preg_replace('/^```[a-z]*\s*/i', "", $t);
$t = preg_replace('/```\s*$/', "", $t);
$i = strpos($t, "{");
$j = strrpos($t, "}");
if ($i === false || $j === false || $j <= $i) {
throw new RuntimeException("no JSON object found - retry with a retry_note");
}
return json_decode(substr($t, $i, $j - $i + 1), true, 512, JSON_THROW_ON_ERROR);
}
$answer = read_answer($job["output"]["output"]);
// 1. Every prescan flag id comes back exactly once, and nothing else does.
$sent = array_column($input["prescan"]["facts"]["flags"], "id");
$counts = array_count_values(array_column($answer["coverage_check"], "id"));
foreach ($sent as $id) {
if (($counts[$id] ?? 0) !== 1) {
throw new RuntimeException("unreconciled prescan flag: " . $id);
}
}
// 2. The lane body.
if ($answer["task"] === "map") {
echo $answer["framing"]["style"], " - ", $answer["framing"]["evidence"], PHP_EOL;
$end = 0;
foreach ($answer["fields"] as $f) {
if ($f["offset"] < $end) {
throw new RuntimeException("fields overlap at offset " . $f["offset"]);
}
$end = $f["offset"] + $f["width"];
printf(" %3d +%-2d %-9s %-8s %s\n", $f["offset"], $f["width"], $f["role"], $f["type"], $f["name"]);
}
if ($answer["posture"] === "decoded" && $answer["unresolved"]) {
throw new RuntimeException("posture says decoded but unresolved[] is not empty");
}
} else {
foreach ($answer["regions"] as $r) {
printf(" %3d +%-3d %-14s %s\n", $r["offset"], $r["width"], $r["character"], $r["technique"]);
}
foreach ($answer["techniques"] as $t) {
echo " [", $t["effort"], "] ", $t["name"], ": ", $t["how_to_confirm"], PHP_EOL;
}
}
// 3. The artifacts, each to its own file.
@mkdir("wire-desk-out");
foreach ($answer["artifacts"] as $a) {
file_put_contents("wire-desk-out/" . $a["name"], $a["content"]);
echo "wrote ", $a["name"], " (", $a["language"], ", ", strlen($a["content"]), " bytes)", PHP_EOL;
}
using System.Text.RegularExpressions;
static JsonElement ReadAnswer(string text)
{
// The web app's reader, exactly: trim, strip fences, first { to last }.
var t = text.Trim();
t = Regex.Replace(t, @"^```[a-z]*\s*", "", RegexOptions.IgnoreCase);
t = Regex.Replace(t, @"```\s*$", "");
var i = t.IndexOf('{');
var j = t.LastIndexOf('}');
if (i < 0 || j <= i) throw new Exception("no JSON object found - retry with a retry_note");
return JsonSerializer.Deserialize<JsonElement>(t[i..(j + 1)]);
}
var answer = ReadAnswer(answerJson!);
// 1. Every prescan flag id comes back exactly once.
var seen = answer.GetProperty("coverage_check").EnumerateArray()
.Select(c => c.GetProperty("id").GetString()).ToList();
foreach (var id in new[] { "MAGIC-PREFIX", "LENGTH-FIELD", "CHECKSUM-VERIFIED", "TOO-FEW-FRAMES" })
{
if (seen.Count(s => s == id) != 1) throw new Exception($"unreconciled prescan flag: {id}");
}
// 2. The lane body.
if (answer.GetProperty("task").GetString() == "map")
{
var end = 0;
foreach (var f in answer.GetProperty("fields").EnumerateArray())
{
var off = f.GetProperty("offset").GetInt32();
if (off < end) throw new Exception($"fields overlap at offset {off}");
end = off + f.GetProperty("width").GetInt32();
Console.WriteLine($" {off,3} {f.GetProperty("role")} {f.GetProperty("type")} {f.GetProperty("name")}");
}
}
else
{
foreach (var r in answer.GetProperty("regions").EnumerateArray())
Console.WriteLine($" {r.GetProperty("offset")} {r.GetProperty("character")} {r.GetProperty("technique")}");
}
// 3. The artifacts, each to its own file.
Directory.CreateDirectory("wire-desk-out");
foreach (var a in answer.GetProperty("artifacts").EnumerateArray())
{
var name = a.GetProperty("name").GetString()!;
File.WriteAllText(Path.Combine("wire-desk-out", name), a.GetProperty("content").GetString());
Console.WriteLine($"wrote {name} ({a.GetProperty("language")})");
}
Idempotency, and why estimate is free
Hash the triple (task, input, attempt) and nothing else. The lane has to
be in the key because map and armor over one capture are two distinct
runs — different prompt, different output cap, different price — and a key that omits the lane makes
the second call dedupe into the first and hand back the wrong lane's answer with
deduped: true and no charge, which looks like a success until someone reads it. The
attempt counter has to be in the key because a retry with a retry_note is a different
body, and replaying a key with a different body is a 409 conflict rather than a fresh
run. wire-desk:map:9f21c4ab8e7d0512:a1 is the whole convention.
What you hash inside the input is a choice worth making deliberately. The web app hashes a
shallow shape — the lane, the raw paste, transport, goal and
context — and leaves prescan out of it, because the prescan is derived from
the same paste and including it would make the key change every time the scanner is improved, turning
a free replay into a fresh charge. If you send a prescan, do the same: key on the
capture, not on your measurements of it. Keep the key stable across a re-run of the same commit and a
CI retry after a network blip costs nothing.
And run /estimate generously, because it is free in the strong sense: no job is created,
no credits are held, nothing is charged, and a guest token is allowed. It is the right call to make
before a batch — once per lane, on one representative capture — so a folder of two hundred saved
captures is priced before the first one runs rather than discovered at the fortieth. The one number
it does not give you is the cost: hold_credits is the reservation against the full output
cap, and the figure to report afterwards is charged_credits on the settled job.