
    q%j0m                        U d Z ddlmZ ddlZddlZddlZddlZddlZddlm	Z	 ddl
mZ ddlmZ  ej                  d      Zdaded	<   dad
ed<   ddZd ZddZddZ G d d      Z e       Zy)uW  階段 10-B:Cooper 全域 ClaudeSDKClient 管理器(暖機 + 常駐 + 序列化)。

設計重點:
  - uvicorn 啟動時 connect 一次(10s),user 看不到
  - 每個 LINE 訊息用 session_id 區分群組(line:<channel_id>)
  - asyncio.Lock 序列化 query — 同一個 client 一次只跑一個,避免狀態錯亂
  - 工具 wrapper 用 ContextVar 取得 requester_id(取代 closure)
  - 9 個 Cooper MCP 工具在 client connect 時就註冊好(不再每次 LINE 訊息重建)

使用方式:
    # uvicorn lifespan startup
    await cooper_client_manager.startup()

    # LINE 訊息進來
    response_text, rate_info, tools_called = await cooper_client_manager.respond(
        prompt_text="...",
        session_id="line:<channel_id>",
        requester_id=user_id,
    )

    # uvicorn lifespan shutdown
    await cooper_client_manager.shutdown()
    )annotationsN)Any)UUID)settingszcoba.cooper_clientzUUID | None_current_requester_idlist[dict] | None_current_tool_callsc                    | a |ay Nr   r	   )requester_idtool_calls_buckets     >/volume1/homes/robertsu/coba/app/app/services/cooper_client.py_set_request_contextr   7   s    (+    c                     d a d ay r   r    r   r   _clear_request_contextr   =   s     r   c                 0    t         t        d      t         S )Nu4   Cooper tool 在沒有 requester context 時被呼叫)r   RuntimeErrorr   r   r   _get_requester_idr   C   s    $QRR  r   c                r    t         1t         j                  | |t        |j                               d       y y )N)nameinputresult_keys)r	   appendlistkeys)r   argsresults      r   _record_tool_callr!   I   s4    &"".$
 	 'r   c                  F    e Zd ZdZd Zd Zd Zd Z	 d	 	 	 	 	 	 	 	 	 d	dZy)
CooperClientManageruA   單例 — 包住 ClaudeSDKClient + lock + 暖機/關閉邏輯。c                    d | _         t        j                         | _        d| _        d | _        t        j                         | _        y )NF)_clientasyncioLock_lock_started_mcp_serverEvent_ready_eventselfs    r   __init__zCooperClientManager.__init__X   s3     \\^
 $#MMOr   c                   ddl m ddlm}m}m}m}m}m}m	}m
}m}	m}
m}m}m}m}m}m}m}m}m}m} ddddddddddddddkfd	} ||d
ddddddddddddg ddddiddgd       ||ddddg ddddiddiddidd       ||ddddd dddidg d!dd"d#gd       ||d$d%dddiddiddiddidd&ddg dddd'dd(d       ||d)d*dddiddiddid+d,gd       ||d-d.dddiddid/d       ||d0d1dd2ddiid       ||d3d4di d       ||	d5d6dd7ddiid       ||
d8d9di d       ||d:d;ddd<ddd=dd>d?gd       ||d@dAddBddCdid       ||dDdEdddiddFddGd       ||dHdIdddiddiddJdddiddidKd       ||dLdMdddiddiddNdddiddddg ddddidOdPddQdgd       ||dRdSdddiddTddUddidVdUddidWdXdUddidYdXdZdgd       ||d[d\dd]dd^did       ||d_d`dddiddidad       ||dbdcdddiddidad       ||dddedddfddg dgdddiddhdddididjgd      gS )lNr   tool)tool_create_tasktool_list_taskstool_update_task_statustool_update_tasktool_reassign_tasktool_delete_tasktool_share_dashboard_linktool_get_team_memberstool_list_assignment_statustool_get_team_completion_ratetool_save_reflectiontool_list_recent_reflectionstool_delete_reflectiontool_update_reflectiontool_convert_reflection_to_tasktool_add_team_membertool_record_meetingtool_list_meetingstool_get_meeting_detailtool_delete_meetingtasksreflectionsmeetingsusers)create_taskupdate_taskupdate_task_statusreassign_taskdelete_tasksave_reflectiondelete_reflectionupdate_reflectionconvert_reflection_to_taskrecord_meetingdelete_meetingadd_team_memberc                6      ||      d fd       }|S )Nc                  K   t               }	  | xs i |       d {   }t        | |       d|vrCj                        }|r0ddlm	} 	  ||       d {    dk(  r |d       d {    d
t        j                  |d      dgd|v dS 7 x# t        $ r<}t        j                  d       dt	        |      j
                   d| i}Y d }~d }~ww xY w7 7 p# t        $ r t        j                  d	       Y w xY ww)Nztool %s failederror: r   )_broadcast_data_changedr1   rS   rG   zbroadcast after tool=%s failedtextF)ensure_asciityper\   )contentis_error)r   	Exceptionlogger	exceptionr_   __name__r!   getapp.services.coba_toolsr[   jsondumps)	r   r   r    eentityr[   TOOL_BROADCAST_ENTITYr   tool_fns	         r   _wrappedzACooperClientManager._build_tools.<locals>._wrap.<locals>._wrapped   s'    02C#*4:2|#DDF "$f5 &(266t<FSU"9&t"LLL#'CC&=gD&Q Q Q
 *0FY^9_ `a '6 1 ' E  C$$%5t<%$q'*:*:);2aS'ABFC M !R( U",,-MtTUs   DB BB *DC& C"C& 3C$4C& 8 DB 	C#2CDCD"C& $C& &DDDD)r   dictreturnro   r   )rm   r   descriptionschemarn   rl   r2   s   ``   r   _wrapz/CooperClientManager._build_tools.<locals>._wrap   s%    $V, -4 Or   rK   uW   建立一個新任務在團隊的行程規畫系統。當使用者明確命令時用。objectstringu   任務標題)r_   rq   u   負責人 display_namez
YYYY-MM-DD)lowmediumhighurgent)r_   enumr_   )titleassignee_namedue_datepriorityrq   r{   )r_   
propertiesrequired
list_tasksu<   查任務(可帶 status / assignee_name / keyword 條件)。)pendingin_progress	completed	cancelledallinteger)statusr|   keywordlimit)r_   r   rM   uM   改任務狀態。用 task_id_prefix(前 8 碼)或 title_keyword 找任務。u   task UUID 前 8 字元)todor   reviewdoner   )task_id_prefixtitle_keyword
new_statusr   rL   u  全功能改任務:可改 title / description / due_date / priority / assignee。不改 status(用 update_task_status)。用 task_id_prefix(前 8 碼)或 title_keyword 鎖定任務。把 new_due_date 設成 'null' 可清除截止日;new_assignee_name 設成 'null' 取消指派。u   YYYY-MM-DD,或 'null' 清除u1   新負責人 display_name,或 'null' 取消指派)r   r   	new_titlenew_descriptionnew_due_datenew_prioritynew_assignee_namerN   uW   把任務轉指派給另一個成員。是 update_task 的 sugar 版,只動 assignee。)r   r   r   r   rO   u   軟刪任務(set deleted_at)。)r   r   share_dashboard_linkuE   給對方一鍵登入 dashboard 的 magic link(只在 1 對 1 用)。target_user_nameget_team_membersu0   取得團隊成員清單(display_name + role)。list_assignment_statusu>   列最近自動建任務的認領狀態(誰接 / 誰沒接)。daysget_team_completion_rateu   列每人本週完成率 KPI。rP   uP   把使用者的覆盤 / 體悟 / 反省內容存進體悟紀錄(不建任務)。u   體悟內容原文u   可選:類別標籤)r`   tagr`   list_recent_reflectionsuq   列最近群組分享的覆盤 / 體悟記錄。回傳含 id_short 讓你後續能 update / delete / 轉任務。hoursu   預設 168(一週)rQ   uu   軟刪一筆覆盤。用 reflection_id_prefix(前 8 碼,從 list_recent_reflections 拿)或 content_keyword 鎖定。u   覆盤內容關鍵字)reflection_id_prefixcontent_keywordrR   um   改一筆覆盤的內容 / 摘要 / 標籤。至少要給 new_content / new_summary / new_tag 其中一個。u   新覆盤完整內容)r   r   new_contentnew_summarynew_tagrS   u   誤分類修正:某筆覆盤其實是任務 → 升級成任務,自動軟刪原覆盤。必填 title。可選 assignee_name / due_date / priority / description / mark_done(已完成的事直接 done)。u   新任務標題,動詞開頭booleanu;   true=立刻標 done(用於『我已經完成 X』類報告))r   r   r{   r|   r}   r~   rq   	mark_donerT   uE  從文字直接記錄一場會議。使用者說「庫柏 記下這次會議:標題 / 結論 / action items」就用這個。必填 title。可選:executive_summary、key_topics、decisions(list[str|dict])、action_items(list[str|dict{description,assignee_name,due_date}])。建好後 status='completed',不走錄音流程。u   2-4 句濃縮摘要array)r_   itemsu   重要決策清單)r_   r   rq   uA   待辦事項清單(可帶 - 阿綠週五前 X 之類自然語言))r{   executive_summary
key_topics	decisionsaction_itemslist_meetingsu=   列最近會議。回傳含 id_short 給後續查詳細用。r   u	   預設 10get_meeting_detailur   看一場會議完整內容(摘要 + 決策 + action items)。用 meeting_id_prefix(8 碼)或 title_keyword 找。)meeting_id_prefixr   rU   u   軟刪會議。rV   u   新增團隊成員到行程規畫系統。必填 display_name;可選 role(owner/manager/member,預設 member)、email、line_user_id、job_title。成功會回 user_id_short + 預設密碼提示給對方第一次登入用。u!   對方在系統內的顯示名稱)ownermanagermemberu   LINE 個人 ID,Uxxxxx 格式)display_nameroleemailline_user_id	job_titler   )r   strrq   r   rr   ro   )claude_agent_sdkr2   rg   r3   r4   r5   r6   r7   r8   r9   r:   r;   r<   r=   r>   r?   r@   rA   rB   rC   rD   rE   rF   )r.   r3   r4   r5   r6   r7   r8   r9   r:   r;   r<   r=   r>   r?   r@   rA   rB   rC   rD   rE   rF   rs   rl   r2   s                         @@r   _build_toolsz CooperClientManager._build_toolsc   s6   )	
 	
 	
 	
 	
 	
4 #"")$",!.!.*7((&!
	> "Mi$*2>!R2:Kc)d-5l$S-5?b$c(.'9# ")	
 /<N$+3=x"y*0();$*H#5"()!4	# )+?_$3;Ld*e*0();/7Aw&x#
 ". "Mt
 %+18*<*0();&,h%7,2H+=19Jh(i19Cf(g6>  PC  .D#$ $oi$+18*<*0();.4h-?#
 "5 5 "M1$+18*<*0();#	 +-CW!2DvxFX1YZ ');B!4 -/GP!&69:M1NO /1K1!4 &(9b$,4EY#Z(0AVW# "+
 .0I D!'I^r;s1tu (*=s %170B4<Md+e#
 (*=$170B,2H+=08I`'a(.'9$*H#5#	 13OE %170B,2H+=*2Ca!b*0();-5l$S-5?b$c(.'9.7  IF  &G	# ")	$ %'7E %"((!36>Od-e/6&(AS&T$+&,h%7+?& %,&,h%7+n)# ")	#4 $oO$#*Y{,["\ )+?G %.4h-?*0();#
 %'7!$.4h-?*0();#	 &(9` %19Jm(n)1;Y Z"((!319Jh(i&,h%7# "0 0
	]^
 ^	
r   c                  K   | j                   ry t        j                  st        j	                  d       y ddlm}m}m}  |dd| j                               | _
        g d}ddlm}  |d	|g |d| j                  id
ddg ddt        j                  d      }d }t        dd      D ]w  }t        d| dd
        ||      | _        	 | j                  j!                          d {    d
| _         | j"                  j%                          t        d| dd
        y  |r|y 7 ># t&        $ r}	|	}t)        |	      d d }
t        d| dt+        |	      j,                   d|
 d
       	 | j                  r#| j                  j/                          d {  7   n# t&        $ r Y nw xY wd | _        |dk  rt1        j2                  d       d {  7   Y d }	~	3d }	~	ww xY ww)Nu:   CLAUDE_CODE_OAUTH_TOKEN 沒設,Cooper SDK client 不啟動r   )ClaudeSDKClientClaudeAgentOptionscreate_sdk_mcp_servercooperz1.0.0)r   versiontools)mcp__cooper__create_taskmcp__cooper__list_tasksmcp__cooper__update_task_statusmcp__cooper__update_taskmcp__cooper__reassign_taskmcp__cooper__delete_task!mcp__cooper__share_dashboard_linkmcp__cooper__get_team_members#mcp__cooper__list_assignment_status%mcp__cooper__get_team_completion_ratemcp__cooper__save_reflection$mcp__cooper__list_recent_reflectionsmcp__cooper__delete_reflectionmcp__cooper__update_reflection'mcp__cooper__convert_reflection_to_taskmcp__cooper__add_team_membermcp__cooper__record_meetingmcp__cooper__list_meetingsmcp__cooper__get_meeting_detailmcp__cooper__delete_meeting)COBA_BASE_PROMPTzclaude-sonnet-4-5T   bypassPermissionsz/tmp )ANTHROPIC_API_KEYCLAUDE_CODE_OAUTH_TOKEN)modelsystem_promptr   allowed_toolsmcp_serversstrict_mcp_config	max_turnspermission_modesetting_sourcescwdenv      z+[COOPER_CLIENT_STARTUP] connecting attempt=z...flush)optionsu/   [COOPER_CLIENT_STARTUP] ✓ connected (attempt=)   z&[COOPER_CLIENT_STARTUP_RETRY] attempt=z fail: rZ         )r)   r   r   rc   warningr   r   r   r   r   r*   app.services.coba_chatr   rangeprintr%   connectr,   setrb   r   r_   re   
disconnectr&   sleep)r.   r   r   r   allowedr   r   last_errattemptrj   err_msgs              r   startupzCooperClientManager.startup  s    ==//NNWX	
 	
 1##%

2 	<$%*!!4#3#34"/%'+3+K+K
& &*Q{G?yLTXY*7;DL+ll**,,, $!!%%'GyPQRZ^_ #. N ' -
  +a&#,>wiwtTUwO_O_N``bcjbkltxy||"ll55777  #Q;!--***+s~   CG!	D'&D%'6D'G!%D''	G09G*)FFFG	F&#G%F&&'GGGG!GG!c                  K   | j                   rI| j                  r=	 t        dd       | j                   j                          d {    t        dd       d| _        d | _         y 7  # t        $ r t
        j                  d       Y 1w xY ww)Nz)[COOPER_CLIENT_SHUTDOWN] disconnecting...Tr   u)   [COOPER_CLIENT_SHUTDOWN] ✓ disconnectedu   Cooper client disconnect 失敗F)r%   r)   r   r   rb   rc   rd   r-   s    r   shutdownzCooperClientManager.shutdown  sy     <<DMMDANll--///AN  0 D  !BCDs:   B*A) A'A) B'A) )B
B	B

BNc                ~  K   | j                   j                         sEt        dd       	 t        j                  | j                   j                         d       d{    | j                  r| j                  st        d      d	d
l
m}m}m}m} g }		 | j                  4 d{    t!        ||	       |rct        d dt#        |       dt#        |       dd       d|dg|z   fd}
| j                  j%                   |
              d{    nAt        d dt#        |       d       | j                  j%                  |       d{    g }d}d}d}| j                  j'                         2 3 d{   }t)        ||      r:|j*                  D ]*  }t)        ||      s|j-                  |j.                         , Ot)        ||      rI|j0                  xs d}t        d|j2                   d|dd|j4                   dt#        |	       d        ngt)        ||      s|j6                  }|j8                  }|j:                  }t        d|j8                   d|j:                   d|j<                   d       dj?                  d |D              jA                         xs d}|||||	dcddd      d{    tC                S 7 x# t        j                  $ r t        d      dw xY w7 W7 7 7 6 7 C# 1 d{  7  sw Y   nxY w	 tC                y# tC                w xY ww) u  跑一次 Cooper 對話。回 dict {response_text, rate_limit_type, total_cost_usd, tool_calls}。

        並發行為:同時間多個 LINE 訊息會被 Lock 序列化(一個 client 一次只跑一個)。

        image_blocks(階段 10-H):若提供,把 text + 圖片合成 multimodal content blocks 送給
            Claude 看圖。每個 block 結構:
                {"type": "image", "source": {"type": "base64",
                  "media_type": "image/jpeg", "data": "<base64...>"}}
        u,   [COOPER_QUERY_WAIT_READY] 等暖機完成...Tr   <   )timeoutNu   Cooper client 暖機 60s 超時u   Cooper client 未啟動r   )AssistantMessage	TextBlockResultMessageRateLimitEventz[COOPER_QUERY] session=z prompt_len=z images=z (multimodal)r\   r^   c                (   K   dd dd d y w)Nuser)r   r`   )r_   messageparent_tool_use_id
session_idr   )content_blocksr  s   r   _msg_genz-CooperClientManager.respond.<locals>._msg_gen,  s"     $*06>'R26*4	 s   )r  g        z[COOPER_RESULT] duration_ms=z cost=z.5fz stop=z tools=z[COOPER_RATE_LIMIT] type=z status=z	 overage=
c              3  ^   K   | ]%  }|j                         s|j                          ' y wr   )strip).0ps     r   	<genexpr>z.CooperClientManager.respond.<locals>.<genexpr>Y  s     )V[AGGI!'')[s   --u   (庫柏沒說話))response_textrate_limit_typerate_limit_statustotal_cost_usd
tool_calls)"r,   is_setr   r&   wait_forwaitTimeoutErrorr   r)   r%   r   r   r  r  r  r(   r   lenqueryreceive_messages
isinstancer`   r   r\   r  duration_msstop_reasonrate_limit_infor  r   overage_statusjoinr  r   )r.   prompt_textr  r   image_blocksr   r  r  r  r   r
  text_piecesobserved_rate_typeobserved_rate_statusr  msgblockinfor  r	  s     `                @r   respondzCooperClientManager.respond  s?    "   '')@MP&&t'8'8'='='?LLL }}DLL899	
 	
 )+G	%zzz$\3DE  1*\#kJZI[ \""%l"3!4MC"
 06{&K%L|%[N ,,,,XZJ,OOO1*\#kJZI[\" ,,,,[Z,PPP)+15"37$!$ "&!>!>!@ #!#'78%([[E)%; + 2 25:: > &1 $C7),););)Bs:3??:K L$$23#7vcoo=N O%%():%;$<> #'	 #C8"22-1-A-A*/3{{,78L8L7M N&&*kk])D<O<O;PR"& !%		)V[)V V \ \ ^ !7#6  &3'9)=&4"3} "zzL #$i M'' P"#DE4OP "* P Q!@G "zzzzL #$"$s   (L=2K K K $1L=L. &K='L. *A,LL ALL&L?L	LL	(L0A?L0BL;L. LL. L=K  K::L==L.  LLL		LL. LLLL. #L=.L::L=r   )
r#  r   r  r   r   r   r$  r   rp   ro   )	re   
__module____qualname____doc__r/   r   r   r   r+  r   r   r   r#   r#   U   s]    K,f
V	[@	& +/h%h% h% 	h%
 (h% 
h%r   r#   )r   r   r   z
list[dict])rp   r   )r   r   r   ro   r    ro   rp   None)r.  
__future__r   r&   contextvarsrh   loggingostypingr   uuidr   
app.configr   	getLoggerrc   r   __annotations__r	   r   r   r   r!   r#   cooper_client_managerr   r   r   <module>r:     s   0 #     	    
		/	0 &* { ))- & -,!O% O%f ,- r   