@magpie-community/opencode-workbuddy-auth 0.1.46 → 0.1.47

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 +5 -2
  2. package/index.mjs +56 -12
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -137,8 +137,11 @@ Each chat carries WorkBuddy's headers:
137
137
  - the conversation's ids: `X-Conversation-ID`, `X-Conversation-Request-ID`,
138
138
  `X-Conversation-Message-ID`, `X-Request-ID`. WorkBuddy's backend counts a
139
139
  user send as one request by `X-Conversation-Request-ID`, so every step of
140
- one send — a tool result sent back, a retry, another account answering —
141
- carries the same one, and the user's next message carries another. The
140
+ one send — a tool result sent back, a retry — carries the same one, and
141
+ the user's next message carries another. Both ids are one account's own,
142
+ as WorkBuddy's client is signed in as one: a conversation sent as another
143
+ account (another of a group, or a sign-in saved since) carries that
144
+ account's ids, never the same ones under two `X-User-Id`s. The
142
145
  conversation's is the session magpie (or OpenCode) names the request by,
143
146
  else the chat's first user message; nothing is made up when neither names
144
147
  one, a made-up conversation of the request's own being what splits one
package/index.mjs CHANGED
@@ -446,7 +446,7 @@ async function enterpriseOf(site, a, who) {
446
446
  // itself, and the request that carried it. Its backend counts a user send as
447
447
  // one request by X-Conversation-Request-ID: a chat carrying a new one every
448
448
  // time is counted on its own, so one send that takes several steps — a tool
449
- // call answered and sent again, a retry, another account answering — shows up
449
+ // call answered and sent again, a retry — shows up
450
450
  // in the usage detail as many requests. The ids below are made from what
451
451
  // names the conversation and the turn, so every step of one send carries the
452
452
  // same turn id and the user's next message carries another, as WorkBuddy's
@@ -719,22 +719,64 @@ function sessionOf(headers) {
719
719
  // turn's (one user send, whatever it takes), and the conversation's. A header
720
720
  // already there — the caller's own, or another plugin's — is kept, and the
721
721
  // message and request ids are the same one, as WorkBuddy's client has them.
722
- function attend(headers, body, session) {
723
- attendChat(headers, chatOf(body), session)
722
+ //
723
+ // who is the account the chat goes as (scopeOf). The ids are that account's
724
+ // own: WorkBuddy's client runs as one account, so one conversation or turn
725
+ // never shows up under two. Without it, one magpie session spread over a
726
+ // group of accounts sent the same X-Conversation-ID and
727
+ // X-Conversation-Request-ID under every account's X-User-Id.
728
+ function attend(headers, body, session, who = "") {
729
+ return attendChat(headers, chatOf(body), session, who)
730
+ }
731
+
732
+ // attendChat is attend with the chat read already. It returns what reattend
733
+ // needs to set the ids again for another account once the chat itself is
734
+ // let go: the ids it set itself (not the caller's), and the digests of what
735
+ // names the turn and the conversation, which are never sent as they are.
736
+ function attendChat(headers, chat, session, who = "") {
737
+ const conv = conversationParts(chat, session)
738
+ const turn = turnParts(chat)
739
+ const ids = {
740
+ set: [],
741
+ // a turn is a conversation's own: two conversations whose user message
742
+ // reads the same are still two sends, not one
743
+ turn: turn.length ? idFor("turn", [...conv, "|", ...turn]) : "",
744
+ conv: conv.length ? idFor("conversation", conv) : "",
745
+ }
746
+ return setIds(headers, ids, who)
724
747
  }
725
748
 
726
- function attendChat(headers, chat, session) {
727
- const conv = conversationParts(chat, session)
749
+ // setIds sets the message's id, and the turn's and conversation's for the
750
+ // account who, where the caller set none.
751
+ function setIds(headers, ids, who) {
728
752
  const msg = hex()
729
753
  headers.set("X-Conversation-Message-ID", msg)
730
754
  headers.set("X-Request-ID", msg)
755
+ const set = []
756
+ const own = (kind, base) => (who ? idFor(kind, [`account:${who}`, "|", base]) : base)
731
757
  if (!headers.has("X-Conversation-Request-ID")) {
732
- const turn = turnParts(chat)
733
- // a turn is a conversation's own: two conversations whose user message
734
- // reads the same are still two sends, not one
735
- headers.set("X-Conversation-Request-ID", turn.length ? idFor("turn", [...conv, "|", ...turn]) : hex())
758
+ headers.set("X-Conversation-Request-ID", ids.turn ? own("turn", ids.turn) : hex())
759
+ set.push("X-Conversation-Request-ID")
760
+ }
761
+ if (ids.conv && !headers.has("X-Conversation-ID")) {
762
+ headers.set("X-Conversation-ID", own("conversation", ids.conv))
763
+ set.push("X-Conversation-ID")
736
764
  }
737
- if (conv.length && !headers.has("X-Conversation-ID")) headers.set("X-Conversation-ID", idFor("conversation", conv))
765
+ return { ...ids, set }
766
+ }
767
+
768
+ // scopeOf names the account a chat goes as for attend: its uid, its
769
+ // enterprise, and for a key account the key (only ever hashed).
770
+ function scopeOf(a) {
771
+ return `${a?.uid ?? ""}|${a?.enterpriseId ?? ""}|${a?.source === "key" ? a.key ?? "" : ""}`
772
+ }
773
+
774
+ // reattend sets a chat's ids again for the account it is now sent as, when
775
+ // that is another account than the one they were set for.
776
+ function reattend(headers, ids, was, now) {
777
+ if (was === now) return ids
778
+ for (const h of ids.set) headers.delete(h)
779
+ return setIds(headers, ids, now)
738
780
  }
739
781
 
740
782
  // FLAGGED are the words WorkBuddy refuses a chat for, "Illegal API
@@ -2510,7 +2552,8 @@ function makePlugin(site) {
2510
2552
  const completion = !native && isCompletion(req ? req.url : input, chat)
2511
2553
  if (completion && chat && !namesEffort(chat) && withDefaultEffort(chat, await effortList(a))) changed = true
2512
2554
  chatHeaders(site, headers)
2513
- attendChat(headers, chat, session)
2555
+ let who = scopeOf(a)
2556
+ let ids = attendChat(headers, chat, session, who)
2514
2557
  const post = (b) => {
2515
2558
  // a chat goes gzipped, as WorkBuddy's client sends it
2516
2559
  // (gzipped); its size on the wire is what unanswered weighs
@@ -2557,6 +2600,7 @@ function makePlugin(site) {
2557
2600
  renewed = true
2558
2601
  a = r.a
2559
2602
  sign(site, a, headers)
2603
+ ids = reattend(headers, ids, who, (who = scopeOf(a)))
2560
2604
  const again = await post(b)
2561
2605
  if (again.status !== 401) return again
2562
2606
  return out("it refused the token renewed for it too", invalidToken(again), again)
@@ -2805,4 +2849,4 @@ export const WorkBuddyAuthPlugin = makePlugin(SITES.workbuddy)
2805
2849
  export const WorkBuddyAIAuthPlugin = makePlugin(SITES["workbuddy-ai"])
2806
2850
 
2807
2851
  // for tests
2808
- 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, turnKey, conversationKey, signatureOf, isMessages, withoutUnsigned, withoutSignedThinking, prepared, answeredBefore, nativeOf, modelOf, configModels, API_MESSAGES: NPM_MESSAGES, API_CHAT: NPM }
2852
+ 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, 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.1.46",
3
+ "version": "0.1.47",
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",