@magpie-community/opencode-workbuddy-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 +8 -0
  2. package/index.mjs +137 -3
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -95,6 +95,14 @@ models to each shape below and Kimi's, GPT's, Gemini's and GLM's to some:
95
95
  `allOf`) at the root are an object, the union's fields merged;
96
96
  - a `pattern`'s `\0` is `\u0000`, which matches the same;
97
97
  - a tuple's `items` (a list) is one schema any of them fits;
98
+ - an `enum` with no `type` takes its values' type (or that and `"null"`),
99
+ and an `enum` of several types is an `anyOf` of one typed `enum` for
100
+ each, at any depth; an empty `enum` is left out (Kimi and DeepSeek, an
101
+ MCP server's tools, #122);
102
+ - `true` and `false` where a schema goes are `{}` and `{"not": {}}`
103
+ (`additionalProperties` is left); a `required` that isn't a list, and a
104
+ `type` JSON Schema doesn't name, are left out, and `"String"` is
105
+ `"string"`;
98
106
  - a tool's name with a character other than letters, digits, `_` and `-`
99
107
  (`.`, `:`, `/`, a space, CJK) is sent with `_` there, and the model's
100
108
  calls come back under the tool's own name.
package/index.mjs CHANGED
@@ -636,6 +636,139 @@ function fitSchema(s, depth = 0) {
636
636
  return changed
637
637
  }
638
638
 
639
+ // Kimi (kimi-k3-1) answers 11133 too, to the tools of an MCP server or a
640
+ // plugin (clearmann, #122). Tried on the owner's account, 2026-10-14, each
641
+ // alone in an otherwise plain chat: Kimi turns away an enum with no type
642
+ // (["a","b"] as much as [1,"a",null]), an empty enum, an enum whose type
643
+ // lists two types besides null, a schema that is true or false where a
644
+ // schema goes (properties.x: true, items: true), a required that isn't a
645
+ // list, and a type it doesn't know ("any", "String"). It takes an enum
646
+ // typed T or [T, "null"], an anyOf of such enums, {"not": {}}, {}, and
647
+ // additionalProperties: true or false. fitShape gives each the shape it
648
+ // takes, meaning the same: none of it narrows or widens what a call may
649
+ // hold, except an empty enum (which nothing fits) is left out.
650
+ const TYPES = new Set(["string", "number", "integer", "boolean", "object", "array", "null"])
651
+
652
+ // SUB_ONE are the keywords whose value is a schema, SUB_LIST a list of
653
+ // schemas, SUB_MAP a map of names to schemas. additionalProperties and
654
+ // unevaluatedProperties are left as they are when true or false.
655
+ const SUB_ONE = ["items", "additionalItems", "unevaluatedItems", "contains", "not", "if", "then", "else", "propertyNames", "additionalProperties", "unevaluatedProperties"]
656
+ const SUB_LIST = ["anyOf", "oneOf", "allOf", "prefixItems"]
657
+ const SUB_MAP = ["properties", "patternProperties", "$defs", "definitions", "dependentSchemas"]
658
+ const BOOL_OK = new Set(["additionalProperties", "unevaluatedProperties"])
659
+
660
+ // valueType is the JSON Schema type of an enum's value.
661
+ function valueType(v) {
662
+ if (v === null) return "null"
663
+ if (Array.isArray(v)) return "array"
664
+ if (typeof v === "number") return Number.isInteger(v) ? "integer" : "number"
665
+ return typeof v
666
+ }
667
+
668
+ // fitEnum gives s's enum a type: the one its values have, or that and
669
+ // "null"; values of several types are an anyOf of an enum for each. It
670
+ // reports whether it changed anything.
671
+ function fitEnum(s) {
672
+ if (!("enum" in s)) return false
673
+ if (!Array.isArray(s.enum) || s.enum.length === 0) {
674
+ delete s.enum
675
+ return true
676
+ }
677
+ const listed = Array.isArray(s.type) ? s.type : typeof s.type === "string" ? [s.type] : []
678
+ if (listed.filter((t) => t !== "null").length === 1) return false
679
+ const byType = new Map()
680
+ for (const v of s.enum) {
681
+ const t = valueType(v)
682
+ if (!byType.has(t)) byType.set(t, [])
683
+ byType.get(t).push(v)
684
+ }
685
+ if (byType.has("integer") && byType.has("number")) {
686
+ byType.get("number").push(...byType.get("integer"))
687
+ byType.delete("integer")
688
+ }
689
+ const hasNull = byType.delete("null")
690
+ if (byType.size <= 1) {
691
+ const [t] = byType.keys()
692
+ const want = t === undefined ? "null" : hasNull ? [t, "null"] : t
693
+ if (JSON.stringify(want) === JSON.stringify(s.type)) return false
694
+ s.type = want
695
+ return true
696
+ }
697
+ const split = [...byType].map(([t, vs]) => ({ type: t, enum: vs }))
698
+ if (hasNull) split.push({ type: "null" })
699
+ delete s.enum
700
+ delete s.type
701
+ if (Array.isArray(s.anyOf)) s.allOf = [...(Array.isArray(s.allOf) ? s.allOf : []), { anyOf: split }]
702
+ else s.anyOf = split
703
+ return true
704
+ }
705
+
706
+ // subSchema is v, a schema found under key, as an object Kimi takes: true
707
+ // is {}, false is {"not": {}}.
708
+ function subSchema(v, key) {
709
+ if (typeof v !== "boolean" || BOOL_OK.has(key)) return v
710
+ return v ? {} : { not: {} }
711
+ }
712
+
713
+ // fitShape mends s, a schema, and each schema under it, in place (see
714
+ // TYPES above); a name under properties is never read as a keyword. It
715
+ // reports whether it changed anything.
716
+ function fitShape(s, depth = 0) {
717
+ if (!s || typeof s !== "object" || Array.isArray(s) || depth > 64) return false
718
+ let changed = false
719
+ if ("type" in s) {
720
+ const list = (Array.isArray(s.type) ? s.type : [s.type]).map((t) => (typeof t === "string" ? t.toLowerCase() : t)).filter((t) => TYPES.has(t))
721
+ const uniq = [...new Set(list)]
722
+ const want = uniq.length === 0 ? undefined : uniq.length === 1 ? uniq[0] : uniq
723
+ if (JSON.stringify(want) !== JSON.stringify(s.type)) {
724
+ if (want === undefined) delete s.type
725
+ else s.type = want
726
+ changed = true
727
+ }
728
+ }
729
+ if ("required" in s && !Array.isArray(s.required)) {
730
+ delete s.required
731
+ changed = true
732
+ } else if (Array.isArray(s.required) && s.required.some((k) => typeof k !== "string")) {
733
+ s.required = s.required.filter((k) => typeof k === "string")
734
+ changed = true
735
+ }
736
+ if (fitEnum(s)) changed = true
737
+ for (const k of SUB_ONE) {
738
+ if (!(k in s)) continue
739
+ const v = subSchema(s[k], k)
740
+ if (v !== s[k]) {
741
+ s[k] = v
742
+ changed = true
743
+ }
744
+ if (fitShape(v, depth + 1)) changed = true
745
+ }
746
+ for (const k of SUB_LIST) {
747
+ if (!Array.isArray(s[k])) continue
748
+ s[k].forEach((v0, i) => {
749
+ const v = subSchema(v0, k)
750
+ if (v !== v0) {
751
+ s[k][i] = v
752
+ changed = true
753
+ }
754
+ if (fitShape(v, depth + 1)) changed = true
755
+ })
756
+ }
757
+ for (const k of SUB_MAP) {
758
+ const m = s[k]
759
+ if (!m || typeof m !== "object" || Array.isArray(m)) continue
760
+ for (const [name, v0] of Object.entries(m)) {
761
+ const v = subSchema(v0, k)
762
+ if (v !== v0) {
763
+ m[name] = v
764
+ changed = true
765
+ }
766
+ if (fitShape(v, depth + 1)) changed = true
767
+ }
768
+ }
769
+ return changed
770
+ }
771
+
639
772
  // localRef is the schema a local $ref in root names ("#/$defs/x",
640
773
  // "#/definitions/x"), or s itself.
641
774
  function localRef(root, s) {
@@ -698,8 +831,8 @@ function objectRoot(ps, root = ps, depth = 0) {
698
831
  }
699
832
 
700
833
  // fitTools mends b's function tools in place: their parameters (fitSchema,
701
- // objectRoot) and names. It reports whether it changed anything, and the
702
- // names it gave, each to the name the client knows (ours), so that the
834
+ // fitShape, objectRoot) and names. It reports whether it changed anything,
835
+ // and the names it gave, each to the name the client knows (ours), so that the
703
836
  // model's calls are named back. A call in the chat's history is named as
704
837
  // its tool now is.
705
838
  function fitTools(b) {
@@ -729,6 +862,7 @@ function fitTools(b) {
729
862
  continue
730
863
  }
731
864
  if (fitSchema(fn.parameters)) changed = true
865
+ if (fitShape(fn.parameters)) changed = true
732
866
  if (objectRoot(fn.parameters)) changed = true
733
867
  }
734
868
  if (theirs.size && Array.isArray(b.messages)) {
@@ -1558,4 +1692,4 @@ export const WorkBuddyAuthPlugin = makePlugin(SITES.workbuddy)
1558
1692
  export const WorkBuddyAIAuthPlugin = makePlugin(SITES["workbuddy-ai"])
1559
1693
 
1560
1694
  // for tests
1561
- export const _internal = { replyAfterTools, AFTER_TOOLS_REFUSED, enterpriseUsageOf, notDesktop, textOnly, runsOutOf, packagesOf, endOf, enterpriseHeaders, enterpriseOf, liveModels, browserSignIn, withSystem, normalizeToolChoice, fitTools, fitted, renamed, unflagged, usageOf, desktopHeld, explained, REFUSED_HINT, RESTRICTED_HINT, EXHAUSTED_HINT, freeCredits, creditsOf, priceOf, fresh, SITES, renewing, renewed, SESSION, attend, turnKey, conversationKey, signatureOf }
1695
+ export const _internal = { fitShape, replyAfterTools, AFTER_TOOLS_REFUSED, enterpriseUsageOf, notDesktop, textOnly, runsOutOf, packagesOf, endOf, enterpriseHeaders, enterpriseOf, liveModels, browserSignIn, withSystem, normalizeToolChoice, fitTools, fitted, renamed, unflagged, usageOf, desktopHeld, explained, REFUSED_HINT, RESTRICTED_HINT, EXHAUSTED_HINT, freeCredits, creditsOf, priceOf, fresh, SITES, renewing, renewed, SESSION, attend, turnKey, conversationKey, signatureOf }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@magpie-community/opencode-workbuddy-auth",
3
- "version": "0.1.21",
3
+ "version": "0.1.22",
4
4
  "description": "OpenCode provider plugins for WorkBuddy's plans: WorkBuddy (CodeBuddy, copilot.tencent.com) and WorkBuddy AI (www.workbuddy.ai)",
5
5
  "type": "module",
6
6
  "main": "./index.mjs",