+
    i$F                        R t ^ RIt^ RIt^ RIt^ RIHtHtHtHt ^RI	H
t
Ht RR.tR R ltR R	 ltR
 R ltRtRtRtR R ltR R ltR R ltRR'RR(RR)/tRtR R ltR R ltR R ltR*R R  lltR+R! R" lltR# R$ ltR% R& ltR# ),z1OpenAI Responses API client for Reddit discovery.N)AnyDictListOptional)httpenvzgpt-4.1zgpt-4oc                $    V ^8  d   QhR\         /#    msgstr)formats   "Y/Users/bowang/.openclaw/workspace/skills/last30days-official/scripts/lib/openai_reddit.py__annotate__r      s      C     c                    \         P                  P                  RV  R24       \         P                  P                  4        R# )zLog error to stderr.z[REDDIT ERROR] 
Nsysstderrwriteflushr   s   &r   
_log_errorr      s-    JJse2./JJr   c                $    V ^8  d   QhR\         /# r	   r   )r   s   "r   r   r      s      3 r   c                    \         P                  P                  RV  R24       \         P                  P                  4        R# )zLog info to stderr.z	[REDDIT] r   Nr   r   s   &r   	_log_infor      s-    JJyR()JJr   c                D    V ^8  d   QhR\         P                  R\        /# )r
   errorreturn)r   	HTTPErrorbool)r   s   "r   r   r      s      $.. T r   c                   a V P                   R9  d   R# V P                  '       g   R# V P                  P                  4       o\        ;QJ d    V3R lR 4       F  '       g   K   R# 	  R# ! V3R lR 4       4      # )z:Check if error is due to model access/verification issues.Fc              3   ,   <"   T F	  qS9   x  K  	  R # 5i)N ).0phrase
body_lowers   & r   	<genexpr>)_is_model_access_error.<locals>.<genexpr>#   s       3# 3s   T)  i  )verifiedzorganization must bezdoes not have accessznot availablez	not found)status_codebodylowerany)r   r(   s   &@r   _is_model_access_errorr1      sq    
*:::!!#J3  3 33 3 3  3   r   z#https://api.openai.com/v1/responsesz/https://chatgpt.com/backend-api/codex/responsesa  You are a research assistant for a skill that summarizes what people are discussing in the last 30 days. Your goal is to find relevant Reddit threads about the topic and return ONLY the required JSON. Be inclusive (return more rather than fewer), but avoid irrelevant results. Prefer threads with discussion and comments. If you can infer a date, include it; otherwise use null. Do not include developers.reddit.com or business.reddit.com.c                h    V ^8  d   QhR\         R\        \        \         \        3,          ,          /# )r
   chunkr    )r   r   r   r   )r   s   "r   r   r   8   s'      C HT#s(^$< r   c                   V P                  R4      p. pV FB  pVP                  R4      '       g   K  VP                  VR,          P                  4       4       KD  	  V'       g   R# RP	                  V4      P                  4       pV'       d   VR8X  d   R#  \
        P                  ! V4      #   \
        P                   d     R# i ; i)z,Parse a single SSE chunk into a JSON object.r   zdata::   NNNz[DONE])split
startswithappendstripjoinjsonloadsJSONDecodeError)r3   lines
data_lineslinedatas   &    r   _parse_sse_chunkrB   8   s    KKEJ??7##d2hnn./  99Z &&(D48#zz$ s   B- -CCc                h    V ^8  d   QhR\         R\        \        \         \        3,          ,          /# r
   rawr    )r   r   r   r   )r   s   "r   r   r   N   s'      s tDcN'; r   c                :   . pRpV P                  RR7       FJ  pW#,          pRV9   g   K  VP                  R^4      w  rB\        V4      pVf   K.  VP                  V4       KA  	  VP	                  4       '       d!   \        V4      pVe   VP                  V4       V# )z6Parse SSE stream from raw text and return JSON events. T)keepends

)
splitlinesr6   rB   r8   r9   )rE   eventsbufferr3   event_chunkevents   &     r   _parse_sse_stream_rawrO   N   s    #%FF."(,,vq"9K$[1E e$ / ||~~ (MM% Mr   c                R    V ^8  d   QhR\         R\        \         \        3,          /# rD   r   r   r   )r   s   "r   r   r   `   s"     # #S #T#s(^ #r   c                   \        V 4      p\        V4       F  p\        V\        4      '       g   K  VP	                  R4      R8X  d1   \        VP	                  R4      \        4      '       d   VR,          u # \        VP	                  R4      \        4      '       g   K  VR,          u # 	  RpV F}  p\        V\        4      '       g   K  VP	                  R4      p\        V\
        4      '       d   W4,          pKL  VP	                  R4      p\        V\
        4      '       g   Ku  W5,          pK  	  V'       d   RRRR	RR
RV/././# / # )z@Parse SSE stream from Codex responses into a response-like dict.typezresponse.completedresponserG   deltatextoutputmessagecontentoutput_text)rO   reversed
isinstancedictgetr   )rE   rK   evtrZ   rU   rV   s   &     r   _parse_codex_streamr`   `   s   "3'F c4  wwv"66:cggjFY[_;`;`:&#''*-t44:&   K#t$$ eS!! KwwvdC  K  I LM
 	
 Ir   quickdefaultdeepu  Find Reddit discussion threads about: {topic}

STEP 1: EXTRACT THE CORE SUBJECT
Get the MAIN NOUN/PRODUCT/TOPIC:
- "best nano banana prompting practices" → "nano banana"
- "killer features of clawdbot" → "clawdbot"
- "top Claude Code skills" → "Claude Code"
DO NOT include "best", "top", "tips", "practices", "features" in your search.

STEP 2: SEARCH BROADLY
Search for the core subject:
1. "[core subject] site:reddit.com"
2. "reddit [core subject]"
3. "[core subject] reddit"

Return as many relevant threads as you find. We filter by date server-side.

STEP 3: INCLUDE ALL MATCHES
- Include ALL threads about the core subject
- Set date to "YYYY-MM-DD" if you can determine it, otherwise null
- We verify dates and filter old content server-side
- DO NOT pre-filter aggressively - include anything relevant

REQUIRED: URLs must contain "/r/" AND "/comments/"
REJECT: developers.reddit.com, business.reddit.com

Find {min_items}-{max_items} threads. Return MORE rather than fewer.

Return JSON:
{{
  "items": [
    {{
      "title": "Thread title",
      "url": "https://www.reddit.com/r/sub/comments/xyz/title/",
      "subreddit": "subreddit_name",
      "date": "YYYY-MM-DD or null",
      "why_relevant": "Why relevant",
      "relevance": 0.85
    }}
  ]
}}c                0    V ^8  d   QhR\         R\         /# r
   topicr    r   )r   s   "r   r   r      s     ) ) ) )r   c                    . ROpV P                  4       P                  4       pV Uu. uF  q3V9  g   K  VNK  	  ppRP                  VR,          4      ;'       g    T # u upi )z2Extract core subject from verbose query for retry. :N   N)besttopzhow toztips for	practicesfeatureskillerguidetutorialrecommendationsadvice	promptingusingforwiththeofinon)r/   r6   r:   )rf   noisewordswresults   &    r   _extract_core_subjectr      sZ    KE KKM!E1A5.aaF188F2J((5( 2s
   AAc                0    V ^8  d   QhR\         R\         /# re   r   )r   s   "r   r   r      s     	+ 	+# 	+# 	+r   c                    \        V 4      pVP                  RR4      P                  RR4      P                  4       pRV R2# )zBuild a subreddit-targeted search query for fallback.

When standard search returns few results, try searching for the
subreddit itself: 'r/kanye', 'r/howie', etc.
.rG   rh   r/z site:reddit.com)r   replacer/   )rf   coresub_names   &  r   _build_subreddit_queryr      sD     !'D||C$,,S"5;;=Hz)**r   c                v    V ^8  d   QhR\         R\         R\         R\         R\        \         \        3,          /# )r
   modelinstructions_text
input_textauth_sourcer    rQ   )r   s   "r   r   r      s=      # # 3 UX ]abegjbj]k r   c                    RV RRRRRRRR	.//.R
R.RVRV/pV\         P                  8X  d   RRRRRRRRV/./.VR&   RVR&   V# )z6Build responses payload for OpenAI or Codex endpoints.r   storeFtoolsrS   
web_searchfiltersallowed_domains
reddit.comincludeweb_search_call.action.sourcesinstructionsinputrX   roleuserrY   r   rV   Tstream)r   AUTH_SOURCE_CODEX)r   r   r   r   payloads   &&&& r   _build_payloadr      s     	%~
 	45)G c+++ 	V\6:FG
 !Nr   c                    V ^8  d   QhR\         R\         R\         R\         R\         R\         R\         R\        \         ,          R	\        \        ,          R
\        R\        \         \        3,          /# )r
   api_keyr   rf   	from_dateto_datedepthr   
account_idmock_response_retryr    )r   r   r   r"   r   )r   s   "r   r   r      s     x0 x0x0x0 x0 	x0
 x0 x0 x0 x0 D>x0 x0 
#s(^x0r   c
           
     b   Ve   V# \         P                  V\         R,          4      w  rV\        P                  8X  d*   V'       g   \	        R4      hRRV  2RVRRR	R
RR/p\
        pMRRV  2RR/p\        pVR8X  d   ^ZM
VR8X  d   ^xM^pV.\         Uu. uF  qV8w  g   K  VNK  	  up,           p\        P                  VVVV
VR7      pV\        P                  8X  d   ^RI
Hp V.VP                   Uu. uF  qV8w  g   K  VNK  	  up,           p\        R,           V,           pRpV F?  p \        VVW&4      p\        P                   ! VVWR7      p\#        T;'       g    R4      u # 	  V'       d   Vh\        P$                  ! R4      hRpV F0  pRVRRRRRR.//.RR.R V/p \        P*                  ! VVWR7      u # 	  V'       d   \/        R%V 24       Vh\        P$                  ! R&4      hu upi u upi   \        P$                   d0   pTpTP&                  R8X  d   \)        RT R24        Rp?EK  h Rp?ii ; i  \        P$                   dV   pTp\-        T4      '       d   \)        RT R!24        Rp?K  TP&                  R"8X  d   \)        R#T R$24        Rp?EK  h Rp?ii ; i)'a  Search Reddit for relevant threads using OpenAI Responses API.

Args:
    api_key: OpenAI API key
    model: Model to use
    topic: Search topic
    from_date: Start date (YYYY-MM-DD) - only include threads after this
    to_date: End date (YYYY-MM-DD) - only include threads before this
    depth: Research depth - "quick", "default", or "deep"
    mock_response: Mock response for testing

Returns:
    Raw API response
Nrb   z)Missing chatgpt_account_id for Codex authAuthorizationzBearer zchatgpt-account-idzOpenAI-Betazresponses=experimental
originatorpizContent-Typeapplication/jsonra   )rf   r   r   	min_items	max_items)modelsrI   )headerstimeoutrG   r+   zModel z+ not supported on Codex, trying fallback...z$No Codex-compatible models availabler   r   rS   r   r   r   r   r   r   r   z# not accessible, trying fallback...  zRate limited on z, trying fallback model...zAll models failed. Last error: zNo models available)DEPTH_CONFIGr^   r   r   
ValueErrorCODEX_RESPONSES_URLOPENAI_RESPONSES_URLMODEL_FALLBACK_ORDERREDDIT_SEARCH_PROMPTr   rG   r   CODEX_FALLBACK_MODELSCODEX_INSTRUCTIONSr   r   post_rawr`   r!   r-   r   postr1   r   )r   r   rf   r   r   r   r   r   r   r   r   r   r   urlr   mmodels_to_tryr   
models_modcodex_models_to_tryr   
last_errorcurrent_modelr   rE   es   &&&&&&&&&&                r   search_redditr      s   4  '++E<	3JKIc+++HIIwwi0 *3$.
 " wwi0.
 # W$b)1C#G G*>M*>Qu*qq*>MMM &,, - J c+++*$gJ4T4T(c4Tq]bXb4T(cc.7*D
0M	(8I5^mmC'S*399"55	 1 nnCDD J&]L)L>  89Z
	99S'7LL! ': 4ZLAB
...
//G N )d >> 
==C'}o5`ab: ~~ 		J%a((F=/1TUV}}#,]O;UVW		sZ   G3"G36G8G8.:G=(I=I"H<;H<<IJ."J) J)(J))J.c                    V ^8  d   QhR\         \        ,          R\        R\        R\        R\        R\         \        \        \        3,          ,          /# )r
   
subredditsrf   r   r   	count_perr    )r   r   intr   r   )r   s   "r   r   r   f  s]     K KS	KK K 	K
 K 
$sCx.Kr   c                b   . p\        V4      pV  EF  pVP                  R4      p RV R2pR\        V4       RV R2p	V RV	 2p
R\        P                  R	R
/p\        P
                  ! W^^R7      pVP                  R/ 4      P                  R. 4      p\        V4       EF  w  rVP                  R4      R8w  d   K  VP                  R/ 4      pVP                  RR4      pV'       g   KL  RR\        V4      ^,            2R\        VP                  RR4      4      P                  4       RRV 2R\        VP                  RV4      4      P                  4       RRRRV R2RR/pVP                  R4      pV'       d   ^R I
Hp VP                  V4      VR&   VP                  V4       EK  	  EK  	  V#   \        P                   dA   p\        R!T R"T 24       TP                   R#8X  d   \        R$4        Rp? T#  Rp?EK  Rp?i\"         d   p\        R%T R"T 24        Rp?EK  Rp?ii ; i)&a  Search specific subreddits via Reddit's free JSON endpoint.

No API key needed. Uses reddit.com/r/{sub}/search/.json endpoint.
Used in Phase 2 supplemental search after entity extraction.

Args:
    subreddits: List of subreddit names (without r/)
    topic: Search topic
    from_date: Start date (YYYY-MM-DD)
    to_date: End date (YYYY-MM-DD)
    count_per: Results to request per subreddit

Returns:
    List of raw item dicts (same format as parse_reddit_response output).
r   zhttps://www.reddit.com/r/z/search/.jsonzq=z&restrict_sr=on&sort=new&limit=z&raw_json=1?z
User-AgentAcceptr   )r   r   retriesrA   childrenkindt3	permalinkrG   idRStitler   zhttps://www.reddit.com	subredditdateNwhy_relevantzFound in r/z supplemental search	relevanceg?created_utc)dateszSubreddit search failed for r/z: r   u;   Reddit rate-limited (429) — skipping remaining subredditszSubreddit search error for r/)r   lstrip_url_encoder   
USER_AGENTr^   	enumeratelenr   r9   rG   r   timestamp_to_dater8   r!   r   r-   	Exception)r   rf   r   r   r   	all_itemsr   subr   paramsfull_urlr   rA   r   ichildr   r   itemr   	dates_modr   s   &&&&&                 r   search_subredditsr   f  s:   , I 'Djj.	B-cU-@C+d+,,KI;VabFax(H doo,G
 88Hr1MD xx+//
B?H%h/99V$,yy, HH["5	  Bs9~a/01S'2!67==?3I;?TXXk3%?!@!F!F!HD"k#6J$K #hh}54#,#>#>{#KDL  &1 0! d  ~~ 	6se2aSAB}}#WX  $  	B5cU"QC@AA	Bs1   CF3)CF33H.-HH.H.H))H.c                0    V ^8  d   QhR\         R\         /# )r
   rV   r    r   )r   s   "r   r   r     s     ) )c )c )r   c                @    ^ RI pVP                  P                  V 4      # )z)Simple URL encoding for query parameters.N)urllib.parseparse
quote_plus)rV   urllibs   & r   r   r     s    <<""4((r   c                    V ^8  d   QhR\         \        \        3,          R\        \         \        \        3,          ,          /# )r
   rT   r    )r   r   r   r   )r   s   "r   r   r     s3     Y YDcN YtDcN7K Yr   c                6   . pRV 9   d   V R,          '       d   V R,          p\        V\        4      '       d   VP                  R\        V4      4      M
\        V4      p\	        RV 24       \
        P                  '       d,   \	        R\        P                  ! V ^R7      R,           24       V# RpRV 9   d   V R,          p\        V\        4      '       d   TpM\        V\        4      '       d   V F  p\        V\        4      '       d   VP                  R	4      R8X  d_   VP                  R
. 4      pV FE  p\        V\        4      '       g   K  VP                  R	4      R8X  g   K3  VP                  RR4      p M,	  M)RV9   d
   VR,          pM\        V\        4      '       d   TpV'       g   K   M	  V'       g:   RV 9   d3   V R,           F%  p	RV	9   g   K  V	R,          P                  R
R4      p M	  V'       g*   \        R\        V P                  4       4       2RR7       V# \        P                  ! RV4      p
V
'       d8    \        P                  ! V
P                  4       4      pVP                  R. 4      p. p\#        V4       EFm  w  r\        V\        4      '       g   K  VP                  RR4      pV'       d   RV9  d   KA  RRV^,            2R\        VP                  RR4      4      P%                  4       RVR\        VP                  RR4      4      P%                  4       P'                  R4      RVP                  R4      R\        VP                  RR4      4      P%                  4       R\)        R\+        R\-        VP                  RR4      4      4      4      /pVR,          '       d3   \        P.                  ! R \        VR,          4      4      '       g   R!VR&   VP1                  V4       EKp  	  V#   \        P                    d     ELi ; i)"zvParse OpenAI response to extract Reddit items.

Args:
    response: Raw API response

Returns:
    List of item dicts
r   rX   zOpenAI API error: zFull error response: )indent:Ni  NrG   rW   rS   rY   rZ   rV   choiceszH[REDDIT WARNING] No output text found in OpenAI response. Keys present: T)r   z\{[\s\S]*"items"[\s\S]*\}itemsr   r   r   Rr   r   r   r   r   r   g      ?g        g      ?z^\d{4}-\d{2}-\d{2}$N)r\   r]   r^   r   r   r   DEBUGr;   dumpslistprintkeysresearchr<   groupr=   r   r9   r   minmaxfloatmatchr8   )rT   r   r   err_msgrZ   rW   r   rY   cchoice
json_matchrA   clean_itemsr   r   
clean_items   &               r   parse_reddit_responser    sp    E (x00!6@6M6M%))Is5z2SVW\S]'y12:::.tzz(1/Me/T.UVW K8(#fc"" K%%dD))xx'94"&((9b"9!(A)!T22quuV}7U./eeFB.? % ")  4&*6lc**"&K;   90y))FF"$Y/33IrB *
 XY]^f^k^k^mYnXopx|} 7EJ	::j..01DHHWb)E
 KU#$%%hhub!l#- AacU)S'2./5573TXXk267==?FFtLDHHV$C <=CCES#c5+s1K+L"MN

 f882C
68J4KLL%)
6":&/ $2 = ## 		s   6O? ?PP)      )   2   )F   d   )rb   r   NNF)r5   )__doc__r;   r   r   typingr   r   r   r   rG   r   r   r   r   r   r1   r   r   r   rB   rO   r`   r   r   r   r   r   r   r   r   r  r%   r   r   <module>r     s    7  	 
 , ,  "8, " = G C ,$#P Xx
I( V)	+:x0vK\)Yr   