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 |
@@ -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:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- uses: oven-sh/setup-bun@v1
|
- uses: ./.github/actions/setup-bun
|
||||||
with:
|
|
||||||
bun-version: 1.2.19
|
|
||||||
|
|
||||||
- run: bun install
|
|
||||||
|
|
||||||
- run: bun sst deploy --stage=${{ github.ref_name }}
|
- run: bun sst deploy --stage=${{ github.ref_name }}
|
||||||
env:
|
env:
|
||||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
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 }}
|
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:
|
on:
|
||||||
release:
|
release:
|
||||||
types: [published] # fires only when a release is published
|
types: [published] # fires only when a release is published
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
notify:
|
notify:
|
||||||
|
|||||||
@@ -24,4 +24,4 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
||||||
with:
|
with:
|
||||||
model: opencode/sonic
|
model: opencode/sonic
|
||||||
|
|||||||
@@ -19,16 +19,13 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- uses: oven-sh/setup-bun@v2
|
- uses: ./.github/actions/setup-bun
|
||||||
with:
|
|
||||||
bun-version: 1.2.19
|
|
||||||
|
|
||||||
- run: git fetch --force --tags
|
- run: git fetch --force --tags
|
||||||
- run: bun install -g @vscode/vsce
|
- run: bun install -g @vscode/vsce
|
||||||
|
|
||||||
- name: Publish
|
- name: Publish
|
||||||
run: |
|
run: |
|
||||||
bun install
|
|
||||||
./script/publish
|
./script/publish
|
||||||
working-directory: ./sdks/vscode
|
working-directory: ./sdks/vscode
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
name: publish
|
name: publish
|
||||||
run-name: "${{ format('v{0}', inputs.version) }}"
|
run-name: "${{ format('release {0}', inputs.bump) }}"
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
version:
|
bump:
|
||||||
description: "Version to publish"
|
description: "Bump major, minor, or patch"
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: choice
|
||||||
title:
|
options:
|
||||||
description: "Custom title for this run"
|
- major
|
||||||
required: false
|
- minor
|
||||||
type: string
|
- patch
|
||||||
|
|
||||||
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
|
||||||
@@ -35,18 +35,7 @@ jobs:
|
|||||||
cache: true
|
cache: true
|
||||||
cache-dependency-path: go.sum
|
cache-dependency-path: go.sum
|
||||||
|
|
||||||
- uses: oven-sh/setup-bun@v2
|
- uses: ./.github/actions/setup-bun
|
||||||
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-
|
|
||||||
|
|
||||||
- name: Install makepkg
|
- name: Install makepkg
|
||||||
run: |
|
run: |
|
||||||
@@ -60,13 +49,16 @@ jobs:
|
|||||||
git config --global user.email "opencode@sst.dev"
|
git config --global user.email "opencode@sst.dev"
|
||||||
git config --global user.name "opencode"
|
git config --global user.name "opencode"
|
||||||
ssh-keyscan -H aur.archlinux.org >> ~/.ssh/known_hosts || true
|
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
|
- name: Publish
|
||||||
run: |
|
run: |
|
||||||
OPENCODE_VERSION=${{ inputs.version }} ./script/publish.ts
|
./script/publish.ts
|
||||||
env:
|
env:
|
||||||
|
OPENCODE_BUMP: ${{ inputs.bump }}
|
||||||
GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }}
|
||||||
AUR_KEY: ${{ secrets.AUR_KEY }}
|
AUR_KEY: ${{ secrets.AUR_KEY }}
|
||||||
NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }}
|
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
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup Bun
|
- name: Setup Bun
|
||||||
uses: oven-sh/setup-bun@v2
|
uses: ./.github/actions/setup-bun
|
||||||
with:
|
|
||||||
bun-version: latest
|
|
||||||
|
|
||||||
- name: Run stats script
|
- name: Run stats script
|
||||||
run: bun script/stats.ts
|
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:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
@@ -13,12 +13,7 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup Bun
|
- name: Setup Bun
|
||||||
uses: oven-sh/setup-bun@v1
|
uses: ./.github/actions/setup-bun
|
||||||
with:
|
|
||||||
bun-version: 1.2.19
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: bun install
|
|
||||||
|
|
||||||
- name: Run typecheck
|
- name: Run typecheck
|
||||||
run: bun typecheck
|
run: bun typecheck
|
||||||
|
|||||||
@@ -8,3 +8,5 @@ node_modules
|
|||||||
openapi.json
|
openapi.json
|
||||||
playground
|
playground
|
||||||
tmp
|
tmp
|
||||||
|
dist
|
||||||
|
.turbo
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
bun run typecheck
|
||||||
@@ -7,3 +7,6 @@ core:
|
|||||||
ci:
|
ci:
|
||||||
ignore:
|
ignore:
|
||||||
wip:
|
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
|
## Debugging
|
||||||
|
|
||||||
- To test opencode in the `packages/opencode` directory you can run `bun dev`
|
- 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">
|
<p align="center">
|
||||||
<a href="https://opencode.ai">
|
<a href="https://opencode.ai">
|
||||||
<picture>
|
<picture>
|
||||||
<source srcset="packages/web/src/assets/logo-ornate-dark.svg" media="(prefers-color-scheme: dark)">
|
<source srcset="packages/console/app/src/asset/logo-ornate-dark.svg" media="(prefers-color-scheme: dark)">
|
||||||
<source srcset="packages/web/src/assets/logo-ornate-light.svg" media="(prefers-color-scheme: light)">
|
<source srcset="packages/console/app/src/asset/logo-ornate-light.svg" media="(prefers-color-scheme: light)">
|
||||||
<img src="packages/web/src/assets/logo-ornate-light.svg" alt="opencode logo">
|
<img src="packages/console/app/src/asset/logo-ornate-light.svg" alt="OpenCode logo">
|
||||||
</picture>
|
</picture>
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</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">
|
<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://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://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>
|
<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>
|
</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
|
### 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
|
### 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.
|
design process with the core team.
|
||||||
|
|
||||||
> [!IMPORTANT]
|
> [!IMPORTANT]
|
||||||
@@ -74,7 +74,7 @@ Take a look at the git history to see what kind of PRs we end up merging.
|
|||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> If you do not follow the above guidelines we might close your PR.
|
> 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
|
- Bun
|
||||||
- Golang 1.24.x
|
- Golang 1.24.x
|
||||||
@@ -88,7 +88,7 @@ $ bun dev
|
|||||||
|
|
||||||
#### Development Notes
|
#### 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
|
### FAQ
|
||||||
|
|
||||||
@@ -97,9 +97,10 @@ $ bun dev
|
|||||||
It's very similar to Claude Code in terms of capability. Here are the key differences:
|
It's very similar to Claude Code in terms of capability. Here are the key differences:
|
||||||
|
|
||||||
- 100% open source
|
- 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.
|
- 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.
|
- Out of the box LSP support
|
||||||
- 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.
|
- 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?
|
#### 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,68 +1,109 @@
|
|||||||
# Download Stats
|
# Download Stats
|
||||||
|
|
||||||
| Date | GitHub Downloads | npm Downloads | Total |
|
| Date | GitHub Downloads | npm Downloads | Total |
|
||||||
| ---------- | ---------------- | ---------------- | ----------------- |
|
| ---------- | ---------------- | ----------------- | ----------------- |
|
||||||
| 2025-06-29 | 18,789 (+0) | 39,420 (+0) | 58,209 (+0) |
|
| 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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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,25 +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>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
), {
|
|
||||||
mode: "sync",
|
|
||||||
})
|
|
||||||
@@ -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,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,69 +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 {
|
|
||||||
appearance: none;
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 0;
|
|
||||||
color: var(--color-text);
|
|
||||||
text-decoration: underline;
|
|
||||||
text-underline-offset: var(--space-0-75);
|
|
||||||
text-decoration-thickness: 1px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
import "./workspace.css"
|
|
||||||
import { useAuthSession } from "~/context/auth.session"
|
|
||||||
import { IconLogo } from "../component/icon"
|
|
||||||
import { withActor } from "~/context/auth.withActor"
|
|
||||||
import "./workspace.css"
|
|
||||||
import { query, action, redirect, createAsync, RouteSectionProps } from "@solidjs/router"
|
|
||||||
import { User } from "@opencode/cloud-core/user.js"
|
|
||||||
import { Actor } from "@opencode/cloud-core/actor.js"
|
|
||||||
|
|
||||||
const getUserInfo = query(async () => {
|
|
||||||
"use server"
|
|
||||||
return withActor(async () => {
|
|
||||||
const actor = Actor.assert("user")
|
|
||||||
const user = await User.fromID(actor.properties.userID)
|
|
||||||
return { user }
|
|
||||||
})
|
|
||||||
}, "userInfo")
|
|
||||||
|
|
||||||
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) {
|
|
||||||
const userInfo = createAsync(() => getUserInfo(), {
|
|
||||||
deferStream: true,
|
|
||||||
})
|
|
||||||
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>{userInfo()?.user.email}</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,474 +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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&[data-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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&[data-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;
|
|
||||||
gap: var(--space-3);
|
|
||||||
padding: var(--space-4);
|
|
||||||
border: 1px solid var(--color-border);
|
|
||||||
border-radius: var(--border-radius-sm);
|
|
||||||
|
|
||||||
input {
|
|
||||||
flex: 1;
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[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,407 +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")
|
|
||||||
|
|
||||||
export default function () {
|
|
||||||
|
|
||||||
/////////////////
|
|
||||||
// Keys section
|
|
||||||
/////////////////
|
|
||||||
const keys = createAsync(() => listKeys(), {
|
|
||||||
deferStream: true,
|
|
||||||
})
|
|
||||||
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(), {
|
|
||||||
deferStream: true,
|
|
||||||
})
|
|
||||||
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">
|
|
||||||
{/* 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
|
|
||||||
data-color="ghost"
|
|
||||||
onClick={() => {
|
|
||||||
setShowCreateForm(false)
|
|
||||||
setKeyName("")
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
data-color="primary"
|
|
||||||
disabled={createKeySubmission.pending || !keyName().trim()}
|
|
||||||
onClick={handleCreateKey}
|
|
||||||
>
|
|
||||||
{createKeySubmission.pending ? "Creating..." : "Create"}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
data-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()!}>
|
|
||||||
{(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 data-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 data-color="primary" disabled={createCheckoutUrlSubmission.pending} onClick={handleBuyCredits}>
|
|
||||||
{createCheckoutUrlSubmission.pending ? "Loading..." : "Buy Credits"}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* 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={billingInfo() && billingInfo()!.usage.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={billingInfo()!.usage}>
|
|
||||||
{(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>
|
|
||||||
|
|
||||||
{/* Payments Section */}
|
|
||||||
<Show when={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={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>
|
|
||||||
|
|
||||||
</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.6.1",
|
|
||||||
"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",
|
"type": "module",
|
||||||
"private": true,
|
"private": true,
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bun": "latest"
|
"@types/bun": "catalog:"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"typescript": "^5"
|
"typescript": "^5"
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { domain } from "./stage"
|
|||||||
|
|
||||||
const GITHUB_APP_ID = new sst.Secret("GITHUB_APP_ID")
|
const GITHUB_APP_ID = new sst.Secret("GITHUB_APP_ID")
|
||||||
const GITHUB_APP_PRIVATE_KEY = new sst.Secret("GITHUB_APP_PRIVATE_KEY")
|
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")
|
const bucket = new sst.cloudflare.Bucket("Bucket")
|
||||||
|
|
||||||
export const api = new sst.cloudflare.Worker("Api", {
|
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", {
|
new sst.cloudflare.x.Astro("Web", {
|
||||||
domain,
|
domain: "docs." + domain,
|
||||||
path: "packages/web",
|
path: "packages/web",
|
||||||
environment: {
|
environment: {
|
||||||
// For astro config
|
// For astro config
|
||||||
|
|||||||
@@ -1,19 +1,42 @@
|
|||||||
import { WebhookEndpoint } from "pulumi-stripe"
|
|
||||||
import { domain } from "./stage"
|
import { domain } from "./stage"
|
||||||
|
import { EMAILOCTOPUS_API_KEY } from "./app"
|
||||||
|
|
||||||
////////////////
|
////////////////
|
||||||
// DATABASE
|
// DATABASE
|
||||||
////////////////
|
////////////////
|
||||||
|
|
||||||
const DATABASE_USERNAME = new sst.Secret("DATABASE_USERNAME")
|
const cluster = planetscale.getDatabaseOutput({
|
||||||
const DATABASE_PASSWORD = new sst.Secret("DATABASE_PASSWORD")
|
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", {
|
export const database = new sst.Linkable("Database", {
|
||||||
properties: {
|
properties: {
|
||||||
host: `aws-us-east-2-${$app.stage === "thdxr" ? "2" : "1"}.pg.psdb.cloud`,
|
host: password.accessHostUrl,
|
||||||
database: "postgres",
|
database: cluster.name,
|
||||||
username: DATABASE_USERNAME.value,
|
username: password.username,
|
||||||
password: DATABASE_PASSWORD.value,
|
password: password.plaintext,
|
||||||
port: 6432,
|
port: 3306,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -21,7 +44,7 @@ new sst.x.DevCommand("Studio", {
|
|||||||
link: [database],
|
link: [database],
|
||||||
dev: {
|
dev: {
|
||||||
command: "bun db studio",
|
command: "bun db studio",
|
||||||
directory: "cloud/core",
|
directory: "packages/console/core",
|
||||||
autostart: true,
|
autostart: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -36,7 +59,7 @@ const GOOGLE_CLIENT_ID = new sst.Secret("GOOGLE_CLIENT_ID")
|
|||||||
const authStorage = new sst.cloudflare.Kv("AuthStorage")
|
const authStorage = new sst.cloudflare.Kv("AuthStorage")
|
||||||
export const auth = new sst.cloudflare.Worker("AuthApi", {
|
export const auth = new sst.cloudflare.Worker("AuthApi", {
|
||||||
domain: `auth.${domain}`,
|
domain: `auth.${domain}`,
|
||||||
handler: "cloud/function/src/auth.ts",
|
handler: "packages/console/function/src/auth.ts",
|
||||||
url: true,
|
url: true,
|
||||||
link: [database, authStorage, GITHUB_CLIENT_ID_CONSOLE, GITHUB_CLIENT_SECRET_CONSOLE, GOOGLE_CLIENT_ID],
|
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
|
// GATEWAY
|
||||||
////////////////
|
////////////////
|
||||||
|
|
||||||
export const stripeWebhook = new WebhookEndpoint("StripeWebhook", {
|
export const stripeWebhook = new stripe.WebhookEndpoint("StripeWebhookEndpoint", {
|
||||||
url: $interpolate`https://console.${domain}/stripe/webhook`,
|
url: $interpolate`https://${domain}/stripe/webhook`,
|
||||||
enabledEvents: [
|
enabledEvents: [
|
||||||
"checkout.session.async_payment_failed",
|
"checkout.session.async_payment_failed",
|
||||||
"checkout.session.async_payment_succeeded",
|
"checkout.session.async_payment_succeeded",
|
||||||
"checkout.session.completed",
|
"checkout.session.completed",
|
||||||
"checkout.session.expired",
|
"checkout.session.expired",
|
||||||
|
"charge.refunded",
|
||||||
"customer.created",
|
"customer.created",
|
||||||
"customer.deleted",
|
"customer.deleted",
|
||||||
"customer.updated",
|
"customer.updated",
|
||||||
@@ -70,15 +94,10 @@ export const stripeWebhook = new WebhookEndpoint("StripeWebhook", {
|
|||||||
"customer.subscription.resumed",
|
"customer.subscription.resumed",
|
||||||
"customer.subscription.trial_will_end",
|
"customer.subscription.trial_will_end",
|
||||||
"customer.subscription.updated",
|
"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 ZEN_MODELS = new sst.Secret("ZEN_MODELS")
|
||||||
const OPENAI_API_KEY = new sst.Secret("OPENAI_API_KEY")
|
|
||||||
const ZHIPU_API_KEY = new sst.Secret("ZHIPU_API_KEY")
|
|
||||||
const STRIPE_SECRET_KEY = new sst.Secret("STRIPE_SECRET_KEY")
|
const STRIPE_SECRET_KEY = new sst.Secret("STRIPE_SECRET_KEY")
|
||||||
const AUTH_API_URL = new sst.Linkable("AUTH_API_URL", {
|
const AUTH_API_URL = new sst.Linkable("AUTH_API_URL", {
|
||||||
properties: { value: auth.url.apply((url) => 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", {
|
const STRIPE_WEBHOOK_SECRET = new sst.Linkable("STRIPE_WEBHOOK_SECRET", {
|
||||||
properties: { value: stripeWebhook.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
|
// CONSOLE
|
||||||
////////////////
|
////////////////
|
||||||
|
|
||||||
export const console = new sst.cloudflare.x.SolidStart("Console", {
|
const AWS_SES_ACCESS_KEY_ID = new sst.Secret("AWS_SES_ACCESS_KEY_ID")
|
||||||
domain: `console.${domain}`,
|
const AWS_SES_SECRET_ACCESS_KEY = new sst.Secret("AWS_SES_SECRET_ACCESS_KEY")
|
||||||
path: "cloud/app",
|
|
||||||
|
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: [
|
link: [
|
||||||
database,
|
database,
|
||||||
AUTH_API_URL,
|
AUTH_API_URL,
|
||||||
STRIPE_WEBHOOK_SECRET,
|
STRIPE_WEBHOOK_SECRET,
|
||||||
STRIPE_SECRET_KEY,
|
STRIPE_SECRET_KEY,
|
||||||
ANTHROPIC_API_KEY,
|
ZEN_MODELS,
|
||||||
OPENAI_API_KEY,
|
EMAILOCTOPUS_API_KEY,
|
||||||
ZHIPU_API_KEY,
|
AWS_SES_ACCESS_KEY_ID,
|
||||||
|
AWS_SES_SECRET_ACCESS_KEY,
|
||||||
],
|
],
|
||||||
environment: {
|
environment: {
|
||||||
//VITE_DOCS_URL: web.url.apply((url) => url!),
|
//VITE_DOCS_URL: web.url.apply((url) => url!),
|
||||||
//VITE_API_URL: gateway.url.apply((url) => url!),
|
//VITE_API_URL: gateway.url.apply((url) => url!),
|
||||||
VITE_AUTH_URL: auth.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"
|
if ($app.stage === "dev") return "dev.opencode.ai"
|
||||||
return `${$app.stage}.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
|
if [ -z "$requested_version" ]; then
|
||||||
url="https://github.com/sst/opencode/releases/latest/download/$filename"
|
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
|
if [[ $? -ne 0 || -z "$specific_version" ]]; then
|
||||||
echo -e "${RED}Failed to fetch version information${NC}"
|
echo -e "${RED}Failed to fetch version information${NC}"
|
||||||
|
|||||||
@@ -1,10 +1,3 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://opencode.ai/config.json",
|
"$schema": "https://opencode.ai/config.json"
|
||||||
|
|
||||||
"mcp": {
|
|
||||||
"weather": {
|
|
||||||
"type": "local",
|
|
||||||
"command": ["opencode", "x", "@h1deya/mcp-server-weather"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,37 +3,43 @@
|
|||||||
"name": "opencode",
|
"name": "opencode",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"packageManager": "bun@1.2.19",
|
"packageManager": "bun@1.3.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "bun run --conditions=development packages/opencode/src/index.ts",
|
"dev": "bun run packages/opencode/src/index.ts",
|
||||||
"typecheck": "bun run --filter='*' typecheck",
|
"typecheck": "bun turbo typecheck",
|
||||||
"generate": "(cd packages/sdk && ./js/script/generate.ts) && (cd packages/sdk/stainless && ./generate.ts)",
|
"prepare": "husky"
|
||||||
"postinstall": "./script/hooks"
|
|
||||||
},
|
},
|
||||||
"workspaces": {
|
"workspaces": {
|
||||||
"packages": [
|
"packages": [
|
||||||
"cloud/*",
|
|
||||||
"packages/*",
|
"packages/*",
|
||||||
|
"packages/console/*",
|
||||||
"packages/sdk/js"
|
"packages/sdk/js"
|
||||||
],
|
],
|
||||||
"catalog": {
|
"catalog": {
|
||||||
|
"@types/bun": "1.3.0",
|
||||||
"@hono/zod-validator": "0.4.2",
|
"@hono/zod-validator": "0.4.2",
|
||||||
|
"@kobalte/core": "0.13.11",
|
||||||
"@types/node": "22.13.9",
|
"@types/node": "22.13.9",
|
||||||
"@tsconfig/node22": "22.0.2",
|
"@tsconfig/node22": "22.0.2",
|
||||||
"ai": "5.0.8",
|
"ai": "5.0.8",
|
||||||
"hono": "4.7.10",
|
"hono": "4.7.10",
|
||||||
|
"fuzzysort": "3.1.0",
|
||||||
|
"luxon": "3.6.1",
|
||||||
"typescript": "5.8.2",
|
"typescript": "5.8.2",
|
||||||
"zod": "3.25.76",
|
"zod": "4.1.8",
|
||||||
"remeda": "2.26.0",
|
"remeda": "2.26.0",
|
||||||
"solid-js": "1.9.9"
|
"solid-js": "1.9.9"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
|
||||||
"pulumi-stripe": "0.0.24"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"prettier": "3.5.3",
|
"@tsconfig/bun": "1.0.9",
|
||||||
"sst": "3.17.12"
|
"husky": "9.1.7",
|
||||||
|
"prettier": "3.6.2",
|
||||||
|
"sst": "3.17.19",
|
||||||
|
"turbo": "2.5.6"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@opencode-ai/sdk": "workspace:*"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -52,8 +58,10 @@
|
|||||||
"tree-sitter-bash",
|
"tree-sitter-bash",
|
||||||
"web-tree-sitter"
|
"web-tree-sitter"
|
||||||
],
|
],
|
||||||
"overrides": {
|
"patchedDependencies": {
|
||||||
"zod": "3.25.76"
|
"@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"
|
import { defineConfig } from "@solidjs/start/config"
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
|
middleware: "./src/middleware.ts",
|
||||||
vite: {
|
vite: {
|
||||||
server: {
|
server: {
|
||||||
allowedHosts: true,
|
allowedHosts: true,
|
||||||
@@ -1,22 +1,29 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode/cloud-app",
|
"name": "@opencode-ai/console-app",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"typecheck": "tsc --noEmit",
|
||||||
"dev": "vinxi dev --host 0.0.0.0",
|
"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",
|
"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",
|
"start": "vinxi start",
|
||||||
"version": "0.6.1"
|
"version": "0.15.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ibm/plex": "6.4.1",
|
"@ibm/plex": "6.4.1",
|
||||||
|
"@kobalte/core": "catalog:",
|
||||||
"@openauthjs/openauth": "0.0.0-20250322224806",
|
"@openauthjs/openauth": "0.0.0-20250322224806",
|
||||||
|
"@opencode-ai/console-core": "workspace:*",
|
||||||
|
"@opencode-ai/console-resource": "workspace:*",
|
||||||
"@solidjs/meta": "^0.29.4",
|
"@solidjs/meta": "^0.29.4",
|
||||||
"@solidjs/router": "^0.15.0",
|
"@solidjs/router": "^0.15.0",
|
||||||
"@solidjs/start": "^1.1.0",
|
"@solidjs/start": "^1.1.0",
|
||||||
"solid-js": "catalog:",
|
"solid-js": "catalog:",
|
||||||
"vinxi": "^0.5.7",
|
"vinxi": "^0.5.7",
|
||||||
"@opencode/cloud-core": "workspace:*"
|
"zod": "catalog:"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"typescript": "catalog:"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=22"
|
"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 { MetaProvider, Title, Meta } from "@solidjs/meta"
|
||||||
import { Router } from "@solidjs/router";
|
import { Router } from "@solidjs/router"
|
||||||
import { FileRoutes } from "@solidjs/start/router";
|
import { FileRoutes } from "@solidjs/start/router"
|
||||||
import { ErrorBoundary, Suspense } from "solid-js";
|
import { ErrorBoundary, Suspense } from "solid-js"
|
||||||
import "@ibm/plex/css/ibm-plex.css";
|
import "@ibm/plex/css/ibm-plex.css"
|
||||||
import "./app.css";
|
import "./app.css"
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
return (
|
return (
|
||||||
<Router
|
<Router
|
||||||
root={props => (
|
explicitLinks={true}
|
||||||
|
root={(props) => (
|
||||||
<MetaProvider>
|
<MetaProvider>
|
||||||
<Title>opencode</Title>
|
<Title>opencode</Title>
|
||||||
<Meta name="description" content="opencode - The AI coding agent built for the terminal." />
|
<Meta name="description" content="opencode - The AI coding agent built for the terminal." />
|
||||||
@@ -18,5 +19,5 @@ export default function App() {
|
|||||||
>
|
>
|
||||||
<FileRoutes />
|
<FileRoutes />
|
||||||
</Router>
|
</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 |
@@ -0,0 +1,18 @@
|
|||||||
|
<svg width="234" height="42" viewBox="0 0 234 42" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M18 30H6V18H18V30Z" fill="#4B4646"/>
|
||||||
|
<path d="M18 12H6V30H18V12ZM24 36H0V6H24V36Z" fill="#B7B1B1"/>
|
||||||
|
<path d="M48 30H36V18H48V30Z" fill="#4B4646"/>
|
||||||
|
<path d="M36 30H48V12H36V30ZM54 36H36V42H30V6H54V36Z" fill="#B7B1B1"/>
|
||||||
|
<path d="M84 24V30H66V24H84Z" fill="#4B4646"/>
|
||||||
|
<path d="M84 24H66V30H84V36H60V6H84V24ZM66 18H78V12H66V18Z" fill="#B7B1B1"/>
|
||||||
|
<path d="M108 36H96V18H108V36Z" fill="#4B4646"/>
|
||||||
|
<path d="M108 12H96V36H90V6H108V12ZM114 36H108V12H114V36Z" fill="#B7B1B1"/>
|
||||||
|
<path d="M144 30H126V18H144V30Z" fill="#4B4646"/>
|
||||||
|
<path d="M144 12H126V30H144V36H120V6H144V12Z" fill="#F1ECEC"/>
|
||||||
|
<path d="M168 30H156V18H168V30Z" fill="#4B4646"/>
|
||||||
|
<path d="M168 12H156V30H168V12ZM174 36H150V6H174V36Z" fill="#F1ECEC"/>
|
||||||
|
<path d="M198 30H186V18H198V30Z" fill="#4B4646"/>
|
||||||
|
<path d="M198 12H186V30H198V12ZM204 36H180V6H198V0H204V36Z" fill="#F1ECEC"/>
|
||||||
|
<path d="M234 24V30H216V24H234Z" fill="#4B4646"/>
|
||||||
|
<path d="M216 12V18H228V12H216ZM234 24H216V30H234V36H210V6H234V24Z" fill="#F1ECEC"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |