@magpie-community/opencode-kiro-auth 0.1.7 → 0.1.9

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.
Files changed (3) hide show
  1. package/README.md +5 -1
  2. package/index.mjs +52 -12
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -73,7 +73,11 @@ request:
73
73
  - The system prompt goes at the head of the first message.
74
74
  - Tool calls and results are paired: an unanswered call gets an error
75
75
  result, and a result longer than 250,000 characters is cut.
76
- - Tool ids Kiro wouldn't take are rewritten.
76
+ - Tool ids Kiro wouldn't take are rewritten. A tool name over 64
77
+ characters (an MCP plugin's, in Claude Code) is sent shortened with a
78
+ hash and its calls come back under the full name.
79
+ - A tool's images (a screenshot, Codex's view_image) go with the
80
+ message that answers the call, as Kiro's tool results hold text alone.
77
81
  - Only the latest images are sent.
78
82
  - Tools the history used but the request doesn't offer are declared.
79
83
  - It turns the AWS event stream Kiro answers with back into Messages'
package/index.mjs CHANGED
@@ -576,6 +576,36 @@ const TOOL_ID = /^[a-zA-Z0-9_.:-]{1,64}$/
576
576
  // one of Kiro's own, the same each time.
577
577
  const toolID = (id) => (TOOL_ID.test(id ?? "") ? id : "t_" + createHash("sha256").update(String(id ?? "")).digest("base64url").slice(0, 32))
578
578
 
579
+ // Kiro answers a tool named past 64 characters, or outside these, "Invalid
580
+ // tool use format." and won't raise the limit (kirodotdev/Kiro#7684), while
581
+ // MCP tools in Claude Code are named mcp__plugin_<plugin>_<server>__<tool>
582
+ // (yetone/magpie#1393).
583
+ const TOOL_NAME = /^[a-zA-Z0-9_-]{1,64}$/
584
+
585
+ // toolName is a tool's name as Kiro takes one: another is its head with a
586
+ // hash of the whole name, the same each time, so the conversation's later
587
+ // turns name it the same way. toolNames maps each such name back.
588
+ const toolName = (name) => {
589
+ const n = String(name ?? "")
590
+ if (TOOL_NAME.test(n)) return n
591
+ const hash = createHash("sha256").update(n).digest("hex").slice(0, 12)
592
+ return n.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64 - 1 - hash.length) + "_" + hash
593
+ }
594
+
595
+ // toolNames is the name the caller knows for each tool a request names
596
+ // that Kiro is sent under another name.
597
+ function toolNames(req) {
598
+ const names = new Map()
599
+ const add = (n) => {
600
+ if (typeof n !== "string" || !n) return
601
+ const k = toolName(n)
602
+ if (k !== n) names.set(k, n)
603
+ }
604
+ for (const t of req.tools ?? []) add(t?.name)
605
+ for (const m of req.messages ?? []) if (Array.isArray(m.content)) for (const p of m.content) if (p?.type === "tool_use") add(p.name)
606
+ return names
607
+ }
608
+
579
609
  function imageFormat(mediaType) {
580
610
  const t = String(mediaType ?? "").toLowerCase()
581
611
  const f = t.replace(/^image\//, "")
@@ -584,6 +614,12 @@ function imageFormat(mediaType) {
584
614
  return f
585
615
  }
586
616
 
617
+ // addImage puts an Anthropic image block in images as Kiro takes it: the
618
+ // bytes inline. One named only by a URL has no bytes to send and is left out.
619
+ function addImage(images, p) {
620
+ if (p.source?.type === "base64" && p.source.data) images.push({ format: imageFormat(p.source.media_type), source: { bytes: p.source.data } })
621
+ }
622
+
587
623
  const textOf = (c) =>
588
624
  typeof c === "string" ? c : Array.isArray(c) ? c.filter((p) => p?.type === "text").map((p) => p.text ?? "").join("\n\n") : ""
589
625
  const joinNonEmpty = (a, b) => (a && b ? a + "\n\n" + b : a + b)
@@ -620,7 +656,7 @@ function buildKiro(req, model, profile, budget) {
620
656
  if (p?.type === "text" && p.text) texts.push(p.text)
621
657
  else if (p?.type === "tool_use") {
622
658
  const input = p.input && typeof p.input === "object" && !Array.isArray(p.input) ? p.input : {}
623
- a.toolUses.push({ name: p.name, toolUseId: toolID(p.id), input })
659
+ a.toolUses.push({ name: toolName(p.name), toolUseId: toolID(p.id), input })
624
660
  }
625
661
  }
626
662
  a.content = texts.join("\n\n")
@@ -639,12 +675,14 @@ function buildKiro(req, model, profile, budget) {
639
675
  if (p?.type === "text") {
640
676
  if (p.text) texts.push(p.text)
641
677
  } else if (p?.type === "document") texts.push(documentText(p))
642
- else if (p?.type === "image") {
643
- if (p.source?.type === "base64" && p.source.data) u.images.push({ format: imageFormat(p.source.media_type), source: { bytes: p.source.data } })
644
- } else if (p?.type === "tool_result") {
678
+ else if (p?.type === "image") addImage(u.images, p)
679
+ else if (p?.type === "tool_result") {
645
680
  let out = textOf(p.content)
646
681
  if (out.length > RESULT_LIMIT) out = out.slice(0, RESULT_LIMIT) + "\n… (cut)"
647
682
  results.push({ toolUseId: toolID(p.tool_use_id), status: p.is_error ? "error" : "success", content: [{ text: out || "(no output)" }] })
683
+ // a tool's images (a screenshot, Codex's view_image) go with the
684
+ // message, as Kiro's tool results carry text alone (magpie #369, #1861)
685
+ if (Array.isArray(p.content)) for (const c of p.content) if (c?.type === "image") addImage(u.images, c)
648
686
  }
649
687
  }
650
688
  u.content = texts.join("\n\n")
@@ -719,8 +757,9 @@ function buildKiro(req, model, profile, budget) {
719
757
  const EMPTY = { type: "object", properties: {} }
720
758
  for (const t of req.tools ?? []) {
721
759
  if (!t?.name || (t.type && t.type !== "custom" && !t.input_schema)) continue // server tools Kiro hasn't
722
- offered.add(t.name)
723
- tools.push({ toolSpecification: { name: t.name, description: t.description || t.name, inputSchema: { json: t.input_schema ?? EMPTY } } })
760
+ const name = toolName(t.name)
761
+ offered.add(name)
762
+ tools.push({ toolSpecification: { name, description: t.description || t.name, inputSchema: { json: t.input_schema ?? EMPTY } } })
724
763
  }
725
764
  for (const e of entries)
726
765
  for (const c of e.assistantResponseMessage?.toolUses ?? [])
@@ -1047,8 +1086,9 @@ const QUOTA_WORDS = /quota|insufficient|balance|credit|billing|exceeded|rate.?li
1047
1086
  const failedBefore = (text) => errorResponse(QUOTA_WORDS.test(text) ? 429 : 502, text)
1048
1087
 
1049
1088
  // reply answers the Messages request from Kiro's events: a stream of
1050
- // Messages' events, or one message.
1051
- async function reply(it, model, stream) {
1089
+ // Messages' events, or one message. names gives back the caller's name of
1090
+ // a tool Kiro was sent under another (toolNames).
1091
+ async function reply(it, model, stream, names = new Map()) {
1052
1092
  const id = "msg_" + randomBytes(12).toString("hex")
1053
1093
  if (!stream) {
1054
1094
  const content = []
@@ -1066,7 +1106,7 @@ async function reply(it, model, stream) {
1066
1106
  else add({ type: "thinking", thinking: e.text, signature: "" })
1067
1107
  } else if (e.kind === "sig") {
1068
1108
  if (last?.type === "thinking") last.signature += e.text
1069
- } else if (e.kind === "toolStart") add({ type: "tool_use", id: e.id, name: e.name, input: "" })
1109
+ } else if (e.kind === "toolStart") add({ type: "tool_use", id: e.id, name: names.get(e.name) ?? e.name, input: "" })
1070
1110
  else if (e.kind === "toolArgs") {
1071
1111
  if (last?.type === "tool_use") last.input += e.text
1072
1112
  } else if (e.kind === "error") {
@@ -1127,7 +1167,7 @@ async function reply(it, model, stream) {
1127
1167
  if (open === "thinking") ctl.enqueue(sse("content_block_delta", { index, delta: { type: "signature_delta", signature: e.text } }))
1128
1168
  break
1129
1169
  case "toolStart":
1130
- begin("tool", { type: "tool_use", id: e.id, name: e.name, input: {} })
1170
+ begin("tool", { type: "tool_use", id: e.id, name: names.get(e.name) ?? e.name, input: {} })
1131
1171
  break
1132
1172
  case "toolArgs":
1133
1173
  if (open === "tool") ctl.enqueue(sse("content_block_delta", { index, delta: { type: "input_json_delta", partial_json: e.text } }))
@@ -1189,7 +1229,7 @@ async function generate(creds, auth, req, signal) {
1189
1229
  const f = failure(res.status, (await res.text()).slice(0, 1 << 20))
1190
1230
  return errorResponse(f.status, f.message)
1191
1231
  }
1192
- return kept(await reply(events(res.body, model, budget), model, req.stream === true))
1232
+ return kept(await reply(events(res.body, model, budget), model, req.stream === true, toolNames(req)))
1193
1233
  }
1194
1234
 
1195
1235
  // ---- signing in ----------------------------------------------------------------------
@@ -1453,4 +1493,4 @@ export async function KiroAuthPlugin({ client } = {}) {
1453
1493
  }
1454
1494
 
1455
1495
  // for tests
1456
- export const _internal = { generate, refresh, usageOf, buildKiro, events, reply, failure, toolID, thinking, readCLI, readIDE, regionOf, planName, frames }
1496
+ export const _internal = { generate, refresh, usageOf, buildKiro, events, reply, failure, toolID, toolName, toolNames, thinking, readCLI, readIDE, regionOf, planName, frames }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@magpie-community/opencode-kiro-auth",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "Kiro subscriptions (Free, Pro, Pro+, Power) in OpenCode and magpie",
5
5
  "type": "module",
6
6
  "main": "./index.mjs",