Akjava commited on
Commit
cec6b19
·
1 Parent(s): 1f88e84
.gitignore ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ __pycache__
2
+ .gradio
3
+ .env
4
+ webhook_header.json
5
+ webhook_request.json
app.py CHANGED
@@ -1,21 +1,21 @@
1
  """
2
- このコードは以下のコードをLinear.appに対応させたものです。公式のコードではありません。
3
  https://github.com/huggingface/huggingface_hub/blob/main/src/huggingface_hub/_webhooks_server.py
4
  https://github.com/huggingface/huggingface_hub/blob/main/src/huggingface_hub/_webhooks_payload.py
5
 
6
- まだ、Issue Objectのごく一部しか対応してません。
7
  https://studio.apollographql.com/public/Linear-API/variant/current/schema/reference/objects/Issue
8
 
9
- .envファイルに、api_key = linear-api-key および、webhook_secret = linear-webhook-secretの設定が必要です。
10
 
11
- ローカルは起動ごとにURLが変わるので、起動時にLinear-APIでURLを更新しています。
12
- target_webhook_label(デフォルト値はGradio)で指定したラベルのwebhookを実行時に上書きます。
13
 
14
- gradio,fastapi,pydanticをあらかじめインストールしておく必要があります。
15
- .envにlinear api_keyおよび、linear-webhook secretを記述する必要があります。
16
- また、このExampleはUpdateのみなので、最初にラベルGradioでWebhookを作っておいてください。
17
 
18
- ** Linear.app 対応部分の著作権表示 **
19
  # Copyright 2025-present, Akihito Miyazaki
20
  #
21
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -30,7 +30,7 @@ gradio,fastapi,pydanticをあらかじめインストールしておく必要が
30
  # See the License for the specific language governing permissions and
31
  # limitations under the License.
32
 
33
- ** Hugging Face Hub ライブラリのライセンス表示 **
34
  # Copyright 2023-present, the HuggingFace Inc. team.
35
  #
36
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -45,8 +45,8 @@ gradio,fastapi,pydanticをあらかじめインストールしておく必要が
45
  # See the License for the specific language governing permissions and
46
  # limitations under the License.
47
 
48
- このコードには Hugging Face Hub ライブラリの一部が含まれており、Apache License, Version 2.0 の下でライセンスされています。
49
- ライセンスの全文は以下から確認できます: http://www.apache.org/licenses/LICENSE-2.0
50
  """
51
 
52
  import os
@@ -60,6 +60,14 @@ from gradio_webhook_server import WebhooksServer
60
  from gradio_webhook_payload import WebhookPayload
61
  from sleep_per_last_token_model import SleepPerLastTokenModelLiteLLM
62
 
 
 
 
 
 
 
 
 
63
 
64
  def get_env_value(key, is_value_error_on_null=True):
65
  value = os.getenv(key)
@@ -109,13 +117,6 @@ for webhook in result["data"]["webhooks"]["nodes"]:
109
 
110
  app = None
111
 
112
- model = SleepPerLastTokenModelLiteLLM(
113
- max_tokens=100,
114
- temperature=0.5,
115
- model_id="groq/llama3-8b-8192",
116
- api_base="https://api.groq.com/openai/v1/",
117
- api_key=groq_api_key,
118
- )
119
 
120
  """
121
  model = HfApiModel(
@@ -127,27 +128,32 @@ model = HfApiModel(
127
  )
128
  """
129
 
130
- agent = CodeAgent(
131
- model=model,
132
- tools=[], ## add your tools here (don't remove final answer)
133
- max_steps=1,
134
- verbosity_level=1,
135
- grammar=None,
136
- planning_interval=None,
137
- name=None,
138
- description=None,
139
- )
140
-
141
 
142
  def update():
143
- result = agent.run(f"how to solve this issue:{app.text}")
144
- return app.text, result
 
145
 
146
 
147
- with gr.Blocks() as ui:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
  gr.HTML("""<h1>Linear.app Webhook Server</h1>
149
  <p>This is Demo of Direct Webhook-triggered AIAgen</p>
150
- <p>it's still just simple code,sadly you have to click to show updates.</p>
151
  <p><b>Imagine an agent, responding instantly.</b></p>
152
  <p>Technically Gradio have no way to update without action<p>
153
  <p></p><br>
@@ -156,20 +162,50 @@ with gr.Blocks() as ui:
156
  <p>If you have any questions, please disable the Space or contact me before taking any action against my account. Thank you for your understanding.</p>
157
  """)
158
  with gr.Row():
159
- issue_box = gr.Textbox(label="Issue")
160
- output_box = gr.Textbox(label="出力")
161
- bt = gr.Button("Ask AI")
162
- bt.click(update, outputs=[issue_box, output_box])
 
 
 
 
 
 
 
 
163
 
164
  app = WebhooksServer(
165
- ui=ui,
166
  webhook_secret=webhook_key, # loaded by load_api_key
167
  )
168
- app.text = "nothing"
 
 
169
 
170
 
171
  @app.add_webhook("/linear_webhook")
172
  async def updated(payload: WebhookPayload):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
  pprint(payload.dict(), indent=4)
174
 
175
  data = payload.dict()["data"]
@@ -182,7 +218,12 @@ async def updated(payload: WebhookPayload):
182
 
183
  if has_label:
184
  text = data["description"]
185
- app.text = text
 
 
 
 
 
186
  return {"message": "ok"}
187
 
188
 
@@ -202,4 +243,5 @@ mutation {
202
  result = execute_query("webhook_update", webhook_update_text, api_key)
203
 
204
 
205
- app.launch(webhook_update=webhook_update)
 
 
1
  """
2
+ This code adapts the following code for Linear.app. It is not official code.
3
  https://github.com/huggingface/huggingface_hub/blob/main/src/huggingface_hub/_webhooks_server.py
4
  https://github.com/huggingface/huggingface_hub/blob/main/src/huggingface_hub/_webhooks_payload.py
5
 
6
+ Currently, it only supports a small subset of the Issue Object.
7
  https://studio.apollographql.com/public/Linear-API/variant/current/schema/reference/objects/Issue
8
 
9
+ You need to set `api_key = linear-api-key` and `webhook_secret = linear-webhook-secret` in your `.env` file.
10
 
11
+ Since the local URL changes with each startup, the URL is updated in the Linear API at startup.
12
+ At startup, it overwrites the webhook with the label specified by `target_webhook_label` (default value: Gradio).
13
 
14
+ You need to pre-install gradio, fastapi, and pydantic.
15
+ You need to describe your Linear API key and Linear webhook secret in the `.env` file.
16
+ Also, since this example is only for Update, please create a Webhook with the label Gradio beforehand.
17
 
18
+ ** Copyright Notice for Linear.app Adaptation **
19
  # Copyright 2025-present, Akihito Miyazaki
20
  #
21
  # Licensed under the Apache License, Version 2.0 (the "License");
 
30
  # See the License for the specific language governing permissions and
31
  # limitations under the License.
32
 
33
+ ** License Notice for Hugging Face Hub Library **
34
  # Copyright 2023-present, the HuggingFace Inc. team.
35
  #
36
  # Licensed under the Apache License, Version 2.0 (the "License");
 
45
  # See the License for the specific language governing permissions and
46
  # limitations under the License.
47
 
48
+ This code includes parts of the Hugging Face Hub library, which is licensed under the Apache License, Version 2.0.
49
+ The full text of the license can be found at: http://www.apache.org/licenses/LICENSE-2.0
50
  """
51
 
52
  import os
 
60
  from gradio_webhook_payload import WebhookPayload
61
  from sleep_per_last_token_model import SleepPerLastTokenModelLiteLLM
62
 
63
+ # .env
64
+ """
65
+ LINEAR_API_KEY="lin_api_***"
66
+ HF_TOKEN = "hf_***"
67
+ LINEAR_WEBHOOK_KEY="lin_wh_***"
68
+ GROQ_API_KEY = "gsk_***"
69
+ """
70
+
71
 
72
  def get_env_value(key, is_value_error_on_null=True):
73
  value = os.getenv(key)
 
117
 
118
  app = None
119
 
 
 
 
 
 
 
 
120
 
121
  """
122
  model = HfApiModel(
 
128
  )
129
  """
130
 
 
 
 
 
 
 
 
 
 
 
 
131
 
132
  def update():
133
+ # result = agent.run(f"how to solve this issue:{app.text}")
134
+ # return app.text, result
135
+ return "", ""
136
 
137
 
138
+ # still testing file or memory
139
+ def load_text(path):
140
+ with open(path, "r") as f:
141
+ return f.read()
142
+
143
+
144
+ def save_text(path, text):
145
+ with open(path, "w") as f:
146
+ f.write(text)
147
+
148
+
149
+ def update_text():
150
+ return app.issue, app.output
151
+
152
+
153
+ with gr.Blocks() as demo:
154
  gr.HTML("""<h1>Linear.app Webhook Server</h1>
155
  <p>This is Demo of Direct Webhook-triggered AIAgen</p>
156
+ <p>it's still just simple code,you have to reload when webhooked.</p>
157
  <p><b>Imagine an agent, responding instantly.</b></p>
158
  <p>Technically Gradio have no way to update without action<p>
159
  <p></p><br>
 
162
  <p>If you have any questions, please disable the Space or contact me before taking any action against my account. Thank you for your understanding.</p>
163
  """)
164
  with gr.Row():
165
+ with gr.Column():
166
+ gr.Markdown("## Issue")
167
+ # issue = gr.Markdown(load_text("issue.md"))
168
+ issue = gr.Markdown("issue")
169
+ with gr.Column():
170
+ gr.Markdown("## Agent advice(Don't trust them completely)")
171
+ # output = gr.Markdown(load_text("output.md"))
172
+ output = gr.Markdown("agent result")
173
+ demo.load(update_text, inputs=None, outputs=[issue, output])
174
+
175
+ # bt = gr.Button("Ask AI")
176
+ # bt.click(update, outputs=[issue_box, output_box])
177
 
178
  app = WebhooksServer(
179
+ ui=demo,
180
  webhook_secret=webhook_key, # loaded by load_api_key
181
  )
182
+
183
+ app.output = "join course"
184
+ app.issue = "how to learn smolagent"
185
 
186
 
187
  @app.add_webhook("/linear_webhook")
188
  async def updated(payload: WebhookPayload):
189
+ def generate_agent():
190
+ model = SleepPerLastTokenModelLiteLLM(
191
+ max_tokens=250,
192
+ temperature=0.5,
193
+ model_id="groq/llama3-8b-8192",
194
+ api_base="https://api.groq.com/openai/v1/",
195
+ api_key=groq_api_key,
196
+ )
197
+ agent = CodeAgent(
198
+ model=model,
199
+ tools=[], ## add your tools here (don't remove final answer)
200
+ max_steps=1,
201
+ verbosity_level=1,
202
+ grammar=None,
203
+ planning_interval=None,
204
+ name=None,
205
+ description=None,
206
+ )
207
+ return agent
208
+
209
  pprint(payload.dict(), indent=4)
210
 
211
  data = payload.dict()["data"]
 
218
 
219
  if has_label:
220
  text = data["description"]
221
+ app.issue = text
222
+ # save_text("issue.md", text)
223
+ agent = generate_agent()
224
+ result = agent.run(f"how to solve this issue:{text}")
225
+ app.output = result
226
+ # save_text("output.md", result)
227
  return {"message": "ok"}
228
 
229
 
 
243
  result = execute_query("webhook_update", webhook_update_text, api_key)
244
 
245
 
246
+ if __name__ == "__main__": # without main call twice
247
+ app.launch(webhook_update=webhook_update)
gradio_webhook_payload.py CHANGED
@@ -1,16 +1,21 @@
1
  """
2
- このコードは以下のコードをLinear.appに対応させたものです。公式のコードではありません。
 
3
  https://github.com/huggingface/huggingface_hub/blob/main/src/huggingface_hub/_webhooks_payload.py
4
 
5
- まだ、Issue Objectのごく一部しか対応してません。実際には、設定次第でissue以外のデーターも飛んでくるようになりますが、対応してません。(Unionとか使うぽい)
6
  https://studio.apollographql.com/public/Linear-API/variant/current/schema/reference/objects/Issue
7
 
8
- Issueの新規・更新・削除は確認しました。
9
 
10
- そして、変更が激しい部分なので、将来 属性が、deprecatedからremovedになりエラーが出るかもしれません。
 
11
 
 
 
 
12
 
13
- ** Linear.app 対応部分の著作権表示 **
14
  # Copyright 2025-present, Akihito Miyazaki
15
  #
16
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -25,7 +30,7 @@ Issueの新規・更新・削除は確認しました。
25
  # See the License for the specific language governing permissions and
26
  # limitations under the License.
27
 
28
- ** Hugging Face Hub ライブラリのライセンス表示 **
29
  # Copyright 2023-present, the HuggingFace Inc. team.
30
  #
31
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -40,8 +45,8 @@ Issueの新規・更新・削除は確認しました。
40
  # See the License for the specific language governing permissions and
41
  # limitations under the License.
42
 
43
- このコードには Hugging Face Hub ライブラリの一部が含まれており、Apache License, Version 2.0 の下でライセンスされています。
44
- ライセンスの全文は以下から確認できます: http://www.apache.org/licenses/LICENSE-2.0
45
  """
46
 
47
  """Contains data structures to parse the webhooks payload."""
 
1
  """
2
+ This code adapts the following code for Linear.app. It is not official code.
3
+ https://github.com/huggingface/huggingface_hub/blob/main/src/huggingface_hub/_webhooks_server.py
4
  https://github.com/huggingface/huggingface_hub/blob/main/src/huggingface_hub/_webhooks_payload.py
5
 
6
+ Currently, it only supports a small subset of the Issue Object.
7
  https://studio.apollographql.com/public/Linear-API/variant/current/schema/reference/objects/Issue
8
 
9
+ You need to set `api_key = linear-api-key` and `webhook_secret = linear-webhook-secret` in your `.env` file.
10
 
11
+ Since the local URL changes with each startup, the URL is updated in the Linear API at startup.
12
+ At startup, it overwrites the webhook with the label specified by `target_webhook_label` (default value: Gradio).
13
 
14
+ You need to pre-install gradio, fastapi, and pydantic.
15
+ You need to describe your Linear API key and Linear webhook secret in the `.env` file.
16
+ Also, since this example is only for Update, please create a Webhook with the label Gradio beforehand.
17
 
18
+ ** Copyright Notice for Linear.app Adaptation **
19
  # Copyright 2025-present, Akihito Miyazaki
20
  #
21
  # Licensed under the Apache License, Version 2.0 (the "License");
 
30
  # See the License for the specific language governing permissions and
31
  # limitations under the License.
32
 
33
+ ** License Notice for Hugging Face Hub Library **
34
  # Copyright 2023-present, the HuggingFace Inc. team.
35
  #
36
  # Licensed under the Apache License, Version 2.0 (the "License");
 
45
  # See the License for the specific language governing permissions and
46
  # limitations under the License.
47
 
48
+ This code includes parts of the Hugging Face Hub library, which is licensed under the Apache License, Version 2.0.
49
+ The full text of the license can be found at: http://www.apache.org/licenses/LICENSE-2.0
50
  """
51
 
52
  """Contains data structures to parse the webhooks payload."""
gradio_webhook_server.py CHANGED
@@ -1,14 +1,21 @@
1
  """
2
- このコードは以下のコードをLinear.appに対応させたものです。公式のコードではありません。
3
- Local Gradioの動作のみ確認・Spaceでのテストはまだしていません。
4
- 特に verify_signature 関数と、webhook 呼び出し時の request ヘッダーの取り扱いを変更しています。
5
  https://github.com/huggingface/huggingface_hub/blob/main/src/huggingface_hub/_webhooks_server.py
 
6
 
7
- なおリクエストのデバッグのため以下に上書きで書き出しを行っています。
8
- webhook_header.json
9
- webhook_request.json
10
 
11
- ** Linear.app 対応部分の著作権表示 **
 
 
 
 
 
 
 
 
 
12
  # Copyright 2025-present, Akihito Miyazaki
13
  #
14
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -23,7 +30,7 @@ webhook_request.json
23
  # See the License for the specific language governing permissions and
24
  # limitations under the License.
25
 
26
- ** Hugging Face Hub ライブラリのライセンス表示 **
27
  # Copyright 2023-present, the HuggingFace Inc. team.
28
  #
29
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -38,8 +45,8 @@ webhook_request.json
38
  # See the License for the specific language governing permissions and
39
  # limitations under the License.
40
 
41
- このコードには Hugging Face Hub ライブラリの一部が含まれており、Apache License, Version 2.0 の下でライセンスされています。
42
- ライセンスの全文は以下から確認できます: http://www.apache.org/licenses/LICENSE-2.0
43
  """
44
 
45
  """Contains `WebhooksServer` and `webhook_endpoint` to create a webhook server easily."""
@@ -58,46 +65,46 @@ from fastapi.encoders import jsonable_encoder
58
 
59
  def verify_signature(request_headers, payload, webhook_secret):
60
  """
61
- リクエストヘッダーの署名と、ペイロードから生成した署名を比較して、リクエストが有効かどうかを検証します。
62
 
63
  Args:
64
- request_headers: リクエストヘッダー (dict-like object, e.g., `request.headers`)
65
- payload: リクエストのペイロード (bytes or string)
66
- webhook_secret: ウェブフックのシークレットキー (string)
67
 
68
  Returns:
69
- True: 署名が一致する場合 (リクエストは有効)
70
- False: 署名が一致しない場合 (リクエストは無効)
71
  """
72
 
73
- # 環境変数からWEBHOOK_SECRETを取得(Netlify.env.get('WEBHOOK_SECRET')の代替)
74
- # 例:webhook_secret = os.environ.get('WEBHOOK_SECRET')
75
- # 実際の環境変数名に合わせてください
 
76
 
77
- # ペイロードが文字列ならバイト列に変換
78
  if isinstance(payload, str):
79
  payload = payload.encode("utf-8")
80
 
81
  # HMAC署名を生成
82
  signature = hmac.new(
83
- webhook_secret.encode("utf-8"), # シークレットキーはバイト列にする
84
  payload,
85
  hashlib.sha256,
86
  ).hexdigest()
87
 
88
- # リクエストヘッダーから署名を取得
89
- # ヘッダー名は大文字小文字を区別しない場合があるので注意
90
  linear_signature = request_headers.get("linear-signature")
91
  if not linear_signature:
92
  linear_signature = request_headers.get(
93
  "Linear-Signature"
94
- ) # ヘッダー名の大文字小文字のバリエーションもチェック
95
  if not linear_signature:
96
  print("Error: linear-signature header not found")
97
  return False
98
 
99
- # 署名を比較
100
- return hmac.compare_digest(signature, linear_signature) # 脆弱性対策
101
 
102
 
103
  # from .utils import experimental, is_fastapi_available, is_gradio_available
@@ -466,7 +473,7 @@ def _wrap_webhook_to_check_secret(func: Callable, webhook_secret: str) -> Callab
466
 
467
  data = json.loads(request_text.decode())
468
 
469
- # ファイルに保存(インデント付き)
470
  with open("webhook_header.json", "w", encoding="utf-8") as f:
471
  serialized = jsonable_encoder(request.headers)
472
  json.dump(serialized, f, indent=2, ensure_ascii=False)
 
1
  """
2
+ This code adapts the following code for Linear.app. It is not official code.
 
 
3
  https://github.com/huggingface/huggingface_hub/blob/main/src/huggingface_hub/_webhooks_server.py
4
+ https://github.com/huggingface/huggingface_hub/blob/main/src/huggingface_hub/_webhooks_payload.py
5
 
6
+ Currently, it only supports a small subset of the Issue Object.
7
+ https://studio.apollographql.com/public/Linear-API/variant/current/schema/reference/objects/Issue
 
8
 
9
+ You need to set `api_key = linear-api-key` and `webhook_secret = linear-webhook-secret` in your `.env` file.
10
+
11
+ Since the local URL changes with each startup, the URL is updated in the Linear API at startup.
12
+ At startup, it overwrites the webhook with the label specified by `target_webhook_label` (default value: Gradio).
13
+
14
+ You need to pre-install gradio, fastapi, and pydantic.
15
+ You need to describe your Linear API key and Linear webhook secret in the `.env` file.
16
+ Also, since this example is only for Update, please create a Webhook with the label Gradio beforehand.
17
+
18
+ ** Copyright Notice for Linear.app Adaptation **
19
  # Copyright 2025-present, Akihito Miyazaki
20
  #
21
  # Licensed under the Apache License, Version 2.0 (the "License");
 
30
  # See the License for the specific language governing permissions and
31
  # limitations under the License.
32
 
33
+ ** License Notice for Hugging Face Hub Library **
34
  # Copyright 2023-present, the HuggingFace Inc. team.
35
  #
36
  # Licensed under the Apache License, Version 2.0 (the "License");
 
45
  # See the License for the specific language governing permissions and
46
  # limitations under the License.
47
 
48
+ This code includes parts of the Hugging Face Hub library, which is licensed under the Apache License, Version 2.0.
49
+ The full text of the license can be found at: http://www.apache.org/licenses/LICENSE-2.0
50
  """
51
 
52
  """Contains `WebhooksServer` and `webhook_endpoint` to create a webhook server easily."""
 
65
 
66
  def verify_signature(request_headers, payload, webhook_secret):
67
  """
68
+ Verifies if the request is valid by comparing the signature in the request header with the signature generated from the payload.
69
 
70
  Args:
71
+ request_headers: Request headers (dict-like object, e.g., `request.headers`)
72
+ payload: Request payload (bytes or string)
73
+ webhook_secret: Webhook secret key (string)
74
 
75
  Returns:
76
+ True: If the signatures match (request is valid)
77
+ False: If the signatures do not match (request is invalid)
78
  """
79
 
80
+ # Retrieve WEBHOOK_SECRET from environment variables
81
+ # (Alternative to Netlify.env.get('WEBHOOK_SECRET'))
82
+ # e.g., webhook_secret = os.environ.get('WEBHOOK_SECRET')
83
+ # Please adjust to match your actual environment variable name
84
 
85
+ # If the payload is a string, convert it to bytes
86
  if isinstance(payload, str):
87
  payload = payload.encode("utf-8")
88
 
89
  # HMAC署名を生成
90
  signature = hmac.new(
91
+ webhook_secret.encode("utf-8"),
92
  payload,
93
  hashlib.sha256,
94
  ).hexdigest()
95
 
96
+ # Retrieve the signature from the request headers
97
+ # Note that header names might be case-insensitive
98
  linear_signature = request_headers.get("linear-signature")
99
  if not linear_signature:
100
  linear_signature = request_headers.get(
101
  "Linear-Signature"
102
+ ) # Check for case variations in header names.
103
  if not linear_signature:
104
  print("Error: linear-signature header not found")
105
  return False
106
 
107
+ return hmac.compare_digest(signature, linear_signature)
 
108
 
109
 
110
  # from .utils import experimental, is_fastapi_available, is_gradio_available
 
473
 
474
  data = json.loads(request_text.decode())
475
 
476
+ #
477
  with open("webhook_header.json", "w", encoding="utf-8") as f:
478
  serialized = jsonable_encoder(request.headers)
479
  json.dump(serialized, f, indent=2, ensure_ascii=False)
linear_api_utils.py CHANGED
@@ -24,7 +24,7 @@ import json
24
  import os
25
  import time
26
 
27
- # from dotenv import load_dotenv
28
  from pprint import pprint
29
  import requests
30
 
@@ -56,6 +56,8 @@ def request_linear(
56
 
57
  def load_api_key(dir="./"):
58
  print(f"{dir}.env")
 
 
59
  load_dotenv(dotenv_path=f"{dir}.env")
60
  if "api_key" in os.environ:
61
  api_key = os.environ["api_key"]
 
24
  import os
25
  import time
26
 
27
+ #
28
  from pprint import pprint
29
  import requests
30
 
 
56
 
57
  def load_api_key(dir="./"):
58
  print(f"{dir}.env")
59
+ from dotenv import load_dotenv
60
+
61
  load_dotenv(dotenv_path=f"{dir}.env")
62
  if "api_key" in os.environ:
63
  api_key = os.environ["api_key"]