super-dsh 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent-agy/bridge/agy_bridge.py +250 -0
- package/agent-agy/bridge/gemini_openai_server.mjs +98 -0
- package/agent-agy/cordis.patch.yml +49 -0
- package/agent-agy/dist/adapter.d.ts +15 -0
- package/agent-agy/dist/adapter.js +42 -0
- package/agent-agy/dist/agent-preset-agy.d.ts +47 -0
- package/agent-agy/dist/agent-preset-agy.js +169 -0
- package/agent-agy/dist/agent-preset-projection.d.ts +31 -0
- package/agent-agy/dist/agent-preset-projection.js +33 -0
- package/agent-agy/dist/agent.d.ts +137 -0
- package/agent-agy/dist/agent.js +493 -0
- package/agent-agy/dist/agy-cli-client.d.ts +137 -0
- package/agent-agy/dist/agy-cli-client.js +301 -0
- package/agent-agy/dist/agy-client.d.ts +160 -0
- package/agent-agy/dist/agy-client.js +252 -0
- package/agent-agy/dist/agy-sessions.d.ts +24 -0
- package/agent-agy/dist/agy-sessions.js +116 -0
- package/agent-agy/dist/agy-store.d.ts +39 -0
- package/agent-agy/dist/agy-store.js +83 -0
- package/agent-agy/dist/inbox.d.ts +26 -0
- package/agent-agy/dist/inbox.js +53 -0
- package/agent-agy/dist/index.d.ts +80 -0
- package/agent-agy/dist/index.js +505 -0
- package/agent-agy/dist/models.d.ts +41 -0
- package/agent-agy/dist/models.js +74 -0
- package/agent-agy/dist/world-plugin.d.ts +9 -0
- package/agent-agy/dist/world-plugin.js +97 -0
- package/agent-agy/package.json +64 -0
- package/agent-claude/cordis.patch.yml +42 -0
- package/agent-claude/dist/adapter.js +62 -0
- package/agent-claude/dist/agent-preset-claude.js +169 -0
- package/agent-claude/dist/agent-preset-projection.js +30 -0
- package/agent-claude/dist/agent.js +1271 -0
- package/agent-claude/dist/claude-client.js +357 -0
- package/agent-claude/dist/claude-events.js +223 -0
- package/agent-claude/dist/claude-home.js +29 -0
- package/agent-claude/dist/commands.js +182 -0
- package/agent-claude/dist/content.js +134 -0
- package/agent-claude/dist/inbox.js +53 -0
- package/agent-claude/dist/index.js +555 -0
- package/agent-claude/dist/input-queue.js +64 -0
- package/agent-claude/dist/interaction.js +271 -0
- package/agent-claude/dist/models.js +158 -0
- package/agent-claude/dist/permission.js +120 -0
- package/agent-claude/dist/session-id.js +20 -0
- package/agent-claude/dist/session-map.js +165 -0
- package/agent-claude/dist/world-plugin.js +14 -0
- package/agent-claude/package.json +74 -0
- package/agent-codex/README.md +74 -0
- package/agent-codex/cordis.patch.yml +77 -0
- package/agent-codex/dist/adapter.js +55 -0
- package/agent-codex/dist/agent-preset-codex.js +171 -0
- package/agent-codex/dist/agent-preset-projection.js +37 -0
- package/agent-codex/dist/agent.js +1056 -0
- package/agent-codex/dist/app-home.js +65 -0
- package/agent-codex/dist/codex-client.js +469 -0
- package/agent-codex/dist/codex-events.js +297 -0
- package/agent-codex/dist/codex-store.js +190 -0
- package/agent-codex/dist/inbox.js +53 -0
- package/agent-codex/dist/index.js +605 -0
- package/agent-codex/dist/models.js +152 -0
- package/agent-codex/dist/permission.js +71 -0
- package/agent-codex/dist/session-map.js +170 -0
- package/agent-codex/dist/spike.js +46 -0
- package/agent-codex/dist/world-plugin.js +96 -0
- package/agent-codex/package.json +78 -0
- package/agent-hermes/README.md +144 -0
- package/agent-hermes/cordis.patch.yml +83 -0
- package/agent-hermes/dist/adapter.js +83 -0
- package/agent-hermes/dist/agent-preset-hermes.js +171 -0
- package/agent-hermes/dist/agent-preset-projection.js +34 -0
- package/agent-hermes/dist/agent.js +1068 -0
- package/agent-hermes/dist/hermes-client.js +953 -0
- package/agent-hermes/dist/hermes-events.js +289 -0
- package/agent-hermes/dist/hermes-store.js +158 -0
- package/agent-hermes/dist/inbox.js +53 -0
- package/agent-hermes/dist/index.js +541 -0
- package/agent-hermes/dist/models.js +324 -0
- package/agent-hermes/dist/permission.js +60 -0
- package/agent-hermes/dist/world-plugin.js +101 -0
- package/agent-hermes/package.json +76 -0
- package/agent-hub/cordis.patch.yml +12 -0
- package/agent-hub/dist/agent-roster.d.ts +34 -0
- package/agent-hub/dist/agent-roster.js +27 -0
- package/agent-hub/dist/carrier.d.ts +43 -0
- package/agent-hub/dist/carrier.js +241 -0
- package/agent-hub/dist/client-shim.d.ts +28 -0
- package/agent-hub/dist/client-shim.js +251 -0
- package/agent-hub/dist/envelope.d.ts +54 -0
- package/agent-hub/dist/envelope.js +92 -0
- package/agent-hub/dist/gateway.d.ts +28 -0
- package/agent-hub/dist/gateway.js +18 -0
- package/agent-hub/dist/index-pass.d.ts +22 -0
- package/agent-hub/dist/index-pass.js +47 -0
- package/agent-hub/dist/index.d.ts +29 -0
- package/agent-hub/dist/index.js +29 -0
- package/agent-hub/dist/labels.d.ts +45 -0
- package/agent-hub/dist/labels.js +83 -0
- package/agent-hub/dist/ownership.d.ts +37 -0
- package/agent-hub/dist/ownership.js +54 -0
- package/agent-hub/dist/roster.d.ts +28 -0
- package/agent-hub/dist/roster.js +38 -0
- package/agent-hub/dist/rpc.d.ts +38 -0
- package/agent-hub/dist/rpc.js +52 -0
- package/agent-hub/dist/spawn-world.d.ts +46 -0
- package/agent-hub/dist/spawn-world.js +72 -0
- package/agent-hub/dist/targets.d.ts +33 -0
- package/agent-hub/dist/targets.js +30 -0
- package/agent-hub/dist/world-entry.d.ts +31 -0
- package/agent-hub/dist/world-entry.js +51 -0
- package/agent-hub/dist/world-host.d.ts +69 -0
- package/agent-hub/dist/world-host.js +44 -0
- package/agent-hub/dist/world-join.d.ts +16 -0
- package/agent-hub/dist/world-join.js +122 -0
- package/agent-hub/dist/world-mount.d.ts +52 -0
- package/agent-hub/dist/world-mount.js +52 -0
- package/agent-hub/dist/world-mux.d.ts +72 -0
- package/agent-hub/dist/world-mux.js +224 -0
- package/agent-hub/dist/world-provision.d.ts +20 -0
- package/agent-hub/dist/world-provision.js +122 -0
- package/agent-hub/dist/world-web-server.d.ts +162 -0
- package/agent-hub/dist/world-web-server.js +248 -0
- package/agent-hub/lib/client/index.js +242 -0
- package/agent-hub/package.json +65 -0
- package/agent-omp/README.md +77 -0
- package/agent-omp/cordis.patch.yml +92 -0
- package/agent-omp/dist/adapter.js +36 -0
- package/agent-omp/dist/agent-preset-omp.js +171 -0
- package/agent-omp/dist/agent-preset-projection.js +36 -0
- package/agent-omp/dist/agent.js +1145 -0
- package/agent-omp/dist/app-home.js +86 -0
- package/agent-omp/dist/discovery.js +116 -0
- package/agent-omp/dist/inbox.js +53 -0
- package/agent-omp/dist/index.js +574 -0
- package/agent-omp/dist/knobs.js +50 -0
- package/agent-omp/dist/lazy-rpc.js +161 -0
- package/agent-omp/dist/mobile/gesture.js +148 -0
- package/agent-omp/dist/mobile/zoom-guard.js +294 -0
- package/agent-omp/dist/mobile-boot.js +38 -0
- package/agent-omp/dist/models.js +299 -0
- package/agent-omp/dist/omp-cli.js +147 -0
- package/agent-omp/dist/omp-disk-discovery.js +178 -0
- package/agent-omp/dist/omp-store.js +518 -0
- package/agent-omp/dist/pairing.js +45 -0
- package/agent-omp/dist/permission.js +96 -0
- package/agent-omp/dist/protocol.js +13 -0
- package/agent-omp/dist/rpc-types.js +1 -0
- package/agent-omp/dist/sdk-client.js +378 -0
- package/agent-omp/dist/sidecar-client.js +195 -0
- package/agent-omp/dist/store/adapter.js +47 -0
- package/agent-omp/dist/store/db.js +210 -0
- package/agent-omp/dist/store/index.js +59 -0
- package/agent-omp/dist/store/reconcile.js +51 -0
- package/agent-omp/dist/store/schema.js +40 -0
- package/agent-omp/dist/world-plugin.js +96 -0
- package/agent-omp/package.json +97 -0
- package/agent-omp/sidecar/main.ts +481 -0
- package/agent-pi/README.md +67 -0
- package/agent-pi/cordis.patch.yml +80 -0
- package/agent-pi/dist/adapter.js +53 -0
- package/agent-pi/dist/agent-preset-pi.js +169 -0
- package/agent-pi/dist/agent-preset-projection.js +29 -0
- package/agent-pi/dist/agent.js +949 -0
- package/agent-pi/dist/inbox.js +53 -0
- package/agent-pi/dist/index.js +595 -0
- package/agent-pi/dist/knobs.js +24 -0
- package/agent-pi/dist/models.js +200 -0
- package/agent-pi/dist/permission.js +66 -0
- package/agent-pi/dist/pi-client.js +224 -0
- package/agent-pi/dist/pi-events.js +155 -0
- package/agent-pi/dist/pi-home.js +55 -0
- package/agent-pi/dist/safe-json.js +25 -0
- package/agent-pi/dist/session-map.js +119 -0
- package/agent-pi/dist/world-plugin.js +97 -0
- package/agent-pi/package.json +77 -0
- package/cordis.patch.yml +22 -19
- package/package.json +88 -25
- package/client.js +0 -4
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code's observed slash commands, registered onto `ctx.commands`.
|
|
3
|
+
*
|
|
4
|
+
* Claude owns a command surface (`session_init.slash_commands`): `/mcp`,
|
|
5
|
+
* `/clear`, `/review`, plugin-namespaced commands, and so on. DSH exposes its
|
|
6
|
+
* own registry to the composer, so the adapter mirrors each observed Claude
|
|
7
|
+
* command there and lets the user pick it from the same menu.
|
|
8
|
+
*
|
|
9
|
+
* Two deliberate rules:
|
|
10
|
+
*
|
|
11
|
+
* - **Every registered id is `claude-<name>`.** Prefixing is total — the id is
|
|
12
|
+
* the only thing this module writes to the shared registry, so it must be
|
|
13
|
+
* collision-free with DSH's own `/compact`, `/plan`, `/permission`, … AND
|
|
14
|
+
* self-explanatory about its owner. A name that already carries the prefix is
|
|
15
|
+
* still prefixed once more (`claude-mcp` -> `claude-claude-mcp`): de-prefixing
|
|
16
|
+
* would alias two genuinely distinct observed names onto one id.
|
|
17
|
+
* - **The handler forwards, it never renders.** `submit` receives
|
|
18
|
+
* `/mcp` plus the raw tail exactly as the user typed it (the registry hands
|
|
19
|
+
* the tail through unnormalized), so Claude's own command parser sees its
|
|
20
|
+
* original line. The panel/output for the command flows back through the
|
|
21
|
+
* ordinary transcript projection.
|
|
22
|
+
*
|
|
23
|
+
* The invocation's id is sanitized for DSH's command-id grammar
|
|
24
|
+
* (`^[a-z][a-z0-9_-]*$`) — lowercase, invalid runs become `-` — but the
|
|
25
|
+
* FORWARDED line keeps the observed spelling, so sanitizing is invisible to
|
|
26
|
+
* Claude.
|
|
27
|
+
*/
|
|
28
|
+
// No Cordis import: the runtime surface this module needs is described
|
|
29
|
+
// structurally below, which keeps the module loadable (and testable) without a
|
|
30
|
+
// Cordis host, and keeps the adapter's runtime dependency set unchanged.
|
|
31
|
+
/** DSH's command-id grammar (`@deepseek-ai/dsh-commands`' `COMMAND_NAME`). */
|
|
32
|
+
const COMMAND_ID_BODY = /[^a-z0-9_-]+/gu;
|
|
33
|
+
/**
|
|
34
|
+
* Sanitize one observed Claude name into a `claude-` prefixed DSH command id.
|
|
35
|
+
*
|
|
36
|
+
* The prefix guarantees the grammar's leading-lowercase-letter requirement, so
|
|
37
|
+
* the only work left is mapping the body to `[a-z0-9_-]`. The mapping is total
|
|
38
|
+
* and deterministic: uppercase folds, every invalid run becomes one `-`.
|
|
39
|
+
*
|
|
40
|
+
* @returns the command id, or `undefined` when nothing usable is left.
|
|
41
|
+
*/
|
|
42
|
+
function claudeCommandId(observed) {
|
|
43
|
+
const body = observed.toLowerCase().replace(COMMAND_ID_BODY, "-");
|
|
44
|
+
return body === "" ? undefined : `claude-${body}`;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Build the discovery summary. The registry rejects an empty description, so a
|
|
48
|
+
* missing/blank observed one falls back to a constant; a real one is prefixed
|
|
49
|
+
* so every entry in the menu declares its owner.
|
|
50
|
+
*/
|
|
51
|
+
function claudeDescription(observed) {
|
|
52
|
+
const text = typeof observed === "string" ? observed.trim() : "";
|
|
53
|
+
return text === "" ? "Claude Code slash command" : `Claude Code: ${text}`;
|
|
54
|
+
}
|
|
55
|
+
/** The observed name, trimmed; blank/non-string input yields `""` (skip). */
|
|
56
|
+
function observedName(command) {
|
|
57
|
+
const raw = command?.name;
|
|
58
|
+
return typeof raw === "string" ? raw.trim() : "";
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Normalize ONE reported entry — a bare name string (`session_init.slash_commands`
|
|
62
|
+
* is `string[]`) or an SDK `SlashCommand` object (`supportedCommands()` carries
|
|
63
|
+
* the description and argument hint) — into the shape this module registers.
|
|
64
|
+
*
|
|
65
|
+
* @returns the entry, or `undefined` when the report carries no usable name.
|
|
66
|
+
*/
|
|
67
|
+
function reportedEntry(raw) {
|
|
68
|
+
const record = typeof raw === "string"
|
|
69
|
+
? { name: raw }
|
|
70
|
+
: typeof raw === "object" && raw !== null ? raw : undefined;
|
|
71
|
+
if (record === undefined)
|
|
72
|
+
return undefined;
|
|
73
|
+
const name = typeof record.name === "string" ? record.name.trim() : "";
|
|
74
|
+
if (name === "")
|
|
75
|
+
return undefined;
|
|
76
|
+
const description = typeof record.description === "string" ? record.description.trim() : "";
|
|
77
|
+
const argumentHint = typeof record.argumentHint === "string" ? record.argumentHint.trim() : "";
|
|
78
|
+
return {
|
|
79
|
+
name,
|
|
80
|
+
...description === "" ? {} : { description },
|
|
81
|
+
...argumentHint === "" ? {} : { argumentHint },
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Normalize everything Claude reported about its command surface into the
|
|
86
|
+
* registration list, de-duplicated by name (first report wins — the SDK's
|
|
87
|
+
* `aliases` are alternate spellings of the same command, deliberately NOT
|
|
88
|
+
* separate menu entries).
|
|
89
|
+
*
|
|
90
|
+
* Accepts the `session_init` name list and the richer `supportedCommands()`
|
|
91
|
+
* objects through the same door, so the two observation sources cannot drift
|
|
92
|
+
* in their filtering rules.
|
|
93
|
+
*
|
|
94
|
+
* @param raw - the reported array, in either shape; anything else yields `[]`.
|
|
95
|
+
* @returns the usable entries in report order.
|
|
96
|
+
*/
|
|
97
|
+
export function slashCommandsFromReported(raw) {
|
|
98
|
+
if (!Array.isArray(raw))
|
|
99
|
+
return [];
|
|
100
|
+
const seen = new Set();
|
|
101
|
+
const entries = [];
|
|
102
|
+
for (const item of raw) {
|
|
103
|
+
const entry = reportedEntry(item);
|
|
104
|
+
if (entry === undefined || seen.has(entry.name))
|
|
105
|
+
continue;
|
|
106
|
+
seen.add(entry.name);
|
|
107
|
+
entries.push(entry);
|
|
108
|
+
}
|
|
109
|
+
return entries;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Mirror every observed Claude slash command onto `ctx.commands`.
|
|
113
|
+
*
|
|
114
|
+
* Skips (never throws on) an observed entry whose name is absent, non-string,
|
|
115
|
+
* or blank; de-duplicates by resulting command id so a repeated observation
|
|
116
|
+
* cannot double-register; and rolls back (releases) every registration already
|
|
117
|
+
* made when a later `register` throws, so a partial mirror never leaks.
|
|
118
|
+
*
|
|
119
|
+
* @param ctx - a context whose `commands` registry is reachable.
|
|
120
|
+
* @param deps - the observed command list plus the exact-line submit sink.
|
|
121
|
+
* @returns a disposer that releases EVERY registration this call made (once).
|
|
122
|
+
*/
|
|
123
|
+
export function registerClaudeCommands(ctx, deps) {
|
|
124
|
+
const disposers = [];
|
|
125
|
+
const registered = new Set();
|
|
126
|
+
let disposed = false;
|
|
127
|
+
/** Release every registration made so far; the returned disposer, guarded. */
|
|
128
|
+
const disposeAll = () => {
|
|
129
|
+
if (disposed)
|
|
130
|
+
return;
|
|
131
|
+
disposed = true;
|
|
132
|
+
let firstError;
|
|
133
|
+
for (const dispose of disposers.splice(0)) {
|
|
134
|
+
try {
|
|
135
|
+
dispose();
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
// One bad disposer must not strand the others: collect and re-raise.
|
|
139
|
+
if (firstError === undefined)
|
|
140
|
+
firstError = error;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (firstError !== undefined)
|
|
144
|
+
throw firstError;
|
|
145
|
+
};
|
|
146
|
+
try {
|
|
147
|
+
for (const command of deps.listSlashCommands() ?? []) {
|
|
148
|
+
const observed = observedName(command);
|
|
149
|
+
if (observed === "")
|
|
150
|
+
continue;
|
|
151
|
+
const name = claudeCommandId(observed);
|
|
152
|
+
if (name === undefined || registered.has(name))
|
|
153
|
+
continue;
|
|
154
|
+
registered.add(name);
|
|
155
|
+
const hint = command?.argumentHint;
|
|
156
|
+
const trimmedHint = typeof hint === "string" ? hint.trim() : "";
|
|
157
|
+
const dispose = ctx.commands.register({
|
|
158
|
+
name,
|
|
159
|
+
description: claudeDescription(command?.description),
|
|
160
|
+
...trimmedHint === "" ? {} : { input: { hint: trimmedHint } },
|
|
161
|
+
handler: (invocation) => {
|
|
162
|
+
deps.submit(invocation.agent, `/${observed}${invocation.rawInput}`);
|
|
163
|
+
return { kind: "success" };
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
if (typeof dispose === "function")
|
|
167
|
+
disposers.push(dispose);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
// A partial registration must not leak: release what landed, then report
|
|
172
|
+
// the original failure (a disposer's own throw must not mask it).
|
|
173
|
+
try {
|
|
174
|
+
disposeAll();
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
/* the registration failure below stays the reported one */
|
|
178
|
+
}
|
|
179
|
+
throw error;
|
|
180
|
+
}
|
|
181
|
+
return disposeAll;
|
|
182
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DSH content blocks -> Anthropic message content blocks.
|
|
3
|
+
*
|
|
4
|
+
* The paste/prompt UI is DSH's own; the adapter owns the transcoding into
|
|
5
|
+
* Claude's wire shape (spec ruling R14). A block whose payload cannot be
|
|
6
|
+
* resolved is SKIPPED AND REPORTED: an unattachable image must never silently
|
|
7
|
+
* vanish, and an unusable placeholder must never be sent to the runtime.
|
|
8
|
+
*
|
|
9
|
+
* The module is deliberately PURE — no SDK import and no I/O. It produces plain
|
|
10
|
+
* JSON blocks and the caller injects whatever attachment reader it owns, so the
|
|
11
|
+
* projection can be pinned by a unit test without a store, a CLI, or a session.
|
|
12
|
+
*
|
|
13
|
+
* ## Skip policy (every skip is loud)
|
|
14
|
+
*
|
|
15
|
+
* - a non-object entry, a missing/empty/non-string text, or an empty text block
|
|
16
|
+
* (the Anthropic API rejects empty text blocks, so normalizing one away is a
|
|
17
|
+
* reported drop, not a silent trim);
|
|
18
|
+
* - an unknown block `type`;
|
|
19
|
+
* - an `image`/`file` with neither inline `data` nor a resolvable
|
|
20
|
+
* `attachmentId`;
|
|
21
|
+
* - an `attachmentId` the injected reader cannot resolve (or one that throws —
|
|
22
|
+
* the reader's failure is contained here and becomes a reported skip);
|
|
23
|
+
* - a missing/empty `mediaType`, or a media type that is not `image/*`: the
|
|
24
|
+
* output must never carry `media_type: undefined` or a non-image source;
|
|
25
|
+
* - ANY `file` block. DSH's `file` block is a verbatim attachment reference —
|
|
26
|
+
* the harness contract is that files never reach a provider natively and are
|
|
27
|
+
* projected to deterministic handle text instead, so this adapter must not
|
|
28
|
+
* launder one into an image block. It is reported instead.
|
|
29
|
+
*/
|
|
30
|
+
/** Whether a media type may ride an Anthropic base64 image source. */
|
|
31
|
+
function isImageMediaType(mediaType) {
|
|
32
|
+
return mediaType.startsWith("image/");
|
|
33
|
+
}
|
|
34
|
+
/** A non-empty string field, or undefined when absent/blank/unusable. */
|
|
35
|
+
function stringField(value) {
|
|
36
|
+
return typeof value === "string" && value !== "" ? value : undefined;
|
|
37
|
+
}
|
|
38
|
+
/** Human-readable detail for a contained reader failure. */
|
|
39
|
+
function errorDetail(error) {
|
|
40
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
41
|
+
return message === "" ? "unknown error" : message;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Transcode DSH user content blocks into Anthropic input blocks.
|
|
45
|
+
*
|
|
46
|
+
* @param blocks - DSH user content blocks in message order. Entries are read
|
|
47
|
+
* defensively: a null/undefined/non-object member is reported, not fatal.
|
|
48
|
+
* @param readAttachment - synchronous resolver for a durable attachment id.
|
|
49
|
+
* Returning `undefined` means "not resolvable" and becomes a reported skip;
|
|
50
|
+
* throwing is contained into a reported skip as well. A block carrying inline
|
|
51
|
+
* `data` never consults it.
|
|
52
|
+
* @returns the attachable blocks in input order plus one actionable reason per
|
|
53
|
+
* block that could not be attached.
|
|
54
|
+
*/
|
|
55
|
+
export function toClaudeContent(blocks, readAttachment) {
|
|
56
|
+
const content = [];
|
|
57
|
+
const skipped = [];
|
|
58
|
+
const source = Array.isArray(blocks) ? blocks : [];
|
|
59
|
+
for (let index = 0; index < source.length; index += 1) {
|
|
60
|
+
const entry = source[index];
|
|
61
|
+
if (entry === null || entry === undefined || typeof entry !== "object") {
|
|
62
|
+
skipped.push(`block at index ${index}: not a content block object`);
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const block = entry;
|
|
66
|
+
if (block.type === "text") {
|
|
67
|
+
const text = block.text;
|
|
68
|
+
if (typeof text !== "string") {
|
|
69
|
+
skipped.push(`text block at index ${index}: missing text`);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (text === "") {
|
|
73
|
+
skipped.push(`text block at index ${index}: empty text`);
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
// Byte-exact, and one output block per input block: consecutive text
|
|
77
|
+
// blocks are legal on the wire, and merging them would change the shape.
|
|
78
|
+
content.push({ type: "text", text });
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (block.type !== "image" && block.type !== "file") {
|
|
82
|
+
skipped.push(`block at index ${index}: unknown block type ${JSON.stringify(block.type)}`);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (block.type === "file") {
|
|
86
|
+
// DSH's `file` block is a verbatim attachment reference. The harness
|
|
87
|
+
// contract is that files never reach a provider natively (they are
|
|
88
|
+
// projected to deterministic handle text), so a file block is NEVER
|
|
89
|
+
// emitted as an image block here — even when its payload looks like a
|
|
90
|
+
// raster image. Reported instead, and never resolved through the image
|
|
91
|
+
// reader.
|
|
92
|
+
const fileMediaType = stringField(block.mediaType);
|
|
93
|
+
skipped.push(`file block at index ${index}: verbatim file references cannot be attached to Claude` +
|
|
94
|
+
(fileMediaType === undefined ? "" : ` (mediaType "${fileMediaType}")`));
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
let mediaType = stringField(block.mediaType);
|
|
98
|
+
let data = stringField(block.data);
|
|
99
|
+
const attachmentId = stringField(block.attachmentId);
|
|
100
|
+
if (data === undefined && attachmentId !== undefined) {
|
|
101
|
+
let resolved;
|
|
102
|
+
try {
|
|
103
|
+
resolved = readAttachment(attachmentId);
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
skipped.push(`${block.type} block at index ${index}: reading attachment "${attachmentId}" failed (${errorDetail(error)})`);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (resolved !== undefined && resolved !== null && typeof resolved === "object") {
|
|
110
|
+
if (mediaType === undefined)
|
|
111
|
+
mediaType = stringField(resolved.mediaType);
|
|
112
|
+
if (data === undefined)
|
|
113
|
+
data = stringField(resolved.data);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (data === undefined) {
|
|
117
|
+
skipped.push(`${block.type} block at index ${index}: no inline data` +
|
|
118
|
+
(attachmentId === undefined
|
|
119
|
+
? " and no attachmentId"
|
|
120
|
+
: ` and attachment "${attachmentId}" could not be resolved`));
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (mediaType === undefined) {
|
|
124
|
+
skipped.push(`${block.type} block at index ${index}: missing mediaType`);
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (!isImageMediaType(mediaType)) {
|
|
128
|
+
skipped.push(`${block.type} block at index ${index}: media type "${mediaType}" is not an image media type`);
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
content.push({ type: "image", source: { type: "base64", media_type: mediaType, data } });
|
|
132
|
+
}
|
|
133
|
+
return { content, skipped };
|
|
134
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export class Inbox {
|
|
2
|
+
nextTurnList = [];
|
|
3
|
+
nextStepList = [];
|
|
4
|
+
get nextTurn() {
|
|
5
|
+
return this.nextTurnList;
|
|
6
|
+
}
|
|
7
|
+
get nextStep() {
|
|
8
|
+
return this.nextStepList;
|
|
9
|
+
}
|
|
10
|
+
clear() {
|
|
11
|
+
this.nextTurnList = [];
|
|
12
|
+
this.nextStepList = [];
|
|
13
|
+
}
|
|
14
|
+
append(target, message) {
|
|
15
|
+
this.list(target).push(message);
|
|
16
|
+
}
|
|
17
|
+
prepend(target, message) {
|
|
18
|
+
this.list(target).unshift(message);
|
|
19
|
+
}
|
|
20
|
+
replace(messageId, newMessage) {
|
|
21
|
+
const list = this.find(messageId);
|
|
22
|
+
if (list === undefined)
|
|
23
|
+
return false;
|
|
24
|
+
const index = list.findIndex((message) => message.id === messageId);
|
|
25
|
+
if (index < 0)
|
|
26
|
+
return false;
|
|
27
|
+
list.splice(index, 1, newMessage);
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
remove(messageId) {
|
|
31
|
+
const list = this.find(messageId);
|
|
32
|
+
if (list === undefined)
|
|
33
|
+
return false;
|
|
34
|
+
const index = list.findIndex((message) => message.id === messageId);
|
|
35
|
+
if (index < 0)
|
|
36
|
+
return false;
|
|
37
|
+
list.splice(index, 1);
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
splice(target, start, deleteCount, inserted) {
|
|
41
|
+
return this.list(target).splice(start, deleteCount, ...inserted);
|
|
42
|
+
}
|
|
43
|
+
list(target) {
|
|
44
|
+
return target === "next-turn" ? this.nextTurnList : this.nextStepList;
|
|
45
|
+
}
|
|
46
|
+
find(messageId) {
|
|
47
|
+
if (this.nextTurnList.some((message) => message.id === messageId))
|
|
48
|
+
return this.nextTurnList;
|
|
49
|
+
if (this.nextStepList.some((message) => message.id === messageId))
|
|
50
|
+
return this.nextStepList;
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
}
|