Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f81c469f17 | ||
|
|
a398013ecb | ||
|
|
53d9717d90 | ||
|
|
5885b691b9 | ||
|
|
fd70b9b057 | ||
|
|
de13ccb757 | ||
|
|
7e1abb7bbf | ||
|
|
83afcb9c42 | ||
|
|
afb406c5ff | ||
|
|
36cf9b9922 | ||
|
|
0d21164255 | ||
|
|
3ad6f84adb | ||
|
|
24a5b16af8 | ||
|
|
b4171aa8e8 | ||
|
|
d7a79733ea | ||
|
|
34e5b9bdb0 | ||
|
|
d32ec9bd52 | ||
|
|
89fcfcc50b | ||
|
|
9a6fd6a5ee | ||
|
|
f144a0384d | ||
|
|
a67920a25e | ||
|
|
67f894e5d0 | ||
|
|
fc1eda5c77 | ||
|
|
371fddc820 | ||
|
|
8e89c38480 | ||
|
|
b732b4caeb | ||
|
|
1940d1cf87 | ||
|
|
1f0ed24402 | ||
|
|
133da0f448 | ||
|
|
f93e1e5c92 | ||
|
|
ae4af54c7d | ||
|
|
9d30bc692c | ||
|
|
44b63dc259 | ||
|
|
de2b4f6538 | ||
|
|
b6b82aa847 | ||
|
|
2d35b78333 | ||
|
|
c7dfbbeed0 | ||
|
|
b946fd21b1 | ||
|
|
daa0ca40f2 | ||
|
|
5b27130d60 | ||
|
|
ee1eb35269 | ||
|
|
4dda7cc6a4 | ||
|
|
cc590364e9 | ||
|
|
f14cd4a3db | ||
|
|
07645e0705 | ||
|
|
f053862018 | ||
|
|
69127aeaa0 | ||
|
|
847455383d | ||
|
|
9da95cb805 | ||
|
|
48008f91ac | ||
|
|
d8b3aa9382 | ||
|
|
ea9b5b8d76 | ||
|
|
4227b89ebc | ||
|
|
ee846235f2 | ||
|
|
9463ce8006 | ||
|
|
756fb61691 | ||
|
|
94d0a3d888 | ||
|
|
d83af721a6 | ||
|
|
0bc00bef32 | ||
|
|
98c13a965b | ||
|
|
310065bd0a | ||
|
|
34ec6cc978 | ||
|
|
5a90e5f9e2 | ||
|
|
5ee3063aab | ||
|
|
920373d252 | ||
|
|
c9155c117a | ||
|
|
28d617d867 | ||
|
|
593d0737b5 | ||
|
|
64409182ec | ||
|
|
8d4607ebd5 | ||
|
|
250393978b | ||
|
|
fec70ae9c9 | ||
|
|
ad7b4b1fcd | ||
|
|
03d5089436 | ||
|
|
9b52d33889 | ||
|
|
bc0e00cbb7 | ||
|
|
096710a8cc | ||
|
|
50bb201187 | ||
|
|
f211fc45a3 | ||
|
|
d91781c639 | ||
|
|
f3b71007d2 | ||
|
|
60dd987efd | ||
|
|
0a96d254e8 | ||
|
|
51e9979457 | ||
|
|
dfc7ac4cf0 | ||
|
|
c2950d26f0 | ||
|
|
47dfebf277 | ||
|
|
f3b5021936 | ||
|
|
7be9a84b72 | ||
|
|
78321a95e8 | ||
|
|
225adc46ba | ||
|
|
eb4b5721cd | ||
|
|
979c9ea569 | ||
|
|
c0bd29155d | ||
|
|
c5b5795636 | ||
|
|
3ed4f1078f | ||
|
|
5b1fd7e539 | ||
|
|
d18b6673e6 | ||
|
|
c93c0d402d | ||
|
|
b168bfe40d | ||
|
|
1d621260ff | ||
|
|
a63fa64dec | ||
|
|
3c282c3c37 | ||
|
|
2046f2e8e7 | ||
|
|
af684c80d4 | ||
|
|
99b72eb1ea | ||
|
|
22a6849ff8 | ||
|
|
dca3a5d80d | ||
|
|
508067ba5d | ||
|
|
b6c9df970a | ||
|
|
1f725cc3ed | ||
|
|
6c99b833e4 | ||
|
|
cd3780b7f5 | ||
|
|
a440e09cfe | ||
|
|
27c211ef86 | ||
|
|
cd528ae78f | ||
|
|
06c42093c8 | ||
|
|
0534bc0c09 | ||
|
|
4f33594b99 | ||
|
|
e3f9e7785e | ||
|
|
a20fc2dfdf | ||
|
|
2bf0e42367 | ||
|
|
10998d62b9 | ||
|
|
aee240150b | ||
|
|
cdd6e98af9 | ||
|
|
6417edf998 | ||
|
|
9a0735de76 | ||
|
|
a470859f6f | ||
|
|
f47c7c5a07 | ||
|
|
c2f57ea74d | ||
|
|
9e8fd16e6e | ||
|
|
1b17d8070b | ||
|
|
1db028dc05 | ||
|
|
b351b75156 | ||
|
|
2faa28e162 | ||
|
|
bdf77701cf | ||
|
|
889c276558 | ||
|
|
9c6192b00d | ||
|
|
d2a4a0375f | ||
|
|
aced8c95f2 | ||
|
|
1bb664869c | ||
|
|
116a006ce6 | ||
|
|
f3c2d1b6c2 | ||
|
|
71a7e8ef36 | ||
|
|
5f7ae6477b | ||
|
|
f41a54b4b0 | ||
|
|
080fce9601 | ||
|
|
b2222cc278 | ||
|
|
82509e8604 | ||
|
|
e7b6ffb314 | ||
|
|
395c41b748 | ||
|
|
a11a608760 | ||
|
|
477586835a | ||
|
|
085f4adbc3 | ||
|
|
9671872059 | ||
|
|
6378e6c06f | ||
|
|
4159db4549 | ||
|
|
79764c8c4c | ||
|
|
006cb5b36d | ||
|
|
8ce7d58e6d | ||
|
|
b622e924b6 | ||
|
|
8e80b8f2fa | ||
|
|
3fa280d218 | ||
|
|
1d58b55482 | ||
|
|
aae387f7dc | ||
|
|
60e21642a5 | ||
|
|
600b512c9c | ||
|
|
3be1f9b67e | ||
|
|
ad0f137e35 | ||
|
|
253105bcf5 | ||
|
|
bd0ba5ab88 | ||
|
|
ea993976b0 | ||
|
|
4c11ccd334 | ||
|
|
d766ca23e8 | ||
|
|
fe4589d335 | ||
|
|
6036a1d611 | ||
|
|
a8341e2b8b | ||
|
|
73115efab1 | ||
|
|
a45fa7a93c | ||
|
|
ae15c91455 | ||
|
|
52f16c496b | ||
|
|
24d9f45506 | ||
|
|
2404d70a33 | ||
|
|
26f1cc87ca | ||
|
|
860e47edea | ||
|
|
e2378f2237 | ||
|
|
9e197a5b67 | ||
|
|
5f4041c58f | ||
|
|
d56e81f02b | ||
|
|
6022d12ea2 | ||
|
|
f7ef1c286f | ||
|
|
b35c6b9fff | ||
|
|
30ec02e82d | ||
|
|
bc9522d5d8 | ||
|
|
b6e80e72f6 | ||
|
|
2ded2aa2d9 | ||
|
|
30dc0cbe58 | ||
|
|
2bd0c9c6d2 | ||
|
|
f9229889a1 | ||
|
|
eb4f55bdf6 | ||
|
|
9ee4e2e3d4 | ||
|
|
decb6ff2d3 | ||
|
|
b9de71dbfa | ||
|
|
124e355a3c | ||
|
|
095fe68786 | ||
|
|
afc67caa48 | ||
|
|
189b7f1172 | ||
|
|
cc955098cd | ||
|
|
8699e896e6 | ||
|
|
ca4cb85dcd | ||
|
|
88474e0653 | ||
|
|
5667a7ed16 | ||
|
|
2ae3231ff9 | ||
|
|
d92fc25e26 | ||
|
|
c8c0373f1d | ||
|
|
bccee29d2f | ||
|
|
ad307f7f89 | ||
|
|
eac11c0753 | ||
|
|
0e804c302c | ||
|
|
fb88cb0aa3 | ||
|
|
8fc6a25142 | ||
|
|
5079ba7ce5 | ||
|
|
19cb211b62 | ||
|
|
b2440e92e7 | ||
|
|
125624489b | ||
|
|
991f85c907 | ||
|
|
c00fbbdcae | ||
|
|
d4e9c60af7 | ||
|
|
0691815c0a | ||
|
|
985fd4d9a8 | ||
|
|
87fa8dc70c | ||
|
|
a782e3dac2 | ||
|
|
70da3a9399 | ||
|
|
1024537b47 | ||
|
|
2a4f21a694 | ||
|
|
5f61945090 | ||
|
|
41ce56494b | ||
|
|
172aeaaf14 | ||
|
|
bd69c5aca8 | ||
|
|
6a7eeb39c3 | ||
|
|
35a608cd53 | ||
|
|
6e19200fca | ||
|
|
fe45a76c55 | ||
|
|
bdac22cb07 | ||
|
|
5a507023a6 | ||
|
|
c398485213 | ||
|
|
bc9ff7e99f | ||
|
|
7447460b5a | ||
|
|
5345c828ca | ||
|
|
edeaab321a | ||
|
|
478ead6a05 | ||
|
|
468201190e | ||
|
|
cc0d460904 | ||
|
|
f8ab0de0ad | ||
|
|
322363f11b | ||
|
|
acd33c2fc5 | ||
|
|
b6fba03a7d | ||
|
|
fbced21b8e | ||
|
|
e7cb5d8345 | ||
|
|
918739057d | ||
|
|
e3a7096e44 | ||
|
|
c148f10bbd | ||
|
|
06495ea964 | ||
|
|
b64cecb079 | ||
|
|
e10bb58cb3 | ||
|
|
89167ae387 | ||
|
|
4b429029df | ||
|
|
c7e5d29109 | ||
|
|
a564267b29 | ||
|
|
594bdb43c2 | ||
|
|
ea66c02633 | ||
|
|
925ce6503e | ||
|
|
8a28d34fe9 | ||
|
|
8bea479df9 | ||
|
|
468d919a92 | ||
|
|
7e5527379d | ||
|
|
fcbc78180b | ||
|
|
bab1ca54e4 | ||
|
|
d644e0b8a7 | ||
|
|
e54ec45002 | ||
|
|
4b94d98f89 | ||
|
|
d0043a4a78 | ||
|
|
26ebf85b0e | ||
|
|
53481f9790 | ||
|
|
eadc2a8535 | ||
|
|
00a5ec5bd2 | ||
|
|
0b6b9062d9 | ||
|
|
c450549d0f | ||
|
|
1ba0155943 | ||
|
|
3d332a06b5 | ||
|
|
f709e0b48b | ||
|
|
57e1bffbd5 | ||
|
|
f321661b4c | ||
|
|
7ecdc1b5d8 | ||
|
|
39917a35ce | ||
|
|
bfe3f03e03 | ||
|
|
d01af65dbc | ||
|
|
80305813f5 | ||
|
|
061877e275 | ||
|
|
05e6c3d8a0 | ||
|
|
093fbca711 | ||
|
|
81deea855f | ||
|
|
5c67bebf86 | ||
|
|
9cc1f2884f | ||
|
|
f2b547cc45 | ||
|
|
70310a37b3 | ||
|
|
eb7f4e20df | ||
|
|
ea21bfd3c6 | ||
|
|
22d5be9bf8 | ||
|
|
1c878c662b | ||
|
|
55d154d4ac | ||
|
|
f5c7a94abe | ||
|
|
7ec3900208 | ||
|
|
5d95846df1 | ||
|
|
d47feb9969 | ||
|
|
8f135d13e3 | ||
|
|
f9ab4102f6 | ||
|
|
f9117bcc7f | ||
|
|
6e712f9faf | ||
|
|
b207ed2b7b | ||
|
|
945de4eddc | ||
|
|
cd655177d9 | ||
|
|
8f90497fc4 | ||
|
|
9659efca46 | ||
|
|
d0377a95cf | ||
|
|
3b20bf6d4f | ||
|
|
c3e52580b0 | ||
|
|
2badfcdcf4 | ||
|
|
f589fc2327 | ||
|
|
d3b6545e7c | ||
|
|
3f911b22b0 | ||
|
|
5199141369 | ||
|
|
d86d3e7ea1 | ||
|
|
fe8d29cb2b | ||
|
|
edd6198999 | ||
|
|
c3b2c27997 | ||
|
|
679aeb29f0 | ||
|
|
190413580f | ||
|
|
8c9fbc7717 | ||
|
|
9d3fdda674 | ||
|
|
449994f120 | ||
|
|
d772fff776 | ||
|
|
71b43fd02e | ||
|
|
f40b91ab7a | ||
|
|
6404bd006d | ||
|
|
75157e515c | ||
|
|
5a96ee8e1b | ||
|
|
ee6ceb4c64 | ||
|
|
9d53628e19 | ||
|
|
869b476145 | ||
|
|
223d487787 | ||
|
|
5ead6d7dd5 | ||
|
|
a98454217f | ||
|
|
cbb75d8577 | ||
|
|
4ab992a9a9 | ||
|
|
80b0a93d64 | ||
|
|
e749d48534 | ||
|
|
d7e873f807 | ||
|
|
c23510346b | ||
|
|
f9c5df05a1 | ||
|
|
02b4d1e2fc | ||
|
|
0b36eb8760 | ||
|
|
36bec9948c | ||
|
|
2db73c39df | ||
|
|
6107666d04 | ||
|
|
cc2bd7141f | ||
|
|
ee442975df | ||
|
|
9b1a508657 | ||
|
|
288c977596 | ||
|
|
6b799b304c | ||
|
|
92c126d875 | ||
|
|
7123fbeb47 | ||
|
|
84bb692193 | ||
|
|
079095d7a9 | ||
|
|
28e1d67ea4 | ||
|
|
c1940d1d2c | ||
|
|
869f629c14 | ||
|
|
a55943e469 | ||
|
|
84d95a0d2a | ||
|
|
7dfed8ca35 | ||
|
|
38ea0fc051 | ||
|
|
9223b6ed8f | ||
|
|
f8528c52d9 | ||
|
|
d63ce40af2 | ||
|
|
5acdd70587 | ||
|
|
b04df6c0d2 | ||
|
|
f1cbdf441c | ||
|
|
9420d80b73 | ||
|
|
c21161b75e | ||
|
|
aaff066457 | ||
|
|
c7fbf9de44 | ||
|
|
d88c17dad0 | ||
|
|
f57c3f7cf6 | ||
|
|
2460108223 | ||
|
|
84e8eea52e | ||
|
|
9efc2eaf2e | ||
|
|
37e2644452 | ||
|
|
22a78cf13f | ||
|
|
2e9806b320 | ||
|
|
ba839d4446 | ||
|
|
2bec21d81d | ||
|
|
e5271f3d1a | ||
|
|
1edb23c2c7 | ||
|
|
b1e6b9c7c9 | ||
|
|
20cb5a7c56 | ||
|
|
b1d44482bc | ||
|
|
e11102c9df | ||
|
|
7be9dc8e49 | ||
|
|
824e035815 | ||
|
|
d652b94a14 | ||
|
|
ebef2ea2d0 | ||
|
|
b5b8a0555d | ||
|
|
ae6154e1c3 | ||
|
|
0e19ca21ed | ||
|
|
baaff81a06 | ||
|
|
ffa5689885 | ||
|
|
0e409842e8 | ||
|
|
5a7a725787 | ||
|
|
f277512938 | ||
|
|
4ceabdffa0 | ||
|
|
c87480cf93 | ||
|
|
0df6fc1226 | ||
|
|
32ba2e02aa | ||
|
|
1ffc8be2b6 | ||
|
|
5f2945ae71 | ||
|
|
65baf76df6 | ||
|
|
3b6c0ec0b3 | ||
|
|
e9d902d844 | ||
|
|
e8b4f593a6 | ||
|
|
fc4f281408 | ||
|
|
f8c4f713a5 | ||
|
|
63c8874d2d | ||
|
|
71076d5c68 | ||
|
|
0319043b49 | ||
|
|
e0334d5569 | ||
|
|
ff6a93f355 | ||
|
|
733b21e22b | ||
|
|
3c3d6b65c2 | ||
|
|
9ca48d3a39 | ||
|
|
16f9edc1a0 | ||
|
|
8c2aec43b8 | ||
|
|
2564801bde | ||
|
|
7c99a03493 | ||
|
|
0e0460f6c0 | ||
|
|
8acd537d1d | ||
|
|
40c206c2f9 | ||
|
|
259c722208 | ||
|
|
e618cbc447 | ||
|
|
abd99aeb7d | ||
|
|
ad5fc76b11 | ||
|
|
ff1f4d6bf9 | ||
|
|
170ea9c32b | ||
|
|
65ced67432 | ||
|
|
9f46068c57 | ||
|
|
479cf2fa4f | ||
|
|
39c54f367f | ||
|
|
8c71107a93 | ||
|
|
ef10097329 | ||
|
|
36ee4b5ede | ||
|
|
ae84d5a734 | ||
|
|
cd53770734 | ||
|
|
4b1eca73eb | ||
|
|
fffcf69cd4 | ||
|
|
d4c01f858b | ||
|
|
8e17570c53 | ||
|
|
7f9d08b556 | ||
|
|
32a045f60b | ||
|
|
91adc3cd41 | ||
|
|
2bb9b4212f | ||
|
|
3472a50928 | ||
|
|
3aeac02bf1 | ||
|
|
52fcdcc37b | ||
|
|
78d6b3a963 | ||
|
|
15df2710fa | ||
|
|
02e492f6eb | ||
|
|
2d5bd26a59 | ||
|
|
8f58fef5ad | ||
|
|
14cb2d2af6 | ||
|
|
52fb571739 | ||
|
|
51c647ca89 | ||
|
|
52fa7840c2 | ||
|
|
2c61b39088 | ||
|
|
6c02d4ce66 | ||
|
|
11154ba697 | ||
|
|
f8ca524bf7 | ||
|
|
56222fff3c | ||
|
|
74f9fcea88 | ||
|
|
bc213e1a61 | ||
|
|
d795a38fc7 | ||
|
|
96698ea070 | ||
|
|
6fff10b670 | ||
|
|
194aea8e54 | ||
|
|
b6d2046b0e | ||
|
|
910ea84360 | ||
|
|
bc2e4e23c9 | ||
|
|
f5e75606e3 | ||
|
|
0707890359 | ||
|
|
6dbba8e326 | ||
|
|
413c9d9ad1 | ||
|
|
5e6dd312eb | ||
|
|
7218a662ab | ||
|
|
d947df3069 | ||
|
|
5bb1f5f0a0 | ||
|
|
d38594d34a | ||
|
|
925284c6c1 | ||
|
|
e716271466 | ||
|
|
df046e5e04 | ||
|
|
e0bfbcb663 | ||
|
|
c1c6aca31e | ||
|
|
725104572e | ||
|
|
4954edf8ae | ||
|
|
c1b4e1f19d | ||
|
|
89d820b1c4 | ||
|
|
e3e459fc50 | ||
|
|
4bf0541bd6 | ||
|
|
c81624aef7 | ||
|
|
df61aa801b | ||
|
|
6af0c2ec21 | ||
|
|
ce9d2ee04f | ||
|
|
4b30705c42 | ||
|
|
1f8d396b76 | ||
|
|
3752bb9717 | ||
|
|
16d66c209d | ||
|
|
6506e48c54 | ||
|
|
f0e8b7c29b | ||
|
|
a00b49d65b | ||
|
|
b1589be4ba | ||
|
|
eb24d2f847 | ||
|
|
9bb25a9260 | ||
|
|
535230dce4 | ||
|
|
555fb53505 | ||
|
|
b1e0a23351 | ||
|
|
2b69bcccdf | ||
|
|
e03f27381f | ||
|
|
aebd50da7e | ||
|
|
c02f58c2af | ||
|
|
c8f4d54f7f | ||
|
|
4983d255dd | ||
|
|
f2b4891ff0 | ||
|
|
efcb5abbf7 | ||
|
|
d37e58719e | ||
|
|
c6c153de95 | ||
|
|
417e8f619c | ||
|
|
f2094b7bb3 | ||
|
|
176dc51b2e | ||
|
|
f7d9a031e6 | ||
|
|
3e2478ebf9 | ||
|
|
1f4e8b4954 | ||
|
|
9a346a00fb | ||
|
|
0a13820927 | ||
|
|
c5fa3ee9f8 | ||
|
|
c294a18155 | ||
|
|
c3dc6d6df6 | ||
|
|
ef3425a177 | ||
|
|
0290b4aaf0 | ||
|
|
4ceee53480 | ||
|
|
469dc9095f | ||
|
|
661d50f95f | ||
|
|
3978a8e636 | ||
|
|
983e3b2ee3 | ||
|
|
1bd198eb34 | ||
|
|
3c502861a7 | ||
|
|
a52b352b24 | ||
|
|
79c73267cf | ||
|
|
54f7fb5019 | ||
|
|
dd97d784b6 | ||
|
|
91832bd5d7 | ||
|
|
3abca8fd4b | ||
|
|
f5b3992479 | ||
|
|
53f1f16122 | ||
|
|
4614e4983e | ||
|
|
84f0c63fa1 | ||
|
|
3e9b451fb4 | ||
|
|
4ccf683527 | ||
|
|
b236ca9047 | ||
|
|
aa9ebe5d7c | ||
|
|
4c94753eda | ||
|
|
c3a55c35bb | ||
|
|
d5275010d5 | ||
|
|
dedfa563c2 | ||
|
|
37b6a55eb1 | ||
|
|
7aa57accf5 | ||
|
|
c2fa28c1be | ||
|
|
30aae66320 | ||
|
|
7b95190df3 | ||
|
|
fa3e7bb9b0 | ||
|
|
5b56848c3d | ||
|
|
780e532094 | ||
|
|
29310957c8 | ||
|
|
2b0577c725 | ||
|
|
bcd656ffae | ||
|
|
0e0c5a9b68 | ||
|
|
d36fcc4f8e | ||
|
|
ea82b60d7d | ||
|
|
ea0285a96c | ||
|
|
6960408ca2 | ||
|
|
fa36195492 | ||
|
|
a6265ea3d2 | ||
|
|
bb3f02b8bb | ||
|
|
bdc0f7c86d | ||
|
|
c8ca036834 | ||
|
|
8c7fee7840 | ||
|
|
e53fb7f8ed | ||
|
|
b05cbc9101 | ||
|
|
38e8c42cf0 | ||
|
|
58fe884327 | ||
|
|
e69d10b6c9 | ||
|
|
10aee9755c | ||
|
|
63384bc214 | ||
|
|
2508e06c58 | ||
|
|
6487d0607b | ||
|
|
a3513244f1 | ||
|
|
32b47fcc1e | ||
|
|
fde03d3c93 | ||
|
|
9045f13acc | ||
|
|
74f0edc7a8 | ||
|
|
dcabafcdce | ||
|
|
02e8242c3b | ||
|
|
57e26bd2fe | ||
|
|
0f263bfefe | ||
|
|
34a33dfc16 | ||
|
|
162a789fa2 | ||
|
|
198a753b62 | ||
|
|
ab3c22b77a | ||
|
|
f0f6e9cad7 | ||
|
|
bbaae459c6 | ||
|
|
eb3c820fb8 | ||
|
|
3468808fc6 | ||
|
|
cd42503e2c | ||
|
|
1cea8b9e77 | ||
|
|
d8fd7b155f | ||
|
|
248a644fb0 | ||
|
|
c8ff81bae4 | ||
|
|
74469a0d3d | ||
|
|
4d481dea7e | ||
|
|
7df32eac2a | ||
|
|
4654fb88de | ||
|
|
e915a3720e | ||
|
|
93c2f5060e | ||
|
|
564143071e | ||
|
|
3cdfc529a0 | ||
|
|
bffe547417 | ||
|
|
dc99005e65 | ||
|
|
8ffedbe157 | ||
|
|
900fe5ca04 | ||
|
|
66a5d58221 | ||
|
|
48328bec6e | ||
|
|
8426a0d595 | ||
|
|
9186c3feae | ||
|
|
f171250033 | ||
|
|
d440ba32ab | ||
|
|
f7ab6beaf3 | ||
|
|
85ac243752 | ||
|
|
03522471a1 | ||
|
|
42b440be0c | ||
|
|
133ae42c55 | ||
|
|
e001af2709 | ||
|
|
a97612287f | ||
|
|
d13467d869 | ||
|
|
368bd97952 | ||
|
|
d0104278fa | ||
|
|
222244719b | ||
|
|
21008d733f | ||
|
|
2808e95ac7 | ||
|
|
70e0d71ac2 | ||
|
|
93f507d330 | ||
|
|
7119ace940 | ||
|
|
4e24e04aec | ||
|
|
469a83a55b | ||
|
|
e8f54b9b38 | ||
|
|
01b18456a3 | ||
|
|
1acd5445b5 | ||
|
|
ff89305ebe | ||
|
|
605f78944d | ||
|
|
9f7e14dc7e | ||
|
|
cb7f3cf2f1 | ||
|
|
4406096974 | ||
|
|
fefaad6226 | ||
|
|
3f9b569575 | ||
|
|
1e4f5710aa | ||
|
|
48a79b1173 | ||
|
|
59e550271d | ||
|
|
afebe920b2 | ||
|
|
6921702605 | ||
|
|
9c6783e88e | ||
|
|
f1a60a0a93 | ||
|
|
4b9cae82e6 | ||
|
|
fdf08ecfab | ||
|
|
22f5c26eec | ||
|
|
b6de122ddc | ||
|
|
0f8cb69bff | ||
|
|
fca2bddc3b | ||
|
|
f65e20b8ce | ||
|
|
93f2805bc2 | ||
|
|
9ad4dc9296 | ||
|
|
23af974bd3 | ||
|
|
36ea46ee67 | ||
|
|
4d2cc9d858 | ||
|
|
610ffbdd61 | ||
|
|
854f9227a2 | ||
|
|
8d368fdfd2 | ||
|
|
1c31c2dd97 | ||
|
|
c1d754bec9 | ||
|
|
c67b721787 | ||
|
|
11e41e7564 | ||
|
|
afd42bf46d | ||
|
|
f740663ded | ||
|
|
751b81af34 | ||
|
|
b725bcd2cd | ||
|
|
c278e16e4e | ||
|
|
4e629c5b64 | ||
|
|
4624f0a260 | ||
|
|
2e16d685eb | ||
|
|
e544cccc70 | ||
|
|
c141b88087 | ||
|
|
023c4532c1 | ||
|
|
042802848d | ||
|
|
a8aa44bd3f | ||
|
|
db2a3a171e | ||
|
|
38a4bee1be | ||
|
|
8952b3d246 | ||
|
|
d6350a7fa6 | ||
|
|
ae83138832 | ||
|
|
3ee4280dfa | ||
|
|
26fbf9e647 | ||
|
|
97a41062c9 | ||
|
|
4a76224268 | ||
|
|
810c9cff1d | ||
|
|
47d4c87bdd | ||
|
|
a9875c5531 | ||
|
|
4c261ab1db | ||
|
|
2fc8263032 | ||
|
|
a431b8922c | ||
|
|
0a01d20850 | ||
|
|
7b62c10553 | ||
|
|
61c7196bd9 | ||
|
|
365fdd9ff8 | ||
|
|
f6bc9238df | ||
|
|
26f75d4e68 | ||
|
|
8ba8d3c7e3 | ||
|
|
f993541e0b | ||
|
|
e2df3eb44d | ||
|
|
38f9ce05f6 | ||
|
|
a6e09363b8 | ||
|
|
49629bb58e | ||
|
|
2bb5b9b13a | ||
|
|
41338d1bf9 | ||
|
|
41ee9c94c7 | ||
|
|
9c16db0f36 | ||
|
|
721869353b | ||
|
|
6d22ade771 | ||
|
|
fbcceeb781 | ||
|
|
95775d68b7 | ||
|
|
cf11669618 | ||
|
|
65dc19e85a | ||
|
|
cfcfceca6d | ||
|
|
9f8899a9f9 | ||
|
|
449a063fe2 | ||
|
|
37530359ee | ||
|
|
65f0bea146 | ||
|
|
e4cc05a975 | ||
|
|
029612d8d5 | ||
|
|
e9826e8a22 | ||
|
|
ad5f209dc8 | ||
|
|
fcfeac57c5 | ||
|
|
2946898934 | ||
|
|
b4d95545e0 | ||
|
|
d3bbaa141c | ||
|
|
8714f23509 |
@@ -0,0 +1,20 @@
|
||||
name: "Setup Bun"
|
||||
description: "Setup Bun with caching and install dependencies"
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
|
||||
- name: Cache ~/.bun
|
||||
id: cache-bun
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.bun
|
||||
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lockb', 'bun.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-bun-
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
shell: bash
|
||||
@@ -15,13 +15,11 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: oven-sh/setup-bun@v1
|
||||
with:
|
||||
bun-version: 1.2.19
|
||||
|
||||
- run: bun install
|
||||
- uses: ./.github/actions/setup-bun
|
||||
|
||||
- run: bun sst deploy --stage=${{ github.ref_name }}
|
||||
env:
|
||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
PLANETSCALE_SERVICE_TOKEN_NAME: ${{ secrets.PLANETSCALE_SERVICE_TOKEN_NAME }}
|
||||
PLANETSCALE_SERVICE_TOKEN: ${{ secrets.PLANETSCALE_SERVICE_TOKEN }}
|
||||
STRIPE_SECRET_KEY: ${{ github.ref_name == 'production' && secrets.STRIPE_SECRET_KEY_PROD || secrets.STRIPE_SECRET_KEY_DEV }}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
name: format
|
||||
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- production
|
||||
pull_request:
|
||||
branches-ignore:
|
||||
- production
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
format:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Setup Bun
|
||||
uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: run
|
||||
run: |
|
||||
./script/format.ts
|
||||
env:
|
||||
CI: true
|
||||
@@ -2,7 +2,7 @@ name: discord
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published] # fires only when a release is published
|
||||
types: [published] # fires only when a release is published
|
||||
|
||||
jobs:
|
||||
notify:
|
||||
|
||||
@@ -24,4 +24,4 @@ jobs:
|
||||
env:
|
||||
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
||||
with:
|
||||
model: opencode/sonic
|
||||
model: opencode/sonic
|
||||
|
||||
@@ -19,16 +19,13 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.2.19
|
||||
- uses: ./.github/actions/setup-bun
|
||||
|
||||
- run: git fetch --force --tags
|
||||
- run: bun install -g @vscode/vsce
|
||||
|
||||
- name: Publish
|
||||
run: |
|
||||
bun install
|
||||
./script/publish
|
||||
working-directory: ./sdks/vscode
|
||||
env:
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
name: publish
|
||||
run-name: "${{ format('v{0}', inputs.version) }}"
|
||||
run-name: "${{ format('release {0}', inputs.bump) }}"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: "Version to publish"
|
||||
bump:
|
||||
description: "Bump major, minor, or patch"
|
||||
required: true
|
||||
type: string
|
||||
title:
|
||||
description: "Custom title for this run"
|
||||
required: false
|
||||
type: string
|
||||
type: choice
|
||||
options:
|
||||
- major
|
||||
- minor
|
||||
- patch
|
||||
|
||||
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
||||
@@ -35,18 +35,7 @@ jobs:
|
||||
cache: true
|
||||
cache-dependency-path: go.sum
|
||||
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.2.19
|
||||
|
||||
- name: Cache ~/.bun
|
||||
id: cache-bun
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.bun
|
||||
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-bun-
|
||||
- uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Install makepkg
|
||||
run: |
|
||||
@@ -60,13 +49,16 @@ jobs:
|
||||
git config --global user.email "opencode@sst.dev"
|
||||
git config --global user.name "opencode"
|
||||
ssh-keyscan -H aur.archlinux.org >> ~/.ssh/known_hosts || true
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
|
||||
- name: Install OpenCode
|
||||
run: curl -fsSL https://opencode.ai/install | bash
|
||||
|
||||
- name: Publish
|
||||
run: |
|
||||
OPENCODE_VERSION=${{ inputs.version }} ./script/publish.ts
|
||||
./script/publish.ts
|
||||
env:
|
||||
OPENCODE_BUMP: ${{ inputs.bump }}
|
||||
GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }}
|
||||
AUR_KEY: ${{ secrets.AUR_KEY }}
|
||||
NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
name: snapshot
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
- opentui
|
||||
|
||||
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- run: git fetch --force --tags
|
||||
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ">=1.24.0"
|
||||
cache: true
|
||||
cache-dependency-path: go.sum
|
||||
|
||||
- uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Publish
|
||||
run: |
|
||||
./packages/opencode/script/publish.ts
|
||||
env:
|
||||
OPENCODE_SNAPSHOT: true
|
||||
OPENCODE_TAG: ${{ github.ref_name }}
|
||||
GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }}
|
||||
NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
@@ -16,9 +16,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Run stats script
|
||||
run: bun script/stats.ts
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
name: test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- production
|
||||
pull_request:
|
||||
branches-ignore:
|
||||
- production
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Setup Bun
|
||||
uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: run
|
||||
run: |
|
||||
git config --global user.email "bot@opencode.ai"
|
||||
git config --global user.name "opencode"
|
||||
bun turbo test
|
||||
env:
|
||||
CI: true
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Typecheck
|
||||
name: typecheck
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
@@ -13,12 +13,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v1
|
||||
with:
|
||||
bun-version: 1.2.19
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Run typecheck
|
||||
run: bun typecheck
|
||||
|
||||
@@ -8,3 +8,5 @@ node_modules
|
||||
openapi.json
|
||||
playground
|
||||
tmp
|
||||
dist
|
||||
.turbo
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
#!/bin/sh
|
||||
bun run typecheck
|
||||
@@ -7,3 +7,6 @@ core:
|
||||
ci:
|
||||
ignore:
|
||||
wip:
|
||||
|
||||
prefer to explain WHY something was done from an end user perspective instead of
|
||||
WHAT was done.
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
description: Spellcheck all markdown file changes
|
||||
---
|
||||
|
||||
Look at all the unstaged changes to markdown (.md, .mdx) files, pull out the lines that have changed, and check for spelling and grammar errors.
|
||||
@@ -14,3 +14,34 @@
|
||||
## Debugging
|
||||
|
||||
- To test opencode in the `packages/opencode` directory you can run `bun dev`
|
||||
|
||||
## Tool Calling
|
||||
|
||||
- ALWAYS USE PARALLEL TOOLS WHEN APPLICABLE. Here is an example illustrating how to execute 3 parallel file reads in this chat environnement:
|
||||
|
||||
json
|
||||
{
|
||||
"recipient_name": "multi_tool_use.parallel",
|
||||
"parameters": {
|
||||
"tool_uses": [
|
||||
{
|
||||
"recipient_name": "functions.read",
|
||||
"parameters": {
|
||||
"filePath": "path/to/file.tsx"
|
||||
}
|
||||
},
|
||||
{
|
||||
"recipient_name": "functions.read",
|
||||
"parameters": {
|
||||
"filePath": "path/to/file.ts"
|
||||
}
|
||||
},
|
||||
{
|
||||
"recipient_name": "functions.read",
|
||||
"parameters": {
|
||||
"filePath": "path/to/file.md"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
<p align="center">
|
||||
<a href="https://opencode.ai">
|
||||
<picture>
|
||||
<source srcset="packages/web/src/assets/logo-ornate-dark.svg" media="(prefers-color-scheme: dark)">
|
||||
<source srcset="packages/web/src/assets/logo-ornate-light.svg" media="(prefers-color-scheme: light)">
|
||||
<img src="packages/web/src/assets/logo-ornate-light.svg" alt="opencode logo">
|
||||
<source srcset="packages/console/app/src/asset/logo-ornate-dark.svg" media="(prefers-color-scheme: dark)">
|
||||
<source srcset="packages/console/app/src/asset/logo-ornate-light.svg" media="(prefers-color-scheme: light)">
|
||||
<img src="packages/console/app/src/asset/logo-ornate-light.svg" alt="OpenCode logo">
|
||||
</picture>
|
||||
</a>
|
||||
</p>
|
||||
<p align="center">AI coding agent, built for the terminal.</p>
|
||||
<p align="center">The AI coding agent built for the terminal.</p>
|
||||
<p align="center">
|
||||
<a href="https://opencode.ai/discord"><img alt="Discord" src="https://img.shields.io/discord/1391832426048651334?style=flat-square&label=discord" /></a>
|
||||
<a href="https://www.npmjs.com/package/opencode-ai"><img alt="npm" src="https://img.shields.io/npm/v/opencode-ai?style=flat-square" /></a>
|
||||
<a href="https://github.com/sst/opencode/actions/workflows/publish.yml"><img alt="Build status" src="https://img.shields.io/github/actions/workflow/status/sst/opencode/publish.yml?style=flat-square&branch=dev" /></a>
|
||||
</p>
|
||||
|
||||
[](https://opencode.ai)
|
||||
[](https://opencode.ai)
|
||||
|
||||
---
|
||||
|
||||
@@ -50,11 +50,11 @@ XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash
|
||||
|
||||
### Documentation
|
||||
|
||||
For more info on how to configure opencode [**head over to our docs**](https://opencode.ai/docs).
|
||||
For more info on how to configure OpenCode [**head over to our docs**](https://opencode.ai/docs).
|
||||
|
||||
### Contributing
|
||||
|
||||
opencode is an opinionated tool so any fundamental feature needs to go through a
|
||||
OpenCode is an opinionated tool so any fundamental feature needs to go through a
|
||||
design process with the core team.
|
||||
|
||||
> [!IMPORTANT]
|
||||
@@ -74,7 +74,7 @@ Take a look at the git history to see what kind of PRs we end up merging.
|
||||
> [!NOTE]
|
||||
> If you do not follow the above guidelines we might close your PR.
|
||||
|
||||
To run opencode locally you need.
|
||||
To run OpenCode locally you need.
|
||||
|
||||
- Bun
|
||||
- Golang 1.24.x
|
||||
@@ -88,7 +88,7 @@ $ bun dev
|
||||
|
||||
#### Development Notes
|
||||
|
||||
**API Client**: After making changes to the TypeScript API endpoints in `packages/opencode/src/server/server.ts`, you will need the opencode team to generate a new stainless sdk for the clients.
|
||||
**API Client**: After making changes to the TypeScript API endpoints in `packages/opencode/src/server/server.ts`, you will need the OpenCode team to generate a new stainless sdk for the clients.
|
||||
|
||||
### FAQ
|
||||
|
||||
@@ -97,9 +97,10 @@ $ bun dev
|
||||
It's very similar to Claude Code in terms of capability. Here are the key differences:
|
||||
|
||||
- 100% open source
|
||||
- Not coupled to any provider. Although Anthropic is recommended, opencode can be used with OpenAI, Google or even local models. As models evolve the gaps between them will close and pricing will drop so being provider-agnostic is important.
|
||||
- A focus on TUI. opencode is built by neovim users and the creators of [terminal.shop](https://terminal.shop); we are going to push the limits of what's possible in the terminal.
|
||||
- A client/server architecture. This for example can allow opencode to run on your computer, while you can drive it remotely from a mobile app. Meaning that the TUI frontend is just one of the possible clients.
|
||||
- Not coupled to any provider. Although Anthropic is recommended, OpenCode can be used with OpenAI, Google or even local models. As models evolve the gaps between them will close and pricing will drop so being provider-agnostic is important.
|
||||
- Out of the box LSP support
|
||||
- A focus on TUI. OpenCode is built by neovim users and the creators of [terminal.shop](https://terminal.shop); we are going to push the limits of what's possible in the terminal.
|
||||
- A client/server architecture. This for example can allow OpenCode to run on your computer, while you can drive it remotely from a mobile app. Meaning that the TUI frontend is just one of the possible clients.
|
||||
|
||||
#### What's the other repo?
|
||||
|
||||
@@ -107,4 +108,4 @@ The other confusingly named repo has no relation to this one. You can [read the
|
||||
|
||||
---
|
||||
|
||||
**Join our community** [Discord](https://discord.gg/opencode) | [YouTube](https://www.youtube.com/c/sst-dev) | [X.com](https://x.com/anomaly_inv)
|
||||
**Join our community** [Discord](https://discord.gg/opencode) | [X.com](https://x.com/opencode)
|
||||
|
||||
@@ -1,66 +1,109 @@
|
||||
# Download Stats
|
||||
|
||||
| Date | GitHub Downloads | npm Downloads | Total |
|
||||
| ---------- | ---------------- | ---------------- | ----------------- |
|
||||
| 2025-06-29 | 18,789 (+0) | 39,420 (+0) | 58,209 (+0) |
|
||||
| 2025-06-30 | 20,127 (+1,338) | 41,059 (+1,639) | 61,186 (+2,977) |
|
||||
| 2025-07-01 | 22,108 (+1,981) | 43,745 (+2,686) | 65,853 (+4,667) |
|
||||
| 2025-07-02 | 24,814 (+2,706) | 46,168 (+2,423) | 70,982 (+5,129) |
|
||||
| 2025-07-03 | 27,834 (+3,020) | 49,955 (+3,787) | 77,789 (+6,807) |
|
||||
| 2025-07-04 | 30,608 (+2,774) | 54,758 (+4,803) | 85,366 (+7,577) |
|
||||
| 2025-07-05 | 32,524 (+1,916) | 58,371 (+3,613) | 90,895 (+5,529) |
|
||||
| 2025-07-06 | 33,766 (+1,242) | 59,694 (+1,323) | 93,460 (+2,565) |
|
||||
| 2025-07-08 | 38,052 (+4,286) | 64,468 (+4,774) | 102,520 (+9,060) |
|
||||
| 2025-07-09 | 40,924 (+2,872) | 67,935 (+3,467) | 108,859 (+6,339) |
|
||||
| 2025-07-10 | 43,796 (+2,872) | 71,402 (+3,467) | 115,198 (+6,339) |
|
||||
| 2025-07-11 | 46,982 (+3,186) | 77,462 (+6,060) | 124,444 (+9,246) |
|
||||
| 2025-07-12 | 49,302 (+2,320) | 82,177 (+4,715) | 131,479 (+7,035) |
|
||||
| 2025-07-13 | 50,803 (+1,501) | 86,394 (+4,217) | 137,197 (+5,718) |
|
||||
| 2025-07-14 | 53,283 (+2,480) | 87,860 (+1,466) | 141,143 (+3,946) |
|
||||
| 2025-07-15 | 57,590 (+4,307) | 91,036 (+3,176) | 148,626 (+7,483) |
|
||||
| 2025-07-16 | 62,313 (+4,723) | 95,258 (+4,222) | 157,571 (+8,945) |
|
||||
| 2025-07-17 | 66,684 (+4,371) | 100,048 (+4,790) | 166,732 (+9,161) |
|
||||
| 2025-07-18 | 70,379 (+3,695) | 102,587 (+2,539) | 172,966 (+6,234) |
|
||||
| 2025-07-19 | 73,497 (+3,117) | 105,904 (+3,317) | 179,401 (+6,434) |
|
||||
| 2025-07-20 | 76,453 (+2,956) | 109,044 (+3,140) | 185,497 (+6,096) |
|
||||
| 2025-07-21 | 80,197 (+3,744) | 113,537 (+4,493) | 193,734 (+8,237) |
|
||||
| 2025-07-22 | 84,251 (+4,054) | 118,073 (+4,536) | 202,324 (+8,590) |
|
||||
| 2025-07-23 | 88,589 (+4,338) | 121,436 (+3,363) | 210,025 (+7,701) |
|
||||
| 2025-07-24 | 92,469 (+3,880) | 124,091 (+2,655) | 216,560 (+6,535) |
|
||||
| 2025-07-25 | 96,417 (+3,948) | 126,985 (+2,894) | 223,402 (+6,842) |
|
||||
| 2025-07-26 | 100,646 (+4,229) | 131,411 (+4,426) | 232,057 (+8,655) |
|
||||
| 2025-07-27 | 102,644 (+1,998) | 134,736 (+3,325) | 237,380 (+5,323) |
|
||||
| 2025-07-28 | 105,446 (+2,802) | 136,016 (+1,280) | 241,462 (+4,082) |
|
||||
| 2025-07-29 | 108,998 (+3,552) | 137,542 (+1,526) | 246,540 (+5,078) |
|
||||
| 2025-07-30 | 113,544 (+4,546) | 140,317 (+2,775) | 253,861 (+7,321) |
|
||||
| 2025-07-31 | 118,339 (+4,795) | 143,344 (+3,027) | 261,683 (+7,822) |
|
||||
| 2025-08-01 | 123,539 (+5,200) | 146,680 (+3,336) | 270,219 (+8,536) |
|
||||
| 2025-08-02 | 127,864 (+4,325) | 149,236 (+2,556) | 277,100 (+6,881) |
|
||||
| 2025-08-03 | 131,397 (+3,533) | 150,451 (+1,215) | 281,848 (+4,748) |
|
||||
| 2025-08-04 | 136,266 (+4,869) | 153,260 (+2,809) | 289,526 (+7,678) |
|
||||
| 2025-08-05 | 141,596 (+5,330) | 155,752 (+2,492) | 297,348 (+7,822) |
|
||||
| 2025-08-06 | 147,067 (+5,471) | 158,309 (+2,557) | 305,376 (+8,028) |
|
||||
| 2025-08-07 | 152,591 (+5,524) | 160,889 (+2,580) | 313,480 (+8,104) |
|
||||
| 2025-08-08 | 158,187 (+5,596) | 163,448 (+2,559) | 321,635 (+8,155) |
|
||||
| 2025-08-09 | 162,770 (+4,583) | 165,721 (+2,273) | 328,491 (+6,856) |
|
||||
| 2025-08-10 | 165,695 (+2,925) | 167,109 (+1,388) | 332,804 (+4,313) |
|
||||
| 2025-08-11 | 169,297 (+3,602) | 167,953 (+844) | 337,250 (+4,446) |
|
||||
| 2025-08-12 | 176,307 (+7,010) | 171,876 (+3,923) | 348,183 (+10,933) |
|
||||
| 2025-08-13 | 182,997 (+6,690) | 177,182 (+5,306) | 360,179 (+11,996) |
|
||||
| 2025-08-14 | 189,063 (+6,066) | 179,741 (+2,559) | 368,804 (+8,625) |
|
||||
| 2025-08-15 | 193,608 (+4,545) | 181,792 (+2,051) | 375,400 (+6,596) |
|
||||
| 2025-08-16 | 198,118 (+4,510) | 184,558 (+2,766) | 382,676 (+7,276) |
|
||||
| 2025-08-17 | 201,299 (+3,181) | 186,269 (+1,711) | 387,568 (+4,892) |
|
||||
| 2025-08-18 | 204,559 (+3,260) | 187,399 (+1,130) | 391,958 (+4,390) |
|
||||
| 2025-08-19 | 209,814 (+5,255) | 189,668 (+2,269) | 399,482 (+7,524) |
|
||||
| 2025-08-20 | 214,497 (+4,683) | 191,481 (+1,813) | 405,978 (+6,496) |
|
||||
| 2025-08-21 | 220,465 (+5,968) | 194,784 (+3,303) | 415,249 (+9,271) |
|
||||
| 2025-08-22 | 225,899 (+5,434) | 197,204 (+2,420) | 423,103 (+7,854) |
|
||||
| 2025-08-23 | 229,005 (+3,106) | 199,238 (+2,034) | 428,243 (+5,140) |
|
||||
| 2025-08-24 | 232,098 (+3,093) | 201,157 (+1,919) | 433,255 (+5,012) |
|
||||
| 2025-08-25 | 236,607 (+4,509) | 202,650 (+1,493) | 439,257 (+6,002) |
|
||||
| 2025-08-26 | 242,783 (+6,176) | 205,242 (+2,592) | 448,025 (+8,768) |
|
||||
| 2025-08-27 | 248,409 (+5,626) | 205,242 (+0) | 453,651 (+5,626) |
|
||||
| 2025-08-28 | 252,796 (+4,387) | 205,242 (+0) | 458,038 (+4,387) |
|
||||
| 2025-08-29 | 256,045 (+3,249) | 211,075 (+5,833) | 467,120 (+9,082) |
|
||||
| 2025-08-30 | 258,863 (+2,818) | 212,397 (+1,322) | 471,260 (+4,140) |
|
||||
| Date | GitHub Downloads | npm Downloads | Total |
|
||||
| ---------- | ---------------- | ----------------- | ----------------- |
|
||||
| 2025-06-29 | 18,789 (+0) | 39,420 (+0) | 58,209 (+0) |
|
||||
| 2025-06-30 | 20,127 (+1,338) | 41,059 (+1,639) | 61,186 (+2,977) |
|
||||
| 2025-07-01 | 22,108 (+1,981) | 43,745 (+2,686) | 65,853 (+4,667) |
|
||||
| 2025-07-02 | 24,814 (+2,706) | 46,168 (+2,423) | 70,982 (+5,129) |
|
||||
| 2025-07-03 | 27,834 (+3,020) | 49,955 (+3,787) | 77,789 (+6,807) |
|
||||
| 2025-07-04 | 30,608 (+2,774) | 54,758 (+4,803) | 85,366 (+7,577) |
|
||||
| 2025-07-05 | 32,524 (+1,916) | 58,371 (+3,613) | 90,895 (+5,529) |
|
||||
| 2025-07-06 | 33,766 (+1,242) | 59,694 (+1,323) | 93,460 (+2,565) |
|
||||
| 2025-07-08 | 38,052 (+4,286) | 64,468 (+4,774) | 102,520 (+9,060) |
|
||||
| 2025-07-09 | 40,924 (+2,872) | 67,935 (+3,467) | 108,859 (+6,339) |
|
||||
| 2025-07-10 | 43,796 (+2,872) | 71,402 (+3,467) | 115,198 (+6,339) |
|
||||
| 2025-07-11 | 46,982 (+3,186) | 77,462 (+6,060) | 124,444 (+9,246) |
|
||||
| 2025-07-12 | 49,302 (+2,320) | 82,177 (+4,715) | 131,479 (+7,035) |
|
||||
| 2025-07-13 | 50,803 (+1,501) | 86,394 (+4,217) | 137,197 (+5,718) |
|
||||
| 2025-07-14 | 53,283 (+2,480) | 87,860 (+1,466) | 141,143 (+3,946) |
|
||||
| 2025-07-15 | 57,590 (+4,307) | 91,036 (+3,176) | 148,626 (+7,483) |
|
||||
| 2025-07-16 | 62,313 (+4,723) | 95,258 (+4,222) | 157,571 (+8,945) |
|
||||
| 2025-07-17 | 66,684 (+4,371) | 100,048 (+4,790) | 166,732 (+9,161) |
|
||||
| 2025-07-18 | 70,379 (+3,695) | 102,587 (+2,539) | 172,966 (+6,234) |
|
||||
| 2025-07-19 | 73,497 (+3,117) | 105,904 (+3,317) | 179,401 (+6,434) |
|
||||
| 2025-07-20 | 76,453 (+2,956) | 109,044 (+3,140) | 185,497 (+6,096) |
|
||||
| 2025-07-21 | 80,197 (+3,744) | 113,537 (+4,493) | 193,734 (+8,237) |
|
||||
| 2025-07-22 | 84,251 (+4,054) | 118,073 (+4,536) | 202,324 (+8,590) |
|
||||
| 2025-07-23 | 88,589 (+4,338) | 121,436 (+3,363) | 210,025 (+7,701) |
|
||||
| 2025-07-24 | 92,469 (+3,880) | 124,091 (+2,655) | 216,560 (+6,535) |
|
||||
| 2025-07-25 | 96,417 (+3,948) | 126,985 (+2,894) | 223,402 (+6,842) |
|
||||
| 2025-07-26 | 100,646 (+4,229) | 131,411 (+4,426) | 232,057 (+8,655) |
|
||||
| 2025-07-27 | 102,644 (+1,998) | 134,736 (+3,325) | 237,380 (+5,323) |
|
||||
| 2025-07-28 | 105,446 (+2,802) | 136,016 (+1,280) | 241,462 (+4,082) |
|
||||
| 2025-07-29 | 108,998 (+3,552) | 137,542 (+1,526) | 246,540 (+5,078) |
|
||||
| 2025-07-30 | 113,544 (+4,546) | 140,317 (+2,775) | 253,861 (+7,321) |
|
||||
| 2025-07-31 | 118,339 (+4,795) | 143,344 (+3,027) | 261,683 (+7,822) |
|
||||
| 2025-08-01 | 123,539 (+5,200) | 146,680 (+3,336) | 270,219 (+8,536) |
|
||||
| 2025-08-02 | 127,864 (+4,325) | 149,236 (+2,556) | 277,100 (+6,881) |
|
||||
| 2025-08-03 | 131,397 (+3,533) | 150,451 (+1,215) | 281,848 (+4,748) |
|
||||
| 2025-08-04 | 136,266 (+4,869) | 153,260 (+2,809) | 289,526 (+7,678) |
|
||||
| 2025-08-05 | 141,596 (+5,330) | 155,752 (+2,492) | 297,348 (+7,822) |
|
||||
| 2025-08-06 | 147,067 (+5,471) | 158,309 (+2,557) | 305,376 (+8,028) |
|
||||
| 2025-08-07 | 152,591 (+5,524) | 160,889 (+2,580) | 313,480 (+8,104) |
|
||||
| 2025-08-08 | 158,187 (+5,596) | 163,448 (+2,559) | 321,635 (+8,155) |
|
||||
| 2025-08-09 | 162,770 (+4,583) | 165,721 (+2,273) | 328,491 (+6,856) |
|
||||
| 2025-08-10 | 165,695 (+2,925) | 167,109 (+1,388) | 332,804 (+4,313) |
|
||||
| 2025-08-11 | 169,297 (+3,602) | 167,953 (+844) | 337,250 (+4,446) |
|
||||
| 2025-08-12 | 176,307 (+7,010) | 171,876 (+3,923) | 348,183 (+10,933) |
|
||||
| 2025-08-13 | 182,997 (+6,690) | 177,182 (+5,306) | 360,179 (+11,996) |
|
||||
| 2025-08-14 | 189,063 (+6,066) | 179,741 (+2,559) | 368,804 (+8,625) |
|
||||
| 2025-08-15 | 193,608 (+4,545) | 181,792 (+2,051) | 375,400 (+6,596) |
|
||||
| 2025-08-16 | 198,118 (+4,510) | 184,558 (+2,766) | 382,676 (+7,276) |
|
||||
| 2025-08-17 | 201,299 (+3,181) | 186,269 (+1,711) | 387,568 (+4,892) |
|
||||
| 2025-08-18 | 204,559 (+3,260) | 187,399 (+1,130) | 391,958 (+4,390) |
|
||||
| 2025-08-19 | 209,814 (+5,255) | 189,668 (+2,269) | 399,482 (+7,524) |
|
||||
| 2025-08-20 | 214,497 (+4,683) | 191,481 (+1,813) | 405,978 (+6,496) |
|
||||
| 2025-08-21 | 220,465 (+5,968) | 194,784 (+3,303) | 415,249 (+9,271) |
|
||||
| 2025-08-22 | 225,899 (+5,434) | 197,204 (+2,420) | 423,103 (+7,854) |
|
||||
| 2025-08-23 | 229,005 (+3,106) | 199,238 (+2,034) | 428,243 (+5,140) |
|
||||
| 2025-08-24 | 232,098 (+3,093) | 201,157 (+1,919) | 433,255 (+5,012) |
|
||||
| 2025-08-25 | 236,607 (+4,509) | 202,650 (+1,493) | 439,257 (+6,002) |
|
||||
| 2025-08-26 | 242,783 (+6,176) | 205,242 (+2,592) | 448,025 (+8,768) |
|
||||
| 2025-08-27 | 248,409 (+5,626) | 205,242 (+0) | 453,651 (+5,626) |
|
||||
| 2025-08-28 | 252,796 (+4,387) | 205,242 (+0) | 458,038 (+4,387) |
|
||||
| 2025-08-29 | 256,045 (+3,249) | 211,075 (+5,833) | 467,120 (+9,082) |
|
||||
| 2025-08-30 | 258,863 (+2,818) | 212,397 (+1,322) | 471,260 (+4,140) |
|
||||
| 2025-08-31 | 262,004 (+3,141) | 213,944 (+1,547) | 475,948 (+4,688) |
|
||||
| 2025-09-01 | 265,359 (+3,355) | 215,115 (+1,171) | 480,474 (+4,526) |
|
||||
| 2025-09-02 | 270,483 (+5,124) | 217,075 (+1,960) | 487,558 (+7,084) |
|
||||
| 2025-09-03 | 274,793 (+4,310) | 219,755 (+2,680) | 494,548 (+6,990) |
|
||||
| 2025-09-04 | 280,430 (+5,637) | 222,103 (+2,348) | 502,533 (+7,985) |
|
||||
| 2025-09-05 | 283,769 (+3,339) | 223,793 (+1,690) | 507,562 (+5,029) |
|
||||
| 2025-09-06 | 286,245 (+2,476) | 225,036 (+1,243) | 511,281 (+3,719) |
|
||||
| 2025-09-07 | 288,623 (+2,378) | 225,866 (+830) | 514,489 (+3,208) |
|
||||
| 2025-09-08 | 293,341 (+4,718) | 227,073 (+1,207) | 520,414 (+5,925) |
|
||||
| 2025-09-09 | 300,036 (+6,695) | 229,788 (+2,715) | 529,824 (+9,410) |
|
||||
| 2025-09-10 | 307,287 (+7,251) | 233,435 (+3,647) | 540,722 (+10,898) |
|
||||
| 2025-09-11 | 314,083 (+6,796) | 237,356 (+3,921) | 551,439 (+10,717) |
|
||||
| 2025-09-12 | 321,046 (+6,963) | 240,728 (+3,372) | 561,774 (+10,335) |
|
||||
| 2025-09-13 | 324,894 (+3,848) | 245,539 (+4,811) | 570,433 (+8,659) |
|
||||
| 2025-09-14 | 328,876 (+3,982) | 248,245 (+2,706) | 577,121 (+6,688) |
|
||||
| 2025-09-15 | 334,201 (+5,325) | 250,983 (+2,738) | 585,184 (+8,063) |
|
||||
| 2025-09-16 | 342,609 (+8,408) | 255,264 (+4,281) | 597,873 (+12,689) |
|
||||
| 2025-09-17 | 351,117 (+8,508) | 260,970 (+5,706) | 612,087 (+14,214) |
|
||||
| 2025-09-18 | 358,717 (+7,600) | 266,922 (+5,952) | 625,639 (+13,552) |
|
||||
| 2025-09-19 | 365,401 (+6,684) | 271,859 (+4,937) | 637,260 (+11,621) |
|
||||
| 2025-09-20 | 372,092 (+6,691) | 276,917 (+5,058) | 649,009 (+11,749) |
|
||||
| 2025-09-21 | 377,079 (+4,987) | 280,261 (+3,344) | 657,340 (+8,331) |
|
||||
| 2025-09-22 | 382,492 (+5,413) | 284,009 (+3,748) | 666,501 (+9,161) |
|
||||
| 2025-09-23 | 387,008 (+4,516) | 289,129 (+5,120) | 676,137 (+9,636) |
|
||||
| 2025-09-24 | 393,325 (+6,317) | 294,927 (+5,798) | 688,252 (+12,115) |
|
||||
| 2025-09-25 | 398,879 (+5,554) | 301,663 (+6,736) | 700,542 (+12,290) |
|
||||
| 2025-09-26 | 404,334 (+5,455) | 306,713 (+5,050) | 711,047 (+10,505) |
|
||||
| 2025-09-27 | 411,618 (+7,284) | 317,763 (+11,050) | 729,381 (+18,334) |
|
||||
| 2025-09-28 | 414,910 (+3,292) | 322,522 (+4,759) | 737,432 (+8,051) |
|
||||
| 2025-09-29 | 419,919 (+5,009) | 328,033 (+5,511) | 747,952 (+10,520) |
|
||||
| 2025-09-30 | 427,991 (+8,072) | 336,472 (+8,439) | 764,463 (+16,511) |
|
||||
| 2025-10-01 | 433,591 (+5,600) | 341,742 (+5,270) | 775,333 (+10,870) |
|
||||
| 2025-10-02 | 440,852 (+7,261) | 348,099 (+6,357) | 788,951 (+13,618) |
|
||||
| 2025-10-03 | 446,829 (+5,977) | 359,937 (+11,838) | 806,766 (+17,815) |
|
||||
| 2025-10-04 | 452,561 (+5,732) | 370,386 (+10,449) | 822,947 (+16,181) |
|
||||
| 2025-10-05 | 455,559 (+2,998) | 374,745 (+4,359) | 830,304 (+7,357) |
|
||||
| 2025-10-06 | 460,927 (+5,368) | 379,489 (+4,744) | 840,416 (+10,112) |
|
||||
| 2025-10-07 | 467,336 (+6,409) | 385,438 (+5,949) | 852,774 (+12,358) |
|
||||
| 2025-10-08 | 474,643 (+7,307) | 394,139 (+8,701) | 868,782 (+16,008) |
|
||||
| 2025-10-09 | 479,203 (+4,560) | 400,526 (+6,387) | 879,729 (+10,947) |
|
||||
| 2025-10-10 | 484,374 (+5,171) | 406,015 (+5,489) | 890,389 (+10,660) |
|
||||
| 2025-10-11 | 488,427 (+4,053) | 414,699 (+8,684) | 903,126 (+12,737) |
|
||||
| 2025-10-12 | 492,125 (+3,698) | 418,745 (+4,046) | 910,870 (+7,744) |
|
||||
|
||||
|
Before Width: | Height: | Size: 17 KiB |
@@ -1,2 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M9 16.17L5.53 12.7a.996.996 0 1 0-1.41 1.41l4.18 4.18c.39.39 1.02.39 1.41 0L20.29 7.71a.996.996 0 1 0-1.41-1.41z"/></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 212 B |
@@ -1,2 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 512 512"><rect width="336" height="336" x="128" y="128" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32" rx="57" ry="57"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="m383.5 128l.5-24a56.16 56.16 0 0 0-56-56H112a64.19 64.19 0 0 0-64 64v216a56.16 56.16 0 0 0 56 56h24"/></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 443 B |
|
Before Width: | Height: | Size: 902 KiB |
|
Before Width: | Height: | Size: 456 KiB |
|
Before Width: | Height: | Size: 998 KiB |
|
Before Width: | Height: | Size: 592 KiB |
@@ -1,19 +0,0 @@
|
||||
<svg width="289" height="50" viewBox="0 0 289 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.5 16.5H24.5V33H8.5V16.5Z" fill="white" fill-opacity="0.2"/>
|
||||
<path d="M48.5 16.5H64.5V33H48.5V16.5Z" fill="white" fill-opacity="0.2"/>
|
||||
<path d="M120.5 16.5H136.5V33H120.5V16.5Z" fill="white" fill-opacity="0.2"/>
|
||||
<path d="M160.5 16.5H176.5V33H160.5V16.5Z" fill="white" fill-opacity="0.2"/>
|
||||
<path d="M192.5 16.5H208.5V33H192.5V16.5Z" fill="white" fill-opacity="0.2"/>
|
||||
<path d="M232.5 16.5H248.5V33H232.5V16.5Z" fill="white" fill-opacity="0.2"/>
|
||||
<path d="M264.5 0H288.5V8.5H272.5V16.5H288.5V25H272.5V33H288.5V41.5H264.5V0Z" fill="white" fill-opacity="0.95"/>
|
||||
<path d="M248.5 0H224.5V41.5H248.5V33H232.5V8.5H248.5V0Z" fill="white" fill-opacity="0.95"/>
|
||||
<path d="M256.5 8.5H248.5V33H256.5V8.5Z" fill="white" fill-opacity="0.95"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M184.5 0H216.5V41.5H184.5V0ZM208.5 8.5H192.5V33H208.5V8.5Z" fill="white" fill-opacity="0.95"/>
|
||||
<path d="M144.5 8.5H136.5V41.5H144.5V8.5Z" fill="white" fill-opacity="0.5"/>
|
||||
<path d="M136.5 0H112.5V41.5H120.5V8.5H136.5V0Z" fill="white" fill-opacity="0.5"/>
|
||||
<path d="M80.5 0H104.5V8.5H88.5V16.5H104.5V25H88.5V33H104.5V41.5H80.5V0Z" fill="white" fill-opacity="0.5"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M40.5 0H72.5V41.5H48.5V49.5H40.5V0ZM64.5 8.5H48.5V33H64.5V8.5Z" fill="white" fill-opacity="0.5"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.5 0H32.5V41.5955H0.5V0ZM24.5 8.5H8.5V33H24.5V8.5Z" fill="white" fill-opacity="0.5"/>
|
||||
<path d="M152.5 0H176.5V8.5H160.5V33H176.5V41.5H152.5V0Z" fill="white" fill-opacity="0.95"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.6 KiB |
@@ -1,18 +0,0 @@
|
||||
<svg width="288" height="50" viewBox="0 0 288 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 16.5H24V33H8V16.5Z" fill="black" fill-opacity="0.15"/>
|
||||
<path d="M48 16.5H64V33H48V16.5Z" fill="black" fill-opacity="0.15"/>
|
||||
<path d="M120 16.5H136V33H120V16.5Z" fill="black" fill-opacity="0.15"/>
|
||||
<path d="M160 16.5H176V33H160V16.5Z" fill="black" fill-opacity="0.15"/>
|
||||
<path d="M192 16.5H208V33H192V16.5Z" fill="black" fill-opacity="0.15"/>
|
||||
<path d="M232 16.5H248V33H232V16.5Z" fill="black" fill-opacity="0.15"/>
|
||||
<path d="M264 0H288V8.5H272V16.5H288V25H272V33H288V41.5H264V0Z" fill="black" fill-opacity="0.95"/>
|
||||
<path d="M248 0H224V41.5H248V33H232V8.5H248V0Z" fill="black" fill-opacity="0.95"/>
|
||||
<path d="M256 8.5H248V33H256V8.5Z" fill="black" fill-opacity="0.95"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M184 0H216V41.5H184V0ZM208 8.5H192V33H208V8.5Z" fill="black" fill-opacity="0.95"/>
|
||||
<path d="M144 8.5H136V41.5H144V8.5Z" fill="black" fill-opacity="0.55"/>
|
||||
<path d="M136 0H112V41.5H120V8.5H136V0Z" fill="black" fill-opacity="0.55"/>
|
||||
<path d="M80 0H104V8.5H88V16.5H104V25H88V33H104V41.5H80V0Z" fill="black" fill-opacity="0.55"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M40 0H72V41.5H48V49.5H40V0ZM64 8.5H48V33H64V8.5Z" fill="black" fill-opacity="0.55"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 0H32V41.5955H0V0ZM24 8.5H8V33H24V8.5Z" fill="black" fill-opacity="0.55"/>
|
||||
<path d="M152 0H176V8.5H160V33H176V41.5H152V0Z" fill="black" fill-opacity="0.95"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -1,12 +0,0 @@
|
||||
<svg width="289" height="50" viewBox="0 0 289 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M264.5 0H288.5V8.5H272.5V16.5H288.5V25H272.5V33H288.5V41.5H264.5V0Z" fill="black"/>
|
||||
<path d="M248.5 0H224.5V41.5H248.5V33H232.5V8.5H248.5V0Z" fill="black"/>
|
||||
<path d="M256.5 8.5H248.5V33H256.5V8.5Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M184.5 0H216.5V41.5H184.5V0ZM208.5 8.5H192.5V33H208.5V8.5Z" fill="black"/>
|
||||
<path d="M144.5 8.5H136.5V41.5H144.5V8.5Z" fill="black"/>
|
||||
<path d="M136.5 0H112.5V41.5H120.5V8.5H136.5V0Z" fill="black"/>
|
||||
<path d="M80.5 0H104.5V8.5H88.5V16.5H104.5V25H88.5V33H104.5V41.5H80.5V0Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M40.5 0H72.5V41.5H48.5V49.5H40.5V0ZM64.5 8.5H48.5V33H64.5V8.5Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.5 0H32.5V41.5955H0.5V0ZM24.5 8.5H8.5V33H24.5V8.5Z" fill="black"/>
|
||||
<path d="M152.5 0H176.5V8.5H160.5V33H176.5V41.5H152.5V0Z" fill="black"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 981 B |
@@ -1,39 +0,0 @@
|
||||
import { JSX } from "solid-js"
|
||||
|
||||
export function IconLogo(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
|
||||
return (
|
||||
<svg {...props} viewBox="0 0 289 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M264.5 0H288.5V8.5H272.5V16.5H288.5V25H272.5V33H288.5V41.5H264.5V0Z" fill="currentColor" />
|
||||
<path d="M248.5 0H224.5V41.5H248.5V33H232.5V8.5H248.5V0Z" fill="currentColor" />
|
||||
<path d="M256.5 8.5H248.5V33H256.5V8.5Z" fill="currentColor" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M184.5 0H216.5V41.5H184.5V0ZM208.5 8.5H192.5V33H208.5V8.5Z" fill="currentColor" />
|
||||
<path d="M144.5 8.5H136.5V41.5H144.5V8.5Z" fill="currentColor" />
|
||||
<path d="M136.5 0H112.5V41.5H120.5V8.5H136.5V0Z" fill="currentColor" />
|
||||
<path d="M80.5 0H104.5V8.5H88.5V16.5H104.5V25H88.5V33H104.5V41.5H80.5V0Z" fill="currentColor" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M40.5 0H72.5V41.5H48.5V49.5H40.5V0ZM64.5 8.5H48.5V33H64.5V8.5Z" fill="currentColor" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.5 0H32.5V41.5955H0.5V0ZM24.5 8.5H8.5V33H24.5V8.5Z" fill="currentColor" />
|
||||
<path d="M152.5 0H176.5V8.5H160.5V33H176.5V41.5H152.5V0Z" fill="currentColor" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function IconCopy(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
viewBox="0 0 512 512" >
|
||||
<rect width="336" height="336" x="128" y="128" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32" rx="57" ry="57"></rect>
|
||||
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="m383.5 128l.5-24a56.16 56.16 0 0 0-56-56H112a64.19 64.19 0 0 0-64 64v216a56.16 56.16 0 0 0 56 56h24"></path>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function IconCheck(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
viewBox="0 0 24 24" >
|
||||
<path fill="currentColor" d="M9 16.17L5.53 12.7a.996.996 0 1 0-1.41 1.41l4.18 4.18c.39.39 1.02.39 1.41 0L20.29 7.71a.996.996 0 1 0-1.41-1.41z"></path>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
import { getRequestEvent } from "solid-js/web"
|
||||
import { and, Database, eq, inArray } from "@opencode/cloud-core/drizzle/index.js"
|
||||
import { WorkspaceTable } from "@opencode/cloud-core/schema/workspace.sql.js"
|
||||
import { UserTable } from "@opencode/cloud-core/schema/user.sql.js"
|
||||
import { query, redirect } from "@solidjs/router"
|
||||
import { AccountTable } from "@opencode/cloud-core/schema/account.sql.js"
|
||||
import { Actor } from "@opencode/cloud-core/actor.js"
|
||||
|
||||
import { createClient } from "@openauthjs/openauth/client"
|
||||
import { useAuthSession } from "./auth.session"
|
||||
|
||||
export const AuthClient = createClient({
|
||||
clientID: "app",
|
||||
issuer: import.meta.env.VITE_AUTH_URL,
|
||||
})
|
||||
|
||||
export const getActor = query(async (): Promise<Actor.Info> => {
|
||||
"use server"
|
||||
const evt = getRequestEvent()
|
||||
if (!evt) throw new Error("No request event")
|
||||
const url = new URL(evt.request.headers.has("x-server-id") ? evt.request.headers.get("referer")! : evt.request.url)
|
||||
const auth = await useAuthSession()
|
||||
const splits = url.pathname.split("/").filter(Boolean)
|
||||
if (splits[0] !== "workspace") {
|
||||
if (auth.data.current) {
|
||||
const current = auth.data.account[auth.data.current]
|
||||
return {
|
||||
type: "account",
|
||||
properties: {
|
||||
email: current.email,
|
||||
accountID: current.id,
|
||||
},
|
||||
}
|
||||
}
|
||||
if (Object.keys(auth.data.account ?? {}).length > 0) {
|
||||
const current = Object.values(auth.data.account)[0]
|
||||
await auth.update((val) => ({
|
||||
...val,
|
||||
current: current.id,
|
||||
}))
|
||||
return {
|
||||
type: "account",
|
||||
properties: {
|
||||
email: current.email,
|
||||
accountID: current.id,
|
||||
},
|
||||
}
|
||||
}
|
||||
return {
|
||||
type: "public",
|
||||
properties: {},
|
||||
}
|
||||
}
|
||||
const workspaceHint = splits[1]
|
||||
const accounts = Object.keys(auth.data.account)
|
||||
const result = await Database.transaction(async (tx) => {
|
||||
return await tx
|
||||
.select({
|
||||
user: UserTable,
|
||||
})
|
||||
.from(AccountTable)
|
||||
.innerJoin(UserTable, and(eq(UserTable.email, AccountTable.email)))
|
||||
.innerJoin(WorkspaceTable, eq(WorkspaceTable.id, UserTable.workspaceID))
|
||||
.where(and(inArray(AccountTable.id, accounts), eq(WorkspaceTable.id, workspaceHint)))
|
||||
.limit(1)
|
||||
.execute()
|
||||
.then((x) => x[0])
|
||||
})
|
||||
if (result) {
|
||||
return {
|
||||
type: "user",
|
||||
properties: {
|
||||
userID: result.user.id,
|
||||
workspaceID: result.user.workspaceID,
|
||||
},
|
||||
}
|
||||
}
|
||||
throw redirect("/auth/authorize")
|
||||
}, "actor")
|
||||
@@ -1,16 +0,0 @@
|
||||
import { Actor } from "@opencode/cloud-core/actor.js"
|
||||
import { getActor } from "./auth"
|
||||
import { query } from "@solidjs/router"
|
||||
|
||||
export async function withActor<T>(fn: () => T) {
|
||||
const actor = await getActor()
|
||||
return Actor.provide(actor.type, actor.properties, fn)
|
||||
}
|
||||
|
||||
export function actorQuery<T>(cb: () => T, name: string) {
|
||||
"use server"
|
||||
return query(async () => {
|
||||
const actor = await getActor()
|
||||
return withActor(cb)
|
||||
}, name)
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
// @refresh reload
|
||||
import { mount, StartClient } from "@solidjs/start/client";
|
||||
|
||||
mount(() => <StartClient />, document.getElementById("app")!);
|
||||
@@ -1,23 +0,0 @@
|
||||
// @refresh reload
|
||||
import { createHandler, StartServer } from "@solidjs/start/server"
|
||||
|
||||
export default createHandler(() => (
|
||||
<StartServer
|
||||
document={({ assets, children, scripts }) => (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" href="/favicon.svg" />
|
||||
<meta property="og:image" content="/social-share.png" />
|
||||
<meta property="twitter:image" content="/social-share.png" />
|
||||
{assets}
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">{children}</div>
|
||||
{scripts}
|
||||
</body>
|
||||
</html>
|
||||
)}
|
||||
/>
|
||||
))
|
||||
@@ -1,19 +0,0 @@
|
||||
import { Title } from "@solidjs/meta";
|
||||
import { HttpStatusCode } from "@solidjs/start";
|
||||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<main>
|
||||
<Title>Not Found</Title>
|
||||
<HttpStatusCode code={404} />
|
||||
<h1>Page Not Found</h1>
|
||||
<p>
|
||||
Visit{" "}
|
||||
<a href="https://start.solidjs.com" target="_blank">
|
||||
start.solidjs.com
|
||||
</a>{" "}
|
||||
to learn how to build SolidStart apps.
|
||||
</p>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import type { APIEvent } from "@solidjs/start/server"
|
||||
import { AuthClient } from "~/context/auth"
|
||||
|
||||
export async function GET(input: APIEvent) {
|
||||
const result = await AuthClient.authorize(new URL("./callback", input.request.url).toString(), "code")
|
||||
return Response.redirect(result.url, 302)
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import type { APIEvent } from "@solidjs/start/server"
|
||||
import { Resource } from "@opencode/cloud-resource"
|
||||
import { json } from "@solidjs/router"
|
||||
|
||||
export async function GET(evt: APIEvent) {
|
||||
return json({
|
||||
data: Resource.Database.host,
|
||||
})
|
||||
}
|
||||
@@ -1,576 +0,0 @@
|
||||
import { Resource } from "@opencode/cloud-resource"
|
||||
import { Billing } from "@opencode/cloud-core/billing.js"
|
||||
import type { APIEvent } from "@solidjs/start/server"
|
||||
import { Database, eq, sql } from "@opencode/cloud-core/drizzle/index.js"
|
||||
import { BillingTable, PaymentTable } from "@opencode/cloud-core/schema/billing.sql.js"
|
||||
import { Identifier } from "@opencode/cloud-core/identifier.js"
|
||||
import { centsToMicroCents } from "@opencode/cloud-core/util/price.js"
|
||||
import { Actor } from "@opencode/cloud-core/actor.js"
|
||||
import { KeyTable } from "@opencode/cloud-core/schema/key.sql.js"
|
||||
|
||||
const SUPPORTED_MODELS = {
|
||||
// "anthropic/claude-sonnet-4": {
|
||||
// input: 0.0000015,
|
||||
// output: 0.000006,
|
||||
// reasoning: 0.0000015,
|
||||
// cacheRead: 0.0000001,
|
||||
// cacheWrite: 0.0000001,
|
||||
// model: () =>
|
||||
// createAnthropic({
|
||||
// apiKey: Resource.ANTHROPIC_API_KEY.value,
|
||||
// })("claude-sonnet-4-20250514"),
|
||||
// },
|
||||
// "openai/gpt-4.1": {
|
||||
// input: 0.0000015,
|
||||
// output: 0.000006,
|
||||
// reasoning: 0.0000015,
|
||||
// cacheRead: 0.0000001,
|
||||
// cacheWrite: 0.0000001,
|
||||
// model: () =>
|
||||
// createOpenAI({
|
||||
// apiKey: Resource.OPENAI_API_KEY.value,
|
||||
// })("gpt-4.1"),
|
||||
// },
|
||||
// "zhipuai/glm-4.5-flash": {
|
||||
// input: 0,
|
||||
// output: 0,
|
||||
// reasoning: 0,
|
||||
// cacheRead: 0,
|
||||
// cacheWrite: 0,
|
||||
// model: () =>
|
||||
// createOpenAICompatible({
|
||||
// name: "Zhipu AI",
|
||||
// baseURL: "https://api.z.ai/api/paas/v4",
|
||||
// apiKey: Resource.ZHIPU_API_KEY.value,
|
||||
// })("glm-4.5-flash"),
|
||||
// },
|
||||
}
|
||||
|
||||
export async function POST(input: APIEvent) {
|
||||
// Check auth header
|
||||
const authHeader = input.request.headers.get("authorization")
|
||||
if (!authHeader || !authHeader.startsWith("Bearer "))
|
||||
return Response.json(
|
||||
{
|
||||
error: {
|
||||
message: "Missing API key.",
|
||||
type: "invalid_request_error",
|
||||
param: null,
|
||||
code: "unauthorized",
|
||||
},
|
||||
},
|
||||
{ status: 401 },
|
||||
)
|
||||
const apiKey = authHeader.split(" ")[1]
|
||||
|
||||
// Check against KeyTable
|
||||
const keyRecord = await Database.use((tx) =>
|
||||
tx
|
||||
.select({
|
||||
id: KeyTable.id,
|
||||
workspaceID: KeyTable.workspaceID,
|
||||
})
|
||||
.from(KeyTable)
|
||||
.where(eq(KeyTable.key, apiKey))
|
||||
.then((rows) => rows[0]),
|
||||
)
|
||||
|
||||
if (!keyRecord)
|
||||
return Response.json(
|
||||
{
|
||||
error: {
|
||||
message: "Invalid API key.",
|
||||
type: "invalid_request_error",
|
||||
param: null,
|
||||
code: "unauthorized",
|
||||
},
|
||||
},
|
||||
{ status: 401 },
|
||||
)
|
||||
|
||||
/*
|
||||
return await Actor.provide("system", { workspaceID: keyRecord.workspaceID }, async () => {
|
||||
try {
|
||||
// Check balance
|
||||
const customer = await Billing.get()
|
||||
if (customer.balance <= 0) {
|
||||
return Response.json(
|
||||
{
|
||||
error: {
|
||||
message: "Insufficient balance",
|
||||
type: "insufficient_quota",
|
||||
param: null,
|
||||
code: "insufficient_quota",
|
||||
},
|
||||
},
|
||||
{ status: 401 },
|
||||
)
|
||||
}
|
||||
|
||||
const body = await input.request.json<ChatCompletionCreateParamsBase>()
|
||||
const model = SUPPORTED_MODELS[body.model as keyof typeof SUPPORTED_MODELS]?.model()
|
||||
if (!model) throw new Error(`Unsupported model: ${body.model}`)
|
||||
|
||||
const requestBody = transformOpenAIRequestToAiSDK()
|
||||
|
||||
return body.stream ? await handleStream() : await handleGenerate()
|
||||
|
||||
async function handleStream() {
|
||||
const result = await model.doStream({
|
||||
...requestBody,
|
||||
})
|
||||
|
||||
const encoder = new TextEncoder()
|
||||
const stream = new ReadableStream({
|
||||
async start(controller) {
|
||||
const id = `chatcmpl-${Date.now()}`
|
||||
const created = Math.floor(Date.now() / 1000)
|
||||
|
||||
try {
|
||||
for await (const chunk of result.stream) {
|
||||
console.log("!!! CHUNK !!! : " + chunk.type)
|
||||
switch (chunk.type) {
|
||||
case "text-delta": {
|
||||
const data = {
|
||||
id,
|
||||
object: "chat.completion.chunk",
|
||||
created,
|
||||
model: body.model,
|
||||
choices: [
|
||||
{
|
||||
index: 0,
|
||||
delta: {
|
||||
content: chunk.delta,
|
||||
},
|
||||
finish_reason: null,
|
||||
},
|
||||
],
|
||||
}
|
||||
controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`))
|
||||
break
|
||||
}
|
||||
|
||||
case "reasoning-delta": {
|
||||
const data = {
|
||||
id,
|
||||
object: "chat.completion.chunk",
|
||||
created,
|
||||
model: body.model,
|
||||
choices: [
|
||||
{
|
||||
index: 0,
|
||||
delta: {
|
||||
reasoning_content: chunk.delta,
|
||||
},
|
||||
finish_reason: null,
|
||||
},
|
||||
],
|
||||
}
|
||||
controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`))
|
||||
break
|
||||
}
|
||||
|
||||
case "tool-call": {
|
||||
const data = {
|
||||
id,
|
||||
object: "chat.completion.chunk",
|
||||
created,
|
||||
model: body.model,
|
||||
choices: [
|
||||
{
|
||||
index: 0,
|
||||
delta: {
|
||||
tool_calls: [
|
||||
{
|
||||
index: 0,
|
||||
id: chunk.toolCallId,
|
||||
type: "function",
|
||||
function: {
|
||||
name: chunk.toolName,
|
||||
arguments: chunk.input,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
finish_reason: null,
|
||||
},
|
||||
],
|
||||
}
|
||||
controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`))
|
||||
break
|
||||
}
|
||||
|
||||
case "error": {
|
||||
const data = {
|
||||
id,
|
||||
object: "chat.completion.chunk",
|
||||
created,
|
||||
model: body.model,
|
||||
choices: [
|
||||
{
|
||||
index: 0,
|
||||
delta: {},
|
||||
finish_reason: "stop",
|
||||
},
|
||||
],
|
||||
error: {
|
||||
message: typeof chunk.error === "string" ? chunk.error : chunk.error,
|
||||
type: "server_error",
|
||||
},
|
||||
}
|
||||
controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`))
|
||||
controller.enqueue(encoder.encode("data: [DONE]\n\n"))
|
||||
controller.close()
|
||||
break
|
||||
}
|
||||
|
||||
case "finish": {
|
||||
const data = {
|
||||
id,
|
||||
object: "chat.completion.chunk",
|
||||
created,
|
||||
model: body.model,
|
||||
choices: [
|
||||
{
|
||||
index: 0,
|
||||
delta: {},
|
||||
finish_reason:
|
||||
{
|
||||
stop: "stop",
|
||||
length: "length",
|
||||
"content-filter": "content_filter",
|
||||
"tool-calls": "tool_calls",
|
||||
error: "stop",
|
||||
other: "stop",
|
||||
unknown: "stop",
|
||||
}[chunk.finishReason] || "stop",
|
||||
},
|
||||
],
|
||||
usage: {
|
||||
prompt_tokens: chunk.usage.inputTokens,
|
||||
completion_tokens: chunk.usage.outputTokens,
|
||||
total_tokens: chunk.usage.totalTokens,
|
||||
completion_tokens_details: {
|
||||
reasoning_tokens: chunk.usage.reasoningTokens,
|
||||
},
|
||||
prompt_tokens_details: {
|
||||
cached_tokens: chunk.usage.cachedInputTokens,
|
||||
},
|
||||
},
|
||||
}
|
||||
await trackUsage(body.model, chunk.usage, chunk.providerMetadata)
|
||||
controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`))
|
||||
controller.enqueue(encoder.encode("data: [DONE]\n\n"))
|
||||
controller.close()
|
||||
break
|
||||
}
|
||||
|
||||
//case "stream-start":
|
||||
//case "response-metadata":
|
||||
case "text-start":
|
||||
case "text-end":
|
||||
case "reasoning-start":
|
||||
case "reasoning-end":
|
||||
case "tool-input-start":
|
||||
case "tool-input-delta":
|
||||
case "tool-input-end":
|
||||
case "raw":
|
||||
default:
|
||||
// Log unknown chunk types for debugging
|
||||
console.warn(`Unknown chunk type: ${(chunk as any).type}`)
|
||||
break
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
controller.error(error)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
return new Response(stream, {
|
||||
headers: {
|
||||
"Content-Type": "text/plain; charset=utf-8",
|
||||
"Cache-Control": "no-cache",
|
||||
Connection: "keep-alive",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async function handleGenerate() {
|
||||
const response = await model.doGenerate({
|
||||
...requestBody,
|
||||
})
|
||||
await trackUsage(body.model, response.usage, response.providerMetadata)
|
||||
return c.json({
|
||||
id: `chatcmpl-${Date.now()}`,
|
||||
object: "chat.completion" as const,
|
||||
created: Math.floor(Date.now() / 1000),
|
||||
model: body.model,
|
||||
choices: [
|
||||
{
|
||||
index: 0,
|
||||
message: {
|
||||
role: "assistant" as const,
|
||||
content: response.content?.find((c) => c.type === "text")?.text ?? "",
|
||||
reasoning_content: response.content?.find((c) => c.type === "reasoning")?.text,
|
||||
tool_calls: response.content
|
||||
?.filter((c) => c.type === "tool-call")
|
||||
.map((toolCall) => ({
|
||||
id: toolCall.toolCallId,
|
||||
type: "function" as const,
|
||||
function: {
|
||||
name: toolCall.toolName,
|
||||
arguments: toolCall.input,
|
||||
},
|
||||
})),
|
||||
},
|
||||
finish_reason:
|
||||
(
|
||||
{
|
||||
stop: "stop",
|
||||
length: "length",
|
||||
"content-filter": "content_filter",
|
||||
"tool-calls": "tool_calls",
|
||||
error: "stop",
|
||||
other: "stop",
|
||||
unknown: "stop",
|
||||
} as const
|
||||
)[response.finishReason] || "stop",
|
||||
},
|
||||
],
|
||||
usage: {
|
||||
prompt_tokens: response.usage?.inputTokens,
|
||||
completion_tokens: response.usage?.outputTokens,
|
||||
total_tokens: response.usage?.totalTokens,
|
||||
completion_tokens_details: {
|
||||
reasoning_tokens: response.usage?.reasoningTokens,
|
||||
},
|
||||
prompt_tokens_details: {
|
||||
cached_tokens: response.usage?.cachedInputTokens,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function transformOpenAIRequestToAiSDK() {
|
||||
const prompt = transformMessages()
|
||||
const tools = transformTools()
|
||||
|
||||
return {
|
||||
prompt,
|
||||
maxOutputTokens: body.max_tokens ?? body.max_completion_tokens ?? undefined,
|
||||
temperature: body.temperature ?? undefined,
|
||||
topP: body.top_p ?? undefined,
|
||||
frequencyPenalty: body.frequency_penalty ?? undefined,
|
||||
presencePenalty: body.presence_penalty ?? undefined,
|
||||
providerOptions: body.reasoning_effort
|
||||
? {
|
||||
anthropic: {
|
||||
reasoningEffort: body.reasoning_effort,
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
stopSequences: (typeof body.stop === "string" ? [body.stop] : body.stop) ?? undefined,
|
||||
responseFormat: (() => {
|
||||
if (!body.response_format) return { type: "text" as const }
|
||||
if (body.response_format.type === "json_schema")
|
||||
return {
|
||||
type: "json" as const,
|
||||
schema: body.response_format.json_schema.schema,
|
||||
name: body.response_format.json_schema.name,
|
||||
description: body.response_format.json_schema.description,
|
||||
}
|
||||
if (body.response_format.type === "json_object") return { type: "json" as const }
|
||||
throw new Error("Unsupported response format")
|
||||
})(),
|
||||
seed: body.seed ?? undefined,
|
||||
tools: tools.tools,
|
||||
toolChoice: tools.toolChoice,
|
||||
}
|
||||
|
||||
function transformTools() {
|
||||
const { tools, tool_choice } = body
|
||||
|
||||
if (!tools || tools.length === 0) {
|
||||
return { tools: undefined, toolChoice: undefined }
|
||||
}
|
||||
|
||||
const aiSdkTools = tools.map((tool) => {
|
||||
return {
|
||||
type: tool.type,
|
||||
name: tool.function.name,
|
||||
description: tool.function.description,
|
||||
inputSchema: tool.function.parameters!,
|
||||
}
|
||||
})
|
||||
|
||||
let aiSdkToolChoice
|
||||
if (tool_choice == null) {
|
||||
aiSdkToolChoice = undefined
|
||||
} else if (tool_choice === "auto") {
|
||||
aiSdkToolChoice = { type: "auto" as const }
|
||||
} else if (tool_choice === "none") {
|
||||
aiSdkToolChoice = { type: "none" as const }
|
||||
} else if (tool_choice === "required") {
|
||||
aiSdkToolChoice = { type: "required" as const }
|
||||
} else if (tool_choice.type === "function") {
|
||||
aiSdkToolChoice = {
|
||||
type: "tool" as const,
|
||||
toolName: tool_choice.function.name,
|
||||
}
|
||||
}
|
||||
|
||||
return { tools: aiSdkTools, toolChoice: aiSdkToolChoice }
|
||||
}
|
||||
|
||||
function transformMessages() {
|
||||
const { messages } = body
|
||||
const prompt: LanguageModelV2Prompt = []
|
||||
|
||||
for (const message of messages) {
|
||||
switch (message.role) {
|
||||
case "system": {
|
||||
prompt.push({
|
||||
role: "system",
|
||||
content: message.content as string,
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
case "user": {
|
||||
if (typeof message.content === "string") {
|
||||
prompt.push({
|
||||
role: "user",
|
||||
content: [{ type: "text", text: message.content }],
|
||||
})
|
||||
} else {
|
||||
const content = message.content.map((part) => {
|
||||
switch (part.type) {
|
||||
case "text":
|
||||
return { type: "text" as const, text: part.text }
|
||||
case "image_url":
|
||||
return {
|
||||
type: "file" as const,
|
||||
mediaType: "image/jpeg" as const,
|
||||
data: part.image_url.url,
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unsupported content part type: ${(part as any).type}`)
|
||||
}
|
||||
})
|
||||
prompt.push({
|
||||
role: "user",
|
||||
content,
|
||||
})
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case "assistant": {
|
||||
const content: Array<
|
||||
| { type: "text"; text: string }
|
||||
| {
|
||||
type: "tool-call"
|
||||
toolCallId: string
|
||||
toolName: string
|
||||
input: any
|
||||
}
|
||||
> = []
|
||||
|
||||
if (message.content) {
|
||||
content.push({
|
||||
type: "text",
|
||||
text: message.content as string,
|
||||
})
|
||||
}
|
||||
|
||||
if (message.tool_calls) {
|
||||
for (const toolCall of message.tool_calls) {
|
||||
content.push({
|
||||
type: "tool-call",
|
||||
toolCallId: toolCall.id,
|
||||
toolName: toolCall.function.name,
|
||||
input: JSON.parse(toolCall.function.arguments),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
prompt.push({
|
||||
role: "assistant",
|
||||
content,
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
case "tool": {
|
||||
prompt.push({
|
||||
role: "tool",
|
||||
content: [
|
||||
{
|
||||
type: "tool-result",
|
||||
toolName: "placeholder",
|
||||
toolCallId: message.tool_call_id,
|
||||
output: {
|
||||
type: "text",
|
||||
value: message.content as string,
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
default: {
|
||||
throw new Error(`Unsupported message role: ${message.role}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return prompt
|
||||
}
|
||||
}
|
||||
|
||||
async function trackUsage(model: string, usage: LanguageModelUsage, providerMetadata?: ProviderMetadata) {
|
||||
const modelData = SUPPORTED_MODELS[model as keyof typeof SUPPORTED_MODELS]
|
||||
if (!modelData) throw new Error(`Unsupported model: ${model}`)
|
||||
|
||||
const inputTokens = usage.inputTokens ?? 0
|
||||
const outputTokens = usage.outputTokens ?? 0
|
||||
const reasoningTokens = usage.reasoningTokens ?? 0
|
||||
const cacheReadTokens = usage.cachedInputTokens ?? 0
|
||||
const cacheWriteTokens =
|
||||
providerMetadata?.["anthropic"]?.["cacheCreationInputTokens"] ??
|
||||
// @ts-expect-error
|
||||
providerMetadata?.["bedrock"]?.["usage"]?.["cacheWriteInputTokens"] ??
|
||||
0
|
||||
|
||||
const inputCost = modelData.input * inputTokens
|
||||
const outputCost = modelData.output * outputTokens
|
||||
const reasoningCost = modelData.reasoning * reasoningTokens
|
||||
const cacheReadCost = modelData.cacheRead * cacheReadTokens
|
||||
const cacheWriteCost = modelData.cacheWrite * cacheWriteTokens
|
||||
const costInCents = (inputCost + outputCost + reasoningCost + cacheReadCost + cacheWriteCost) * 100
|
||||
|
||||
await Billing.consume({
|
||||
model,
|
||||
inputTokens,
|
||||
outputTokens,
|
||||
reasoningTokens,
|
||||
cacheReadTokens,
|
||||
cacheWriteTokens,
|
||||
costInCents,
|
||||
})
|
||||
|
||||
await Database.use((tx) =>
|
||||
tx
|
||||
.update(KeyTable)
|
||||
.set({ timeUsed: sql`now()` })
|
||||
.where(eq(KeyTable.id, keyRecord.id)),
|
||||
)
|
||||
}
|
||||
} catch (error: any) {
|
||||
return Response.json({ error: { message: error.message } }, { status: 500 })
|
||||
}
|
||||
})
|
||||
*/
|
||||
}
|
||||
@@ -1,523 +0,0 @@
|
||||
[data-page="home"] {
|
||||
--color-text: hsl(224, 10%, 10%);
|
||||
--color-text-secondary: hsl(224, 7%, 46%);
|
||||
--color-text-dimmed: hsl(224, 6%, 63%);
|
||||
|
||||
--color-border: hsl(224, 6%, 77%);
|
||||
}
|
||||
|
||||
[data-page="home"] {
|
||||
@media (prefers-color-scheme: dark) {
|
||||
--color-text: hsl(0, 0%, 100%);
|
||||
--color-text-secondary: hsl(224, 6%, 66%);
|
||||
--color-text-dimmed: hsl(224, 7%, 46%);
|
||||
|
||||
--color-border: hsl(224, 6%, 36%);
|
||||
}
|
||||
}
|
||||
|
||||
[data-page="home"] {
|
||||
--padding: 3rem;
|
||||
--vertical-padding: 1.5rem;
|
||||
--heading-font-size: 1.5625rem;
|
||||
|
||||
@media (max-width: 30rem) {
|
||||
--padding: 1rem;
|
||||
--vertical-padding: 0.75rem;
|
||||
--heading-font-size: 1.375rem;
|
||||
}
|
||||
|
||||
font-family: var(--font-mono);
|
||||
color: var(--color-text);
|
||||
padding: calc(var(--padding) + 1rem);
|
||||
|
||||
a {
|
||||
color: var(--color-text);
|
||||
text-decoration: underline;
|
||||
text-underline-offset: var(--space-0-75);
|
||||
text-decoration-thickness: 1px;
|
||||
}
|
||||
|
||||
[data-component="content"] {
|
||||
max-width: 67.5rem;
|
||||
margin: 0 auto;
|
||||
border: 2px solid var(--color-border);
|
||||
}
|
||||
|
||||
[data-component="top"] {
|
||||
padding: var(--padding);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: start;
|
||||
gap: calc(var(--vertical-padding) / 2);
|
||||
|
||||
img {
|
||||
height: auto;
|
||||
width: clamp(200px, 70vw, 400px);
|
||||
}
|
||||
|
||||
[data-slot="logo dark"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
[data-slot="logo light"] {
|
||||
display: none;
|
||||
}
|
||||
[data-slot="logo dark"] {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="title"] {
|
||||
line-height: 1.25;
|
||||
font-size: var(--heading-font-size);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
|
||||
[data-component="cta"] {
|
||||
border-top: 2px solid var(--color-border);
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr auto;
|
||||
align-items: stretch;
|
||||
|
||||
& > div + div {
|
||||
border-left: 2px solid var(--color-border);
|
||||
}
|
||||
|
||||
[data-slot="left"],
|
||||
[data-slot="right"] {
|
||||
text-align: center;
|
||||
line-height: 1.4;
|
||||
padding: var(--vertical-padding) 2rem;
|
||||
text-transform: uppercase;
|
||||
|
||||
@media (max-width: 30rem) {
|
||||
padding-bottom: calc(var(--vertical-padding) + 4px);
|
||||
}
|
||||
|
||||
@media (max-width: 30rem) {
|
||||
padding-left: 0.5rem;
|
||||
padding-right: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="center"] {
|
||||
padding: var(--vertical-padding) 1rem;
|
||||
}
|
||||
|
||||
@media (max-width: 55rem) {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-rows: auto auto;
|
||||
|
||||
[data-slot="left"] {
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
}
|
||||
|
||||
[data-slot="right"] {
|
||||
grid-column: 2;
|
||||
grid-row: 1;
|
||||
}
|
||||
|
||||
[data-slot="center"] {
|
||||
grid-column: 1 / -1;
|
||||
grid-row: 2;
|
||||
border-left: none;
|
||||
border-top: 2px solid var(--color-border);
|
||||
}
|
||||
|
||||
@media (max-width: 22rem) {
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: auto auto auto;
|
||||
|
||||
[data-slot="left"] {
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
}
|
||||
|
||||
[data-slot="right"] {
|
||||
grid-column: 1;
|
||||
grid-row: 2;
|
||||
border-top: 2px solid var(--color-border);
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
[data-slot="center"] {
|
||||
grid-column: 1;
|
||||
grid-row: 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="command"] {
|
||||
all: unset;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 1.125rem;
|
||||
font-family: var(--font-mono);
|
||||
gap: var(--space-2);
|
||||
width: 100%;
|
||||
|
||||
& > span {
|
||||
@media (max-width: 24rem) {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
@media (max-width: 64rem) {
|
||||
[data-slot="protocol"] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@media (max-width: 38rem) {
|
||||
text-align: center;
|
||||
span:first-child {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="highlight"] {
|
||||
color: var(--color-text);
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
[data-component="features"] {
|
||||
border-top: 2px solid var(--color-border);
|
||||
padding: var(--padding);
|
||||
|
||||
[data-slot="list"] {
|
||||
padding-left: var(--space-4);
|
||||
margin: 0;
|
||||
list-style: disc;
|
||||
|
||||
li {
|
||||
margin-bottom: var(--space-4);
|
||||
|
||||
strong {
|
||||
text-transform: uppercase;
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
li:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-component="install"] {
|
||||
border-top: 2px solid var(--color-border);
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
|
||||
@media (max-width: 40rem) {
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: auto;
|
||||
}
|
||||
}
|
||||
|
||||
[data-component="method"] {
|
||||
display: flex;
|
||||
padding: calc(var(--vertical-padding) / 2) calc(var(--padding) / 2) calc(var(--vertical-padding) / 2 + 0.125rem);
|
||||
flex-direction: column;
|
||||
text-align: left;
|
||||
gap: var(--space-2-5);
|
||||
|
||||
@media (max-width: 30rem) {
|
||||
gap: 0.3125rem;
|
||||
}
|
||||
|
||||
@media (max-width: 40rem) {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
border-left: 2px solid var(--color-border);
|
||||
|
||||
@media (max-width: 40rem) {
|
||||
border-left: none;
|
||||
border-top: 2px solid var(--color-border);
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
border-top: 2px solid var(--color-border);
|
||||
}
|
||||
|
||||
&:nth-child(4) {
|
||||
border-top: 2px solid var(--color-border);
|
||||
border-left: 2px solid var(--color-border);
|
||||
|
||||
@media (max-width: 40rem) {
|
||||
border-left: none;
|
||||
}
|
||||
}
|
||||
|
||||
[data-component="title"] {
|
||||
letter-spacing: -0.03125rem;
|
||||
text-transform: uppercase;
|
||||
font-weight: normal;
|
||||
font-size: 1rem;
|
||||
flex-shrink: 0;
|
||||
color: var(--color-text-dimmed);
|
||||
|
||||
@media (max-width: 30rem) {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="button"] {
|
||||
all: unset;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--color-text-secondary);
|
||||
gap: var(--space-2-5);
|
||||
font-size: 1rem;
|
||||
|
||||
@media (max-width: 24rem) {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
strong {
|
||||
color: var(--color-text);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@media (max-width: 40rem) {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
@media (max-width: 30rem) {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-component="screenshots"] {
|
||||
--images-height: 600px;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-rows: var(--images-height);
|
||||
border-top: 2px solid var(--color-border);
|
||||
|
||||
& > div.left {
|
||||
display: flex;
|
||||
grid-row: 1;
|
||||
grid-column: 1;
|
||||
}
|
||||
|
||||
& > div.right {
|
||||
display: grid;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
grid-row: 1;
|
||||
grid-column: 2;
|
||||
border-left: 2px solid var(--color-border);
|
||||
|
||||
& > div.row1 {
|
||||
display: flex;
|
||||
grid-row: 1;
|
||||
border-bottom: 2px solid var(--color-border);
|
||||
height: calc(var(--images-height) / 2);
|
||||
}
|
||||
|
||||
& > div.row2 {
|
||||
display: flex;
|
||||
grid-row: 2;
|
||||
height: calc(var(--images-height) / 2);
|
||||
}
|
||||
}
|
||||
|
||||
figure {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: calc(var(--padding) / 4);
|
||||
padding: calc(var(--padding) / 2);
|
||||
border-width: 0;
|
||||
border-style: solid;
|
||||
border-color: var(--color-border);
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
|
||||
& > div,
|
||||
figcaption {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
& > div {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
a {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
figcaption {
|
||||
letter-spacing: -0.03125rem;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-text-dimmed);
|
||||
flex-shrink: 0;
|
||||
|
||||
@media (max-width: 30rem) {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& > div.left figure {
|
||||
height: var(--images-height);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
& > div.right figure {
|
||||
height: calc(var(--images-height) / 2);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
& > div.left img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-width: 0;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
& > div.right img {
|
||||
width: 100%;
|
||||
height: calc(100% - 2rem);
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
|
||||
@media (max-width: 30rem) {
|
||||
& {
|
||||
--images-height: auto;
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: auto auto;
|
||||
}
|
||||
|
||||
& > div.left {
|
||||
grid-row: 1;
|
||||
grid-column: 1;
|
||||
}
|
||||
|
||||
& > div.right {
|
||||
grid-row: 2;
|
||||
grid-column: 1;
|
||||
border-left: none;
|
||||
border-top: 2px solid var(--color-border);
|
||||
|
||||
& > div.row1,
|
||||
& > div.row2 {
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
& > div.left figure,
|
||||
& > div.right figure {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
& > div.left img,
|
||||
& > div.right img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
max-height: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-component="copy-status"] {
|
||||
@media (max-width: 38rem) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
[data-slot="copy"] {
|
||||
display: block;
|
||||
width: var(--space-4);
|
||||
height: var(--space-4);
|
||||
color: var(--color-text-dimmed);
|
||||
|
||||
[data-copied] & {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="check"] {
|
||||
display: none;
|
||||
width: var(--space-4);
|
||||
height: var(--space-4);
|
||||
color: var(--color-text);
|
||||
|
||||
[data-copied] & {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-component="footer"] {
|
||||
border-top: 2px solid var(--color-border);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
[data-slot="cell"] {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
padding: var(--vertical-padding) 0.5rem;
|
||||
}
|
||||
|
||||
[data-slot="cell"] + [data-slot="cell"] {
|
||||
border-left: 2px solid var(--color-border);
|
||||
}
|
||||
|
||||
/* Small desktop: first two columns shrink to content, third expands */
|
||||
@media (max-width: 57rem) {
|
||||
[data-slot="cell"]:nth-child(1),
|
||||
[data-slot="cell"]:nth-child(2) {
|
||||
flex: 0 0 auto;
|
||||
padding-left: calc(var(--padding) / 2);
|
||||
padding-right: calc(var(--padding) / 2);
|
||||
}
|
||||
|
||||
[data-slot="cell"]:nth-child(3) {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Mobile: third column on its own row */
|
||||
@media (max-width: 40rem) {
|
||||
flex-wrap: wrap;
|
||||
|
||||
[data-slot="cell"]:nth-child(1),
|
||||
[data-slot="cell"]:nth-child(2) {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
[data-slot="cell"]:nth-child(3) {
|
||||
flex: 1 0 100%;
|
||||
border-left: none;
|
||||
border-top: 2px solid var(--color-border);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
import { Billing } from "@opencode/cloud-core/billing.js"
|
||||
import type { APIEvent } from "@solidjs/start/server"
|
||||
import { Database, eq, sql } from "@opencode/cloud-core/drizzle/index.js"
|
||||
import { BillingTable, PaymentTable } from "@opencode/cloud-core/schema/billing.sql.js"
|
||||
import { Identifier } from "@opencode/cloud-core/identifier.js"
|
||||
import { centsToMicroCents } from "@opencode/cloud-core/util/price.js"
|
||||
import { Actor } from "@opencode/cloud-core/actor.js"
|
||||
import { Resource } from "@opencode/cloud-resource"
|
||||
|
||||
export async function POST(input: APIEvent) {
|
||||
const body = await Billing.stripe().webhooks.constructEventAsync(
|
||||
await input.request.text(),
|
||||
input.request.headers.get("stripe-signature")!,
|
||||
Resource.STRIPE_WEBHOOK_SECRET.value,
|
||||
)
|
||||
|
||||
console.log(body.type, JSON.stringify(body, null, 2))
|
||||
if (body.type === "checkout.session.completed") {
|
||||
const workspaceID = body.data.object.metadata?.workspaceID
|
||||
const customerID = body.data.object.customer as string
|
||||
const paymentID = body.data.object.payment_intent as string
|
||||
const amount = body.data.object.amount_total
|
||||
|
||||
if (!workspaceID) throw new Error("Workspace ID not found")
|
||||
if (!customerID) throw new Error("Customer ID not found")
|
||||
if (!amount) throw new Error("Amount not found")
|
||||
if (!paymentID) throw new Error("Payment ID not found")
|
||||
|
||||
await Actor.provide("system", { workspaceID }, async () => {
|
||||
const customer = await Billing.get()
|
||||
if (customer?.customerID && customer.customerID !== customerID) throw new Error("Customer ID mismatch")
|
||||
|
||||
// set customer metadata
|
||||
if (!customer?.customerID) {
|
||||
await Billing.stripe().customers.update(customerID, {
|
||||
metadata: {
|
||||
workspaceID,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// get payment method for the payment intent
|
||||
const paymentIntent = await Billing.stripe().paymentIntents.retrieve(paymentID, {
|
||||
expand: ["payment_method"],
|
||||
})
|
||||
const paymentMethod = paymentIntent.payment_method
|
||||
if (!paymentMethod || typeof paymentMethod === "string") throw new Error("Payment method not expanded")
|
||||
|
||||
await Database.transaction(async (tx) => {
|
||||
await tx
|
||||
.update(BillingTable)
|
||||
.set({
|
||||
balance: sql`${BillingTable.balance} + ${centsToMicroCents(amount)}`,
|
||||
customerID,
|
||||
paymentMethodID: paymentMethod.id,
|
||||
paymentMethodLast4: paymentMethod.card!.last4,
|
||||
})
|
||||
.where(eq(BillingTable.workspaceID, workspaceID))
|
||||
await tx.insert(PaymentTable).values({
|
||||
workspaceID,
|
||||
id: Identifier.create("payment"),
|
||||
amount: centsToMicroCents(amount),
|
||||
paymentID,
|
||||
customerID,
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
console.log("finished handling")
|
||||
|
||||
return Response.json("ok", { status: 200 })
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
[data-page="workspace"] {
|
||||
line-height: 1;
|
||||
|
||||
@media (max-width: 30rem) {
|
||||
padding: var(--space-4);
|
||||
gap: var(--space-5);
|
||||
}
|
||||
|
||||
/* Workspace Header */
|
||||
[data-component="workspace-header"] {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: var(--space-4) var(--space-4);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
background-color: var(--color-bg);
|
||||
|
||||
@media (max-width: 30rem) {
|
||||
padding: 0 var(--space-4);
|
||||
margin: calc(-1 * var(--space-4));
|
||||
margin-bottom: var(--space-5);
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="header-brand"] {
|
||||
flex: 0 0 auto;
|
||||
padding-top: 4px;
|
||||
|
||||
svg {
|
||||
width: 138px;
|
||||
}
|
||||
|
||||
[data-component="site-title"] {
|
||||
font-size: var(--font-size-lg);
|
||||
font-weight: 600;
|
||||
color: var(--color-text);
|
||||
text-decoration: none;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="header-actions"] {
|
||||
display: flex;
|
||||
gap: var(--space-4);
|
||||
align-items: center;
|
||||
font-size: var(--font-size-sm);
|
||||
|
||||
span {
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
a,
|
||||
button {
|
||||
border: none;
|
||||
padding: 0;
|
||||
color: var(--color-text);
|
||||
text-decoration: underline;
|
||||
text-underline-offset: var(--space-0-75);
|
||||
text-decoration-thickness: 1px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
import "./workspace.css"
|
||||
import { useAuthSession } from "~/context/auth.session"
|
||||
import { IconLogo } from "../component/icon"
|
||||
import { action, redirect, RouteSectionProps } from "@solidjs/router"
|
||||
|
||||
const logout = action(async () => {
|
||||
"use server"
|
||||
const auth = await useAuthSession()
|
||||
const current = auth.data.current
|
||||
if (current)
|
||||
await auth.update((val) => {
|
||||
delete val.account[current]
|
||||
return val
|
||||
})
|
||||
|
||||
return redirect("/")
|
||||
})
|
||||
|
||||
export default function WorkspaceLayout(props: RouteSectionProps) {
|
||||
return (
|
||||
<main data-page="workspace">
|
||||
<header data-component="workspace-header">
|
||||
<div data-slot="header-brand">
|
||||
<a href="/" data-component="site-title">
|
||||
<IconLogo />
|
||||
</a>
|
||||
</div>
|
||||
<div data-slot="header-actions">
|
||||
<span>name@example.com</span>
|
||||
<form action={logout} method="post">
|
||||
<button type="submit" formaction={logout}>Logout</button>
|
||||
</form>
|
||||
</div>
|
||||
</header>
|
||||
<div data-slot="content">{props.children}</div>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
@@ -1,477 +0,0 @@
|
||||
/* Root container */
|
||||
[data-slot="root"] {
|
||||
max-width: 64rem;
|
||||
padding: var(--space-10) var(--space-4);
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-10);
|
||||
|
||||
[data-slot="sections"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-16);
|
||||
|
||||
section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-6);
|
||||
}
|
||||
section:not(:last-child) {
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
padding-bottom: var(--space-16);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Common elements */
|
||||
button {
|
||||
padding: var(--space-3) var(--space-4);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius-sm);
|
||||
background-color: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
font-size: var(--font-size-sm);
|
||||
font-family: var(--font-sans);
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-surface-hover);
|
||||
border-color: var(--color-accent);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-bg);
|
||||
border-color: var(--color-border);
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
&[color="primary"] {
|
||||
background-color: var(--color-primary);
|
||||
border-color: var(--color-primary);
|
||||
color: var(--color-primary-text);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-primary-hover);
|
||||
border-color: var(--color-primary-hover);
|
||||
}
|
||||
}
|
||||
|
||||
&[color="ghost"] {
|
||||
background-color: transparent;
|
||||
border-color: transparent;
|
||||
color: var(--color-text-muted);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-surface-hover);
|
||||
border-color: var(--color-border);
|
||||
color: var(--color-text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--color-text);
|
||||
text-decoration: underline;
|
||||
text-underline-offset: var(--space-0-75);
|
||||
text-decoration-thickness: 1px;
|
||||
}
|
||||
|
||||
[data-slot="empty-state"] {
|
||||
padding: var(--space-20) var(--space-6);
|
||||
text-align: center;
|
||||
border: 1px dashed var(--color-border);
|
||||
border-radius: var(--border-radius-sm);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-2);
|
||||
|
||||
p {
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--color-text-muted);
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Title section */
|
||||
[data-slot="title-section"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-2);
|
||||
padding-bottom: var(--space-8);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
|
||||
h1 {
|
||||
font-size: var(--font-size-2xl);
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
letter-spacing: -0.03125rem;
|
||||
margin: 0;
|
||||
text-transform: uppercase;
|
||||
|
||||
@media (max-width: 30rem) {
|
||||
font-size: var(--font-size-xl);
|
||||
line-height: 1.25;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: var(--font-size-md);
|
||||
color: var(--color-text-muted);
|
||||
|
||||
a {
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Section titles */
|
||||
[data-slot="section-title"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-1);
|
||||
|
||||
h2 {
|
||||
font-size: var(--font-size-md);
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
letter-spacing: -0.03125rem;
|
||||
margin: 0;
|
||||
color: var(--color-text-secondary);
|
||||
text-transform: uppercase;
|
||||
|
||||
@media (max-width: 30rem) {
|
||||
font-size: var(--font-size-lg);
|
||||
line-height: 1.25;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
}
|
||||
|
||||
/* API Keys Section */
|
||||
[data-slot="api-keys-section"] {
|
||||
[data-slot="create-form"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-4);
|
||||
background-color: var(--color-bg-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius-sm);
|
||||
max-width: 32rem;
|
||||
|
||||
input {
|
||||
padding: var(--space-2) var(--space-3);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius-sm);
|
||||
background-color: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
font-size: var(--font-size-sm);
|
||||
font-family: var(--font-mono);
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-accent);
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: var(--color-text-disabled);
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="form-actions"] {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="api-keys-table"] {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
[data-slot="api-keys-table-element"] {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: var(--font-size-sm);
|
||||
|
||||
thead {
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
th {
|
||||
padding: var(--space-3) var(--space-4);
|
||||
text-align: left;
|
||||
font-weight: normal;
|
||||
color: var(--color-text-muted);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: var(--space-3) var(--space-4);
|
||||
border-bottom: 1px solid var(--color-border-muted);
|
||||
color: var(--color-text-muted);
|
||||
font-family: var(--font-mono);
|
||||
|
||||
&[data-slot="key-name"] {
|
||||
color: var(--color-text);
|
||||
font-family: var(--font-sans);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&[data-slot="key-value"] {
|
||||
font-family: var(--font-mono);
|
||||
|
||||
div {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
}
|
||||
|
||||
&[data-slot="key-date"] {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
&[data-slot="key-actions"] {
|
||||
font-family: var(--font-sans);
|
||||
}
|
||||
}
|
||||
|
||||
tbody tr {
|
||||
&:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 40rem) {
|
||||
th,
|
||||
td {
|
||||
padding: var(--space-2) var(--space-3);
|
||||
font-size: var(--font-size-xs);
|
||||
}
|
||||
|
||||
th {
|
||||
&:nth-child(3) /* Date */ {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
&:nth-child(3) /* Date */ {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Balance Section */
|
||||
[data-slot="balance-section"] {
|
||||
[data-slot="balance"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-4);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius-sm);
|
||||
min-width: 14.5rem;
|
||||
width: fit-content;
|
||||
|
||||
[data-slot="amount"] {
|
||||
padding: var(--space-3-5) var(--space-4);
|
||||
background-color: var(--color-bg-surface);
|
||||
border-radius: var(--border-radius-sm);
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: var(--space-1);
|
||||
justify-content: flex-end;
|
||||
|
||||
&.danger {
|
||||
[data-slot="value"] {
|
||||
color: var(--color-danger);
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="currency"] {
|
||||
position: relative;
|
||||
bottom: 2px;
|
||||
font-size: var(--font-size-lg);
|
||||
color: var(--color-text-muted);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
[data-slot="value"] {
|
||||
font-size: var(--font-size-3xl);
|
||||
font-weight: 500;
|
||||
color: var(--color-text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Payments Section */
|
||||
[data-slot="payments-section"] {
|
||||
[data-slot="payments-table"] {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
[data-slot="payments-table-element"] {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: var(--font-size-sm);
|
||||
|
||||
thead {
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
th {
|
||||
padding: var(--space-3) var(--space-4);
|
||||
text-align: left;
|
||||
font-weight: normal;
|
||||
color: var(--color-text-muted);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: var(--space-3) var(--space-4);
|
||||
border-bottom: 1px solid var(--color-border-muted);
|
||||
color: var(--color-text-muted);
|
||||
font-family: var(--font-mono);
|
||||
|
||||
&[data-slot="payment-date"] {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
&[data-slot="payment-id"] {
|
||||
font-family: var(--font-mono);
|
||||
font-weight: 400;
|
||||
color: var(--color-text-muted);
|
||||
max-width: 200px;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
&[data-slot="payment-amount"] {
|
||||
color: var(--color-text);
|
||||
}
|
||||
}
|
||||
|
||||
tbody tr {
|
||||
&:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 40rem) {
|
||||
th,
|
||||
td {
|
||||
padding: var(--space-2) var(--space-3);
|
||||
font-size: var(--font-size-xs);
|
||||
}
|
||||
|
||||
th {
|
||||
&:nth-child(2) /* Payment ID */ {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
&:nth-child(2) /* Payment ID */ {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Usage Section */
|
||||
[data-slot="usage-section"] {
|
||||
[data-slot="usage-table"] {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
[data-slot="usage-table-element"] {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: var(--font-size-sm);
|
||||
|
||||
thead {
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
th {
|
||||
padding: var(--space-3) var(--space-4);
|
||||
text-align: left;
|
||||
font-weight: normal;
|
||||
color: var(--color-text-muted);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: var(--space-3) var(--space-4);
|
||||
border-bottom: 1px solid var(--color-border-muted);
|
||||
color: var(--color-text-muted);
|
||||
font-family: var(--font-mono);
|
||||
|
||||
&[data-slot="usage-date"] {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
&[data-slot="usage-model"] {
|
||||
font-family: var(--font-sans);
|
||||
font-weight: 400;
|
||||
color: var(--color-text-secondary);
|
||||
max-width: 200px;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
&[data-slot="usage-cost"] {
|
||||
color: var(--color-text);
|
||||
}
|
||||
}
|
||||
|
||||
tbody tr {
|
||||
&:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 40rem) {
|
||||
th,
|
||||
td {
|
||||
padding: var(--space-2) var(--space-3);
|
||||
font-size: var(--font-size-xs);
|
||||
}
|
||||
|
||||
th {
|
||||
&:nth-child(2) /* Model */ {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
&:nth-child(2) /* Model */ {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,523 +0,0 @@
|
||||
import { Billing } from "@opencode/cloud-core/billing.js"
|
||||
import { Key } from "@opencode/cloud-core/key.js"
|
||||
import { action, createAsync, query, useAction, useSubmission, json } from "@solidjs/router"
|
||||
import { createSignal, For, onMount, Show } from "solid-js"
|
||||
import { getActor } from "~/context/auth"
|
||||
import { withActor } from "~/context/auth.withActor"
|
||||
import { IconCopy, IconCheck } from "~/component/icon"
|
||||
import "./[id].css"
|
||||
import { User } from "@opencode/cloud-core/user.js"
|
||||
import { Actor } from "@opencode/cloud-core/actor.js"
|
||||
|
||||
/////////////////////////////////////
|
||||
// Keys related queries and actions
|
||||
/////////////////////////////////////
|
||||
|
||||
|
||||
const listKeys = query(() => {
|
||||
"use server"
|
||||
return withActor(() => Key.list())
|
||||
}, "key.list")
|
||||
|
||||
const createKey = action(async (name: string) => {
|
||||
"use server"
|
||||
return json(
|
||||
withActor(() => Key.create({ name })),
|
||||
{ revalidate: listKeys.key },
|
||||
)
|
||||
}, "key.create")
|
||||
|
||||
const removeKey = action(async (id: string) => {
|
||||
"use server"
|
||||
return json(
|
||||
withActor(() => Key.remove({ id })),
|
||||
{ revalidate: listKeys.key },
|
||||
)
|
||||
}, "key.remove")
|
||||
|
||||
/////////////////////////////////////
|
||||
// Billing related queries and actions
|
||||
/////////////////////////////////////
|
||||
|
||||
const getBillingInfo = query(async () => {
|
||||
"use server"
|
||||
return withActor(async () => {
|
||||
const actor = Actor.assert("user")
|
||||
const [user, billing, payments, usage] = await Promise.all([
|
||||
User.fromID(actor.properties.userID),
|
||||
Billing.get(),
|
||||
Billing.payments(),
|
||||
Billing.usages(),
|
||||
])
|
||||
return { user, billing, payments, usage }
|
||||
})
|
||||
}, "billingInfo")
|
||||
|
||||
const createCheckoutUrl = action(async (successUrl: string, cancelUrl: string) => {
|
||||
"use server"
|
||||
return withActor(() => Billing.generateCheckoutUrl({ successUrl, cancelUrl }))
|
||||
}, "checkoutUrl")
|
||||
|
||||
const createPortalUrl = action(async (returnUrl: string) => {
|
||||
"use server"
|
||||
return withActor(() => Billing.generatePortalUrl({ returnUrl }))
|
||||
}, "portalUrl")
|
||||
|
||||
const dummyUsageData = [
|
||||
{
|
||||
model: "claude-3-5-sonnet-20241022",
|
||||
inputTokens: 1250,
|
||||
outputTokens: 890,
|
||||
reasoningTokens: 150,
|
||||
cacheReadTokens: 0,
|
||||
cacheWriteTokens: 45,
|
||||
cost: 12340000,
|
||||
timeCreated: new Date("2025-01-28T10:30:00Z"),
|
||||
},
|
||||
{
|
||||
model: "claude-3-haiku-20240307",
|
||||
inputTokens: 2100,
|
||||
outputTokens: 450,
|
||||
reasoningTokens: null,
|
||||
cacheReadTokens: 120,
|
||||
cacheWriteTokens: 0,
|
||||
cost: 5670000,
|
||||
timeCreated: new Date("2025-01-27T15:22:00Z"),
|
||||
},
|
||||
{
|
||||
model: "claude-3-5-sonnet-20241022",
|
||||
inputTokens: 850,
|
||||
outputTokens: 1200,
|
||||
reasoningTokens: 220,
|
||||
cacheReadTokens: 30,
|
||||
cacheWriteTokens: 15,
|
||||
cost: 18990000,
|
||||
timeCreated: new Date("2025-01-27T09:15:00Z"),
|
||||
},
|
||||
{
|
||||
model: "claude-3-opus-20240229",
|
||||
inputTokens: 3200,
|
||||
outputTokens: 1800,
|
||||
reasoningTokens: 400,
|
||||
cacheReadTokens: 0,
|
||||
cacheWriteTokens: 100,
|
||||
cost: 45670000,
|
||||
timeCreated: new Date("2025-01-26T14:45:00Z"),
|
||||
},
|
||||
{
|
||||
model: "claude-3-haiku-20240307",
|
||||
inputTokens: 650,
|
||||
outputTokens: 280,
|
||||
reasoningTokens: null,
|
||||
cacheReadTokens: 200,
|
||||
cacheWriteTokens: 0,
|
||||
cost: 2340000,
|
||||
timeCreated: new Date("2025-01-25T16:18:00Z"),
|
||||
},
|
||||
]
|
||||
|
||||
const dummyPaymentData = [
|
||||
{
|
||||
id: "pay_1Ab2Cd3Ef4Gh5678",
|
||||
amount: 2000000000,
|
||||
timeCreated: new Date("2025-01-28T14:32:00Z"),
|
||||
},
|
||||
{
|
||||
id: "pay_9Ij8Kl7Mn6Op5432",
|
||||
amount: 1000000000,
|
||||
timeCreated: new Date("2025-01-25T09:18:00Z"),
|
||||
},
|
||||
{
|
||||
id: "pay_5Qr4St3Uv2Wx1098",
|
||||
amount: 5000000000,
|
||||
timeCreated: new Date("2025-01-20T16:45:00Z"),
|
||||
},
|
||||
{
|
||||
id: "pay_7Yz6Ab5Cd4Ef3210",
|
||||
amount: 1500000000,
|
||||
timeCreated: new Date("2025-01-15T11:22:00Z"),
|
||||
},
|
||||
{
|
||||
id: "pay_3Gh2Ij1Kl0Mn9876",
|
||||
amount: 3000000000,
|
||||
timeCreated: new Date("2025-01-10T13:55:00Z"),
|
||||
},
|
||||
]
|
||||
|
||||
const dummyApiKeyData = [
|
||||
{
|
||||
id: "key_1Ab2Cd3Ef4Gh5678",
|
||||
name: "Production API",
|
||||
key: "oc_live_sk_1Ab2Cd3Ef4Gh567890123456789012345678901234567890",
|
||||
timeCreated: new Date("2025-01-28T14:32:00Z"),
|
||||
timeUsed: new Date("2025-01-29T09:15:00Z"),
|
||||
},
|
||||
{
|
||||
id: "key_9Ij8Kl7Mn6Op5432",
|
||||
name: "Development Key",
|
||||
key: "oc_test_sk_9Ij8Kl7Mn6Op543210987654321098765432109876543210",
|
||||
timeCreated: new Date("2025-01-25T09:18:00Z"),
|
||||
timeUsed: null,
|
||||
},
|
||||
{
|
||||
id: "key_5Qr4St3Uv2Wx1098",
|
||||
name: "CI/CD Pipeline",
|
||||
key: "oc_live_sk_5Qr4St3Uv2Wx109876543210987654321098765432109876",
|
||||
timeCreated: new Date("2025-01-20T16:45:00Z"),
|
||||
timeUsed: new Date("2025-01-28T12:30:00Z"),
|
||||
},
|
||||
]
|
||||
|
||||
export default function () {
|
||||
const actor = createAsync(() => getActor())
|
||||
onMount(() => {
|
||||
console.log("MOUNTED", actor())
|
||||
})
|
||||
|
||||
/////////////////
|
||||
// Keys section
|
||||
/////////////////
|
||||
const keys = createAsync(() => listKeys())
|
||||
const createKeyAction = useAction(createKey)
|
||||
const removeKeyAction = useAction(removeKey)
|
||||
const createKeySubmission = useSubmission(createKey)
|
||||
const [showCreateForm, setShowCreateForm] = createSignal(false)
|
||||
const [keyName, setKeyName] = createSignal("")
|
||||
const [copiedKeyId, setCopiedKeyId] = createSignal<string | null>(null)
|
||||
|
||||
const formatDate = (date: Date) => {
|
||||
return date.toLocaleDateString()
|
||||
}
|
||||
|
||||
const formatDateForTable = (date: Date) => {
|
||||
const options: Intl.DateTimeFormatOptions = {
|
||||
day: "numeric",
|
||||
month: "short",
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
hour12: true,
|
||||
}
|
||||
return date.toLocaleDateString("en-GB", options).replace(",", ",")
|
||||
}
|
||||
|
||||
const formatDateUTC = (date: Date) => {
|
||||
const options: Intl.DateTimeFormatOptions = {
|
||||
weekday: "short",
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
timeZoneName: "short",
|
||||
timeZone: "UTC",
|
||||
}
|
||||
return date.toLocaleDateString("en-US", options)
|
||||
}
|
||||
|
||||
const formatKey = (key: string) => {
|
||||
if (key.length <= 11) return key
|
||||
return `${key.slice(0, 7)}...${key.slice(-4)}`
|
||||
}
|
||||
|
||||
const copyToClipboard = async (text: string) => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text)
|
||||
} catch (error) {
|
||||
console.error("Failed to copy to clipboard:", error)
|
||||
}
|
||||
}
|
||||
|
||||
const copyKeyToClipboard = async (text: string, keyId: string) => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text)
|
||||
setCopiedKeyId(keyId)
|
||||
setTimeout(() => setCopiedKeyId(null), 1500)
|
||||
} catch (error) {
|
||||
console.error("Failed to copy to clipboard:", error)
|
||||
}
|
||||
}
|
||||
|
||||
const handleCreateKey = async () => {
|
||||
if (!keyName().trim()) return
|
||||
|
||||
try {
|
||||
await createKeyAction(keyName().trim())
|
||||
setKeyName("")
|
||||
setShowCreateForm(false)
|
||||
} catch (error) {
|
||||
console.error("Failed to create API key:", error)
|
||||
}
|
||||
}
|
||||
|
||||
const handleDeleteKey = async (keyId: string) => {
|
||||
if (!confirm("Are you sure you want to delete this API key?")) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await removeKeyAction(keyId)
|
||||
} catch (error) {
|
||||
console.error("Failed to delete API key:", error)
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////
|
||||
// Billing section
|
||||
/////////////////
|
||||
const billingInfo = createAsync(() => getBillingInfo())
|
||||
const createCheckoutUrlAction = useAction(createCheckoutUrl)
|
||||
const createCheckoutUrlSubmission = useSubmission(createCheckoutUrl)
|
||||
|
||||
const handleBuyCredits = async () => {
|
||||
try {
|
||||
const baseUrl = window.location.href
|
||||
const checkoutUrl = await createCheckoutUrlAction(baseUrl, baseUrl)
|
||||
if (checkoutUrl) {
|
||||
window.location.href = checkoutUrl
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to get checkout URL:", error)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-slot="root">
|
||||
{/* Title */}
|
||||
<section data-slot="title-section">
|
||||
<h1>Gateway</h1>
|
||||
<p>
|
||||
Coding models optimized for use with opencode. <a href="/docs">Learn more</a>.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<div data-slot="sections">
|
||||
{/* Actor Section */}
|
||||
<section data-slot="actor-section">
|
||||
<div data-slot="section-title">
|
||||
<h2>Actor</h2>
|
||||
<p>Current authenticated user information and session details.</p>
|
||||
</div>
|
||||
<div>{JSON.stringify(actor())}</div>
|
||||
</section>
|
||||
|
||||
{/* API Keys Section */}
|
||||
<section data-slot="api-keys-section">
|
||||
<div data-slot="section-title">
|
||||
<h2>API Keys</h2>
|
||||
<p>Manage your API keys for accessing opencode services.</p>
|
||||
</div>
|
||||
<Show
|
||||
when={!showCreateForm()}
|
||||
fallback={
|
||||
<div data-slot="create-form">
|
||||
<input
|
||||
data-component="input"
|
||||
type="text"
|
||||
placeholder="Enter key name"
|
||||
value={keyName()}
|
||||
onInput={(e) => setKeyName(e.currentTarget.value)}
|
||||
onKeyPress={(e) => e.key === "Enter" && handleCreateKey()}
|
||||
/>
|
||||
<div data-slot="form-actions">
|
||||
<button
|
||||
color="primary"
|
||||
disabled={createKeySubmission.pending || !keyName().trim()}
|
||||
onClick={handleCreateKey}
|
||||
>
|
||||
{createKeySubmission.pending ? "Creating..." : "Create"}
|
||||
</button>
|
||||
<button
|
||||
color="ghost"
|
||||
onClick={() => {
|
||||
setShowCreateForm(false)
|
||||
setKeyName("")
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<button
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
console.log("clicked")
|
||||
setShowCreateForm(true)
|
||||
}}
|
||||
>
|
||||
Create API Key
|
||||
</button>
|
||||
</Show>
|
||||
<div data-slot="api-keys-table">
|
||||
<Show
|
||||
when={keys()?.length}
|
||||
fallback={
|
||||
<div data-slot="empty-state">
|
||||
<p>Create an opencode Gateway API key</p>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<table data-slot="api-keys-table-element">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Key</th>
|
||||
<th>Created</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<For each={keys()!}>
|
||||
{/* Real data: keys() */}
|
||||
{(key) => (
|
||||
<tr>
|
||||
<td data-slot="key-name">{key.name}</td>
|
||||
<td data-slot="key-value">
|
||||
<div onClick={() => copyKeyToClipboard(key.key, key.id)} title="Click to copy API key">
|
||||
<span>{formatKey(key.key)}</span>
|
||||
<Show
|
||||
when={copiedKeyId() === key.id}
|
||||
fallback={<IconCopy style={{ width: "14px", height: "14px" }} />}
|
||||
>
|
||||
<IconCheck style={{ width: "14px", height: "14px" }} />
|
||||
</Show>
|
||||
</div>
|
||||
</td>
|
||||
<td data-slot="key-date" title={formatDateUTC(key.timeCreated)}>
|
||||
{formatDateForTable(key.timeCreated)}
|
||||
</td>
|
||||
<td data-slot="key-actions">
|
||||
<button color="ghost" onClick={() => handleDeleteKey(key.id)} title="Delete API key">
|
||||
Delete
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</For>
|
||||
</tbody>
|
||||
</table>
|
||||
</Show>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Balance Section */}
|
||||
<section data-slot="balance-section">
|
||||
<div data-slot="section-title">
|
||||
<h2>Balance</h2>
|
||||
<p>Add credits to your account.</p>
|
||||
</div>
|
||||
<div data-slot="balance">
|
||||
<div
|
||||
data-slot="amount"
|
||||
classList={{
|
||||
danger: (() => {
|
||||
const balanceStr = ((billingInfo()?.billing?.balance ?? 0) / 100000000).toFixed(2)
|
||||
return balanceStr === "0.00" || balanceStr === "-0.00"
|
||||
})(),
|
||||
}}
|
||||
>
|
||||
<span data-slot="currency">$</span>
|
||||
<span data-slot="value">
|
||||
{(() => {
|
||||
const balanceStr = ((billingInfo()?.billing?.balance ?? 0) / 100000000).toFixed(2)
|
||||
return balanceStr === "-0.00" ? "0.00" : balanceStr
|
||||
})()}
|
||||
</span>
|
||||
</div>
|
||||
<button color="primary" disabled={createCheckoutUrlSubmission.pending} onClick={handleBuyCredits}>
|
||||
{createCheckoutUrlSubmission.pending ? "Loading..." : "Buy Credits"}
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Payments Section */}
|
||||
<Show when={dummyPaymentData.length > 0}>
|
||||
{/* Real data condition: billingInfo() && billingInfo()!.payments.length > 0 */}
|
||||
<section data-slot="payments-section">
|
||||
<div data-slot="section-title">
|
||||
<h2>Payments History</h2>
|
||||
<p>Recent payment transactions.</p>
|
||||
</div>
|
||||
<div data-slot="payments-table">
|
||||
<table data-slot="payments-table-element">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Payment ID</th>
|
||||
<th>Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<For each={dummyPaymentData}>
|
||||
{/* Real data: billingInfo()?.payments */}
|
||||
{(payment) => {
|
||||
const date = new Date(payment.timeCreated)
|
||||
return (
|
||||
<tr>
|
||||
<td data-slot="payment-date" title={formatDateUTC(date)}>
|
||||
{formatDateForTable(date)}
|
||||
</td>
|
||||
<td data-slot="payment-id">{payment.id}</td>
|
||||
<td data-slot="payment-amount">${((payment.amount ?? 0) / 100000000).toFixed(2)}</td>
|
||||
</tr>
|
||||
)
|
||||
}}
|
||||
</For>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</Show>
|
||||
|
||||
{/* Usage Section */}
|
||||
<section data-slot="usage-section">
|
||||
<div data-slot="section-title">
|
||||
<h2>Usage History</h2>
|
||||
<p>Recent API usage and costs.</p>
|
||||
</div>
|
||||
<div data-slot="usage-table">
|
||||
<Show
|
||||
when={dummyUsageData.length > 0}
|
||||
fallback={
|
||||
<div data-slot="empty-state">
|
||||
<p>Make your first API call to get started.</p>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<table data-slot="usage-table-element">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Model</th>
|
||||
<th>Tokens</th>
|
||||
<th>Cost</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<For each={dummyUsageData}>
|
||||
{(usage) => {
|
||||
const totalTokens = usage.inputTokens + usage.outputTokens + (usage.reasoningTokens || 0)
|
||||
const date = new Date(usage.timeCreated)
|
||||
return (
|
||||
<tr>
|
||||
<td data-slot="usage-date" title={formatDateUTC(date)}>
|
||||
{formatDateForTable(date)}
|
||||
</td>
|
||||
<td data-slot="usage-model">{usage.model}</td>
|
||||
<td data-slot="usage-tokens">{totalTokens.toLocaleString()}</td>
|
||||
<td data-slot="usage-cost">${((usage.cost ?? 0) / 100000000).toFixed(4)}</td>
|
||||
</tr>
|
||||
)
|
||||
}}
|
||||
</For>
|
||||
</tbody>
|
||||
</table>
|
||||
</Show>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
html {
|
||||
line-height: 1;
|
||||
background-color: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-sans);
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
CREATE TABLE "billing" (
|
||||
"id" varchar(30) NOT NULL,
|
||||
"workspace_id" varchar(30) NOT NULL,
|
||||
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"time_deleted" timestamp with time zone,
|
||||
"customer_id" varchar(255),
|
||||
"payment_method_id" varchar(255),
|
||||
"payment_method_last4" varchar(4),
|
||||
"balance" bigint NOT NULL,
|
||||
"reload" boolean,
|
||||
CONSTRAINT "billing_workspace_id_id_pk" PRIMARY KEY("workspace_id","id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "payment" (
|
||||
"id" varchar(30) NOT NULL,
|
||||
"workspace_id" varchar(30) NOT NULL,
|
||||
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"time_deleted" timestamp with time zone,
|
||||
"customer_id" varchar(255),
|
||||
"payment_id" varchar(255),
|
||||
"amount" bigint NOT NULL,
|
||||
CONSTRAINT "payment_workspace_id_id_pk" PRIMARY KEY("workspace_id","id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "usage" (
|
||||
"id" varchar(30) NOT NULL,
|
||||
"workspace_id" varchar(30) NOT NULL,
|
||||
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"time_deleted" timestamp with time zone,
|
||||
"request_id" varchar(255),
|
||||
"model" varchar(255) NOT NULL,
|
||||
"input_tokens" integer NOT NULL,
|
||||
"output_tokens" integer NOT NULL,
|
||||
"reasoning_tokens" integer,
|
||||
"cache_read_tokens" integer,
|
||||
"cache_write_tokens" integer,
|
||||
"cost" bigint NOT NULL,
|
||||
CONSTRAINT "usage_workspace_id_id_pk" PRIMARY KEY("workspace_id","id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "user" (
|
||||
"id" varchar(30) NOT NULL,
|
||||
"workspace_id" varchar(30) NOT NULL,
|
||||
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"time_deleted" timestamp with time zone,
|
||||
"email" text NOT NULL,
|
||||
"name" varchar(255) NOT NULL,
|
||||
"time_seen" timestamp with time zone,
|
||||
"color" integer,
|
||||
CONSTRAINT "user_workspace_id_id_pk" PRIMARY KEY("workspace_id","id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "workspace" (
|
||||
"id" varchar(30) PRIMARY KEY NOT NULL,
|
||||
"slug" varchar(255),
|
||||
"name" varchar(255),
|
||||
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"time_deleted" timestamp with time zone
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "billing" ADD CONSTRAINT "billing_workspace_id_workspace_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspace"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "payment" ADD CONSTRAINT "payment_workspace_id_workspace_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspace"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "usage" ADD CONSTRAINT "usage_workspace_id_workspace_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspace"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "user" ADD CONSTRAINT "user_workspace_id_workspace_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspace"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "user_email" ON "user" USING btree ("workspace_id","email");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "slug" ON "workspace" USING btree ("slug");
|
||||
@@ -1,8 +0,0 @@
|
||||
CREATE TABLE "account" (
|
||||
"id" varchar(30) NOT NULL,
|
||||
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"time_deleted" timestamp with time zone,
|
||||
"email" varchar(255) NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "email" ON "account" USING btree ("email");
|
||||
@@ -1,14 +0,0 @@
|
||||
CREATE TABLE "key" (
|
||||
"id" varchar(30) NOT NULL,
|
||||
"workspace_id" varchar(30) NOT NULL,
|
||||
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"time_deleted" timestamp with time zone,
|
||||
"user_id" text NOT NULL,
|
||||
"name" varchar(255) NOT NULL,
|
||||
"key" varchar(255) NOT NULL,
|
||||
"time_used" timestamp with time zone,
|
||||
CONSTRAINT "key_workspace_id_id_pk" PRIMARY KEY("workspace_id","id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "key" ADD CONSTRAINT "key_workspace_id_workspace_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspace"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "global_key" ON "key" USING btree ("key");
|
||||
@@ -1 +0,0 @@
|
||||
ALTER TABLE "usage" DROP COLUMN "request_id";
|
||||
@@ -1,461 +0,0 @@
|
||||
{
|
||||
"id": "9b5cec8c-8b59-4d7a-bb5c-76ade1c83d6f",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.billing": {
|
||||
"name": "billing",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "varchar(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"workspace_id": {
|
||||
"name": "workspace_id",
|
||||
"type": "varchar(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"customer_id": {
|
||||
"name": "customer_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"payment_method_id": {
|
||||
"name": "payment_method_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"payment_method_last4": {
|
||||
"name": "payment_method_last4",
|
||||
"type": "varchar(4)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"balance": {
|
||||
"name": "balance",
|
||||
"type": "bigint",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"reload": {
|
||||
"name": "reload",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"billing_workspace_id_workspace_id_fk": {
|
||||
"name": "billing_workspace_id_workspace_id_fk",
|
||||
"tableFrom": "billing",
|
||||
"tableTo": "workspace",
|
||||
"columnsFrom": [
|
||||
"workspace_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"billing_workspace_id_id_pk": {
|
||||
"name": "billing_workspace_id_id_pk",
|
||||
"columns": [
|
||||
"workspace_id",
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.payment": {
|
||||
"name": "payment",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "varchar(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"workspace_id": {
|
||||
"name": "workspace_id",
|
||||
"type": "varchar(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"customer_id": {
|
||||
"name": "customer_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"payment_id": {
|
||||
"name": "payment_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"amount": {
|
||||
"name": "amount",
|
||||
"type": "bigint",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"payment_workspace_id_workspace_id_fk": {
|
||||
"name": "payment_workspace_id_workspace_id_fk",
|
||||
"tableFrom": "payment",
|
||||
"tableTo": "workspace",
|
||||
"columnsFrom": [
|
||||
"workspace_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"payment_workspace_id_id_pk": {
|
||||
"name": "payment_workspace_id_id_pk",
|
||||
"columns": [
|
||||
"workspace_id",
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.usage": {
|
||||
"name": "usage",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "varchar(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"workspace_id": {
|
||||
"name": "workspace_id",
|
||||
"type": "varchar(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"request_id": {
|
||||
"name": "request_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"model": {
|
||||
"name": "model",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"input_tokens": {
|
||||
"name": "input_tokens",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"output_tokens": {
|
||||
"name": "output_tokens",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"reasoning_tokens": {
|
||||
"name": "reasoning_tokens",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"cache_read_tokens": {
|
||||
"name": "cache_read_tokens",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"cache_write_tokens": {
|
||||
"name": "cache_write_tokens",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"cost": {
|
||||
"name": "cost",
|
||||
"type": "bigint",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"usage_workspace_id_workspace_id_fk": {
|
||||
"name": "usage_workspace_id_workspace_id_fk",
|
||||
"tableFrom": "usage",
|
||||
"tableTo": "workspace",
|
||||
"columnsFrom": [
|
||||
"workspace_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"usage_workspace_id_id_pk": {
|
||||
"name": "usage_workspace_id_id_pk",
|
||||
"columns": [
|
||||
"workspace_id",
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.user": {
|
||||
"name": "user",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "varchar(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"workspace_id": {
|
||||
"name": "workspace_id",
|
||||
"type": "varchar(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"time_seen": {
|
||||
"name": "time_seen",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"color": {
|
||||
"name": "color",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"user_email": {
|
||||
"name": "user_email",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "workspace_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"user_workspace_id_workspace_id_fk": {
|
||||
"name": "user_workspace_id_workspace_id_fk",
|
||||
"tableFrom": "user",
|
||||
"tableTo": "workspace",
|
||||
"columnsFrom": [
|
||||
"workspace_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"user_workspace_id_id_pk": {
|
||||
"name": "user_workspace_id_id_pk",
|
||||
"columns": [
|
||||
"workspace_id",
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.workspace": {
|
||||
"name": "workspace",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "varchar(30)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"slug": {
|
||||
"name": "slug",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"slug": {
|
||||
"name": "slug",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "slug",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
@@ -1,515 +0,0 @@
|
||||
{
|
||||
"id": "bf9e9084-4073-4ecb-8e56-5610816c9589",
|
||||
"prevId": "9b5cec8c-8b59-4d7a-bb5c-76ade1c83d6f",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.account": {
|
||||
"name": "account",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "varchar(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"email": {
|
||||
"name": "email",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.billing": {
|
||||
"name": "billing",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "varchar(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"workspace_id": {
|
||||
"name": "workspace_id",
|
||||
"type": "varchar(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"customer_id": {
|
||||
"name": "customer_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"payment_method_id": {
|
||||
"name": "payment_method_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"payment_method_last4": {
|
||||
"name": "payment_method_last4",
|
||||
"type": "varchar(4)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"balance": {
|
||||
"name": "balance",
|
||||
"type": "bigint",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"reload": {
|
||||
"name": "reload",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"billing_workspace_id_workspace_id_fk": {
|
||||
"name": "billing_workspace_id_workspace_id_fk",
|
||||
"tableFrom": "billing",
|
||||
"tableTo": "workspace",
|
||||
"columnsFrom": [
|
||||
"workspace_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"billing_workspace_id_id_pk": {
|
||||
"name": "billing_workspace_id_id_pk",
|
||||
"columns": [
|
||||
"workspace_id",
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.payment": {
|
||||
"name": "payment",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "varchar(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"workspace_id": {
|
||||
"name": "workspace_id",
|
||||
"type": "varchar(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"customer_id": {
|
||||
"name": "customer_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"payment_id": {
|
||||
"name": "payment_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"amount": {
|
||||
"name": "amount",
|
||||
"type": "bigint",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"payment_workspace_id_workspace_id_fk": {
|
||||
"name": "payment_workspace_id_workspace_id_fk",
|
||||
"tableFrom": "payment",
|
||||
"tableTo": "workspace",
|
||||
"columnsFrom": [
|
||||
"workspace_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"payment_workspace_id_id_pk": {
|
||||
"name": "payment_workspace_id_id_pk",
|
||||
"columns": [
|
||||
"workspace_id",
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.usage": {
|
||||
"name": "usage",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "varchar(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"workspace_id": {
|
||||
"name": "workspace_id",
|
||||
"type": "varchar(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"request_id": {
|
||||
"name": "request_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"model": {
|
||||
"name": "model",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"input_tokens": {
|
||||
"name": "input_tokens",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"output_tokens": {
|
||||
"name": "output_tokens",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"reasoning_tokens": {
|
||||
"name": "reasoning_tokens",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"cache_read_tokens": {
|
||||
"name": "cache_read_tokens",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"cache_write_tokens": {
|
||||
"name": "cache_write_tokens",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"cost": {
|
||||
"name": "cost",
|
||||
"type": "bigint",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"usage_workspace_id_workspace_id_fk": {
|
||||
"name": "usage_workspace_id_workspace_id_fk",
|
||||
"tableFrom": "usage",
|
||||
"tableTo": "workspace",
|
||||
"columnsFrom": [
|
||||
"workspace_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"usage_workspace_id_id_pk": {
|
||||
"name": "usage_workspace_id_id_pk",
|
||||
"columns": [
|
||||
"workspace_id",
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.user": {
|
||||
"name": "user",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "varchar(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"workspace_id": {
|
||||
"name": "workspace_id",
|
||||
"type": "varchar(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"time_seen": {
|
||||
"name": "time_seen",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"color": {
|
||||
"name": "color",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"user_email": {
|
||||
"name": "user_email",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "workspace_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"user_workspace_id_workspace_id_fk": {
|
||||
"name": "user_workspace_id_workspace_id_fk",
|
||||
"tableFrom": "user",
|
||||
"tableTo": "workspace",
|
||||
"columnsFrom": [
|
||||
"workspace_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"user_workspace_id_id_pk": {
|
||||
"name": "user_workspace_id_id_pk",
|
||||
"columns": [
|
||||
"workspace_id",
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.workspace": {
|
||||
"name": "workspace",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "varchar(30)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"slug": {
|
||||
"name": "slug",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"slug": {
|
||||
"name": "slug",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "slug",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
{
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "7",
|
||||
"when": 1754518198186,
|
||||
"tag": "0000_amused_mojo",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 1,
|
||||
"version": "7",
|
||||
"when": 1754609655262,
|
||||
"tag": "0001_thankful_chat",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 2,
|
||||
"version": "7",
|
||||
"when": 1754627626945,
|
||||
"tag": "0002_stale_jackal",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 3,
|
||||
"version": "7",
|
||||
"when": 1754672464106,
|
||||
"tag": "0003_tranquil_spencer_smythe",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode/cloud-core",
|
||||
"version": "0.5.29",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-sts": "3.782.0",
|
||||
"@opencode/cloud-resource": "workspace:*",
|
||||
"drizzle-orm": "0.41.0",
|
||||
"postgres": "3.4.7",
|
||||
"stripe": "18.0.0",
|
||||
"ulid": "3.0.0"
|
||||
},
|
||||
"exports": {
|
||||
"./*": "./src/*"
|
||||
},
|
||||
"scripts": {
|
||||
"db": "sst shell drizzle-kit"
|
||||
},
|
||||
"devDependencies": {
|
||||
"drizzle-kit": "0.30.5"
|
||||
}
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
import { Stripe } from "stripe"
|
||||
import { Database, eq, sql } from "./drizzle"
|
||||
import { BillingTable, PaymentTable, UsageTable } from "./schema/billing.sql"
|
||||
import { Actor } from "./actor"
|
||||
import { fn } from "./util/fn"
|
||||
import { z } from "zod"
|
||||
import { Identifier } from "./identifier"
|
||||
import { centsToMicroCents } from "./util/price"
|
||||
import { User } from "./user"
|
||||
import { Resource } from "@opencode/cloud-resource"
|
||||
|
||||
export namespace Billing {
|
||||
export const stripe = () =>
|
||||
new Stripe(Resource.STRIPE_SECRET_KEY.value, {
|
||||
apiVersion: "2025-03-31.basil",
|
||||
})
|
||||
|
||||
export const get = async () => {
|
||||
return Database.use(async (tx) =>
|
||||
tx
|
||||
.select({
|
||||
customerID: BillingTable.customerID,
|
||||
paymentMethodID: BillingTable.paymentMethodID,
|
||||
balance: BillingTable.balance,
|
||||
reload: BillingTable.reload,
|
||||
})
|
||||
.from(BillingTable)
|
||||
.where(eq(BillingTable.workspaceID, Actor.workspace()))
|
||||
.then((r) => r[0]),
|
||||
)
|
||||
}
|
||||
|
||||
export const payments = async () => {
|
||||
return await Database.use((tx) =>
|
||||
tx
|
||||
.select()
|
||||
.from(PaymentTable)
|
||||
.where(eq(PaymentTable.workspaceID, Actor.workspace()))
|
||||
.orderBy(sql`${PaymentTable.timeCreated} DESC`)
|
||||
.limit(100),
|
||||
)
|
||||
}
|
||||
|
||||
export const usages = async () => {
|
||||
return await Database.use((tx) =>
|
||||
tx
|
||||
.select()
|
||||
.from(UsageTable)
|
||||
.where(eq(UsageTable.workspaceID, Actor.workspace()))
|
||||
.orderBy(sql`${UsageTable.timeCreated} DESC`)
|
||||
.limit(100),
|
||||
)
|
||||
}
|
||||
|
||||
export const consume = fn(
|
||||
z.object({
|
||||
requestID: z.string().optional(),
|
||||
model: z.string(),
|
||||
inputTokens: z.number(),
|
||||
outputTokens: z.number(),
|
||||
reasoningTokens: z.number().optional(),
|
||||
cacheReadTokens: z.number().optional(),
|
||||
cacheWriteTokens: z.number().optional(),
|
||||
costInCents: z.number(),
|
||||
}),
|
||||
async (input) => {
|
||||
const workspaceID = Actor.workspace()
|
||||
const cost = centsToMicroCents(input.costInCents)
|
||||
|
||||
return await Database.transaction(async (tx) => {
|
||||
await tx.insert(UsageTable).values({
|
||||
workspaceID,
|
||||
id: Identifier.create("usage"),
|
||||
requestID: input.requestID,
|
||||
model: input.model,
|
||||
inputTokens: input.inputTokens,
|
||||
outputTokens: input.outputTokens,
|
||||
reasoningTokens: input.reasoningTokens,
|
||||
cacheReadTokens: input.cacheReadTokens,
|
||||
cacheWriteTokens: input.cacheWriteTokens,
|
||||
cost,
|
||||
})
|
||||
const [updated] = await tx
|
||||
.update(BillingTable)
|
||||
.set({
|
||||
balance: sql`${BillingTable.balance} - ${cost}`,
|
||||
})
|
||||
.where(eq(BillingTable.workspaceID, workspaceID))
|
||||
.returning()
|
||||
return updated.balance
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
export const generateCheckoutUrl = fn(
|
||||
z.object({
|
||||
successUrl: z.string(),
|
||||
cancelUrl: z.string(),
|
||||
}),
|
||||
async (input) => {
|
||||
const account = Actor.assert("user")
|
||||
const { successUrl, cancelUrl } = input
|
||||
|
||||
const user = await User.fromID(account.properties.userID)
|
||||
const customer = await Billing.get()
|
||||
const session = await Billing.stripe().checkout.sessions.create({
|
||||
mode: "payment",
|
||||
line_items: [
|
||||
{
|
||||
price_data: {
|
||||
currency: "usd",
|
||||
product_data: {
|
||||
name: "opencode credits",
|
||||
},
|
||||
unit_amount: 2000, // $20 minimum
|
||||
},
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
payment_intent_data: {
|
||||
setup_future_usage: "on_session",
|
||||
},
|
||||
...(customer.customerID
|
||||
? { customer: customer.customerID }
|
||||
: {
|
||||
customer_email: user.email,
|
||||
customer_creation: "always",
|
||||
}),
|
||||
metadata: {
|
||||
workspaceID: Actor.workspace(),
|
||||
},
|
||||
currency: "usd",
|
||||
payment_method_types: ["card"],
|
||||
success_url: successUrl,
|
||||
cancel_url: cancelUrl,
|
||||
})
|
||||
|
||||
return session.url
|
||||
},
|
||||
)
|
||||
|
||||
export const generatePortalUrl = fn(
|
||||
z.object({
|
||||
returnUrl: z.string(),
|
||||
}),
|
||||
async (input) => {
|
||||
const { returnUrl } = input
|
||||
|
||||
const customer = await Billing.get()
|
||||
if (!customer?.customerID) {
|
||||
throw new Error("No stripe customer ID")
|
||||
}
|
||||
|
||||
const session = await Billing.stripe().billingPortal.sessions.create({
|
||||
customer: customer.customerID,
|
||||
return_url: returnUrl,
|
||||
})
|
||||
|
||||
return session.url
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
import { z } from "zod"
|
||||
import { fn } from "./util/fn"
|
||||
import { Actor } from "./actor"
|
||||
import { and, Database, eq, sql } from "./drizzle"
|
||||
import { Identifier } from "./identifier"
|
||||
import { KeyTable } from "./schema/key.sql"
|
||||
|
||||
export namespace Key {
|
||||
export const list = async () => {
|
||||
const user = Actor.assert("user")
|
||||
const keys = await Database.use((tx) =>
|
||||
tx
|
||||
.select({
|
||||
id: KeyTable.id,
|
||||
name: KeyTable.name,
|
||||
key: KeyTable.key,
|
||||
userID: KeyTable.userID,
|
||||
timeCreated: KeyTable.timeCreated,
|
||||
timeUsed: KeyTable.timeUsed,
|
||||
})
|
||||
.from(KeyTable)
|
||||
.where(eq(KeyTable.workspaceID, user.properties.workspaceID))
|
||||
.orderBy(sql`${KeyTable.timeCreated} DESC`),
|
||||
)
|
||||
return keys
|
||||
}
|
||||
|
||||
export const create = fn(z.object({ name: z.string().min(1).max(255) }), async (input) => {
|
||||
const user = Actor.assert("user")
|
||||
const { name } = input
|
||||
|
||||
// Generate secret key: sk- + 64 random characters (upper, lower, numbers)
|
||||
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
||||
let randomPart = ""
|
||||
for (let i = 0; i < 64; i++) {
|
||||
randomPart += chars.charAt(Math.floor(Math.random() * chars.length))
|
||||
}
|
||||
const secretKey = `sk-${randomPart}`
|
||||
|
||||
const keyRecord = await Database.use((tx) =>
|
||||
tx
|
||||
.insert(KeyTable)
|
||||
.values({
|
||||
id: Identifier.create("key"),
|
||||
workspaceID: user.properties.workspaceID,
|
||||
userID: user.properties.userID,
|
||||
name,
|
||||
key: secretKey,
|
||||
timeUsed: null,
|
||||
})
|
||||
.returning(),
|
||||
)
|
||||
|
||||
return {
|
||||
key: secretKey,
|
||||
id: keyRecord[0].id,
|
||||
name: keyRecord[0].name,
|
||||
created: keyRecord[0].timeCreated,
|
||||
}
|
||||
})
|
||||
|
||||
export const remove = fn(z.object({ id: z.string() }), async (input) => {
|
||||
const user = Actor.assert("user")
|
||||
const { id } = input
|
||||
|
||||
const result = await Database.use((tx) =>
|
||||
tx
|
||||
.delete(KeyTable)
|
||||
.where(and(eq(KeyTable.id, id), eq(KeyTable.workspaceID, user.properties.workspaceID)))
|
||||
.returning({ id: KeyTable.id }),
|
||||
)
|
||||
|
||||
if (result.length === 0) {
|
||||
throw new Error("Key not found")
|
||||
}
|
||||
|
||||
return { id: result[0].id }
|
||||
})
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
import { bigint, boolean, integer, pgTable, varchar } from "drizzle-orm/pg-core"
|
||||
import { timestamps, workspaceColumns } from "../drizzle/types"
|
||||
import { workspaceIndexes } from "./workspace.sql"
|
||||
|
||||
export const BillingTable = pgTable(
|
||||
"billing",
|
||||
{
|
||||
...workspaceColumns,
|
||||
...timestamps,
|
||||
customerID: varchar("customer_id", { length: 255 }),
|
||||
paymentMethodID: varchar("payment_method_id", { length: 255 }),
|
||||
paymentMethodLast4: varchar("payment_method_last4", { length: 4 }),
|
||||
balance: bigint("balance", { mode: "number" }).notNull(),
|
||||
reload: boolean("reload"),
|
||||
},
|
||||
(table) => [...workspaceIndexes(table)],
|
||||
)
|
||||
|
||||
export const PaymentTable = pgTable(
|
||||
"payment",
|
||||
{
|
||||
...workspaceColumns,
|
||||
...timestamps,
|
||||
customerID: varchar("customer_id", { length: 255 }),
|
||||
paymentID: varchar("payment_id", { length: 255 }),
|
||||
amount: bigint("amount", { mode: "number" }).notNull(),
|
||||
},
|
||||
(table) => [...workspaceIndexes(table)],
|
||||
)
|
||||
|
||||
export const UsageTable = pgTable(
|
||||
"usage",
|
||||
{
|
||||
...workspaceColumns,
|
||||
...timestamps,
|
||||
model: varchar("model", { length: 255 }).notNull(),
|
||||
inputTokens: integer("input_tokens").notNull(),
|
||||
outputTokens: integer("output_tokens").notNull(),
|
||||
reasoningTokens: integer("reasoning_tokens"),
|
||||
cacheReadTokens: integer("cache_read_tokens"),
|
||||
cacheWriteTokens: integer("cache_write_tokens"),
|
||||
cost: bigint("cost", { mode: "number" }).notNull(),
|
||||
},
|
||||
(table) => [...workspaceIndexes(table)],
|
||||
)
|
||||
@@ -1,16 +0,0 @@
|
||||
import { text, pgTable, uniqueIndex, varchar, integer } from "drizzle-orm/pg-core"
|
||||
import { timestamps, utc, workspaceColumns } from "../drizzle/types"
|
||||
import { workspaceIndexes } from "./workspace.sql"
|
||||
|
||||
export const UserTable = pgTable(
|
||||
"user",
|
||||
{
|
||||
...workspaceColumns,
|
||||
...timestamps,
|
||||
email: text("email").notNull(),
|
||||
name: varchar("name", { length: 255 }).notNull(),
|
||||
timeSeen: utc("time_seen"),
|
||||
color: integer("color"),
|
||||
},
|
||||
(table) => [...workspaceIndexes(table), uniqueIndex("user_email").on(table.workspaceID, table.email)],
|
||||
)
|
||||
@@ -1,18 +0,0 @@
|
||||
import { z } from "zod"
|
||||
import { eq } from "drizzle-orm"
|
||||
import { fn } from "./util/fn"
|
||||
import { Database } from "./drizzle"
|
||||
import { UserTable } from "./schema/user.sql"
|
||||
|
||||
export namespace User {
|
||||
export const fromID = fn(z.string(), async (id) =>
|
||||
Database.transaction(async (tx) => {
|
||||
return tx
|
||||
.select()
|
||||
.from(UserTable)
|
||||
.where(eq(UserTable.id, id))
|
||||
.execute()
|
||||
.then((rows) => rows[0])
|
||||
}),
|
||||
)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import { z } from "zod"
|
||||
|
||||
export function fn<T extends z.ZodType, Result>(
|
||||
schema: T,
|
||||
cb: (input: z.output<T>) => Result,
|
||||
) {
|
||||
const result = (input: z.input<T>) => {
|
||||
const parsed = schema.parse(input)
|
||||
return cb(parsed)
|
||||
}
|
||||
result.force = (input: z.input<T>) => cb(input)
|
||||
result.schema = schema
|
||||
return result
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
export function memo<T>(fn: () => T) {
|
||||
let value: T | undefined
|
||||
let loaded = false
|
||||
|
||||
return (): T => {
|
||||
if (loaded) return value as T
|
||||
loaded = true
|
||||
value = fn()
|
||||
return value as T
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
import { z } from "zod"
|
||||
import { fn } from "./util/fn"
|
||||
import { centsToMicroCents } from "./util/price"
|
||||
import { Actor } from "./actor"
|
||||
import { Database, eq } from "./drizzle"
|
||||
import { Identifier } from "./identifier"
|
||||
import { UserTable } from "./schema/user.sql"
|
||||
import { BillingTable } from "./schema/billing.sql"
|
||||
import { WorkspaceTable } from "./schema/workspace.sql"
|
||||
import { Key } from "./key"
|
||||
|
||||
export namespace Workspace {
|
||||
export const create = fn(z.void(), async () => {
|
||||
const account = Actor.assert("account")
|
||||
const workspaceID = Identifier.create("workspace")
|
||||
await Database.transaction(async (tx) => {
|
||||
await tx.insert(WorkspaceTable).values({
|
||||
id: workspaceID,
|
||||
})
|
||||
await tx.insert(UserTable).values({
|
||||
workspaceID,
|
||||
id: Identifier.create("user"),
|
||||
email: account.properties.email,
|
||||
name: "",
|
||||
})
|
||||
await tx.insert(BillingTable).values({
|
||||
workspaceID,
|
||||
id: Identifier.create("billing"),
|
||||
balance: centsToMicroCents(100),
|
||||
})
|
||||
})
|
||||
await Actor.provide(
|
||||
"system",
|
||||
{
|
||||
workspaceID,
|
||||
},
|
||||
async () => {
|
||||
await Key.create({ name: "Default API Key" })
|
||||
},
|
||||
)
|
||||
return workspaceID
|
||||
})
|
||||
|
||||
export async function list() {
|
||||
const account = Actor.assert("account")
|
||||
return Database.use(async (tx) => {
|
||||
return tx
|
||||
.select({
|
||||
id: WorkspaceTable.id,
|
||||
slug: WorkspaceTable.slug,
|
||||
name: WorkspaceTable.name,
|
||||
})
|
||||
.from(UserTable)
|
||||
.innerJoin(WorkspaceTable, eq(UserTable.workspaceID, WorkspaceTable.id))
|
||||
.where(eq(UserTable.email, account.properties.email))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,596 +0,0 @@
|
||||
import { Hono, MiddlewareHandler } from "hono"
|
||||
import { type ProviderMetadata, type LanguageModelUsage } from "ai"
|
||||
import { createAnthropic } from "@ai-sdk/anthropic"
|
||||
import { createOpenAI } from "@ai-sdk/openai"
|
||||
import { createOpenAICompatible } from "@ai-sdk/openai-compatible"
|
||||
import type { LanguageModelV2Prompt } from "@ai-sdk/provider"
|
||||
import { type ChatCompletionCreateParamsBase } from "openai/resources/chat/completions"
|
||||
import { Actor } from "@opencode/cloud-core/actor.js"
|
||||
import { Database, eq, sql } from "@opencode/cloud-core/drizzle/index.js"
|
||||
import { KeyTable } from "@opencode/cloud-core/schema/key.sql.js"
|
||||
import { Billing } from "@opencode/cloud-core/billing.js"
|
||||
import { Resource } from "@opencode/cloud-resource"
|
||||
|
||||
type Env = {}
|
||||
|
||||
const SUPPORTED_MODELS = {
|
||||
"anthropic/claude-sonnet-4": {
|
||||
input: 0.0000015,
|
||||
output: 0.000006,
|
||||
reasoning: 0.0000015,
|
||||
cacheRead: 0.0000001,
|
||||
cacheWrite: 0.0000001,
|
||||
model: () =>
|
||||
createAnthropic({
|
||||
apiKey: Resource.ANTHROPIC_API_KEY.value,
|
||||
})("claude-sonnet-4-20250514"),
|
||||
},
|
||||
"openai/gpt-4.1": {
|
||||
input: 0.0000015,
|
||||
output: 0.000006,
|
||||
reasoning: 0.0000015,
|
||||
cacheRead: 0.0000001,
|
||||
cacheWrite: 0.0000001,
|
||||
model: () =>
|
||||
createOpenAI({
|
||||
apiKey: Resource.OPENAI_API_KEY.value,
|
||||
})("gpt-4.1"),
|
||||
},
|
||||
"zhipuai/glm-4.5-flash": {
|
||||
input: 0,
|
||||
output: 0,
|
||||
reasoning: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
model: () =>
|
||||
createOpenAICompatible({
|
||||
name: "Zhipu AI",
|
||||
baseURL: "https://api.z.ai/api/paas/v4",
|
||||
apiKey: Resource.ZHIPU_API_KEY.value,
|
||||
})("glm-4.5-flash"),
|
||||
},
|
||||
}
|
||||
|
||||
const GatewayAuth: MiddlewareHandler = async (c, next) => {
|
||||
const authHeader = c.req.header("authorization")
|
||||
|
||||
if (!authHeader || !authHeader.startsWith("Bearer ")) {
|
||||
return c.json(
|
||||
{
|
||||
error: {
|
||||
message: "Missing API key.",
|
||||
type: "invalid_request_error",
|
||||
param: null,
|
||||
code: "unauthorized",
|
||||
},
|
||||
},
|
||||
401,
|
||||
)
|
||||
}
|
||||
|
||||
const apiKey = authHeader.split(" ")[1]
|
||||
|
||||
// Check against KeyTable
|
||||
const keyRecord = await Database.use((tx) =>
|
||||
tx
|
||||
.select({
|
||||
id: KeyTable.id,
|
||||
workspaceID: KeyTable.workspaceID,
|
||||
})
|
||||
.from(KeyTable)
|
||||
.where(eq(KeyTable.key, apiKey))
|
||||
.then((rows) => rows[0]),
|
||||
)
|
||||
|
||||
if (!keyRecord) {
|
||||
return c.json(
|
||||
{
|
||||
error: {
|
||||
message: "Invalid API key.",
|
||||
type: "invalid_request_error",
|
||||
param: null,
|
||||
code: "unauthorized",
|
||||
},
|
||||
},
|
||||
401,
|
||||
)
|
||||
}
|
||||
|
||||
c.set("keyRecord", keyRecord)
|
||||
await next()
|
||||
}
|
||||
|
||||
const app = new Hono<{ Bindings: Env; Variables: { keyRecord?: { id: string; workspaceID: string } } }>()
|
||||
.get("/", (c) => c.text("Hello, world!"))
|
||||
.post("/v1/chat/completions", GatewayAuth, async (c) => {
|
||||
const keyRecord = c.get("keyRecord")!
|
||||
|
||||
return await Actor.provide("system", { workspaceID: keyRecord.workspaceID }, async () => {
|
||||
try {
|
||||
// Check balance
|
||||
const customer = await Billing.get()
|
||||
if (customer.balance <= 0) {
|
||||
return c.json(
|
||||
{
|
||||
error: {
|
||||
message: "Insufficient balance",
|
||||
type: "insufficient_quota",
|
||||
param: null,
|
||||
code: "insufficient_quota",
|
||||
},
|
||||
},
|
||||
401,
|
||||
)
|
||||
}
|
||||
|
||||
const body = await c.req.json<ChatCompletionCreateParamsBase>()
|
||||
const model = SUPPORTED_MODELS[body.model as keyof typeof SUPPORTED_MODELS]?.model()
|
||||
if (!model) throw new Error(`Unsupported model: ${body.model}`)
|
||||
|
||||
const requestBody = transformOpenAIRequestToAiSDK()
|
||||
|
||||
return body.stream ? await handleStream() : await handleGenerate()
|
||||
|
||||
async function handleStream() {
|
||||
const result = await model.doStream({
|
||||
...requestBody,
|
||||
})
|
||||
|
||||
const encoder = new TextEncoder()
|
||||
const stream = new ReadableStream({
|
||||
async start(controller) {
|
||||
const id = `chatcmpl-${Date.now()}`
|
||||
const created = Math.floor(Date.now() / 1000)
|
||||
|
||||
try {
|
||||
for await (const chunk of result.stream) {
|
||||
console.log("!!! CHUNK !!! : " + chunk.type)
|
||||
switch (chunk.type) {
|
||||
case "text-delta": {
|
||||
const data = {
|
||||
id,
|
||||
object: "chat.completion.chunk",
|
||||
created,
|
||||
model: body.model,
|
||||
choices: [
|
||||
{
|
||||
index: 0,
|
||||
delta: {
|
||||
content: chunk.delta,
|
||||
},
|
||||
finish_reason: null,
|
||||
},
|
||||
],
|
||||
}
|
||||
controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`))
|
||||
break
|
||||
}
|
||||
|
||||
case "reasoning-delta": {
|
||||
const data = {
|
||||
id,
|
||||
object: "chat.completion.chunk",
|
||||
created,
|
||||
model: body.model,
|
||||
choices: [
|
||||
{
|
||||
index: 0,
|
||||
delta: {
|
||||
reasoning_content: chunk.delta,
|
||||
},
|
||||
finish_reason: null,
|
||||
},
|
||||
],
|
||||
}
|
||||
controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`))
|
||||
break
|
||||
}
|
||||
|
||||
case "tool-call": {
|
||||
const data = {
|
||||
id,
|
||||
object: "chat.completion.chunk",
|
||||
created,
|
||||
model: body.model,
|
||||
choices: [
|
||||
{
|
||||
index: 0,
|
||||
delta: {
|
||||
tool_calls: [
|
||||
{
|
||||
index: 0,
|
||||
id: chunk.toolCallId,
|
||||
type: "function",
|
||||
function: {
|
||||
name: chunk.toolName,
|
||||
arguments: chunk.input,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
finish_reason: null,
|
||||
},
|
||||
],
|
||||
}
|
||||
controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`))
|
||||
break
|
||||
}
|
||||
|
||||
case "error": {
|
||||
const data = {
|
||||
id,
|
||||
object: "chat.completion.chunk",
|
||||
created,
|
||||
model: body.model,
|
||||
choices: [
|
||||
{
|
||||
index: 0,
|
||||
delta: {},
|
||||
finish_reason: "stop",
|
||||
},
|
||||
],
|
||||
error: {
|
||||
message: typeof chunk.error === "string" ? chunk.error : chunk.error,
|
||||
type: "server_error",
|
||||
},
|
||||
}
|
||||
controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`))
|
||||
controller.enqueue(encoder.encode("data: [DONE]\n\n"))
|
||||
controller.close()
|
||||
break
|
||||
}
|
||||
|
||||
case "finish": {
|
||||
const data = {
|
||||
id,
|
||||
object: "chat.completion.chunk",
|
||||
created,
|
||||
model: body.model,
|
||||
choices: [
|
||||
{
|
||||
index: 0,
|
||||
delta: {},
|
||||
finish_reason:
|
||||
{
|
||||
stop: "stop",
|
||||
length: "length",
|
||||
"content-filter": "content_filter",
|
||||
"tool-calls": "tool_calls",
|
||||
error: "stop",
|
||||
other: "stop",
|
||||
unknown: "stop",
|
||||
}[chunk.finishReason] || "stop",
|
||||
},
|
||||
],
|
||||
usage: {
|
||||
prompt_tokens: chunk.usage.inputTokens,
|
||||
completion_tokens: chunk.usage.outputTokens,
|
||||
total_tokens: chunk.usage.totalTokens,
|
||||
completion_tokens_details: {
|
||||
reasoning_tokens: chunk.usage.reasoningTokens,
|
||||
},
|
||||
prompt_tokens_details: {
|
||||
cached_tokens: chunk.usage.cachedInputTokens,
|
||||
},
|
||||
},
|
||||
}
|
||||
await trackUsage(body.model, chunk.usage, chunk.providerMetadata)
|
||||
controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`))
|
||||
controller.enqueue(encoder.encode("data: [DONE]\n\n"))
|
||||
controller.close()
|
||||
break
|
||||
}
|
||||
|
||||
//case "stream-start":
|
||||
//case "response-metadata":
|
||||
case "text-start":
|
||||
case "text-end":
|
||||
case "reasoning-start":
|
||||
case "reasoning-end":
|
||||
case "tool-input-start":
|
||||
case "tool-input-delta":
|
||||
case "tool-input-end":
|
||||
case "raw":
|
||||
default:
|
||||
// Log unknown chunk types for debugging
|
||||
console.warn(`Unknown chunk type: ${(chunk as any).type}`)
|
||||
break
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
controller.error(error)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
return new Response(stream, {
|
||||
headers: {
|
||||
"Content-Type": "text/plain; charset=utf-8",
|
||||
"Cache-Control": "no-cache",
|
||||
Connection: "keep-alive",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async function handleGenerate() {
|
||||
const response = await model.doGenerate({
|
||||
...requestBody,
|
||||
})
|
||||
await trackUsage(body.model, response.usage, response.providerMetadata)
|
||||
return c.json({
|
||||
id: `chatcmpl-${Date.now()}`,
|
||||
object: "chat.completion" as const,
|
||||
created: Math.floor(Date.now() / 1000),
|
||||
model: body.model,
|
||||
choices: [
|
||||
{
|
||||
index: 0,
|
||||
message: {
|
||||
role: "assistant" as const,
|
||||
content: response.content?.find((c) => c.type === "text")?.text ?? "",
|
||||
reasoning_content: response.content?.find((c) => c.type === "reasoning")?.text,
|
||||
tool_calls: response.content
|
||||
?.filter((c) => c.type === "tool-call")
|
||||
.map((toolCall) => ({
|
||||
id: toolCall.toolCallId,
|
||||
type: "function" as const,
|
||||
function: {
|
||||
name: toolCall.toolName,
|
||||
arguments: toolCall.input,
|
||||
},
|
||||
})),
|
||||
},
|
||||
finish_reason:
|
||||
(
|
||||
{
|
||||
stop: "stop",
|
||||
length: "length",
|
||||
"content-filter": "content_filter",
|
||||
"tool-calls": "tool_calls",
|
||||
error: "stop",
|
||||
other: "stop",
|
||||
unknown: "stop",
|
||||
} as const
|
||||
)[response.finishReason] || "stop",
|
||||
},
|
||||
],
|
||||
usage: {
|
||||
prompt_tokens: response.usage?.inputTokens,
|
||||
completion_tokens: response.usage?.outputTokens,
|
||||
total_tokens: response.usage?.totalTokens,
|
||||
completion_tokens_details: {
|
||||
reasoning_tokens: response.usage?.reasoningTokens,
|
||||
},
|
||||
prompt_tokens_details: {
|
||||
cached_tokens: response.usage?.cachedInputTokens,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function transformOpenAIRequestToAiSDK() {
|
||||
const prompt = transformMessages()
|
||||
const tools = transformTools()
|
||||
|
||||
return {
|
||||
prompt,
|
||||
maxOutputTokens: body.max_tokens ?? body.max_completion_tokens ?? undefined,
|
||||
temperature: body.temperature ?? undefined,
|
||||
topP: body.top_p ?? undefined,
|
||||
frequencyPenalty: body.frequency_penalty ?? undefined,
|
||||
presencePenalty: body.presence_penalty ?? undefined,
|
||||
providerOptions: body.reasoning_effort
|
||||
? {
|
||||
anthropic: {
|
||||
reasoningEffort: body.reasoning_effort,
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
stopSequences: (typeof body.stop === "string" ? [body.stop] : body.stop) ?? undefined,
|
||||
responseFormat: (() => {
|
||||
if (!body.response_format) return { type: "text" as const }
|
||||
if (body.response_format.type === "json_schema")
|
||||
return {
|
||||
type: "json" as const,
|
||||
schema: body.response_format.json_schema.schema,
|
||||
name: body.response_format.json_schema.name,
|
||||
description: body.response_format.json_schema.description,
|
||||
}
|
||||
if (body.response_format.type === "json_object") return { type: "json" as const }
|
||||
throw new Error("Unsupported response format")
|
||||
})(),
|
||||
seed: body.seed ?? undefined,
|
||||
tools: tools.tools,
|
||||
toolChoice: tools.toolChoice,
|
||||
}
|
||||
|
||||
function transformTools() {
|
||||
const { tools, tool_choice } = body
|
||||
|
||||
if (!tools || tools.length === 0) {
|
||||
return { tools: undefined, toolChoice: undefined }
|
||||
}
|
||||
|
||||
const aiSdkTools = tools.map((tool) => {
|
||||
return {
|
||||
type: tool.type,
|
||||
name: tool.function.name,
|
||||
description: tool.function.description,
|
||||
inputSchema: tool.function.parameters!,
|
||||
}
|
||||
})
|
||||
|
||||
let aiSdkToolChoice
|
||||
if (tool_choice == null) {
|
||||
aiSdkToolChoice = undefined
|
||||
} else if (tool_choice === "auto") {
|
||||
aiSdkToolChoice = { type: "auto" as const }
|
||||
} else if (tool_choice === "none") {
|
||||
aiSdkToolChoice = { type: "none" as const }
|
||||
} else if (tool_choice === "required") {
|
||||
aiSdkToolChoice = { type: "required" as const }
|
||||
} else if (tool_choice.type === "function") {
|
||||
aiSdkToolChoice = {
|
||||
type: "tool" as const,
|
||||
toolName: tool_choice.function.name,
|
||||
}
|
||||
}
|
||||
|
||||
return { tools: aiSdkTools, toolChoice: aiSdkToolChoice }
|
||||
}
|
||||
|
||||
function transformMessages() {
|
||||
const { messages } = body
|
||||
const prompt: LanguageModelV2Prompt = []
|
||||
|
||||
for (const message of messages) {
|
||||
switch (message.role) {
|
||||
case "system": {
|
||||
prompt.push({
|
||||
role: "system",
|
||||
content: message.content as string,
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
case "user": {
|
||||
if (typeof message.content === "string") {
|
||||
prompt.push({
|
||||
role: "user",
|
||||
content: [{ type: "text", text: message.content }],
|
||||
})
|
||||
} else {
|
||||
const content = message.content.map((part) => {
|
||||
switch (part.type) {
|
||||
case "text":
|
||||
return { type: "text" as const, text: part.text }
|
||||
case "image_url":
|
||||
return {
|
||||
type: "file" as const,
|
||||
mediaType: "image/jpeg" as const,
|
||||
data: part.image_url.url,
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unsupported content part type: ${(part as any).type}`)
|
||||
}
|
||||
})
|
||||
prompt.push({
|
||||
role: "user",
|
||||
content,
|
||||
})
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case "assistant": {
|
||||
const content: Array<
|
||||
| { type: "text"; text: string }
|
||||
| {
|
||||
type: "tool-call"
|
||||
toolCallId: string
|
||||
toolName: string
|
||||
input: any
|
||||
}
|
||||
> = []
|
||||
|
||||
if (message.content) {
|
||||
content.push({
|
||||
type: "text",
|
||||
text: message.content as string,
|
||||
})
|
||||
}
|
||||
|
||||
if (message.tool_calls) {
|
||||
for (const toolCall of message.tool_calls) {
|
||||
content.push({
|
||||
type: "tool-call",
|
||||
toolCallId: toolCall.id,
|
||||
toolName: toolCall.function.name,
|
||||
input: JSON.parse(toolCall.function.arguments),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
prompt.push({
|
||||
role: "assistant",
|
||||
content,
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
case "tool": {
|
||||
prompt.push({
|
||||
role: "tool",
|
||||
content: [
|
||||
{
|
||||
type: "tool-result",
|
||||
toolName: "placeholder",
|
||||
toolCallId: message.tool_call_id,
|
||||
output: {
|
||||
type: "text",
|
||||
value: message.content as string,
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
default: {
|
||||
throw new Error(`Unsupported message role: ${message.role}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return prompt
|
||||
}
|
||||
}
|
||||
|
||||
async function trackUsage(model: string, usage: LanguageModelUsage, providerMetadata?: ProviderMetadata) {
|
||||
const modelData = SUPPORTED_MODELS[model as keyof typeof SUPPORTED_MODELS]
|
||||
if (!modelData) throw new Error(`Unsupported model: ${model}`)
|
||||
|
||||
const inputTokens = usage.inputTokens ?? 0
|
||||
const outputTokens = usage.outputTokens ?? 0
|
||||
const reasoningTokens = usage.reasoningTokens ?? 0
|
||||
const cacheReadTokens = usage.cachedInputTokens ?? 0
|
||||
const cacheWriteTokens =
|
||||
providerMetadata?.["anthropic"]?.["cacheCreationInputTokens"] ??
|
||||
// @ts-expect-error
|
||||
providerMetadata?.["bedrock"]?.["usage"]?.["cacheWriteInputTokens"] ??
|
||||
0
|
||||
|
||||
const inputCost = modelData.input * inputTokens
|
||||
const outputCost = modelData.output * outputTokens
|
||||
const reasoningCost = modelData.reasoning * reasoningTokens
|
||||
const cacheReadCost = modelData.cacheRead * cacheReadTokens
|
||||
const cacheWriteCost = modelData.cacheWrite * cacheWriteTokens
|
||||
const costInCents = (inputCost + outputCost + reasoningCost + cacheReadCost + cacheWriteCost) * 100
|
||||
|
||||
await Billing.consume({
|
||||
model,
|
||||
inputTokens,
|
||||
outputTokens,
|
||||
reasoningTokens,
|
||||
cacheReadTokens,
|
||||
cacheWriteTokens,
|
||||
costInCents,
|
||||
})
|
||||
|
||||
await Database.use((tx) =>
|
||||
tx
|
||||
.update(KeyTable)
|
||||
.set({ timeUsed: sql`now()` })
|
||||
.where(eq(KeyTable.id, keyRecord.id)),
|
||||
)
|
||||
}
|
||||
} catch (error: any) {
|
||||
return c.json({ error: { message: error.message } }, 500)
|
||||
}
|
||||
})
|
||||
})
|
||||
.all("*", (c) => c.text("Not Found"))
|
||||
|
||||
export type ApiType = typeof app
|
||||
|
||||
export default app
|
||||
@@ -4,7 +4,7 @@
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest"
|
||||
"@types/bun": "catalog:"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5"
|
||||
|
||||
@@ -2,6 +2,7 @@ import { domain } from "./stage"
|
||||
|
||||
const GITHUB_APP_ID = new sst.Secret("GITHUB_APP_ID")
|
||||
const GITHUB_APP_PRIVATE_KEY = new sst.Secret("GITHUB_APP_PRIVATE_KEY")
|
||||
export const EMAILOCTOPUS_API_KEY = new sst.Secret("EMAILOCTOPUS_API_KEY")
|
||||
const bucket = new sst.cloudflare.Bucket("Bucket")
|
||||
|
||||
export const api = new sst.cloudflare.Worker("Api", {
|
||||
@@ -33,8 +34,8 @@ export const api = new sst.cloudflare.Worker("Api", {
|
||||
},
|
||||
})
|
||||
|
||||
export const web = new sst.cloudflare.x.Astro("Web", {
|
||||
domain,
|
||||
new sst.cloudflare.x.Astro("Web", {
|
||||
domain: "docs." + domain,
|
||||
path: "packages/web",
|
||||
environment: {
|
||||
// For astro config
|
||||
|
||||
@@ -1,19 +1,42 @@
|
||||
import { WebhookEndpoint } from "pulumi-stripe"
|
||||
import { domain } from "./stage"
|
||||
import { EMAILOCTOPUS_API_KEY } from "./app"
|
||||
|
||||
////////////////
|
||||
// DATABASE
|
||||
////////////////
|
||||
|
||||
const DATABASE_USERNAME = new sst.Secret("DATABASE_USERNAME")
|
||||
const DATABASE_PASSWORD = new sst.Secret("DATABASE_PASSWORD")
|
||||
const cluster = planetscale.getDatabaseOutput({
|
||||
name: "opencode",
|
||||
organization: "anomalyco",
|
||||
})
|
||||
|
||||
const branch =
|
||||
$app.stage === "production"
|
||||
? planetscale.getBranchOutput({
|
||||
name: "production",
|
||||
organization: cluster.organization,
|
||||
database: cluster.name,
|
||||
})
|
||||
: new planetscale.Branch("DatabaseBranch", {
|
||||
database: cluster.name,
|
||||
organization: cluster.organization,
|
||||
name: $app.stage,
|
||||
parentBranch: "production",
|
||||
})
|
||||
const password = new planetscale.Password("DatabasePassword", {
|
||||
name: $app.stage,
|
||||
database: cluster.name,
|
||||
organization: cluster.organization,
|
||||
branch: branch.name,
|
||||
})
|
||||
|
||||
export const database = new sst.Linkable("Database", {
|
||||
properties: {
|
||||
host: `aws-us-east-2-${$app.stage === "thdxr" ? "2" : "1"}.pg.psdb.cloud`,
|
||||
database: "postgres",
|
||||
username: DATABASE_USERNAME.value,
|
||||
password: DATABASE_PASSWORD.value,
|
||||
port: 5432,
|
||||
host: password.accessHostUrl,
|
||||
database: cluster.name,
|
||||
username: password.username,
|
||||
password: password.plaintext,
|
||||
port: 3306,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -21,7 +44,7 @@ new sst.x.DevCommand("Studio", {
|
||||
link: [database],
|
||||
dev: {
|
||||
command: "bun db studio",
|
||||
directory: "cloud/core",
|
||||
directory: "packages/console/core",
|
||||
autostart: true,
|
||||
},
|
||||
})
|
||||
@@ -36,7 +59,7 @@ const GOOGLE_CLIENT_ID = new sst.Secret("GOOGLE_CLIENT_ID")
|
||||
const authStorage = new sst.cloudflare.Kv("AuthStorage")
|
||||
export const auth = new sst.cloudflare.Worker("AuthApi", {
|
||||
domain: `auth.${domain}`,
|
||||
handler: "cloud/function/src/auth.ts",
|
||||
handler: "packages/console/function/src/auth.ts",
|
||||
url: true,
|
||||
link: [database, authStorage, GITHUB_CLIENT_ID_CONSOLE, GITHUB_CLIENT_SECRET_CONSOLE, GOOGLE_CLIENT_ID],
|
||||
})
|
||||
@@ -45,13 +68,14 @@ export const auth = new sst.cloudflare.Worker("AuthApi", {
|
||||
// GATEWAY
|
||||
////////////////
|
||||
|
||||
export const stripeWebhook = new WebhookEndpoint("StripeWebhook", {
|
||||
url: $interpolate`https://console.${domain}/stripe/webhook`,
|
||||
export const stripeWebhook = new stripe.WebhookEndpoint("StripeWebhookEndpoint", {
|
||||
url: $interpolate`https://${domain}/stripe/webhook`,
|
||||
enabledEvents: [
|
||||
"checkout.session.async_payment_failed",
|
||||
"checkout.session.async_payment_succeeded",
|
||||
"checkout.session.completed",
|
||||
"checkout.session.expired",
|
||||
"charge.refunded",
|
||||
"customer.created",
|
||||
"customer.deleted",
|
||||
"customer.updated",
|
||||
@@ -70,15 +94,10 @@ export const stripeWebhook = new WebhookEndpoint("StripeWebhook", {
|
||||
"customer.subscription.resumed",
|
||||
"customer.subscription.trial_will_end",
|
||||
"customer.subscription.updated",
|
||||
"customer.tax_id.created",
|
||||
"customer.tax_id.deleted",
|
||||
"customer.tax_id.updated",
|
||||
],
|
||||
})
|
||||
|
||||
const ANTHROPIC_API_KEY = new sst.Secret("ANTHROPIC_API_KEY")
|
||||
const OPENAI_API_KEY = new sst.Secret("OPENAI_API_KEY")
|
||||
const ZHIPU_API_KEY = new sst.Secret("ZHIPU_API_KEY")
|
||||
const ZEN_MODELS = new sst.Secret("ZEN_MODELS")
|
||||
const STRIPE_SECRET_KEY = new sst.Secret("STRIPE_SECRET_KEY")
|
||||
const AUTH_API_URL = new sst.Linkable("AUTH_API_URL", {
|
||||
properties: { value: auth.url.apply((url) => url!) },
|
||||
@@ -86,40 +105,49 @@ const AUTH_API_URL = new sst.Linkable("AUTH_API_URL", {
|
||||
const STRIPE_WEBHOOK_SECRET = new sst.Linkable("STRIPE_WEBHOOK_SECRET", {
|
||||
properties: { value: stripeWebhook.secret },
|
||||
})
|
||||
export const gateway = new sst.cloudflare.Worker("GatewayApi", {
|
||||
domain: `api.gateway.${domain}`,
|
||||
handler: "cloud/function/src/gateway.ts",
|
||||
url: true,
|
||||
link: [
|
||||
database,
|
||||
AUTH_API_URL,
|
||||
STRIPE_WEBHOOK_SECRET,
|
||||
STRIPE_SECRET_KEY,
|
||||
ANTHROPIC_API_KEY,
|
||||
OPENAI_API_KEY,
|
||||
ZHIPU_API_KEY,
|
||||
],
|
||||
})
|
||||
|
||||
////////////////
|
||||
// CONSOLE
|
||||
////////////////
|
||||
|
||||
export const console = new sst.cloudflare.x.SolidStart("Console", {
|
||||
domain: `console.${domain}`,
|
||||
path: "cloud/app",
|
||||
const AWS_SES_ACCESS_KEY_ID = new sst.Secret("AWS_SES_ACCESS_KEY_ID")
|
||||
const AWS_SES_SECRET_ACCESS_KEY = new sst.Secret("AWS_SES_SECRET_ACCESS_KEY")
|
||||
|
||||
let logProcessor
|
||||
if ($app.stage === "production" || $app.stage === "frank") {
|
||||
const HONEYCOMB_API_KEY = new sst.Secret("HONEYCOMB_API_KEY")
|
||||
logProcessor = new sst.cloudflare.Worker("LogProcessor", {
|
||||
handler: "packages/console/function/src/log-processor.ts",
|
||||
link: [HONEYCOMB_API_KEY],
|
||||
})
|
||||
}
|
||||
|
||||
new sst.cloudflare.x.SolidStart("Console", {
|
||||
domain,
|
||||
path: "packages/console/app",
|
||||
link: [
|
||||
database,
|
||||
AUTH_API_URL,
|
||||
STRIPE_WEBHOOK_SECRET,
|
||||
STRIPE_SECRET_KEY,
|
||||
ANTHROPIC_API_KEY,
|
||||
OPENAI_API_KEY,
|
||||
ZHIPU_API_KEY,
|
||||
ZEN_MODELS,
|
||||
EMAILOCTOPUS_API_KEY,
|
||||
AWS_SES_ACCESS_KEY_ID,
|
||||
AWS_SES_SECRET_ACCESS_KEY,
|
||||
],
|
||||
environment: {
|
||||
//VITE_DOCS_URL: web.url.apply((url) => url!),
|
||||
//VITE_API_URL: gateway.url.apply((url) => url!),
|
||||
VITE_AUTH_URL: auth.url.apply((url) => url!),
|
||||
},
|
||||
transform: {
|
||||
server: {
|
||||
transform: {
|
||||
worker: {
|
||||
placement: { mode: "smart" },
|
||||
tailConsumers: logProcessor ? [{ service: logProcessor.nodes.worker.scriptName }] : [],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,10 @@
|
||||
import { domain } from "./stage"
|
||||
|
||||
new sst.cloudflare.StaticSite("Desktop", {
|
||||
domain: "desktop." + domain,
|
||||
path: "packages/desktop",
|
||||
build: {
|
||||
command: "bun turbo build",
|
||||
output: "./dist",
|
||||
},
|
||||
})
|
||||
@@ -3,3 +3,11 @@ export const domain = (() => {
|
||||
if ($app.stage === "dev") return "dev.opencode.ai"
|
||||
return `${$app.stage}.dev.opencode.ai`
|
||||
})()
|
||||
|
||||
export const zoneID = "430ba34c138cfb5360826c4909f99be8"
|
||||
|
||||
new cloudflare.RegionalHostname("RegionalHostname", {
|
||||
hostname: domain,
|
||||
regionKey: "us",
|
||||
zoneId: zoneID,
|
||||
})
|
||||
|
||||
@@ -46,7 +46,7 @@ mkdir -p "$INSTALL_DIR"
|
||||
|
||||
if [ -z "$requested_version" ]; then
|
||||
url="https://github.com/sst/opencode/releases/latest/download/$filename"
|
||||
specific_version=$(curl -s https://api.github.com/repos/sst/opencode/releases/latest | awk -F'"' '/"tag_name": "/ {gsub(/^v/, "", $4); print $4}')
|
||||
specific_version=$(curl -s https://api.github.com/repos/sst/opencode/releases/latest | sed -n 's/.*"tag_name": *"v\([^"]*\)".*/\1/p')
|
||||
|
||||
if [[ $? -ne 0 || -z "$specific_version" ]]; then
|
||||
echo -e "${RED}Failed to fetch version information${NC}"
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
|
||||
"mcp": {
|
||||
"weather": {
|
||||
"type": "local",
|
||||
"command": ["opencode", "x", "@h1deya/mcp-server-weather"]
|
||||
}
|
||||
}
|
||||
"$schema": "https://opencode.ai/config.json"
|
||||
}
|
||||
|
||||
@@ -3,37 +3,43 @@
|
||||
"name": "opencode",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"packageManager": "bun@1.2.19",
|
||||
"packageManager": "bun@1.3.0",
|
||||
"scripts": {
|
||||
"dev": "bun run --conditions=development packages/opencode/src/index.ts",
|
||||
"typecheck": "bun run --filter='*' typecheck",
|
||||
"generate": "(cd packages/sdk && ./js/script/generate.ts) && (cd packages/sdk/stainless && ./generate.ts)",
|
||||
"postinstall": "./script/hooks"
|
||||
"dev": "bun run packages/opencode/src/index.ts",
|
||||
"typecheck": "bun turbo typecheck",
|
||||
"prepare": "husky"
|
||||
},
|
||||
"workspaces": {
|
||||
"packages": [
|
||||
"cloud/*",
|
||||
"packages/*",
|
||||
"packages/console/*",
|
||||
"packages/sdk/js"
|
||||
],
|
||||
"catalog": {
|
||||
"@types/bun": "1.3.0",
|
||||
"@hono/zod-validator": "0.4.2",
|
||||
"@kobalte/core": "0.13.11",
|
||||
"@types/node": "22.13.9",
|
||||
"@tsconfig/node22": "22.0.2",
|
||||
"ai": "5.0.8",
|
||||
"hono": "4.7.10",
|
||||
"fuzzysort": "3.1.0",
|
||||
"luxon": "3.6.1",
|
||||
"typescript": "5.8.2",
|
||||
"zod": "3.25.76",
|
||||
"zod": "4.1.8",
|
||||
"remeda": "2.26.0",
|
||||
"solid-js": "1.9.9"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"pulumi-stripe": "0.0.24"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "3.5.3",
|
||||
"sst": "3.17.12"
|
||||
"@tsconfig/bun": "1.0.9",
|
||||
"husky": "9.1.7",
|
||||
"prettier": "3.6.2",
|
||||
"sst": "3.17.19",
|
||||
"turbo": "2.5.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -52,8 +58,10 @@
|
||||
"tree-sitter-bash",
|
||||
"web-tree-sitter"
|
||||
],
|
||||
"overrides": {
|
||||
"zod": "3.25.76"
|
||||
"patchedDependencies": {
|
||||
"@solidjs/start@1.1.7": "patches/@solidjs%2Fstart@1.1.7.patch"
|
||||
},
|
||||
"patchedDependencies": {}
|
||||
"overrides": {
|
||||
"@types/bun": "catalog:"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { defineConfig } from "@solidjs/start/config"
|
||||
|
||||
export default defineConfig({
|
||||
middleware: "./src/middleware.ts",
|
||||
vite: {
|
||||
server: {
|
||||
allowedHosts: true,
|
||||
@@ -1,22 +1,29 @@
|
||||
{
|
||||
"name": "@opencode/cloud-app",
|
||||
"name": "@opencode-ai/console-app",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"typecheck": "tsc --noEmit",
|
||||
"dev": "vinxi dev --host 0.0.0.0",
|
||||
"dev:remote": "VITE_AUTH_URL=https://auth.dev.opencode.ai bun sst shell --stage=dev bun dev",
|
||||
"build": "vinxi build",
|
||||
"build": "vinxi build && ../../opencode/script/schema.ts ./.output/public/config.json",
|
||||
"start": "vinxi start",
|
||||
"version": "0.5.29"
|
||||
"version": "0.15.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ibm/plex": "6.4.1",
|
||||
"@kobalte/core": "catalog:",
|
||||
"@openauthjs/openauth": "0.0.0-20250322224806",
|
||||
"@opencode-ai/console-core": "workspace:*",
|
||||
"@opencode-ai/console-resource": "workspace:*",
|
||||
"@solidjs/meta": "^0.29.4",
|
||||
"@solidjs/router": "^0.15.0",
|
||||
"@solidjs/start": "^1.1.0",
|
||||
"solid-js": "catalog:",
|
||||
"vinxi": "^0.5.7",
|
||||
"@opencode/cloud-core": "workspace:*"
|
||||
"zod": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "catalog:"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22"
|
||||
@@ -0,0 +1 @@
|
||||
../../mail/emails/templates/static
|
||||
@@ -0,0 +1,23 @@
|
||||
<svg width="400" height="400" viewBox="0 0 400 400" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="400" height="400" fill="#FDFCFC"/>
|
||||
<path d="M96 122.001V70.001H148V122.001H96Z" fill="#17181C"/>
|
||||
<path d="M148.004 122.001V70.001H200.004V122.001H148.004Z" fill="#17181C"/>
|
||||
<path d="M200.008 122.001V70.001H252.008V122.001H200.008Z" fill="#17181C"/>
|
||||
<path d="M251.996 122.001V70.001H303.996V122.001H251.996Z" fill="#17181C"/>
|
||||
<path d="M251.996 173.988V121.988H303.996V173.988H251.996Z" fill="#17181C"/>
|
||||
<path d="M96 225.998V173.998H148V225.998H96Z" fill="#CFCECD"/>
|
||||
<rect width="52" height="52" transform="translate(148.004 173.998)" fill="#17181C"/>
|
||||
<path d="M148.004 225.998V173.998H200.004V225.998H148.004Z" fill="#17181C" fill-opacity="0.1"/>
|
||||
<path d="M200.008 225.998V173.998H252.008V225.998H200.008Z" fill="#17181C"/>
|
||||
<path d="M252.016 225.998V173.998H304.016V225.998H252.016Z" fill="#CFCECD"/>
|
||||
<rect width="52" height="52" transform="translate(96 226.002)" fill="#17181C"/>
|
||||
<path d="M96 278.002V226.002H148V278.002H96Z" fill="#17181C" fill-opacity="0.1"/>
|
||||
<rect width="52" height="52" transform="translate(148.004 226.002)" fill="white"/>
|
||||
<path d="M148.004 278.002V226.002H200.004V278.002H148.004Z" fill="#CFCECD"/>
|
||||
<path d="M200.008 278.002V226.002H252.008V278.002H200.008Z" fill="#CFCECD"/>
|
||||
<path d="M252.016 278.002V226.002H304.016V278.002H252.016Z" fill="#CFCECD"/>
|
||||
<path d="M96 330.012V278.012H148V330.012H96Z" fill="#17181C"/>
|
||||
<path d="M148.004 330.012V278.012H200.004V330.012H148.004Z" fill="#17181C"/>
|
||||
<path d="M200.008 329.99V277.99H252.008V329.99H200.008Z" fill="#17181C"/>
|
||||
<path d="M251.996 330.012V278.012H303.996V330.012H251.996Z" fill="#17181C"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -0,0 +1,4 @@
|
||||
<svg width="400" height="400" viewBox="0 0 400 400" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="400" height="400" fill="#0E0E0E"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M312 340H88V60H312V340ZM256 116H144V284H256V116Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 269 B |
@@ -0,0 +1,5 @@
|
||||
User-agent: *
|
||||
Allow: /
|
||||
|
||||
# Disallow shared content pages
|
||||
Disallow: /s/
|
||||
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 8.7 KiB |
@@ -0,0 +1,182 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"$schema": {
|
||||
"type": "string",
|
||||
"description": "JSON schema reference for configuration validation"
|
||||
},
|
||||
"defs": {
|
||||
"type": "object",
|
||||
"description": "Color definitions that can be referenced in the theme",
|
||||
"patternProperties": {
|
||||
"^[a-zA-Z][a-zA-Z0-9_]*$": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"pattern": "^#[0-9a-fA-F]{6}$",
|
||||
"description": "Hex color value"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 255,
|
||||
"description": "ANSI color code (0-255)"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["none"],
|
||||
"description": "No color (uses terminal default)"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"theme": {
|
||||
"type": "object",
|
||||
"description": "Theme color definitions",
|
||||
"properties": {
|
||||
"primary": { "$ref": "#/definitions/colorValue" },
|
||||
"secondary": { "$ref": "#/definitions/colorValue" },
|
||||
"accent": { "$ref": "#/definitions/colorValue" },
|
||||
"error": { "$ref": "#/definitions/colorValue" },
|
||||
"warning": { "$ref": "#/definitions/colorValue" },
|
||||
"success": { "$ref": "#/definitions/colorValue" },
|
||||
"info": { "$ref": "#/definitions/colorValue" },
|
||||
"text": { "$ref": "#/definitions/colorValue" },
|
||||
"textMuted": { "$ref": "#/definitions/colorValue" },
|
||||
"background": { "$ref": "#/definitions/colorValue" },
|
||||
"backgroundPanel": { "$ref": "#/definitions/colorValue" },
|
||||
"backgroundElement": { "$ref": "#/definitions/colorValue" },
|
||||
"border": { "$ref": "#/definitions/colorValue" },
|
||||
"borderActive": { "$ref": "#/definitions/colorValue" },
|
||||
"borderSubtle": { "$ref": "#/definitions/colorValue" },
|
||||
"diffAdded": { "$ref": "#/definitions/colorValue" },
|
||||
"diffRemoved": { "$ref": "#/definitions/colorValue" },
|
||||
"diffContext": { "$ref": "#/definitions/colorValue" },
|
||||
"diffHunkHeader": { "$ref": "#/definitions/colorValue" },
|
||||
"diffHighlightAdded": { "$ref": "#/definitions/colorValue" },
|
||||
"diffHighlightRemoved": { "$ref": "#/definitions/colorValue" },
|
||||
"diffAddedBg": { "$ref": "#/definitions/colorValue" },
|
||||
"diffRemovedBg": { "$ref": "#/definitions/colorValue" },
|
||||
"diffContextBg": { "$ref": "#/definitions/colorValue" },
|
||||
"diffLineNumber": { "$ref": "#/definitions/colorValue" },
|
||||
"diffAddedLineNumberBg": { "$ref": "#/definitions/colorValue" },
|
||||
"diffRemovedLineNumberBg": { "$ref": "#/definitions/colorValue" },
|
||||
"markdownText": { "$ref": "#/definitions/colorValue" },
|
||||
"markdownHeading": { "$ref": "#/definitions/colorValue" },
|
||||
"markdownLink": { "$ref": "#/definitions/colorValue" },
|
||||
"markdownLinkText": { "$ref": "#/definitions/colorValue" },
|
||||
"markdownCode": { "$ref": "#/definitions/colorValue" },
|
||||
"markdownBlockQuote": { "$ref": "#/definitions/colorValue" },
|
||||
"markdownEmph": { "$ref": "#/definitions/colorValue" },
|
||||
"markdownStrong": { "$ref": "#/definitions/colorValue" },
|
||||
"markdownHorizontalRule": { "$ref": "#/definitions/colorValue" },
|
||||
"markdownListItem": { "$ref": "#/definitions/colorValue" },
|
||||
"markdownListEnumeration": { "$ref": "#/definitions/colorValue" },
|
||||
"markdownImage": { "$ref": "#/definitions/colorValue" },
|
||||
"markdownImageText": { "$ref": "#/definitions/colorValue" },
|
||||
"markdownCodeBlock": { "$ref": "#/definitions/colorValue" },
|
||||
"syntaxComment": { "$ref": "#/definitions/colorValue" },
|
||||
"syntaxKeyword": { "$ref": "#/definitions/colorValue" },
|
||||
"syntaxFunction": { "$ref": "#/definitions/colorValue" },
|
||||
"syntaxVariable": { "$ref": "#/definitions/colorValue" },
|
||||
"syntaxString": { "$ref": "#/definitions/colorValue" },
|
||||
"syntaxNumber": { "$ref": "#/definitions/colorValue" },
|
||||
"syntaxType": { "$ref": "#/definitions/colorValue" },
|
||||
"syntaxOperator": { "$ref": "#/definitions/colorValue" },
|
||||
"syntaxPunctuation": { "$ref": "#/definitions/colorValue" }
|
||||
},
|
||||
"required": ["primary", "secondary", "accent", "text", "textMuted", "background"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": ["theme"],
|
||||
"additionalProperties": false,
|
||||
"definitions": {
|
||||
"colorValue": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"pattern": "^#[0-9a-fA-F]{6}$",
|
||||
"description": "Hex color value (same for dark and light)"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 255,
|
||||
"description": "ANSI color code (0-255, same for dark and light)"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["none"],
|
||||
"description": "No color (uses terminal default)"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"pattern": "^[a-zA-Z][a-zA-Z0-9_]*$",
|
||||
"description": "Reference to another color in the theme or defs"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"dark": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"pattern": "^#[0-9a-fA-F]{6}$",
|
||||
"description": "Hex color value for dark mode"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 255,
|
||||
"description": "ANSI color code for dark mode"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["none"],
|
||||
"description": "No color (uses terminal default)"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"pattern": "^[a-zA-Z][a-zA-Z0-9_]*$",
|
||||
"description": "Reference to another color for dark mode"
|
||||
}
|
||||
]
|
||||
},
|
||||
"light": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"pattern": "^#[0-9a-fA-F]{6}$",
|
||||
"description": "Hex color value for light mode"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 255,
|
||||
"description": "ANSI color code for light mode"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["none"],
|
||||
"description": "No color (uses terminal default)"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"pattern": "^[a-zA-Z][a-zA-Z0-9_]*$",
|
||||
"description": "Reference to another color for light mode"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": ["dark", "light"],
|
||||
"additionalProperties": false,
|
||||
"description": "Separate colors for dark and light modes"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,15 @@
|
||||
import { MetaProvider, Title, Meta } from "@solidjs/meta";
|
||||
import { Router } from "@solidjs/router";
|
||||
import { FileRoutes } from "@solidjs/start/router";
|
||||
import { ErrorBoundary, Suspense } from "solid-js";
|
||||
import "@ibm/plex/css/ibm-plex.css";
|
||||
import "./app.css";
|
||||
import { MetaProvider, Title, Meta } from "@solidjs/meta"
|
||||
import { Router } from "@solidjs/router"
|
||||
import { FileRoutes } from "@solidjs/start/router"
|
||||
import { ErrorBoundary, Suspense } from "solid-js"
|
||||
import "@ibm/plex/css/ibm-plex.css"
|
||||
import "./app.css"
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<Router
|
||||
root={props => (
|
||||
explicitLinks={true}
|
||||
root={(props) => (
|
||||
<MetaProvider>
|
||||
<Title>opencode</Title>
|
||||
<Meta name="description" content="opencode - The AI coding agent built for the terminal." />
|
||||
@@ -18,5 +19,5 @@ export default function App() {
|
||||
>
|
||||
<FileRoutes />
|
||||
</Router>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
After Width: | Height: | Size: 7.9 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 9.2 KiB |
|
After Width: | Height: | Size: 7.4 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 14.3581L10.0541 17.7027L18 7" stroke="#8E8B8B" stroke-width="1.5" stroke-linecap="square"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 207 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.75 8.75V2.75H21.25V15.25H15.25M15.25 8.75H2.75V21.25H15.25V8.75Z" stroke="#8E8B8B" stroke-width="1.5" stroke-linecap="square"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 243 B |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 227 KiB |
|
After Width: | Height: | Size: 130 KiB |
|
After Width: | Height: | Size: 495 KiB |
|
After Width: | Height: | Size: 136 KiB |