@magpie-community/opencode-workbuddy-auth 0.1.12 → 0.1.13

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 +44 -2
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -43,6 +43,14 @@ included, doesn't mark the account, as the built-in never did; magpie
43
43
  tries again later, and the old token is used meanwhile.
44
44
  OpenCode doesn't call `auth.refresh`; the check before each request stays.
45
45
 
46
+ An enterprise (team) account is asked for as the enterprise. A browser
47
+ sign-in keeps the enterprise id WorkBuddy names for the account (from
48
+ `/login/account`, else its account list), and desktop's is read from the
49
+ app's file. Every request then carries `X-Enterprise-Id` and `X-Tenant-Id`,
50
+ as WorkBuddy's own client sends them; without them WorkBuddy answers as the
51
+ personal account, with the personal plan's models. A browser sign-in made
52
+ before 0.1.13 has no enterprise id kept: sign in again.
53
+
46
54
  Desktop's sign-in is refreshed only when the app hasn't refreshed it
47
55
  itself, and the new token is kept in memory, never written back, as magpie's
48
56
  built-in WorkBuddy does.
package/index.mjs CHANGED
@@ -172,6 +172,7 @@ function renew(site, a, save) {
172
172
  "X-Refresh-Token": a.refresh,
173
173
  "X-Auth-Refresh-Source": "plugin",
174
174
  "X-Domain": domainOf(site, a),
175
+ ...enterpriseHeaders(a),
175
176
  }, {})
176
177
  if (!got?.accessToken) throw Object.assign(new Error("WorkBuddy gave no refreshed token"), { bare: true })
177
178
  const next = merge(a, got)
@@ -259,10 +260,48 @@ function sign(site, a, headers) {
259
260
  headers.set("X-Agent-Purpose", "conversation")
260
261
  headers.set("X-IDE-Name", "WorkBuddy")
261
262
  headers.set("X-IDE-Version", UA_VERSION)
263
+ for (const [k, v] of Object.entries(enterpriseHeaders(a))) headers.set(k, v)
262
264
  if (!site.ai) return
263
265
  headers.set("X-Agent-Type", "main")
264
266
  }
265
267
 
268
+ // enterpriseHeaders names the enterprise (team) an account signed in as, as
269
+ // WorkBuddy's own client does on every request it makes for that account
270
+ // (its auth interceptor: X-Enterprise-Id and X-Tenant-Id, both the
271
+ // account's enterpriseId). Without them WorkBuddy answers as the personal
272
+ // account: /v3/config lists the personal plan's models and not the
273
+ // enterprise's (#75). A personal account has none and sends none.
274
+ function enterpriseHeaders(a) {
275
+ const id = typeof a?.enterpriseId === "string" ? a.enterpriseId.trim() : ""
276
+ return id ? { "X-Enterprise-Id": id, "X-Tenant-Id": id } : {}
277
+ }
278
+
279
+ // enterpriseOf is the enterprise the account a sign-in gave belongs to:
280
+ // the one /login/account names, else, for an account that isn't personal,
281
+ // the one WorkBuddy's account list has for it — the same choice WorkBuddy's
282
+ // own client makes (mergeCurrentAccountDetails): the uid's only entry of that
283
+ // type, or the only one it last signed in to. None found is personal.
284
+ async function enterpriseOf(site, a, who) {
285
+ const own = typeof who?.enterpriseId === "string" ? who.enterpriseId.trim() : ""
286
+ if (own) return own
287
+ const type = String(who?.type ?? "").trim().toLowerCase()
288
+ if (!type || type === "personal") return ""
289
+ let list
290
+ try {
291
+ list = await call(site, "GET", "/v2/plugin/accounts", {
292
+ Authorization: "Bearer " + a.access,
293
+ "X-Domain": domainOf(site, a),
294
+ })
295
+ } catch {
296
+ return ""
297
+ }
298
+ const mine = (list?.accounts ?? []).filter((x) => x?.uid === who.uid && typeof x.enterpriseId === "string" && x.enterpriseId.trim())
299
+ const same = mine.filter((x) => String(x.type ?? "").trim().toLowerCase() === type)
300
+ const last = mine.filter((x) => x.lastLogin)
301
+ const pick = same.length === 1 ? same[0] : last.length === 1 ? last[0] : mine.length === 1 ? mine[0] : undefined
302
+ return pick ? pick.enterpriseId.trim() : ""
303
+ }
304
+
266
305
  // WorkBuddy's own client names each chat to its gateway four ways: the
267
306
  // conversation it belongs to, the user send (turn) it answers, the message
268
307
  // itself, and the request that carried it. Its backend counts a user send as
@@ -759,6 +798,7 @@ async function browserSignIn(site) {
759
798
  "X-No-Enterprise-Id": "true",
760
799
  }, [12151], deadline)
761
800
  a.uid = who.uid ?? ""
801
+ a.enterpriseId = await enterpriseOf(site, a, who)
762
802
  return success(site, a, who.nickname || who.phoneNumber || who.uid)
763
803
  } catch (e) {
764
804
  return { type: "failed", error: e.message }
@@ -776,6 +816,7 @@ function success(site, a, name) {
776
816
  accountId: name || site.name,
777
817
  uid: a.uid ?? "",
778
818
  domain: a.domain ?? "",
819
+ ...(a.enterpriseId ? { enterpriseId: a.enterpriseId } : {}),
779
820
  ...(a.refreshExpiresAt ? { refreshExpiresAt: a.refreshExpiresAt } : {}),
780
821
  ...(a.tokenType ? { tokenType: a.tokenType } : {}),
781
822
  }
@@ -808,6 +849,7 @@ function readDesktop(site) {
808
849
  domain: typeof t.domain === "string" ? t.domain : "",
809
850
  tokenType: typeof t.tokenType === "string" ? t.tokenType : "",
810
851
  uid: acct.uid,
852
+ ...(typeof acct.enterpriseId === "string" && acct.enterpriseId.trim() ? { enterpriseId: acct.enterpriseId.trim() } : {}),
811
853
  name: acct.nickname || acct.phoneNumber || acct.uid,
812
854
  }
813
855
  } catch {
@@ -880,7 +922,7 @@ async function meter(site, a, path) {
880
922
  method: "POST",
881
923
  headers: { "Content-Type": "application/json", Accept: "application/json", "User-Agent": "WorkBuddy/" + UA_VERSION,
882
924
  Authorization: "Bearer " + a.access, "X-User-Id": a.uid ?? "", "X-Domain": domainOf(site, a), "X-Product": "SaaS",
883
- "X-IDE-Type": "WorkBuddy" },
925
+ "X-IDE-Type": "WorkBuddy", ...enterpriseHeaders(a) },
884
926
  body: "{}",
885
927
  signal: AbortSignal.timeout(20000),
886
928
  })
@@ -1216,4 +1258,4 @@ export const WorkBuddyAuthPlugin = makePlugin(SITES.workbuddy)
1216
1258
  export const WorkBuddyAIAuthPlugin = makePlugin(SITES["workbuddy-ai"])
1217
1259
 
1218
1260
  // for tests
1219
- export const _internal = { withSystem, fitTools, fitted, renamed, unflagged, usageOf, desktopHeld, explained, REFUSED_HINT, EXHAUSTED_HINT, freeCredits, creditsOf, fresh, SITES, renewing, renewed, SESSION, attend, turnKey, conversationKey, signatureOf }
1261
+ export const _internal = { enterpriseHeaders, enterpriseOf, liveModels, browserSignIn, withSystem, fitTools, fitted, renamed, unflagged, usageOf, desktopHeld, explained, REFUSED_HINT, EXHAUSTED_HINT, freeCredits, creditsOf, 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.12",
3
+ "version": "0.1.13",
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",