@magpie-community/opencode-workbuddy-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.
- package/README.md +8 -0
- package/index.mjs +157 -4
- 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,
|
|
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)) {
|
|
@@ -1075,6 +1209,24 @@ function enterpriseUsageOf(u) {
|
|
|
1075
1209
|
return w
|
|
1076
1210
|
}
|
|
1077
1211
|
|
|
1212
|
+
// spareFree has the credits windows not count the models that cost none
|
|
1213
|
+
// (notModels): a model at x0.00 draws no credits, and with the credits
|
|
1214
|
+
// used up WorkBuddy still serves it (deepseek-v4.1-flash at 560 / 560,
|
|
1215
|
+
// magpie-community/plugins#138, hheei). Counted, the window at 100% put
|
|
1216
|
+
// the account last, and behind every other, for its free models too. A
|
|
1217
|
+
// model free only some hours (a night promotion) is not free here, as in
|
|
1218
|
+
// the list (priceOf). A list that can't be read leaves the windows as they
|
|
1219
|
+
// are: every model counted, as before.
|
|
1220
|
+
async function spareFree(site, a, windows) {
|
|
1221
|
+
if (!windows.length) return windows
|
|
1222
|
+
let free = []
|
|
1223
|
+
try {
|
|
1224
|
+
free = (await liveModels(site, a)).filter((m) => m.free).map((m) => m.id)
|
|
1225
|
+
} catch {}
|
|
1226
|
+
if (free.length) for (const w of windows) w.notModels = free
|
|
1227
|
+
return windows
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1078
1230
|
// The packages' codes, as WorkBuddy's own client lists them when it asks
|
|
1079
1231
|
// for each package's credits (its COMMODITY_CODES): the API refuses a
|
|
1080
1232
|
// detail list without them (10001 "PackageCodes required").
|
|
@@ -1443,7 +1595,7 @@ function makePlugin(site) {
|
|
|
1443
1595
|
try {
|
|
1444
1596
|
ent = enterpriseUsageOf(await meter(site, a, "/billing/meter/get-enterprise-user-usage"))
|
|
1445
1597
|
} catch {}
|
|
1446
|
-
if (ent) return { plan: auth.plan || "Enterprise", windows: [ent], signIn: "kept" }
|
|
1598
|
+
if (ent) return { plan: auth.plan || "Enterprise", windows: await spareFree(site, a, [ent]), signIn: "kept" }
|
|
1447
1599
|
}
|
|
1448
1600
|
// when the credits left run out, for routing to spend them first;
|
|
1449
1601
|
// a list that can't be read leaves it unknown, the card stands
|
|
@@ -1453,6 +1605,7 @@ function makePlugin(site) {
|
|
|
1453
1605
|
Object.assign(credits, runsOutOf(sum, await packagesOf(site, a)) ?? {})
|
|
1454
1606
|
} catch {}
|
|
1455
1607
|
}
|
|
1608
|
+
await spareFree(site, a, out.windows)
|
|
1456
1609
|
return { ...out, signIn: "kept" }
|
|
1457
1610
|
} catch (e) {
|
|
1458
1611
|
return { error: e?.message ?? String(e), signIn: "kept" }
|
|
@@ -1558,4 +1711,4 @@ export const WorkBuddyAuthPlugin = makePlugin(SITES.workbuddy)
|
|
|
1558
1711
|
export const WorkBuddyAIAuthPlugin = makePlugin(SITES["workbuddy-ai"])
|
|
1559
1712
|
|
|
1560
1713
|
// 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 }
|
|
1714
|
+
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.
|
|
3
|
+
"version": "0.1.23",
|
|
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",
|