@magpie-community/opencode-workbuddy-auth 0.2.0 → 0.2.1

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 +39 -12
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -61,6 +61,19 @@ as WorkBuddy's own client sends them; without them WorkBuddy answers as the
61
61
  personal account, with the personal plan's models. A browser sign-in made
62
62
  before 0.1.13 has no enterprise id kept: sign in again.
63
63
 
64
+ Which account a browser sign-in gives is chosen on WorkBuddy's own sign-in
65
+ page. After a phone sign-in it lists the person's accounts (personal, a
66
+ team, 「WorkBuddy 教育公益版」…) and binds the one picked to the sign-in, so
67
+ to add an education or team account, pick it there. Its account is named
68
+ with the enterprise too (`nickname · enterprise name`, or the end of the
69
+ enterprise id when WorkBuddy names none): one person's personal and
70
+ enterprise accounts share the uid and the nickname, and magpie keeps one
71
+ account per name and uid, so before 0.2.1 signing in to the enterprise
72
+ account replaced the personal one. An enterprise account signed in before
73
+ 0.2.1 keeps its old name until its Sign in again, which renames it in
74
+ place; its free-model window, read from usage rows kept under the old
75
+ name, starts again from the new one.
76
+
64
77
  Desktop's sign-in is refreshed only when the app hasn't refreshed it
65
78
  itself, and the new token is kept in memory, never written back, as magpie's
66
79
  built-in WorkBuddy does.
package/index.mjs CHANGED
@@ -415,16 +415,26 @@ function enterpriseHeaders(a) {
415
415
  return id ? { "X-Enterprise-Id": id, "X-Tenant-Id": id } : {}
416
416
  }
417
417
 
418
- // enterpriseOf is the enterprise the account a sign-in gave belongs to:
419
- // the one /login/account names, else, for an account that isn't personal,
420
- // the one WorkBuddy's account list has for it — the same choice WorkBuddy's
421
- // own client makes (mergeCurrentAccountDetails): the uid's only entry of that
422
- // type, or the only one it last signed in to. None found is personal.
418
+ // enterpriseOf is the enterprise the account a sign-in gave belongs to,
419
+ // { id, name }: the one /login/account names, else, for an account that
420
+ // isn't personal, the one WorkBuddy's account list has for it — the same
421
+ // choice WorkBuddy's own client makes (mergeCurrentAccountDetails): the
422
+ // uid's only entry of that type, or the only one it last signed in to. None
423
+ // found is personal ({ id: "" }).
424
+ //
425
+ // Which account that is was chosen on WorkBuddy's sign-in page: after a
426
+ // phone sign-in it lists the person's accounts (personal, a team,
427
+ // 「WorkBuddy 教育公益版」…) and binds the one picked to the sign-in's state
428
+ // (POST /console/login/enterprise/<id>?state=…), so the token and
429
+ // /login/account this plugin reads by that state are the chosen account's.
423
430
  async function enterpriseOf(site, a, who) {
431
+ const name = (x) => (typeof x?.enterpriseName === "string" ? x.enterpriseName.trim() : "")
424
432
  const own = typeof who?.enterpriseId === "string" ? who.enterpriseId.trim() : ""
425
- if (own) return own
426
433
  const type = String(who?.type ?? "").trim().toLowerCase()
427
- if (!type || type === "personal") return ""
434
+ if (!own && (!type || type === "personal")) return { id: "", name: "" }
435
+ // a name /login/account leaves out is the account list's, as the
436
+ // client merges the two
437
+ if (own && name(who)) return { id: own, name: name(who) }
428
438
  let list
429
439
  try {
430
440
  list = await call(site, "GET", "/v2/plugin/accounts", {
@@ -432,13 +442,29 @@ async function enterpriseOf(site, a, who) {
432
442
  "X-Domain": domainOf(site, a),
433
443
  })
434
444
  } catch {
435
- return ""
445
+ return { id: own, name: "" }
436
446
  }
437
447
  const mine = (list?.accounts ?? []).filter((x) => x?.uid === who.uid && typeof x.enterpriseId === "string" && x.enterpriseId.trim())
448
+ if (own) return { id: own, name: name(mine.find((x) => x.enterpriseId.trim() === own)) }
438
449
  const same = mine.filter((x) => String(x.type ?? "").trim().toLowerCase() === type)
439
450
  const last = mine.filter((x) => x.lastLogin)
440
451
  const pick = same.length === 1 ? same[0] : last.length === 1 ? last[0] : mine.length === 1 ? mine[0] : undefined
441
- return pick ? pick.enterpriseId.trim() : ""
452
+ return pick ? { id: pick.enterpriseId.trim(), name: name(pick) } : { id: "", name: "" }
453
+ }
454
+
455
+ // accountName is what magpie lists an account a sign-in gave as: the
456
+ // person's name, and for an enterprise (team, education) account the
457
+ // enterprise's too. One person's personal account and their enterprise one
458
+ // share the uid and the nickname, and magpie's plugin host keeps one
459
+ // account per name and uid (settle in host.js): named alike, signing in to
460
+ // 「WorkBuddy 教育公益版」 took the place of the same person's personal
461
+ // account, and the two could never be in one pool. An enterprise WorkBuddy
462
+ // names no name for is told by the end of its id.
463
+ function accountName(who, ent) {
464
+ const person = who?.nickname || who?.phoneNumber || who?.uid || ""
465
+ if (!ent?.id) return person
466
+ const team = ent.name || `enterprise ${ent.id.slice(-6)}`
467
+ return person ? `${person} · ${team}` : team
442
468
  }
443
469
 
444
470
  // WorkBuddy's own client names each chat to its gateway four ways: the
@@ -1541,8 +1567,9 @@ async function browserSignIn(site) {
1541
1567
  "X-No-Enterprise-Id": "true",
1542
1568
  }, [12151], deadline)
1543
1569
  a.uid = who.uid ?? ""
1544
- a.enterpriseId = await enterpriseOf(site, a, who)
1545
- return success(site, a, who.nickname || who.phoneNumber || who.uid)
1570
+ const ent = await enterpriseOf(site, a, who)
1571
+ a.enterpriseId = ent.id
1572
+ return success(site, a, accountName(who, ent))
1546
1573
  } catch (e) {
1547
1574
  return { type: "failed", error: e.message }
1548
1575
  }
@@ -3504,4 +3531,4 @@ export const WorkBuddyAuthPlugin = makePlugin(SITES.workbuddy)
3504
3531
  export const WorkBuddyAIAuthPlugin = makePlugin(SITES["workbuddy-ai"])
3505
3532
 
3506
3533
  // for tests
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 }
3534
+ 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, accountName, 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.2.0",
3
+ "version": "0.2.1",
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",