Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion app/buddy_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ def _chat(client, headers, record, model):
"X-Parent-Conversation-ID": conversation, "X-Request-ID": request_id,
"X-Root-Request-ID": request_id, "X-Conversation-Request-ID": request_id,
"X-Conversation-Message-ID": request_id, "X-Agent-Intent": "craft",
"X-Agent-Purpose": "conversation", "X-Agent-Type": "main"}
"X-Agent-Purpose": "conversation", "X-Agent-Type": "auxiliary",
"X-Agent-Runtime": "local-agent"}
event = {"eventCode": "chat_request_send", "id": conversation, "extra": {
"inputLength": len(PROMPT), "requestModelId": model["id"], "requestModelName": model["name"],
"mode": "craft", "command": "", "expertId": ""}}
Expand Down
83 changes: 76 additions & 7 deletions app/client_profiles.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
"""Build separate CLI and WorkBuddy identity headers with shared authentication fields."""

import base64
import binascii
import hashlib
import json
import uuid

from .site_routing import domain_for_auth, profile_for_auth, profile_product, profile_region

CLI_VERSION = "2.165.0"
WORKBUDDY_VERSION = "5.9.2"
WORKBUDDY_VERSION = "5.9.3"
WORKBUDDY_CLI_VERSION = CLI_VERSION
INTL_CLI_VERSION = "2.161.0"
INTL_WORKBUDDY_VERSION = "5.8.4"
Expand Down Expand Up @@ -61,23 +64,89 @@ def _auth_headers(auth: dict, account: dict) -> dict:
"X-Domain": domain_for_auth(auth),
"X-Product": "SaaS", "X-Requested-With": "XMLHttpRequest",
}
headers.update(identity_headers(profile_for_auth(auth)))
profile = profile_for_auth(auth)
headers.update(identity_headers(profile))
if profile in ("cn-cli", "cn-work"):
# CLI 2.165+ disables model optimization for enterprise accounts.
headers["X-Private-Data"] = "true" if is_enterprise_account(account) else "false"
return headers


def _jwt_claims(auth: dict) -> dict:
"""Read unsigned claims only as client metadata; upstream still authenticates the token."""
token = auth.get("accessToken")
if not isinstance(token, str) or token.count(".") != 2:
return {}
try:
encoded = token.split(".")[1]
decoded = base64.urlsafe_b64decode(encoded + "=" * (-len(encoded) % 4))
payload = json.loads(decoded)
except (ValueError, binascii.Error, UnicodeError):
return {}
return payload if isinstance(payload, dict) else {}


def _identity_value(value) -> str:
if not isinstance(value, str):
return ""
value = value.strip()
return value if value and len(value) <= 128 and value.isascii() and value.isprintable() else ""


def _identity_claim_headers(auth: dict, account: dict) -> dict:
claims = _jwt_claims(auth)
id_source = _identity_value(account.get("idSource") or account.get("id_source") or claims.get("id_source"))
token_source = _identity_value(claims.get("token_source") or auth.get("method"))
enterprise_id = enterprise_id_of(account) or ""
uid = canonical_uid(account)
headers = {}
if id_source:
headers["X-Id-Source"] = id_source
if token_source:
headers["X-Auth-Method"] = token_source
if uid and (enterprise_id or id_source or token_source):
userinfo = {"uin": uid}
if enterprise_id:
userinfo["owner_uin"] = enterprise_id
if id_source:
userinfo["id_source"] = id_source
if token_source:
userinfo["token_source"] = token_source
encoded = json.dumps(userinfo, separators=(",", ":")).encode("utf-8")
headers["X-Userinfo"] = base64.b64encode(encoded).decode("ascii")
return headers


def credential_headers(auth: dict, account: dict) -> dict:
return {**SDK_HEADERS, **_auth_headers(auth, account)}
return {**SDK_HEADERS, **_auth_headers(auth, account), **_identity_claim_headers(auth, account)}


def _cn_work_nonchat_headers(headers: dict, profile: str) -> dict:
if profile == "cn-work":
headers["Accept"] = "application/json, text/plain, */*"
headers["X-Request-ID"] = uuid.uuid4().hex
headers.pop("X-CodeBuddy-Request", None)
return headers


def credential_refresh_headers(auth: dict, account: dict) -> dict:
headers = credential_headers(auth, account)
return _cn_work_nonchat_headers(headers, profile_for_auth(auth))


def refresh_headers(domain: str, uid: str = "") -> dict:
"""Build headers for a refresh exchange that has no access token yet."""
headers = {**SDK_HEADERS, **_auth_headers({"accessToken": "", "domain": domain}, {"uid": uid})}
auth = {"accessToken": "", "domain": domain}
headers = {**SDK_HEADERS, **_auth_headers(auth, {"uid": uid})}
headers.pop("Authorization", None) # The refresh token is the only credential in this request.
return headers
return _cn_work_nonchat_headers(headers, profile_for_auth(auth))


def catalog_headers(auth: dict, account: dict | None = None, *, user_agent="") -> dict:
headers = _auth_headers(auth, account or {})
profile = profile_for_auth(auth)
headers = _cn_work_nonchat_headers(_auth_headers(auth, account or {}), profile)
headers["Connection"] = "close"
if profile_product(profile_for_auth(auth)) == "cli":
if profile_product(profile) == "cli":
headers["x-client-platform"] = "cli"
if user_agent:
headers["User-Agent"] = user_agent
Expand Down
3 changes: 3 additions & 0 deletions app/credits.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import math
import os
import re
import secrets
import threading
import time
from pathlib import Path
Expand Down Expand Up @@ -166,6 +167,8 @@ def _checkin_request(access_token, uid, domain, path):
headers = {"accept": "application/json", "content-type": "application/json",
"authorization": f"Bearer {access_token}", "x-user-id": str(uid or ""),
"x-domain": str(domain or "")}
if profile_for_auth({"accessToken": access_token, "domain": domain}) == "cn-work":
headers.update({"X-Request-ID": secrets.token_hex(16), "User-Agent": "axios/1.16.1"})
url = host + path
with httpx.Client() as client:
try:
Expand Down
30 changes: 17 additions & 13 deletions app/growth_events.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Growth-center behavior chains: desktop/web event reporting and real-chat helpers.

Payload shapes mirror the official WorkBuddy desktop client (5.5.6) field-for-field, as
Payload shapes mirror the official WorkBuddy desktop client (5.9.3) field-for-field, as
validated by the upstream panel across multiple live accounts. Report hosts follow the
account's own product endpoint; the desktop fingerprint is stable per uid so repeated
chains look like one persistent device.
Expand All @@ -14,8 +14,8 @@

import httpx

DESKTOP_UA = ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) WorkBuddyDesktop/5.5.6 "
"Chrome/132.0.6834.83 Electron/34.0.0 Safari/537.36")
DESKTOP_UA = ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) WorkBuddyDesktop/5.9.3 "
"Chrome/148.0.7778.97 Electron/41.8.1 Safari/537.36")
WEB_UA = ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/152.0.0.0 Safari/537.36")
REPORT_PATH = "/v2/report"
Expand All @@ -42,13 +42,13 @@ def fingerprint(uid, nickname=""):
now = int(time.time() * 1000)
return {"timezone": "Asia/Shanghai", "reportDelay": 2000, "userId": uid,
"username": nickname, "userNickname": nickname, "product": "SaaS",
"releaseDate": 1789036585355,
"commit": "5f9692923c93033111c51ad7b003eb80204a9b75",
"ideName": "WorkBuddy", "ideType": "WorkBuddy", "ideVersion": "5.5.6",
"releaseDate": 1789632000000,
"commit": "c41253385fa07bebfcae8a7a4728914f09af96d0",
"ideName": "WorkBuddy", "ideType": "WorkBuddy", "ideVersion": "5.9.3",
"machineId": derive_id(uid, "machine"), "sessionId": derive_id(uid, "session"),
"extName": "workbuddy-desktop", "extVersion": "5.5.6",
"os": "win32", "arch": "x64", "osVersion": "10.0.26220",
"cpuCores": 20, "memorySize": 24, "timestamp": now, "presentAt": now}
"extName": "workbuddy-desktop", "extVersion": "5.9.3",
"os": "win32", "arch": "x64", "osVersion": "10.0.26200",
"cpuCores": 24, "memorySize": 32, "timestamp": now, "presentAt": now}


def _post_json(base, path, headers, payload):
Expand Down Expand Up @@ -106,13 +106,15 @@ def chat_request_event(uid, conversation_id, request_id="", model_id="", model_n
"inputLength": 12, "requestModelId": model_id,
"requestModelName": model_name or model_id, "isPlan": False,
"isAutoExecuteTerminal": False, "isAutoModify": False, "codebaseEnable": False,
"maxToken": 0, "maxSteps": 0, "temperature": 0, "maxRetries": 0,
"maxToken": 0, "maxSteps": 0, "temperature": 0.7, "maxRetries": 2,
"mentionContexts": [], "knowledgeId": [], "knowledgeName": [], "codebaseId": "",
"mentionContextCount": 0, "command": "", "expertId": "", "recommendId": "",
"skillId": "", "skillCount": 0, "totalCount": 0, "fileUri": "",
"presentAt": now, "traceId": "", "rootRequestId": request_id,
"parentConversationId": conversation_id, "agentName": "default",
"agentType": "conversation", "userId": uid}
"agentType": "conversation", "device_id": uid, "device_type": "desktop",
"sdk_version": "5.9.3", "chat_session_uuid": conversation_id,
"platform": "windows", "title": "对话", "userId": uid}


def report_chat_activity(base, token, uid, conversation_id, request_id="", model_id="",
Expand Down Expand Up @@ -260,8 +262,10 @@ def desktop_chat(base, headers, expert_id="", model=FAST_MODEL, prompt="1+1等
"stream_options": {"include_usage": True}}
outgoing = {**headers, "X-Domain": base, "X-Product": "SaaS", "X-Conversation-ID": conversation_id,
"X-Request-ID": str(int(time.time() * 1000)), "X-Agent-Intent": "craft",
"X-Agent-Type": "main", "X-IDE-Name": "WorkBuddy", "X-IDE-Type": "WorkBuddy",
"X-IDE-Version": "5.5.6", "x-codebuddy-request": "1",
"X-Agent-Type": "auxiliary", "X-Agent-Runtime": "local-agent",
"X-IDE-Name": "WorkBuddy", "X-IDE-Type": "WorkBuddy",
"X-IDE-Version": "5.9.3", "x-codebuddy-request": "1",
"X-Requested-With": "XMLHttpRequest", "User-Agent": DESKTOP_UA,
"Accept": "text/event-stream"}
outgoing.pop("X-Client-Platform", None)
if expert_id:
Expand Down
6 changes: 5 additions & 1 deletion app/rate_measurement.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import httpx

from .observability import normalize_usage
from .site_routing import profile_for_headers
from .upstream_compat import channel_fallback_url
from .upstream_io import open_backend_stream_sync

Expand Down Expand Up @@ -201,9 +202,12 @@ def probe_once(url, headers, model, *, timeout=120.0):
"messages": [{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": probe_prompt()}]}
usage = None
request_headers = {**headers, "Accept": "text/event-stream"}
if profile_for_headers(headers) == "cn-work":
request_headers.update({"X-Agent-Type": "auxiliary", "X-Agent-Runtime": "local-agent"})
with httpx.Client(follow_redirects=False, timeout=httpx.Timeout(timeout, connect=10)) as client:
with open_backend_stream_sync(
client, url, {**headers, "Accept": "text/event-stream"}, body,
client, url, request_headers, body,
fallback_url=channel_fallback_url(url)) as (response, raw):
if response.status_code != 200:
raise _upstream_error(response, raw)
Expand Down
45 changes: 38 additions & 7 deletions app/request_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,23 +69,53 @@ class Attempt:


_WORKBUDDY_PROFILES = frozenset({"cn-work", "intl-work"})
_UUID_TIME_ANCHOR_MS = 1767225600000
_UUID_TIME_WINDOW_MS = 24 * 30 * 24 * 60 * 60 * 1000


def _uuid_text(value):
return str(uuid.UUID(hex=value)) if isinstance(value, str) and len(value) == 32 else str(value)


def build_attempt_headers(headers, profile, *, root, message_id, request_id, trace_id, span, parent):
def _stable_uuid7(parts):
"""Derive a stable UUIDv7-shaped identifier without persisting client session state."""
raw = bytearray.fromhex(_digest(parts)[:32])
timestamp = _UUID_TIME_ANCHOR_MS + int.from_bytes(raw[:8], "big") % _UUID_TIME_WINDOW_MS
raw[:6] = timestamp.to_bytes(6, "big")
raw[6] = (raw[6] & 0x0F) | 0x70
raw[8] = (raw[8] & 0x3F) | 0x80
return str(uuid.UUID(bytes=bytes(raw)))


def session_conversation_id(profile, account, session_key):
parts = [profile, account, session_key]
return _stable_uuid7(parts) if profile == "cn-work" else str(uuid.UUID(hex=_digest(parts)[:32]))


def build_attempt_headers(headers, profile, *, root, message_id, request_id, trace_id, span, parent,
conversation_id=None):
"""Build one upstream attempt with profile-specific ID relationships."""
if profile in _WORKBUDDY_PROFILES:
message_id, request_id = _uuid_text(message_id), _uuid_text(request_id)
else:
request_id, trace_id = message_id, root
return {**headers, "X-Request-ID": request_id, "X-Conversation-Message-ID": message_id,
"X-Conversation-Request-ID": root, "X-Root-Request-ID": root, "X-Trace-ID": trace_id,
"traceparent": f"00-{trace_id}-{span}-01", "b3": f"{trace_id}-{span}-1-{parent}",
"X-B3-TraceId": trace_id, "X-B3-SpanId": span, "X-B3-ParentSpanId": parent,
"X-B3-Sampled": "1"}
outgoing = {**headers, "X-Request-ID": request_id, "X-Conversation-Message-ID": message_id,
"X-Conversation-Request-ID": root, "X-Root-Request-ID": root, "X-Trace-ID": trace_id,
"traceparent": f"00-{trace_id}-{span}-01", "b3": f"{trace_id}-{span}-1-{parent}",
"X-B3-TraceId": trace_id, "X-B3-SpanId": span, "X-B3-ParentSpanId": parent,
"X-B3-Sampled": "1"}
if profile == "cn-work":
outgoing["X-Agent-Runtime"] = "local-agent"
if conversation_id:
outgoing["X-Session-ID"] = conversation_id
outgoing["X-Root-Request-ID"] = _stable_uuid7(["root", conversation_id]).replace("-", "")
outgoing["acp-connection-id"] = _stable_uuid7(["acp", conversation_id])
outgoing["baggage"] = (
f"buddyId=,buddyName=,codebuddy.session_id={conversation_id},"
f"codebuddy.conversation_request_id={root},x-agent-runtime=local-agent,"
f"x-agent-intent={headers.get('X-Agent-Intent', 'craft')}"
)
return outgoing


class RequestContext:
Expand Down Expand Up @@ -136,7 +166,7 @@ def bind_session(self, payload, messages):
self._bound = True

def conversation_id(self, profile, account):
return str(uuid.UUID(hex=_digest([profile, account, self.session_key])[:32]))
return session_conversation_id(profile, account, self.session_key)

def start_attempt(self):
with self._lock:
Expand All @@ -153,6 +183,7 @@ def attempt_headers(self, headers, attempt, profile=None):
headers, profile, root=self.request_id, message_id=attempt.id,
request_id=attempt.request_id, trace_id=attempt.trace_id,
span=attempt.span, parent=self._root_span,
conversation_id=headers.get("X-Conversation-ID"),
)

def attempt_metadata(self):
Expand Down
27 changes: 25 additions & 2 deletions app/upstream_io.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Bound upstream retries and prohibit replay after a response has opened."""

import asyncio
import gzip
from contextlib import asynccontextmanager, contextmanager
from datetime import timezone
from email.utils import parsedate_to_datetime
Expand All @@ -12,6 +13,7 @@

from app.content_filter import ContentFilterDetector
from app.upstream_compat import should_channel_fallback
from app.site_routing import profile_for_headers


class UpstreamResponseError(Exception):
Expand Down Expand Up @@ -394,6 +396,26 @@ def read_bounded_sync_error(response, limit: int = ERROR_BODY_LIMIT) -> bytes:
return buffer.bytes()


def _json_request_kwargs(headers, body, url):
"""Serialize replay-safe chat bytes using the profile's gzip contract."""
if httpx.URL(url).path not in ("/v2/chat/completions", "/console/chat/completions"):
return {"headers": dict(headers), "json": body}
request_headers = {**headers, "Accept-Encoding": "identity"}
names = {str(name).lower() for name in headers}
if not names.intersection({"x-domain", "x-ide-type"}):
return {"headers": request_headers, "json": body}
profile = profile_for_headers(headers)
raw = json.dumps(body, ensure_ascii=False, separators=(",", ":")).encode("utf-8")
compressed = (profile in ("cn-cli", "intl-cli", "intl-work") or
(profile == "cn-work" and len(raw) >= 1024))
if not compressed:
return {"headers": request_headers, "json": body}
if not any(str(name).lower() == "content-type" for name in request_headers):
request_headers["Content-Type"] = "application/json"
request_headers["Content-Encoding"] = "gzip"
return {"headers": request_headers, "content": gzip.compress(raw, compresslevel=6, mtime=0)}


@contextmanager
def open_backend_stream_sync(client, url, headers, body, *, timeout=None, fallback_url=None):
"""Open a sync upstream stream, retrying once on the console endpoint after a channel rejection.
Expand All @@ -404,7 +426,7 @@ def open_backend_stream_sync(client, url, headers, body, *, timeout=None, fallba
"""
while True:
raw = None
with client.stream("POST", url, headers=headers, json=body, timeout=timeout) as response:
with client.stream("POST", url, timeout=timeout, **_json_request_kwargs(headers, body, url)) as response:
if fallback_url and response.status_code == 400:
raw = read_bounded_sync_error(response)
if should_channel_fallback(fallback_url, response.status_code, raw):
Expand Down Expand Up @@ -443,7 +465,8 @@ async def open_backend_stream(url, headers, body, *, read_timeout=300, on_retry=
try:
async with _attempt_client(url, timeout, clients if attempt == 0 else None) as client:
attempt_headers = headers_for_attempt() if headers_for_attempt is not None else headers
async with client.stream("POST", url, headers=attempt_headers, json=body, timeout=timeout) as response:
async with client.stream("POST", url, timeout=timeout,
**_json_request_kwargs(attempt_headers, body, url)) as response:
opened = True
yield response
return
Expand Down
Loading
Loading