@magpie-community/opencode-kiro-auth 0.1.21 → 0.1.22

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 +9 -0
  2. package/index.mjs +64 -3
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -108,6 +108,10 @@ request:
108
108
  profile ARN names.
109
109
  - It turns the request into a Kiro conversation:
110
110
  - The system prompt goes at the head of the first message.
111
+ - System messages that end the conversation (Claude Code's environment
112
+ context) are sent as a turn before the question, answered by Kiro IDE's
113
+ "Understood.", so the question is the message answered (#538). After
114
+ tool results they stay with them.
111
115
  - Tool calls and results are paired: an unanswered call gets an error
112
116
  result, and a result longer than 250,000 characters is cut.
113
117
  - Tool ids Kiro wouldn't take are rewritten. A tool name over 64
@@ -136,6 +140,11 @@ request:
136
140
  the prompt, with a budget: low 10k, medium 20k, high 30k, xhigh and max
137
141
  50k (as magpie's built-in Kiro: #120). The
138
142
  `<thinking>` block of the reply comes back as thinking.
143
+ A model whose Kiro catalog lists effort levels with adaptive thinking
144
+ (`additionalModelRequestFieldsSchema`) is sent the level instead, as Kiro
145
+ IDE sends it: `additionalModelRequestFields` with `thinking.type:
146
+ "adaptive"` and `output_config.effort` (or `reasoning.effort`), and no
147
+ budget. So xhigh and max stay apart there (#538).
139
148
  - Anthropic's hosted web search is answered with Kiro's own web search
140
149
  (see [Web search](#web-search)).
141
150
  - Kiro's failures keep their meaning:
package/index.mjs CHANGED
@@ -574,6 +574,24 @@ function account(client) {
574
574
  // ---- models ----------------------------------------------------------------------
575
575
 
576
576
  const windows = new Map() // a model's context, as Kiro last listed it
577
+ const natives = new Map() // a model's effort levels sent as fields, as Kiro last listed them (nativeOf)
578
+
579
+ // nativeOf is what a model's additionalModelRequestFieldsSchema in Kiro's
580
+ // list offers, read as Kiro IDE reads it: the effort levels in the enum of
581
+ // output_config's (or reasoning's) effort, and whether thinking.type may be
582
+ // "adaptive". A model offering both is sent the caller's effort in
583
+ // additionalModelRequestFields, as the IDE sends it; any other keeps the
584
+ // <max_thinking_length> budget (#538).
585
+ function nativeOf(s) {
586
+ if (!s || typeof s !== "object" || Array.isArray(s)) return undefined
587
+ const types = s.properties?.thinking?.properties?.type?.enum
588
+ if (!Array.isArray(types) || !types.includes("adaptive")) return undefined
589
+ for (const path of ["output_config", "reasoning"]) {
590
+ const levels = s.properties?.[path]?.properties?.effort?.enum
591
+ if (Array.isArray(levels) && levels.length) return { path, levels }
592
+ }
593
+ return undefined
594
+ }
577
595
 
578
596
  const thinks = (id) => /claude/i.test(id) || id === "auto"
579
597
  // the levels a thinking model offers, as the built-in thinks them (xhigh as
@@ -653,6 +671,7 @@ async function listModels(a) {
653
671
  }
654
672
  if (!list.length) throw new Error("Kiro listed no models")
655
673
  for (const m of list) windows.set(m.id, m.context)
674
+ for (const m of out?.models ?? []) if (m?.modelId) natives.set(m.modelId, nativeOf(m.additionalModelRequestFieldsSchema))
656
675
  return list
657
676
  }
658
677
 
@@ -834,6 +853,17 @@ function thinking(req, model) {
834
853
  return 20000
835
854
  }
836
855
 
856
+ // nativeFields is the additionalModelRequestFields a thinking request is
857
+ // sent with when Kiro's list says the model takes its effort as a field:
858
+ // adaptive thinking at the caller's level, so xhigh and max reach the model
859
+ // as two levels rather than one 50000 budget (#538). undefined otherwise.
860
+ function nativeFields(req, model) {
861
+ const n = natives.get(model)
862
+ const effort = req.output_config?.effort
863
+ if (!n || typeof effort !== "string" || !n.levels.includes(effort) || !thinking(req, model)) return undefined
864
+ return { thinking: { type: "adaptive" }, [n.path]: { effort } }
865
+ }
866
+
837
867
  // formatText is the structured output a request asks for, said in words:
838
868
  // Anthropic's output_config.format (or the AI SDK's output_format) with a
839
869
  // JSON schema, "" when it asks for none. A JSON object with no schema
@@ -872,6 +902,34 @@ async function kiroBody(req, model, profile, budget) {
872
902
  return JSON.stringify(await fitImages(kiroRequest(req, model, profile, budget)))
873
903
  }
874
904
 
905
+ // UNDERSTOOD is the reply Kiro IDE puts in its history where a turn has
906
+ // none of its own, before the message being answered
907
+ const UNDERSTOOD = "Understood."
908
+
909
+ // contextFirst is a conversation that ends in system messages (Claude
910
+ // Code's environment context, sent after the user's question) with that
911
+ // context said first: a turn of its own, answered by Kiro IDE's
912
+ // placeholder, before the question, which stays the message being
913
+ // answered rather than the head of "question, then context" (#538). The
914
+ // instructions' own field isn't used: Kiro refuses it ("systemPrompt field
915
+ // is not enabled for this user"). A question carrying tool results, or
916
+ // following tool calls, keeps the context in it as before, so no call is
917
+ // parted from its result.
918
+ function contextFirst(messages) {
919
+ let end = messages.length
920
+ while (end > 0 && messages[end - 1]?.role === "system") end--
921
+ if (end === messages.length) return messages
922
+ let start = end
923
+ while (start > 0 && messages[start - 1]?.role === "user") start--
924
+ const parts = (m) => (typeof m?.content === "string" ? [{ type: "text", text: m.content }] : Array.isArray(m?.content) ? m.content : [])
925
+ const context = messages.slice(end)
926
+ if (start === end || !context.some((m) => parts(m).some((p) => p?.type !== "text" || p.text))) return messages
927
+ const turn = messages.slice(start, end)
928
+ if (turn.some((m) => parts(m).some((p) => p?.type === "tool_result"))) return messages
929
+ if (parts(messages[start - 1]).some((p) => p?.type === "tool_use" || p?.type === "server_tool_use" || p?.type === "web_search_tool_result")) return messages
930
+ return [...messages.slice(0, start), ...context.map((m) => ({ ...m, role: "user" })), { role: "assistant", content: UNDERSTOOD }, ...turn]
931
+ }
932
+
875
933
  function kiroRequest(req, model, profile, budget) {
876
934
  const entries = []
877
935
  // the messages the caller marked for its prompt cache (cache_control),
@@ -882,7 +940,7 @@ function kiroRequest(req, model, profile, budget) {
882
940
  }
883
941
  const user = () => ({ content: "", modelId: model, origin: "KIRO_CLI" })
884
942
  const resultsOnly = new WeakSet() // the messages made to carry a reply's web search results
885
- for (const m of req.messages ?? []) {
943
+ for (const m of contextFirst(req.messages ?? [])) {
886
944
  const parts = typeof m.content === "string" ? [{ type: "text", text: m.content }] : m.content ?? []
887
945
  const texts = []
888
946
  if (m.role === "assistant") {
@@ -1013,7 +1071,9 @@ function kiroRequest(req, model, profile, budget) {
1013
1071
  // end of the instructions, as magpie's built-in tells it (inSystem)
1014
1072
  const format = formatText(req)
1015
1073
  if (format) system = system ? system + "\n\n" + format : format
1016
- if (budget) system = `<thinking_mode>enabled</thinking_mode><max_thinking_length>${budget}</max_thinking_length>` + (system ? "\n" + system : "")
1074
+ // a level Kiro takes as a field is sent as one, not also as a budget
1075
+ const native = budget ? nativeFields(req, model) : undefined
1076
+ if (budget && !native) system = `<thinking_mode>enabled</thinking_mode><max_thinking_length>${budget}</max_thinking_length>` + (system ? "\n" + system : "")
1017
1077
  if (system) entries[0].userInputMessage.content = system + "\n\n" + entries[0].userInputMessage.content
1018
1078
  if (Array.isArray(req.system)) mark(entries[0].userInputMessage, req.system)
1019
1079
 
@@ -1063,6 +1123,7 @@ function kiroRequest(req, model, profile, budget) {
1063
1123
  if (entries.length > 1) state.history = entries.slice(0, -1)
1064
1124
  const out = { conversationState: state, agentMode: "vibe" }
1065
1125
  if (profile) out.profileArn = profile
1126
+ if (native) out.additionalModelRequestFields = native
1066
1127
  return out
1067
1128
  }
1068
1129
 
@@ -2309,4 +2370,4 @@ export async function KiroAuthPlugin({ client } = {}) {
2309
2370
  }
2310
2371
 
2311
2372
  // for tests
2312
- export const _internal = { generate, refresh, usageOf, buildKiro, kiroBody, IMAGE_LIMIT, events, reply, failure, toolID, toolName, toolNames, thinking, configModel, runtimeModel, readCLI, readIDE, regionOf, planName, frames }
2373
+ export const _internal = { generate, refresh, usageOf, buildKiro, kiroBody, IMAGE_LIMIT, events, reply, failure, toolID, toolName, toolNames, thinking, nativeFields, listModels, contextFirst, configModel, runtimeModel, 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.21",
3
+ "version": "0.1.22",
4
4
  "description": "Kiro subscriptions (Free, Pro, Pro+, Power) in OpenCode and magpie",
5
5
  "type": "module",
6
6
  "main": "./index.mjs",