@magpie-community/opencode-workbuddy-auth 0.1.48 → 0.2.0
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 +23 -4
- package/index.mjs +106 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -397,11 +397,30 @@ entry. An already signed-in browser can simply renew another saved account.
|
|
|
397
397
|
|
|
398
398
|
## Not here
|
|
399
399
|
|
|
400
|
-
magpie's built-in WorkBuddy does more than this package:
|
|
401
|
-
|
|
402
|
-
- the credits meter (`/billing/meter/get-user-resource-summary`);
|
|
403
|
-
- several accounts under one provider.
|
|
400
|
+
magpie's built-in WorkBuddy does more than this package: several accounts
|
|
401
|
+
under one provider.
|
|
404
402
|
|
|
405
403
|
OpenCode's plugin API has no place for these.
|
|
406
404
|
|
|
407
405
|
Credit multipliers accept the international catalog's `x0.34 credits` text as well as `x0.34`. An empty credits value (for example Auto) stays unknown, never free.
|
|
406
|
+
|
|
407
|
+
## Daily check-in
|
|
408
|
+
|
|
409
|
+
The account's daily credits are claimed through the billing meter. magpie
|
|
410
|
+
can run it once a day per account when its WorkBuddy check-in switch is on;
|
|
411
|
+
Settings → Usage → Plugins has that switch and a *Check in now* button.
|
|
412
|
+
The activity status is read first: inactive is `inactive`, a day already
|
|
413
|
+
checked in is `done`, and only an active unchecked day is claimed. A claim
|
|
414
|
+
that WorkBuddy answers 1001, 1002 or 1003 is `done`, `ineligible` or
|
|
415
|
+
`inactive`. A claim turned away 401 while the account's credits still read
|
|
416
|
+
is `ineligible`: that check-in takes WorkBuddy's own app. A transient
|
|
417
|
+
billing failure is retried twice before the card shows the failure.
|
|
418
|
+
Only WorkBuddy (China) checks in: the AI build has no claim endpoint, and
|
|
419
|
+
its plugin carries no check-in hook.
|
|
420
|
+
A request that gets no answer at all leaves the hook as a thrown error, so
|
|
421
|
+
magpie retries it on its 1-minute offline backoff rather than the 30-minute
|
|
422
|
+
one it gives a failed answer (#149).
|
|
423
|
+
|
|
424
|
+
An `inactive` answer is kept for the whole Beijing day: the plugin host
|
|
425
|
+
gives it no recheck, and a manual refresh reads the kept one (tracked in
|
|
426
|
+
[yetone/magpie#2696](https://github.com/yetone/magpie/issues/2696)).
|
package/index.mjs
CHANGED
|
@@ -1868,23 +1868,99 @@ function signedOut(site, a, why, expired, note) {
|
|
|
1868
1868
|
// the same: WorkBuddy's message, else its code, else the HTTP status.
|
|
1869
1869
|
async function meter(site, a, path, body = {}) {
|
|
1870
1870
|
const res = await meterAsked(site, a, path, body)
|
|
1871
|
+
const raw = await res.text()
|
|
1871
1872
|
let env = {}
|
|
1873
|
+
let parsed = true
|
|
1872
1874
|
try {
|
|
1873
|
-
env = JSON.parse(
|
|
1874
|
-
} catch {
|
|
1875
|
+
env = JSON.parse(raw) ?? {}
|
|
1876
|
+
} catch {
|
|
1877
|
+
parsed = false
|
|
1878
|
+
}
|
|
1879
|
+
if (res.ok && !parsed) {
|
|
1880
|
+
const unreadable = raw.trim().slice(0, 120) || "(empty)"
|
|
1881
|
+
throw Object.assign(new Error(`unreadable reply ${unreadable}`), { status: res.status, code: 0, unreadable: true })
|
|
1882
|
+
}
|
|
1875
1883
|
const code = Number.parseInt(env.code, 10) || 0
|
|
1884
|
+
// the status, the code and whether the token itself was refused go with
|
|
1885
|
+
// it, for usage's one renewal on a 401 (reissued) and check-in's outcomes
|
|
1886
|
+
const said = { status: res.status, code }
|
|
1887
|
+
if (!res.ok) said.invalidToken = invalidToken(res)
|
|
1888
|
+
const err = (message) => Object.assign(new Error(message), said)
|
|
1876
1889
|
if (!res.ok) {
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
throw Object.assign(new Error(statusText(res.status)), said)
|
|
1883
|
-
}
|
|
1884
|
-
if (code) throw new Error(env.msg || `error ${code}`)
|
|
1890
|
+
if (code) throw err(env.msg || `error ${code}`)
|
|
1891
|
+
if (env.msg) throw err(env.msg)
|
|
1892
|
+
throw err(statusText(res.status))
|
|
1893
|
+
}
|
|
1894
|
+
if (code) throw err(env.msg || `error ${code}`)
|
|
1885
1895
|
return env.data ?? null
|
|
1886
1896
|
}
|
|
1887
1897
|
|
|
1898
|
+
// checkinOf claims the account's daily credits. China has the /v2 meter;
|
|
1899
|
+
// after the activity status names an active, unchecked day. Transient
|
|
1900
|
+
// upstream failures are asked again twice (the bounded retry quota-hub
|
|
1901
|
+
// observed on the billing endpoint; callers may shorten the waits for a
|
|
1902
|
+
// test).
|
|
1903
|
+
async function checkinOf(site, a, { waits = [0, 0] } = {}) {
|
|
1904
|
+
let status
|
|
1905
|
+
try {
|
|
1906
|
+
status = await meter(site, a, "/v2/billing/meter/checkin-activity-status")
|
|
1907
|
+
} catch (e) {
|
|
1908
|
+
if (e?.code === 1001) return { outcome: "done" }
|
|
1909
|
+
// a reply WorkBuddy sent is the day's failed answer; only a request
|
|
1910
|
+
// that got no answer leaves as a throw, for magpie's offline backoff
|
|
1911
|
+
if (e?.status !== undefined || e?.code !== undefined) return { outcome: "failed", message: e.message }
|
|
1912
|
+
throw e
|
|
1913
|
+
}
|
|
1914
|
+
if (!status?.active) return { outcome: "inactive" }
|
|
1915
|
+
if (status.today_checked_in ?? status.todayCheckedIn) {
|
|
1916
|
+
// today_credit is what today actually gave, the built-in's word for a
|
|
1917
|
+
// day already in; daily_credit is the day's grant
|
|
1918
|
+
const credit = Number(status.today_credit ?? status.daily_credit)
|
|
1919
|
+
return { outcome: "done", ...(Number.isFinite(credit) && credit > 0 ? { credit } : {}) }
|
|
1920
|
+
}
|
|
1921
|
+
const paths = ["/v2/billing/meter/daily-checkin"]
|
|
1922
|
+
for (let attempt = 0; ; attempt++) {
|
|
1923
|
+
let i = 0
|
|
1924
|
+
while (i < paths.length) {
|
|
1925
|
+
try {
|
|
1926
|
+
const got = await meter(site, a, paths[i++])
|
|
1927
|
+
// the claim's own answer, as the built-in hands it on: the credit
|
|
1928
|
+
// and streak it granted and what it said, for the card
|
|
1929
|
+
const credit = Number(got?.credit)
|
|
1930
|
+
const streak = Number(got?.streak_days)
|
|
1931
|
+
return {
|
|
1932
|
+
outcome: "claimed",
|
|
1933
|
+
...(Number.isFinite(credit) && credit > 0 ? { credit } : {}),
|
|
1934
|
+
...(Number.isFinite(streak) && streak > 0 ? { streak } : {}),
|
|
1935
|
+
...(got?.message ? { message: String(got.message) } : {}),
|
|
1936
|
+
}
|
|
1937
|
+
} catch (e) {
|
|
1938
|
+
if (e?.code === 1001) return { outcome: "done" }
|
|
1939
|
+
if (e?.code === 1002) return { outcome: "ineligible", message: e.message }
|
|
1940
|
+
if (e?.code === 1003) return { outcome: "inactive", message: e.message }
|
|
1941
|
+
if (e?.status === 401) {
|
|
1942
|
+
try {
|
|
1943
|
+
await meter(site, a, "/billing/meter/get-user-resource-summary")
|
|
1944
|
+
} catch {
|
|
1945
|
+
throw e
|
|
1946
|
+
}
|
|
1947
|
+
return { outcome: "ineligible", message: "WorkBuddy's check-in takes WorkBuddy's own app" }
|
|
1948
|
+
}
|
|
1949
|
+
if (e?.unreadable) return { outcome: "failed", message: `check-in: ${e.message}` }
|
|
1950
|
+
const transient = e?.status >= 500 || e?.code === 10000
|
|
1951
|
+
if (transient && attempt < waits.length) {
|
|
1952
|
+
await new Promise((r) => setTimeout(r, waits[attempt]))
|
|
1953
|
+
break
|
|
1954
|
+
}
|
|
1955
|
+
// a reply WorkBuddy sent is the day's failed answer; only a request
|
|
1956
|
+
// that got no answer leaves as a throw, for magpie's offline backoff
|
|
1957
|
+
if (e?.status !== undefined || e?.code !== undefined) return { outcome: "failed", message: e.message }
|
|
1958
|
+
throw e
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1888
1964
|
// capacity is a capacity the billing API sends, as a string ("438.88")
|
|
1889
1965
|
// or now and then a number; empty is 0.
|
|
1890
1966
|
function capacity(v) {
|
|
@@ -3036,6 +3112,25 @@ function makePlugin(site) {
|
|
|
3036
3112
|
// and is renewed as before.
|
|
3037
3113
|
refreshLead: LEAD_MS,
|
|
3038
3114
|
refresh: (auth) => refreshed(site, auth),
|
|
3115
|
+
// the account's daily check-in: magpie can run it once a day per
|
|
3116
|
+
// account, when its WorkBuddy check-in switch is on; the button in
|
|
3117
|
+
// Settings asks right away. China's meter only: the AI build has no
|
|
3118
|
+
// check-in (#47), so the hook stays off its plugin. Errors leave as
|
|
3119
|
+
// throws, not as failed answers: host.js answers a throwing hook to
|
|
3120
|
+
// magpie as an error, whose wbCheckinFailed keeps the offline words
|
|
3121
|
+
// and magpie's 1-minute backoff, where a returned failed waits the
|
|
3122
|
+
// full 30 minutes (magpie-community/plugins#149). checkinOf returns
|
|
3123
|
+
// an outcome only for answers it read and classified.
|
|
3124
|
+
...(site.ai ? {} : {
|
|
3125
|
+
async checkin(getAuth) {
|
|
3126
|
+
const auth = await getAuth()
|
|
3127
|
+
// an API key reads no credits (see usage), so it has no day to claim
|
|
3128
|
+
if (auth?.type === "api") return { outcome: "ineligible", message: "check-in needs a signed-in account, not an API key" }
|
|
3129
|
+
if (auth?.type !== "oauth" || !(auth.access || auth.source)) return { outcome: "failed", message: "not signed in" }
|
|
3130
|
+
const a = await current(site, client, auth)
|
|
3131
|
+
return await checkinOf(site, a, { waits: [2000, 4000] })
|
|
3132
|
+
},
|
|
3133
|
+
}),
|
|
3039
3134
|
options: [
|
|
3040
3135
|
{
|
|
3041
3136
|
key: FREE_ONLY,
|
|
@@ -3409,4 +3504,4 @@ export const WorkBuddyAuthPlugin = makePlugin(SITES.workbuddy)
|
|
|
3409
3504
|
export const WorkBuddyAIAuthPlugin = makePlugin(SITES["workbuddy-ai"])
|
|
3410
3505
|
|
|
3411
3506
|
// for tests
|
|
3412
|
-
export const _internal = { idFor, replyAfterToolsOn, withDefaultEffort, defaultEffortOf, gzipped, FREE_ONLY, PRICE_TTL, keyAccount, remoteConfig, cycleOf, typeUnions, systemAsUser, systemKey, DEFAULT_SYSTEM, EDGE_READS, fitShape, replyAfterTools, AFTER_TOOLS_REFUSED, enterpriseUsageOf, notDesktop, textOnly, runsOutOf, packagesOf, endOf, enterpriseHeaders, enterpriseOf, liveModels, browserSignIn, withSystem, asReasoning, normalizeToolChoice, fitTools, fitted, renamed, unflagged, usageOf, desktopHeld, explained, REFUSED_HINT, RESTRICTED_HINT, EXHAUSTED_HINT, freeCredits, creditsOf, priceOf, fresh, SITES, renewing, renewed, SESSION, attend, scopeOf, turnKey, conversationKey, signatureOf, isMessages, withoutUnsigned, withoutSignedThinking, prepared, answeredBefore, nativeOf, modelOf, configModels, freeWindowOf, tokensShort, freeWindowRows, freeWindowLedger, readLedgerTail, pinned, rowTime, rowAccount, ofAccount, windowOffset, windowReset, FREE_WINDOW_MODELS, FREE_WINDOW_SPAN, FREE_WINDOW_GAP, FREE_WINDOW_LEAD, API_MESSAGES: NPM_MESSAGES, API_CHAT: NPM }
|
|
3507
|
+
export const _internal = { idFor, replyAfterToolsOn, withDefaultEffort, defaultEffortOf, gzipped, FREE_ONLY, PRICE_TTL, keyAccount, remoteConfig, cycleOf, typeUnions, systemAsUser, systemKey, DEFAULT_SYSTEM, EDGE_READS, fitShape, replyAfterTools, AFTER_TOOLS_REFUSED, enterpriseUsageOf, notDesktop, textOnly, runsOutOf, packagesOf, endOf, enterpriseHeaders, enterpriseOf, liveModels, browserSignIn, withSystem, asReasoning, normalizeToolChoice, fitTools, fitted, renamed, unflagged, usageOf, desktopHeld, explained, REFUSED_HINT, RESTRICTED_HINT, EXHAUSTED_HINT, freeCredits, creditsOf, priceOf, fresh, SITES, renewing, renewed, SESSION, attend, scopeOf, turnKey, conversationKey, signatureOf, isMessages, withoutUnsigned, withoutSignedThinking, prepared, answeredBefore, nativeOf, modelOf, configModels, freeWindowOf, tokensShort, freeWindowRows, freeWindowLedger, readLedgerTail, pinned, rowTime, rowAccount, ofAccount, windowOffset, windowReset, FREE_WINDOW_MODELS, FREE_WINDOW_SPAN, FREE_WINDOW_GAP, FREE_WINDOW_LEAD, checkinOf, API_MESSAGES: NPM_MESSAGES, API_CHAT: NPM }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@magpie-community/opencode-workbuddy-auth",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
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",
|