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

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 +13 -0
  2. package/index.mjs +71 -5
  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,15 @@ 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).
148
+ Thinking is asked for only when the request's `thinking` is `enabled` or
149
+ `adaptive`. An effort with no `thinking`, or with it `disabled` (Claude
150
+ Code's title requests, or a turn with thinking off), is sent with no
151
+ thinking at any level.
139
152
  - Anthropic's hosted web search is answered with Kiro's own web search
140
153
  (see [Web search](#web-search)).
141
154
  - 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
 
@@ -823,10 +842,15 @@ function documentText(p) {
823
842
  }
824
843
 
825
844
  // thinking is whether the model is asked to think aloud, and for how
826
- // long: when the caller asked for it, of a Claude model (or Auto).
845
+ // long: when the caller asked for it, of a Claude model (or Auto). Only a
846
+ // thinking of type enabled or adaptive asks for it, as Anthropic's API
847
+ // reads a request: an effort with no thinking (Claude Code's title and
848
+ // other side requests, or its main turn with thinking turned off), or with
849
+ // thinking disabled, is sent no thinking at any level (#538), as magpie's
850
+ // gateway reads it too.
827
851
  function thinking(req, model) {
828
852
  const effort = req.output_config?.effort ?? ""
829
- const on = req.thinking?.type === "enabled" || req.thinking?.type === "adaptive" || (effort && effort !== "low")
853
+ const on = req.thinking?.type === "enabled" || req.thinking?.type === "adaptive"
830
854
  if (!on || !thinks(String(model).toLowerCase())) return 0
831
855
  const budget = { low: 10000, high: 30000, xhigh: 50000, max: 50000 }[effort]
832
856
  if (budget) return budget
@@ -834,6 +858,17 @@ function thinking(req, model) {
834
858
  return 20000
835
859
  }
836
860
 
861
+ // nativeFields is the additionalModelRequestFields a thinking request is
862
+ // sent with when Kiro's list says the model takes its effort as a field:
863
+ // adaptive thinking at the caller's level, so xhigh and max reach the model
864
+ // as two levels rather than one 50000 budget (#538). undefined otherwise.
865
+ function nativeFields(req, model) {
866
+ const n = natives.get(model)
867
+ const effort = req.output_config?.effort
868
+ if (!n || typeof effort !== "string" || !n.levels.includes(effort) || !thinking(req, model)) return undefined
869
+ return { thinking: { type: "adaptive" }, [n.path]: { effort } }
870
+ }
871
+
837
872
  // formatText is the structured output a request asks for, said in words:
838
873
  // Anthropic's output_config.format (or the AI SDK's output_format) with a
839
874
  // JSON schema, "" when it asks for none. A JSON object with no schema
@@ -872,6 +907,34 @@ async function kiroBody(req, model, profile, budget) {
872
907
  return JSON.stringify(await fitImages(kiroRequest(req, model, profile, budget)))
873
908
  }
874
909
 
910
+ // UNDERSTOOD is the reply Kiro IDE puts in its history where a turn has
911
+ // none of its own, before the message being answered
912
+ const UNDERSTOOD = "Understood."
913
+
914
+ // contextFirst is a conversation that ends in system messages (Claude
915
+ // Code's environment context, sent after the user's question) with that
916
+ // context said first: a turn of its own, answered by Kiro IDE's
917
+ // placeholder, before the question, which stays the message being
918
+ // answered rather than the head of "question, then context" (#538). The
919
+ // instructions' own field isn't used: Kiro refuses it ("systemPrompt field
920
+ // is not enabled for this user"). A question carrying tool results, or
921
+ // following tool calls, keeps the context in it as before, so no call is
922
+ // parted from its result.
923
+ function contextFirst(messages) {
924
+ let end = messages.length
925
+ while (end > 0 && messages[end - 1]?.role === "system") end--
926
+ if (end === messages.length) return messages
927
+ let start = end
928
+ while (start > 0 && messages[start - 1]?.role === "user") start--
929
+ const parts = (m) => (typeof m?.content === "string" ? [{ type: "text", text: m.content }] : Array.isArray(m?.content) ? m.content : [])
930
+ const context = messages.slice(end)
931
+ if (start === end || !context.some((m) => parts(m).some((p) => p?.type !== "text" || p.text))) return messages
932
+ const turn = messages.slice(start, end)
933
+ if (turn.some((m) => parts(m).some((p) => p?.type === "tool_result"))) return messages
934
+ if (parts(messages[start - 1]).some((p) => p?.type === "tool_use" || p?.type === "server_tool_use" || p?.type === "web_search_tool_result")) return messages
935
+ return [...messages.slice(0, start), ...context.map((m) => ({ ...m, role: "user" })), { role: "assistant", content: UNDERSTOOD }, ...turn]
936
+ }
937
+
875
938
  function kiroRequest(req, model, profile, budget) {
876
939
  const entries = []
877
940
  // the messages the caller marked for its prompt cache (cache_control),
@@ -882,7 +945,7 @@ function kiroRequest(req, model, profile, budget) {
882
945
  }
883
946
  const user = () => ({ content: "", modelId: model, origin: "KIRO_CLI" })
884
947
  const resultsOnly = new WeakSet() // the messages made to carry a reply's web search results
885
- for (const m of req.messages ?? []) {
948
+ for (const m of contextFirst(req.messages ?? [])) {
886
949
  const parts = typeof m.content === "string" ? [{ type: "text", text: m.content }] : m.content ?? []
887
950
  const texts = []
888
951
  if (m.role === "assistant") {
@@ -1013,7 +1076,9 @@ function kiroRequest(req, model, profile, budget) {
1013
1076
  // end of the instructions, as magpie's built-in tells it (inSystem)
1014
1077
  const format = formatText(req)
1015
1078
  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 : "")
1079
+ // a level Kiro takes as a field is sent as one, not also as a budget
1080
+ const native = budget ? nativeFields(req, model) : undefined
1081
+ if (budget && !native) system = `<thinking_mode>enabled</thinking_mode><max_thinking_length>${budget}</max_thinking_length>` + (system ? "\n" + system : "")
1017
1082
  if (system) entries[0].userInputMessage.content = system + "\n\n" + entries[0].userInputMessage.content
1018
1083
  if (Array.isArray(req.system)) mark(entries[0].userInputMessage, req.system)
1019
1084
 
@@ -1063,6 +1128,7 @@ function kiroRequest(req, model, profile, budget) {
1063
1128
  if (entries.length > 1) state.history = entries.slice(0, -1)
1064
1129
  const out = { conversationState: state, agentMode: "vibe" }
1065
1130
  if (profile) out.profileArn = profile
1131
+ if (native) out.additionalModelRequestFields = native
1066
1132
  return out
1067
1133
  }
1068
1134
 
@@ -2309,4 +2375,4 @@ export async function KiroAuthPlugin({ client } = {}) {
2309
2375
  }
2310
2376
 
2311
2377
  // 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 }
2378
+ 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.23",
4
4
  "description": "Kiro subscriptions (Free, Pro, Pro+, Power) in OpenCode and magpie",
5
5
  "type": "module",
6
6
  "main": "./index.mjs",