Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7df32eac2a | ||
|
|
4654fb88de | ||
|
|
e915a3720e | ||
|
|
93c2f5060e | ||
|
|
564143071e | ||
|
|
3cdfc529a0 | ||
|
|
bffe547417 | ||
|
|
dc99005e65 | ||
|
|
8ffedbe157 | ||
|
|
900fe5ca04 | ||
|
|
66a5d58221 | ||
|
|
8426a0d595 | ||
|
|
9186c3feae | ||
|
|
f171250033 | ||
|
|
d440ba32ab | ||
|
|
f7ab6beaf3 | ||
|
|
85ac243752 | ||
|
|
03522471a1 | ||
|
|
42b440be0c | ||
|
|
133ae42c55 | ||
|
|
e001af2709 | ||
|
|
a97612287f | ||
|
|
d13467d869 | ||
|
|
368bd97952 | ||
|
|
d0104278fa | ||
|
|
222244719b | ||
|
|
21008d733f | ||
|
|
2808e95ac7 | ||
|
|
70e0d71ac2 | ||
|
|
93f507d330 | ||
|
|
7119ace940 | ||
|
|
4e24e04aec | ||
|
|
469a83a55b | ||
|
|
e8f54b9b38 | ||
|
|
01b18456a3 | ||
|
|
1acd5445b5 | ||
|
|
ff89305ebe | ||
|
|
605f78944d | ||
|
|
9f7e14dc7e | ||
|
|
cb7f3cf2f1 | ||
|
|
4406096974 | ||
|
|
fefaad6226 | ||
|
|
3f9b569575 | ||
|
|
1e4f5710aa | ||
|
|
48a79b1173 | ||
|
|
59e550271d | ||
|
|
afebe920b2 | ||
|
|
6921702605 | ||
|
|
9c6783e88e | ||
|
|
f1a60a0a93 | ||
|
|
4b9cae82e6 | ||
|
|
fdf08ecfab | ||
|
|
22f5c26eec | ||
|
|
b6de122ddc | ||
|
|
0f8cb69bff | ||
|
|
fca2bddc3b | ||
|
|
f65e20b8ce | ||
|
|
93f2805bc2 | ||
|
|
9ad4dc9296 | ||
|
|
23af974bd3 | ||
|
|
36ea46ee67 | ||
|
|
4d2cc9d858 | ||
|
|
610ffbdd61 | ||
|
|
854f9227a2 | ||
|
|
8d368fdfd2 | ||
|
|
1c31c2dd97 | ||
|
|
c1d754bec9 | ||
|
|
c67b721787 | ||
|
|
11e41e7564 | ||
|
|
afd42bf46d | ||
|
|
f740663ded | ||
|
|
751b81af34 | ||
|
|
b725bcd2cd | ||
|
|
c278e16e4e | ||
|
|
4e629c5b64 | ||
|
|
4624f0a260 | ||
|
|
2e16d685eb | ||
|
|
e544cccc70 | ||
|
|
c141b88087 | ||
|
|
023c4532c1 | ||
|
|
042802848d | ||
|
|
a8aa44bd3f | ||
|
|
db2a3a171e | ||
|
|
38a4bee1be | ||
|
|
8952b3d246 | ||
|
|
d6350a7fa6 | ||
|
|
ae83138832 | ||
|
|
3ee4280dfa | ||
|
|
26fbf9e647 | ||
|
|
97a41062c9 | ||
|
|
4a76224268 | ||
|
|
810c9cff1d | ||
|
|
47d4c87bdd | ||
|
|
a9875c5531 | ||
|
|
4c261ab1db | ||
|
|
2fc8263032 | ||
|
|
a431b8922c | ||
|
|
0a01d20850 | ||
|
|
7b62c10553 | ||
|
|
61c7196bd9 | ||
|
|
365fdd9ff8 | ||
|
|
f6bc9238df | ||
|
|
26f75d4e68 | ||
|
|
8ba8d3c7e3 | ||
|
|
f993541e0b | ||
|
|
e2df3eb44d | ||
|
|
38f9ce05f6 | ||
|
|
a6e09363b8 | ||
|
|
49629bb58e | ||
|
|
2bb5b9b13a | ||
|
|
41338d1bf9 | ||
|
|
41ee9c94c7 | ||
|
|
9c16db0f36 | ||
|
|
721869353b | ||
|
|
6d22ade771 | ||
|
|
fbcceeb781 | ||
|
|
95775d68b7 | ||
|
|
cf11669618 | ||
|
|
65dc19e85a | ||
|
|
cfcfceca6d | ||
|
|
9f8899a9f9 | ||
|
|
449a063fe2 | ||
|
|
37530359ee | ||
|
|
65f0bea146 | ||
|
|
e4cc05a975 | ||
|
|
029612d8d5 | ||
|
|
e9826e8a22 | ||
|
|
ad5f209dc8 | ||
|
|
fcfeac57c5 | ||
|
|
2946898934 | ||
|
|
b4d95545e0 | ||
|
|
d3bbaa141c | ||
|
|
8714f23509 | ||
|
|
c676f12306 | ||
|
|
dac821229e | ||
|
|
3625766ad4 | ||
|
|
924e84b0de | ||
|
|
70db3cffb0 | ||
|
|
0c30a6f303 | ||
|
|
0c7a887dbc | ||
|
|
48e01cfee7 | ||
|
|
b54aa65f5f | ||
|
|
52b3eddeee | ||
|
|
f821b55514 | ||
|
|
37f284f9a9 | ||
|
|
0178eab29b | ||
|
|
a3f4a030b4 | ||
|
|
9a330b4f0f | ||
|
|
25e53e090b | ||
|
|
46927ee9a5 | ||
|
|
c3a25eff78 | ||
|
|
b40c02e258 | ||
|
|
058163333d | ||
|
|
28c341ad32 | ||
|
|
a05e677412 | ||
|
|
918dd58a15 | ||
|
|
9c02c4cfe8 | ||
|
|
fd355c15db | ||
|
|
12eb1391b9 | ||
|
|
4496cd4b64 | ||
|
|
7f5e5fccc8 | ||
|
|
1a5b456bb6 | ||
|
|
b55231c106 | ||
|
|
d7a9f343c5 | ||
|
|
5ecd7fdd0c | ||
|
|
1aaf8f11cf | ||
|
|
7fab12da28 | ||
|
|
6daf0fdb2b | ||
|
|
f2f4d87cc0 | ||
|
|
8a0e773add | ||
|
|
9b27d61fe8 | ||
|
|
7d1eb010c1 | ||
|
|
3fa02623c3 | ||
|
|
403f9b2f1b | ||
|
|
4d81f90dde | ||
|
|
36ec9dddb2 | ||
|
|
5a0e7698e1 | ||
|
|
c6ef92634d | ||
|
|
f97fdceb01 | ||
|
|
3f225e3248 | ||
|
|
151ff05381 | ||
|
|
e37e878e72 | ||
|
|
3de1ce467f | ||
|
|
eff50c0aab | ||
|
|
02e014b0a0 | ||
|
|
a928a35c96 | ||
|
|
555202f3b1 | ||
|
|
37cf262094 | ||
|
|
aa9ab0a304 | ||
|
|
4331d77b9e | ||
|
|
cf79262dc4 | ||
|
|
43e8047ad6 | ||
|
|
63c7c921ed | ||
|
|
bce1398b73 | ||
|
|
87cf08a9e7 | ||
|
|
ad8ea82611 | ||
|
|
d984dbd876 | ||
|
|
2d794ed03d | ||
|
|
8749c0c707 | ||
|
|
3359417378 | ||
|
|
8381760b27 | ||
|
|
0fbd7c84fd | ||
|
|
5c17ee52c5 | ||
|
|
3606775b79 | ||
|
|
6233251fc0 | ||
|
|
587b8ae7ee | ||
|
|
877855d1ee | ||
|
|
eebca580e3 | ||
|
|
e73a7c23d0 | ||
|
|
11de2e59f3 | ||
|
|
f4b69df7a3 | ||
|
|
83b9b67c4c | ||
|
|
d9de78cfe8 | ||
|
|
ef6bff6386 | ||
|
|
cb03655aac | ||
|
|
012a292948 | ||
|
|
d2e2eae4b8 | ||
|
|
fd84e8d405 | ||
|
|
567a1964c0 | ||
|
|
564418f1ff | ||
|
|
d7c4faec58 | ||
|
|
34982b5d18 | ||
|
|
5b5bd146ea | ||
|
|
836c2060c7 | ||
|
|
6357136ca5 | ||
|
|
0a0b363587 | ||
|
|
f5f6167146 | ||
|
|
f1684c9e15 | ||
|
|
f597b7287b | ||
|
|
95fabc1407 | ||
|
|
315c366e11 | ||
|
|
5d68a7c2e0 | ||
|
|
1b2d3bf659 | ||
|
|
24e4f5b051 | ||
|
|
2992c5a6bf | ||
|
|
ca2660ccf8 | ||
|
|
6b4bd590ac | ||
|
|
60ba42af15 | ||
|
|
f22827bdfa | ||
|
|
f9b5b6d129 | ||
|
|
cc66e06101 | ||
|
|
d4c8d95ec6 | ||
|
|
0fd312346b | ||
|
|
b80046120c | ||
|
|
07ed2a8391 | ||
|
|
e9f52934e9 | ||
|
|
732b67f8ce | ||
|
|
d47bb96784 | ||
|
|
6456350564 | ||
|
|
c5c0a2ca6e | ||
|
|
3706b2bca7 | ||
|
|
1f57b9a70f | ||
|
|
004f53f741 | ||
|
|
cf29ec0a59 | ||
|
|
b5e08acdf7 | ||
|
|
7ddeeeb4f8 | ||
|
|
0f1697b2ab | ||
|
|
6e626afdcb | ||
|
|
0fe94c1616 | ||
|
|
a42b004c72 | ||
|
|
35f57768fd | ||
|
|
9a90ce84fb | ||
|
|
133fe41cd5 | ||
|
|
74c1085103 | ||
|
|
497fc170fd | ||
|
|
3edab60560 | ||
|
|
3f2ac2b9b0 | ||
|
|
1577b44087 | ||
|
|
39f52f48f2 | ||
|
|
4fadbcfb90 | ||
|
|
08c5c401ba | ||
|
|
ba2e86c7ef | ||
|
|
6d056789c7 | ||
|
|
5d508cc9c2 | ||
|
|
d9233872b9 | ||
|
|
aa4dba1541 | ||
|
|
947a3e8aff | ||
|
|
9a3186317b | ||
|
|
b1e584ca1d | ||
|
|
bca523eb63 | ||
|
|
2ff4cd2c2b | ||
|
|
d686269377 | ||
|
|
491abd6b5b | ||
|
|
4518f96e3d | ||
|
|
a9dcbedf99 | ||
|
|
9231043eb4 | ||
|
|
04dcd87170 | ||
|
|
c31fd9ed79 | ||
|
|
2989d92794 | ||
|
|
256d074411 | ||
|
|
8b01676ec0 | ||
|
|
34c6c8494a | ||
|
|
522bed6b7d | ||
|
|
dda672284c | ||
|
|
6018364164 | ||
|
|
bc0d438cee | ||
|
|
abef91c223 | ||
|
|
1bbf6d38e5 | ||
|
|
c9c9db1e8d | ||
|
|
b11fe9fbc6 | ||
|
|
60f3d413de | ||
|
|
1df2d78b85 | ||
|
|
2286a872c1 | ||
|
|
8a83301e0d | ||
|
|
9bc40f00e3 | ||
|
|
c3c440948a | ||
|
|
aa10f8a7f6 | ||
|
|
a2db58f125 | ||
|
|
574be9febf | ||
|
|
5b05ede748 | ||
|
|
4032426185 | ||
|
|
8d8045ff95 | ||
|
|
b3c8bec019 | ||
|
|
25f43adaa0 | ||
|
|
4913ee6afd | ||
|
|
c59ded82b3 | ||
|
|
40bdbf92a3 | ||
|
|
ad76d7e57d | ||
|
|
863ae6fa7d | ||
|
|
8f230ad4b4 | ||
|
|
c0f90eb564 | ||
|
|
50fb337270 | ||
|
|
e08ec077b0 | ||
|
|
796245d146 | ||
|
|
303a1044a8 | ||
|
|
f19586cebd | ||
|
|
5d12cadba7 | ||
|
|
745988f9e3 | ||
|
|
61580e6dce | ||
|
|
2dea8f0f6b | ||
|
|
446ce488c0 | ||
|
|
21b000aed0 | ||
|
|
0cdd8be70a | ||
|
|
2f4db2777c | ||
|
|
667ff90dd6 | ||
|
|
cd3d91209a | ||
|
|
75ed131abf |
@@ -24,4 +24,6 @@ jobs:
|
|||||||
- 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 }}
|
||||||
|
|||||||
@@ -32,14 +32,12 @@ jobs:
|
|||||||
"webfetch": "deny"
|
"webfetch": "deny"
|
||||||
}
|
}
|
||||||
run: |
|
run: |
|
||||||
opencode run -m anthropic/claude-sonnet-4-20250514 "A new issue has been created: '${{ github.event.issue.title }}'
|
opencode run -m anthropic/claude-sonnet-4-20250514 "A new issue has been created:'
|
||||||
|
|
||||||
Issue number:
|
Issue number:
|
||||||
${{ github.event.issue.number }}
|
${{ github.event.issue.number }}
|
||||||
Issue body:
|
|
||||||
${{ github.event.issue.body }}
|
|
||||||
|
|
||||||
Please search through existing issues (excluding #${{ github.event.issue.number }}) in this repository to find any potential duplicates of this new issue.
|
Lookup this issue and search through existing issues (excluding #${{ github.event.issue.number }}) in this repository to find any potential duplicates of this new issue.
|
||||||
Consider:
|
Consider:
|
||||||
1. Similar titles or descriptions
|
1. Similar titles or descriptions
|
||||||
2. Same error messages or symptoms
|
2. Same error messages or symptoms
|
||||||
|
|||||||
@@ -8,22 +8,20 @@ jobs:
|
|||||||
opencode:
|
opencode:
|
||||||
if: |
|
if: |
|
||||||
contains(github.event.comment.body, ' /oc') ||
|
contains(github.event.comment.body, ' /oc') ||
|
||||||
startsWith(github.event.comment.body, '/oc') ||
|
contains(github.event.comment.body, ' /opencode')
|
||||||
contains(github.event.comment.body, ' /opencode') ||
|
|
||||||
startsWith(github.event.comment.body, '/opencode')
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
|
||||||
id-token: write
|
id-token: write
|
||||||
|
contents: read
|
||||||
|
pull-requests: read
|
||||||
|
issues: read
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
fetch-depth: 1
|
|
||||||
|
|
||||||
- name: Run opencode
|
- name: Run opencode
|
||||||
uses: sst/opencode/github@latest
|
uses: sst/opencode/github@latest
|
||||||
env:
|
env:
|
||||||
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
||||||
with:
|
with:
|
||||||
model: anthropic/claude-sonnet-4-20250514
|
model: opencode/sonic
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
node_modules
|
node_modules
|
||||||
|
.worktrees
|
||||||
.sst
|
.sst
|
||||||
.env
|
.env
|
||||||
.idea
|
.idea
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
---
|
---
|
||||||
model: openai/gpt-5
|
|
||||||
reasoningEffort: medium
|
|
||||||
description: ALWAYS use this when writing docs
|
description: ALWAYS use this when writing docs
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -8,7 +6,26 @@ You are an expert technical documentation writer
|
|||||||
|
|
||||||
You are not verbose
|
You are not verbose
|
||||||
|
|
||||||
Every chunk of text should be followed by an example or something besides text
|
The title of the page should be a word or a 2-3 word phrase
|
||||||
to look at.
|
|
||||||
|
|
||||||
Chunks of text should not be more than 2 sentences long.
|
The description should be one short line, should not start with "The", should
|
||||||
|
avoid repeating the title of the page, should be 5-10 words long
|
||||||
|
|
||||||
|
Chunks of text should not be more than 2 sentences long
|
||||||
|
|
||||||
|
Each section is spearated by a divider of 3 dashes
|
||||||
|
|
||||||
|
The section titles are short with only the first letter of the word capitalized
|
||||||
|
|
||||||
|
The section titles are in the imperative mood
|
||||||
|
|
||||||
|
The section titles should not repeat the term used in the page title, for
|
||||||
|
example, if the page title is "Models", avoid using a section title like "Add
|
||||||
|
new models". This might be unavoidable in some cases, but try to avoid it.
|
||||||
|
|
||||||
|
Check out the /packages/web/src/content/docs/docs/index.mdx as an example.
|
||||||
|
|
||||||
|
For JS or TS code snippets remove trailing semicolons and any trailing commas
|
||||||
|
that might not be needed.
|
||||||
|
|
||||||
|
If you are making a commit prefix the commit message with `docs:`
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
commit and push
|
||||||
|
|
||||||
|
make sure it includes a prefix like
|
||||||
|
docs:
|
||||||
|
tui:
|
||||||
|
core:
|
||||||
|
ci:
|
||||||
|
ignore:
|
||||||
|
wip:
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
description: hello world
|
||||||
|
---
|
||||||
|
|
||||||
|
hey there $ARGUMENTS
|
||||||
|
|
||||||
|
!`ls`
|
||||||
|
check out @README.md
|
||||||
@@ -10,3 +10,7 @@
|
|||||||
- AVOID `let` statements
|
- AVOID `let` statements
|
||||||
- PREFER single word variable names where possible
|
- PREFER single word variable names where possible
|
||||||
- Use as many bun apis as possible like Bun.file()
|
- Use as many bun apis as possible like Bun.file()
|
||||||
|
|
||||||
|
## Debugging
|
||||||
|
|
||||||
|
- To test opencode in the `packages/opencode` directory you can run `bun dev`
|
||||||
|
|||||||
@@ -107,4 +107,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/SST_dev)
|
**Join our community** [Discord](https://discord.gg/opencode) | [YouTube](https://www.youtube.com/c/sst-dev) | [X.com](https://x.com/anomaly_inv)
|
||||||
|
|||||||
@@ -51,3 +51,24 @@
|
|||||||
| 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-19 | 209,814 (+5,255) | 189,668 (+2,269) | 399,482 (+7,524) |
|
||||||
|
| 2025-08-20 | 214,497 (+4,683) | 191,481 (+1,813) | 405,978 (+6,496) |
|
||||||
|
| 2025-08-21 | 220,465 (+5,968) | 194,784 (+3,303) | 415,249 (+9,271) |
|
||||||
|
| 2025-08-22 | 225,899 (+5,434) | 197,204 (+2,420) | 423,103 (+7,854) |
|
||||||
|
| 2025-08-23 | 229,005 (+3,106) | 199,238 (+2,034) | 428,243 (+5,140) |
|
||||||
|
| 2025-08-24 | 232,098 (+3,093) | 201,157 (+1,919) | 433,255 (+5,012) |
|
||||||
|
| 2025-08-25 | 236,607 (+4,509) | 202,650 (+1,493) | 439,257 (+6,002) |
|
||||||
|
| 2025-08-26 | 242,783 (+6,176) | 205,242 (+2,592) | 448,025 (+8,768) |
|
||||||
|
| 2025-08-27 | 248,409 (+5,626) | 205,242 (+0) | 453,651 (+5,626) |
|
||||||
|
| 2025-08-28 | 252,796 (+4,387) | 205,242 (+0) | 458,038 (+4,387) |
|
||||||
|
| 2025-08-29 | 256,045 (+3,249) | 211,075 (+5,833) | 467,120 (+9,082) |
|
||||||
|
| 2025-08-30 | 258,863 (+2,818) | 212,397 (+1,322) | 471,260 (+4,140) |
|
||||||
|
| 2025-08-31 | 262,004 (+3,141) | 213,944 (+1,547) | 475,948 (+4,688) |
|
||||||
|
| 2025-09-01 | 265,359 (+3,355) | 215,115 (+1,171) | 480,474 (+4,526) |
|
||||||
|
| 2025-09-02 | 270,483 (+5,124) | 217,075 (+1,960) | 487,558 (+7,084) |
|
||||||
|
| 2025-09-03 | 274,793 (+4,310) | 219,755 (+2,680) | 494,548 (+6,990) |
|
||||||
|
| 2025-09-04 | 280,430 (+5,637) | 222,103 (+2,348) | 502,533 (+7,985) |
|
||||||
|
| 2025-09-05 | 283,769 (+3,339) | 223,793 (+1,690) | 507,562 (+5,029) |
|
||||||
|
| 2025-09-06 | 286,245 (+2,476) | 225,036 (+1,243) | 511,281 (+3,719) |
|
||||||
|
| 2025-09-07 | 288,623 (+2,378) | 225,866 (+830) | 514,489 (+3,208) |
|
||||||
|
|||||||
@@ -1,9 +1,23 @@
|
|||||||
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,
|
||||||
},
|
},
|
||||||
|
build: {
|
||||||
|
rollupOptions: {
|
||||||
|
external: ["cloudflare:workers"],
|
||||||
|
},
|
||||||
|
minify: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
compatibilityDate: "2024-09-19",
|
||||||
|
preset: "cloudflare_module",
|
||||||
|
cloudflare: {
|
||||||
|
nodeCompat: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,18 +2,22 @@
|
|||||||
"name": "@opencode/cloud-app",
|
"name": "@opencode/cloud-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",
|
||||||
"build": "vinxi build",
|
"dev:remote": "VITE_AUTH_URL=https://auth.dev.opencode.ai bun sst shell --stage=dev bun dev",
|
||||||
|
"build": "vinxi build && ../../packages/opencode/script/schema.ts ./.output/public/config.json",
|
||||||
"start": "vinxi start",
|
"start": "vinxi start",
|
||||||
"version": "0.5.5"
|
"version": "0.6.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ibm/plex": "6.4.1",
|
"@ibm/plex": "6.4.1",
|
||||||
|
"@openauthjs/openauth": "0.0.0-20250322224806",
|
||||||
"@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": "^1.9.5",
|
"solid-js": "catalog:",
|
||||||
"vinxi": "^0.5.7"
|
"vinxi": "^0.5.7",
|
||||||
|
"@opencode/cloud-core": "workspace:*"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=22"
|
"node": ">=22"
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 664 B |
@@ -0,0 +1,5 @@
|
|||||||
|
<svg width="600" height="600" viewBox="0 0 600 600" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect width="600" height="600" fill="black"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M115 180H300V420H115V180ZM253.75 229.044H161.25V370.405H253.75V229.044Z" fill="white"/>
|
||||||
|
<path d="M346.25 180H485V229.044H392.5V370.405H485V419.449H346.25V180Z" fill="white"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 377 B |
@@ -0,0 +1,5 @@
|
|||||||
|
User-agent: *
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
# Disallow shared content pages
|
||||||
|
Disallow: /s/
|
||||||
|
After Width: | Height: | Size: 17 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,16 +1,18 @@
|
|||||||
import { MetaProvider, Title } 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 { 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
|
||||||
|
explicitLinks={true}
|
||||||
root={props => (
|
root={props => (
|
||||||
<MetaProvider>
|
<MetaProvider>
|
||||||
<Title>SolidStart - Basic</Title>
|
<Title>opencode</Title>
|
||||||
|
<Meta name="description" content="opencode - The AI coding agent built for the terminal." />
|
||||||
<Suspense>{props.children}</Suspense>
|
<Suspense>{props.children}</Suspense>
|
||||||
</MetaProvider>
|
</MetaProvider>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
<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>
|
||||||
|
|
||||||
|
After Width: | Height: | Size: 212 B |
@@ -0,0 +1,2 @@
|
|||||||
|
<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>
|
||||||
|
|
||||||
|
After Width: | Height: | Size: 443 B |
|
Before Width: | Height: | Size: 902 KiB After Width: | Height: | Size: 902 KiB |
|
Before Width: | Height: | Size: 456 KiB After Width: | Height: | Size: 456 KiB |
|
Before Width: | Height: | Size: 998 KiB After Width: | Height: | Size: 998 KiB |
|
After Width: | Height: | Size: 592 KiB |
@@ -0,0 +1,18 @@
|
|||||||
|
<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>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,12 @@
|
|||||||
|
<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>
|
||||||
|
After Width: | Height: | Size: 981 B |
@@ -1,6 +1,21 @@
|
|||||||
|
|
||||||
import { JSX } from "solid-js"
|
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>) {
|
export function IconCopy(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { useSession } from "vinxi/http"
|
||||||
|
|
||||||
|
export interface AuthSession {
|
||||||
|
account?: Record<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
id: string
|
||||||
|
email: string
|
||||||
|
}
|
||||||
|
>
|
||||||
|
current?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useAuthSession() {
|
||||||
|
return useSession<AuthSession>({
|
||||||
|
password: "0".repeat(32),
|
||||||
|
name: "auth",
|
||||||
|
cookie: {
|
||||||
|
secure: false,
|
||||||
|
httpOnly: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
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 { 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 = async (workspace?: string): Promise<Actor.Info> => {
|
||||||
|
"use server"
|
||||||
|
const evt = getRequestEvent()
|
||||||
|
if (!evt) throw new Error("No request event")
|
||||||
|
if (evt.locals.actor) return evt.locals.actor
|
||||||
|
evt.locals.actor = (async () => {
|
||||||
|
const auth = await useAuthSession()
|
||||||
|
if (!workspace) {
|
||||||
|
const account = auth.data.account ?? {}
|
||||||
|
const current = account[auth.data.current ?? ""]
|
||||||
|
if (current) {
|
||||||
|
return {
|
||||||
|
type: "account",
|
||||||
|
properties: {
|
||||||
|
email: current.email,
|
||||||
|
accountID: current.id,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Object.keys(account).length > 0) {
|
||||||
|
const current = Object.values(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 accounts = Object.keys(auth.data.account ?? {})
|
||||||
|
if (accounts.length) {
|
||||||
|
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, workspace)))
|
||||||
|
.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")
|
||||||
|
})()
|
||||||
|
return evt.locals.actor
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { Actor } from "@opencode/cloud-core/actor.js"
|
||||||
|
import { getActor } from "./auth"
|
||||||
|
|
||||||
|
export async function withActor<T>(fn: () => T, workspace?: string) {
|
||||||
|
const actor = await getActor(workspace)
|
||||||
|
return Actor.provide(actor.type, actor.properties, fn)
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
// @refresh reload
|
// @refresh reload
|
||||||
import { createHandler, StartServer } from "@solidjs/start/server";
|
import { createHandler, StartServer } from "@solidjs/start/server"
|
||||||
|
|
||||||
|
|
||||||
export default createHandler(() => (
|
export default createHandler(() => (
|
||||||
<StartServer
|
<StartServer
|
||||||
@@ -8,14 +9,18 @@ export default createHandler(() => (
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<link rel="icon" href="/favicon.ico" />
|
<link rel="icon" href="/favicon.svg" />
|
||||||
|
<meta property="og:image" content="/social-share.png" />
|
||||||
|
<meta property="twitter:image" content="/social-share.png" />
|
||||||
{assets}
|
{assets}
|
||||||
</head>
|
</head>
|
||||||
<body data-color-mode="dark">
|
<body>
|
||||||
<div id="app">{children}</div>
|
<div id="app">{children}</div>
|
||||||
{scripts}
|
{scripts}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
));
|
), {
|
||||||
|
mode: "async",
|
||||||
|
})
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { defineMiddleware } from "vinxi/http"
|
||||||
|
|
||||||
|
export default defineMiddleware({
|
||||||
|
onBeforeResponse() {},
|
||||||
|
})
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
[data-page="not-found"] {
|
||||||
|
--color-text: hsl(224, 10%, 10%);
|
||||||
|
--color-text-secondary: hsl(224, 7%, 46%);
|
||||||
|
--color-text-dimmed: hsl(224, 6%, 63%);
|
||||||
|
--color-text-inverted: hsl(0, 0%, 100%);
|
||||||
|
|
||||||
|
--color-border: hsl(224, 6%, 77%);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-page="not-found"] {
|
||||||
|
@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-text-inverted: hsl(224, 10%, 10%);
|
||||||
|
|
||||||
|
--color-border: hsl(224, 6%, 36%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-page="not-found"] {
|
||||||
|
--padding: 3rem;
|
||||||
|
--vertical-padding: 1.5rem;
|
||||||
|
--heading-font-size: 1.375rem;
|
||||||
|
|
||||||
|
@media (max-width: 30rem) {
|
||||||
|
--padding: 1rem;
|
||||||
|
--vertical-padding: 0.75rem;
|
||||||
|
--heading-font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
color: var(--color-text);
|
||||||
|
padding: calc(var(--padding) + 1rem);
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--color-text);
|
||||||
|
text-decoration: underline;
|
||||||
|
text-underline-offset: var(--space-0-75);
|
||||||
|
text-decoration-thickness: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-component="content"] {
|
||||||
|
max-width: 40rem;
|
||||||
|
width: 100%;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-component="top"] {
|
||||||
|
padding: var(--padding);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: calc(var(--vertical-padding) / 2);
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
[data-slot="logo-link"] {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
height: auto;
|
||||||
|
width: clamp(200px, 85vw, 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-weight: 500;
|
||||||
|
text-align: center;
|
||||||
|
font-size: var(--heading-font-size);
|
||||||
|
color: var(--color-text);
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-component="actions"] {
|
||||||
|
border-top: 1px solid var(--color-border);
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
[data-slot="action"] {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.4;
|
||||||
|
padding: var(--vertical-padding) 1rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 1rem;
|
||||||
|
|
||||||
|
a {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
color: var(--color-text);
|
||||||
|
text-decoration: underline;
|
||||||
|
text-underline-offset: var(--space-0-75);
|
||||||
|
text-decoration-thickness: 1px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="action"] + [data-slot="action"] {
|
||||||
|
border-left: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 40rem) {
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
[data-slot="action"] + [data-slot="action"] {
|
||||||
|
border-left: none;
|
||||||
|
border-top: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,19 +1,38 @@
|
|||||||
import { Title } from "@solidjs/meta";
|
import "./[...404].css"
|
||||||
import { HttpStatusCode } from "@solidjs/start";
|
import { Title } from "@solidjs/meta"
|
||||||
|
import { HttpStatusCode } from "@solidjs/start"
|
||||||
|
import logoLight from "../asset/logo-ornate-light.svg"
|
||||||
|
import logoDark from "../asset/logo-ornate-dark.svg"
|
||||||
|
|
||||||
export default function NotFound() {
|
export default function NotFound() {
|
||||||
return (
|
return (
|
||||||
<main>
|
<main data-page="not-found">
|
||||||
<Title>Not Found</Title>
|
<Title>Not Found | opencode</Title>
|
||||||
<HttpStatusCode code={404} />
|
<HttpStatusCode code={404} />
|
||||||
<h1>Page Not Found</h1>
|
<div data-component="content">
|
||||||
<p>
|
<section data-component="top">
|
||||||
Visit{" "}
|
<a href="/" data-slot="logo-link">
|
||||||
<a href="https://start.solidjs.com" target="_blank">
|
<img data-slot="logo light" src={logoLight} alt="opencode logo light" />
|
||||||
start.solidjs.com
|
<img data-slot="logo dark" src={logoDark} alt="opencode logo dark" />
|
||||||
</a>{" "}
|
</a>
|
||||||
to learn how to build SolidStart apps.
|
<h1 data-slot="title">404 - Page Not Found</h1>
|
||||||
</p>
|
</section>
|
||||||
|
|
||||||
|
<section data-component="actions">
|
||||||
|
<div data-slot="action">
|
||||||
|
<a href="/">Home</a>
|
||||||
|
</div>
|
||||||
|
<div data-slot="action">
|
||||||
|
<a href="/docs">Docs</a>
|
||||||
|
</div>
|
||||||
|
<div data-slot="action">
|
||||||
|
<a href="https://github.com/sst/opencode">GitHub</a>
|
||||||
|
</div>
|
||||||
|
<div data-slot="action">
|
||||||
|
<a href="/discord">Discord</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { redirect } from "@solidjs/router"
|
||||||
|
import type { APIEvent } from "@solidjs/start/server"
|
||||||
|
import { AuthClient } from "~/context/auth"
|
||||||
|
import { useAuthSession } from "~/context/auth.session"
|
||||||
|
|
||||||
|
export async function GET(input: APIEvent) {
|
||||||
|
const url = new URL(input.request.url)
|
||||||
|
const code = url.searchParams.get("code")
|
||||||
|
if (!code) throw new Error("No code found")
|
||||||
|
const result = await AuthClient.exchange(code, `${url.origin}${url.pathname}`)
|
||||||
|
if (result.err) {
|
||||||
|
throw new Error(result.err.message)
|
||||||
|
}
|
||||||
|
const decoded = AuthClient.decode(result.tokens.access, {} as any)
|
||||||
|
if (decoded.err) throw new Error(decoded.err.message)
|
||||||
|
const session = await useAuthSession()
|
||||||
|
const id = decoded.subject.properties.accountID
|
||||||
|
await session.update((value) => {
|
||||||
|
return {
|
||||||
|
...value,
|
||||||
|
account: {
|
||||||
|
[id]: {
|
||||||
|
id,
|
||||||
|
email: decoded.subject.properties.email,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
current: id,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return redirect("/auth")
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { Account } from "@opencode/cloud-core/account.js"
|
||||||
|
import { redirect } from "@solidjs/router"
|
||||||
|
import type { APIEvent } from "@solidjs/start/server"
|
||||||
|
import { withActor } from "~/context/auth.withActor"
|
||||||
|
|
||||||
|
export async function GET(input: APIEvent) {
|
||||||
|
try {
|
||||||
|
const workspaces = await withActor(async () => Account.workspaces())
|
||||||
|
return redirect(`/workspace/${workspaces[0].id}`)
|
||||||
|
} catch {
|
||||||
|
return redirect("/auth/authorize")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import type { APIEvent } from "@solidjs/start/server"
|
||||||
|
import { json } from "@solidjs/router"
|
||||||
|
import { Database } from "@opencode/cloud-core/drizzle/index.js"
|
||||||
|
import { UserTable } from "@opencode/cloud-core/schema/user.sql.js"
|
||||||
|
|
||||||
|
export async function GET(evt: APIEvent) {
|
||||||
|
return json({
|
||||||
|
data: await Database.use(async (tx) => {
|
||||||
|
const result = await tx.$count(UserTable)
|
||||||
|
return result
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { redirect } from "@solidjs/router"
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
return redirect("https://discord.gg/opencode")
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import type { APIEvent } from "@solidjs/start/server"
|
||||||
|
|
||||||
|
async function handler(evt: APIEvent) {
|
||||||
|
const req = evt.request.clone()
|
||||||
|
const url = new URL(req.url)
|
||||||
|
const targetUrl = `https://docs.opencode.ai${url.pathname}${url.search}`
|
||||||
|
const response = await fetch(targetUrl, {
|
||||||
|
method: req.method,
|
||||||
|
headers: req.headers,
|
||||||
|
body: req.body,
|
||||||
|
})
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GET = handler
|
||||||
|
export const POST = handler
|
||||||
|
export const PUT = handler
|
||||||
|
export const DELETE = handler
|
||||||
|
export const OPTIONS = handler
|
||||||
|
export const PATCH = handler
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import type { APIEvent } from "@solidjs/start/server"
|
||||||
|
|
||||||
|
async function handler(evt: APIEvent) {
|
||||||
|
const req = evt.request.clone()
|
||||||
|
const url = new URL(req.url)
|
||||||
|
const targetUrl = `https://docs.opencode.ai${url.pathname}${url.search}`
|
||||||
|
const response = await fetch(targetUrl, {
|
||||||
|
method: req.method,
|
||||||
|
headers: req.headers,
|
||||||
|
body: req.body,
|
||||||
|
})
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GET = handler
|
||||||
|
export const POST = handler
|
||||||
|
export const PUT = handler
|
||||||
|
export const DELETE = handler
|
||||||
|
export const OPTIONS = handler
|
||||||
|
export const PATCH = handler
|
||||||
@@ -0,0 +1,308 @@
|
|||||||
|
/**
|
||||||
|
* @deprecated Use zen/v1/chat/completions instead
|
||||||
|
*/
|
||||||
|
import { Resource } from "@opencode/cloud-resource"
|
||||||
|
import type { APIEvent } from "@solidjs/start/server"
|
||||||
|
import { Database, eq, sql } from "@opencode/cloud-core/drizzle/index.js"
|
||||||
|
import { KeyTable } from "@opencode/cloud-core/schema/key.sql.js"
|
||||||
|
import { BillingTable, UsageTable } from "@opencode/cloud-core/schema/billing.sql.js"
|
||||||
|
import { centsToMicroCents } from "@opencode/cloud-core/util/price.js"
|
||||||
|
import { Identifier } from "@opencode/cloud-core/identifier.js"
|
||||||
|
|
||||||
|
const MODELS = {
|
||||||
|
// "anthropic/claude-sonnet-4": {
|
||||||
|
// auth: true,
|
||||||
|
// api: "https://api.anthropic.com",
|
||||||
|
// apiKey: Resource.ANTHROPIC_API_KEY.value,
|
||||||
|
// model: "claude-sonnet-4-20250514",
|
||||||
|
// cost: {
|
||||||
|
// input: 0.0000015,
|
||||||
|
// output: 0.000006,
|
||||||
|
// reasoning: 0.0000015,
|
||||||
|
// cacheRead: 0.0000001,
|
||||||
|
// cacheWrite: 0.0000001,
|
||||||
|
// },
|
||||||
|
// headerMappings: {},
|
||||||
|
// },
|
||||||
|
"qwen/qwen3-coder": {
|
||||||
|
id: "qwen/qwen3-coder",
|
||||||
|
auth: true,
|
||||||
|
api: "https://inference.baseten.co",
|
||||||
|
apiKey: Resource.BASETEN_API_KEY.value,
|
||||||
|
model: "Qwen/Qwen3-Coder-480B-A35B-Instruct",
|
||||||
|
cost: {
|
||||||
|
input: 0.00000038,
|
||||||
|
output: 0.00000153,
|
||||||
|
reasoning: 0,
|
||||||
|
cacheRead: 0,
|
||||||
|
cacheWrite: 0,
|
||||||
|
},
|
||||||
|
headerMappings: {},
|
||||||
|
},
|
||||||
|
"grok-code": {
|
||||||
|
id: "x-ai/grok-code-fast-1",
|
||||||
|
auth: false,
|
||||||
|
api: "https://api.x.ai",
|
||||||
|
apiKey: Resource.XAI_API_KEY.value,
|
||||||
|
model: "grok-code",
|
||||||
|
cost: {
|
||||||
|
input: 0,
|
||||||
|
output: 0,
|
||||||
|
reasoning: 0,
|
||||||
|
cacheRead: 0,
|
||||||
|
cacheWrite: 0,
|
||||||
|
},
|
||||||
|
headerMappings: {
|
||||||
|
"x-grok-conv-id": "x-opencode-session",
|
||||||
|
"x-grok-req-id": "x-opencode-request",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
class AuthError extends Error {}
|
||||||
|
class CreditsError extends Error {}
|
||||||
|
class ModelError extends Error {}
|
||||||
|
|
||||||
|
export async function POST(input: APIEvent) {
|
||||||
|
try {
|
||||||
|
const url = new URL(input.request.url)
|
||||||
|
const body = await input.request.json()
|
||||||
|
const MODEL = validateModel()
|
||||||
|
const apiKey = await authenticate()
|
||||||
|
await checkCredits()
|
||||||
|
|
||||||
|
// Request to model provider
|
||||||
|
const res = await fetch(new URL(url.pathname.replace(/^\/gateway/, "") + url.search, MODEL.api), {
|
||||||
|
method: "POST",
|
||||||
|
headers: (() => {
|
||||||
|
const headers = input.request.headers
|
||||||
|
headers.delete("host")
|
||||||
|
headers.delete("content-length")
|
||||||
|
headers.set("authorization", `Bearer ${MODEL.apiKey}`)
|
||||||
|
Object.entries(MODEL.headerMappings ?? {}).forEach(([k, v]) => {
|
||||||
|
headers.set(k, headers.get(v)!)
|
||||||
|
})
|
||||||
|
return headers
|
||||||
|
})(),
|
||||||
|
body: JSON.stringify({
|
||||||
|
...body,
|
||||||
|
model: MODEL.model,
|
||||||
|
stream_options: {
|
||||||
|
include_usage: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
// Scrub response headers
|
||||||
|
const resHeaders = new Headers()
|
||||||
|
const keepHeaders = ["content-type", "cache-control"]
|
||||||
|
for (const [k, v] of res.headers.entries()) {
|
||||||
|
if (keepHeaders.includes(k.toLowerCase())) {
|
||||||
|
resHeaders.set(k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle non-streaming response
|
||||||
|
if (!body.stream) {
|
||||||
|
const body = await res.json()
|
||||||
|
await trackUsage(body)
|
||||||
|
return new Response(JSON.stringify(body), {
|
||||||
|
status: res.status,
|
||||||
|
statusText: res.statusText,
|
||||||
|
headers: resHeaders,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle streaming response
|
||||||
|
const stream = new ReadableStream({
|
||||||
|
start(c) {
|
||||||
|
const reader = res.body?.getReader()
|
||||||
|
const decoder = new TextDecoder()
|
||||||
|
let buffer = ""
|
||||||
|
|
||||||
|
function pump(): Promise<void> {
|
||||||
|
return (
|
||||||
|
reader?.read().then(async ({ done, value }) => {
|
||||||
|
if (done) {
|
||||||
|
c.close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer += decoder.decode(value, { stream: true })
|
||||||
|
|
||||||
|
const parts = buffer.split("\n\n")
|
||||||
|
buffer = parts.pop() ?? ""
|
||||||
|
|
||||||
|
const usage = parts
|
||||||
|
.map((part) => part.trim())
|
||||||
|
.filter((part) => part.startsWith("data: "))
|
||||||
|
.map((part) => {
|
||||||
|
try {
|
||||||
|
return JSON.parse(part.slice(6))
|
||||||
|
} catch (e) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.find((part) => part.usage)
|
||||||
|
if (usage) await trackUsage(usage)
|
||||||
|
|
||||||
|
c.enqueue(value)
|
||||||
|
|
||||||
|
return pump()
|
||||||
|
}) || Promise.resolve()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return pump()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return new Response(stream, {
|
||||||
|
status: res.status,
|
||||||
|
statusText: res.statusText,
|
||||||
|
headers: resHeaders,
|
||||||
|
})
|
||||||
|
|
||||||
|
function validateModel() {
|
||||||
|
if (!(body.model in MODELS)) {
|
||||||
|
throw new ModelError(`Model ${body.model} not supported`)
|
||||||
|
}
|
||||||
|
return MODELS[body.model as keyof typeof MODELS]
|
||||||
|
}
|
||||||
|
|
||||||
|
async function authenticate() {
|
||||||
|
try {
|
||||||
|
const authHeader = input.request.headers.get("authorization")
|
||||||
|
if (!authHeader || !authHeader.startsWith("Bearer ")) throw new AuthError("Missing API key.")
|
||||||
|
|
||||||
|
const apiKey = authHeader.split(" ")[1]
|
||||||
|
const key = await Database.use((tx) =>
|
||||||
|
tx
|
||||||
|
.select({
|
||||||
|
id: KeyTable.id,
|
||||||
|
workspaceID: KeyTable.workspaceID,
|
||||||
|
})
|
||||||
|
.from(KeyTable)
|
||||||
|
.where(eq(KeyTable.key, apiKey))
|
||||||
|
.then((rows) => rows[0]),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!key) throw new AuthError("Invalid API key.")
|
||||||
|
return key
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
// ignore error if model does not require authentication
|
||||||
|
if (!MODEL.auth) return
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkCredits() {
|
||||||
|
if (!apiKey || !MODEL.auth) return
|
||||||
|
|
||||||
|
const billing = await Database.use((tx) =>
|
||||||
|
tx
|
||||||
|
.select({
|
||||||
|
balance: BillingTable.balance,
|
||||||
|
})
|
||||||
|
.from(BillingTable)
|
||||||
|
.where(eq(BillingTable.workspaceID, apiKey.workspaceID))
|
||||||
|
.then((rows) => rows[0]),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (billing.balance <= 0) throw new CreditsError("Insufficient balance")
|
||||||
|
}
|
||||||
|
|
||||||
|
async function trackUsage(chunk: any) {
|
||||||
|
console.log(`trackUsage ${apiKey}`)
|
||||||
|
|
||||||
|
if (!apiKey) return
|
||||||
|
|
||||||
|
const usage = chunk.usage
|
||||||
|
const inputTokens = usage.prompt_tokens ?? 0
|
||||||
|
const outputTokens = usage.completion_tokens ?? 0
|
||||||
|
const reasoningTokens = usage.completion_tokens_details?.reasoning_tokens ?? 0
|
||||||
|
const cacheReadTokens = usage.prompt_tokens_details?.cached_tokens ?? 0
|
||||||
|
//const cacheWriteTokens = providerMetadata?.["anthropic"]?.["cacheCreationInputTokens"] ?? 0
|
||||||
|
const cacheWriteTokens = 0
|
||||||
|
|
||||||
|
const inputCost = MODEL.cost.input * inputTokens
|
||||||
|
const outputCost = MODEL.cost.output * outputTokens
|
||||||
|
const reasoningCost = MODEL.cost.reasoning * reasoningTokens
|
||||||
|
const cacheReadCost = MODEL.cost.cacheRead * cacheReadTokens
|
||||||
|
const cacheWriteCost = MODEL.cost.cacheWrite * cacheWriteTokens
|
||||||
|
const costInCents = (inputCost + outputCost + reasoningCost + cacheReadCost + cacheWriteCost) * 100
|
||||||
|
const cost = centsToMicroCents(costInCents)
|
||||||
|
|
||||||
|
await Database.transaction(async (tx) => {
|
||||||
|
await tx.insert(UsageTable).values({
|
||||||
|
workspaceID: apiKey.workspaceID,
|
||||||
|
id: Identifier.create("usage"),
|
||||||
|
model: MODEL.id,
|
||||||
|
inputTokens,
|
||||||
|
outputTokens,
|
||||||
|
reasoningTokens,
|
||||||
|
cacheReadTokens,
|
||||||
|
cacheWriteTokens,
|
||||||
|
cost,
|
||||||
|
})
|
||||||
|
await tx
|
||||||
|
.update(BillingTable)
|
||||||
|
.set({
|
||||||
|
balance: sql`${BillingTable.balance} - ${cost}`,
|
||||||
|
})
|
||||||
|
.where(eq(BillingTable.workspaceID, apiKey.workspaceID))
|
||||||
|
})
|
||||||
|
|
||||||
|
await Database.use((tx) =>
|
||||||
|
tx
|
||||||
|
.update(KeyTable)
|
||||||
|
.set({ timeUsed: sql`now()` })
|
||||||
|
.where(eq(KeyTable.id, apiKey.id)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error instanceof AuthError) {
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
error: {
|
||||||
|
message: error.message,
|
||||||
|
type: "invalid_request_error",
|
||||||
|
param: null,
|
||||||
|
code: "unauthorized",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
status: 401,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error instanceof CreditsError) {
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
error: {
|
||||||
|
message: error.message,
|
||||||
|
type: "insufficient_quota",
|
||||||
|
param: null,
|
||||||
|
code: "insufficient_quota",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
status: 401,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error instanceof ModelError) {
|
||||||
|
return new Response(JSON.stringify({ error: { message: error.message } }), {
|
||||||
|
status: 401,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(error)
|
||||||
|
return new Response(JSON.stringify({ error: { message: error.message } }), {
|
||||||
|
status: 500,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,81 +1,155 @@
|
|||||||
[data-page="home"] {
|
[data-page="home"] {
|
||||||
--color-bg: oklch(0.2097 0.008 274.53);
|
--color-text: hsl(224, 10%, 10%);
|
||||||
--color-border: oklch(0.46 0.02 269.88);
|
--color-text-secondary: hsl(224, 7%, 46%);
|
||||||
--color-text: #ffffff;
|
--color-text-dimmed: hsl(224, 6%, 63%);
|
||||||
--color-text-secondary: oklch(0.72 0.01 270.15);
|
--color-text-inverted: hsl(0, 0%, 100%);
|
||||||
|
|
||||||
|
--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-text-dimmed: hsl(224, 7%, 46%);
|
||||||
padding: var(--space-6);
|
--color-text-inverted: hsl(224, 10%, 10%);
|
||||||
|
|
||||||
|
--color-border: hsl(224, 6%, 36%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-page="home"] {
|
||||||
|
--padding: 3rem;
|
||||||
|
--vertical-padding: 1.5rem;
|
||||||
|
--heading-font-size: 1.375rem;
|
||||||
|
|
||||||
|
@media (max-width: 30rem) {
|
||||||
|
--padding: 1rem;
|
||||||
|
--vertical-padding: 0.75rem;
|
||||||
|
--heading-font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
font-family: var(--font-mono);
|
font-family: var(--font-mono);
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
|
padding: calc(var(--padding) + 1rem);
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
text-underline-offset: var(--space-0-75);
|
text-underline-offset: var(--space-0-75);
|
||||||
|
text-decoration-thickness: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
background: var(--color-bg);
|
|
||||||
position: fixed;
|
|
||||||
overflow-y: scroll;
|
|
||||||
inset: 0;
|
|
||||||
|
|
||||||
[data-component="content"] {
|
[data-component="content"] {
|
||||||
max-width: 67.5rem;
|
max-width: 67.5rem;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
border: 2px solid var(--color-border);
|
border: 1px solid var(--color-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-component="top"] {
|
[data-component="top"] {
|
||||||
padding: var(--space-12);
|
padding: var(--padding);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: start;
|
align-items: center;
|
||||||
gap: var(--space-4);
|
gap: calc(var(--vertical-padding) / 2);
|
||||||
|
|
||||||
[data-slot="logo"] {
|
img {
|
||||||
height: 70px;
|
height: auto;
|
||||||
|
width: clamp(200px, 85vw, 552px);
|
||||||
|
}
|
||||||
|
|
||||||
|
[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"] {
|
[data-slot="title"] {
|
||||||
font-size: var(--font-size-2xl);
|
line-height: 1.25;
|
||||||
|
font-weight: 500;
|
||||||
|
text-align: center;
|
||||||
|
font-size: var(--heading-font-size);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-component="cta"] {
|
[data-component="cta"] {
|
||||||
height: var(--space-19);
|
border-top: 1px solid var(--color-border);
|
||||||
border-top: 2px solid var(--color-border);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
|
& > div + div {
|
||||||
|
border-left: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
[data-slot="left"] {
|
[data-slot="left"] {
|
||||||
display: flex;
|
flex: 0 0 auto;
|
||||||
padding: 0 var(--space-12);
|
text-align: center;
|
||||||
|
line-height: 1.4;
|
||||||
|
padding: var(--vertical-padding) 2rem;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
text-decoration: underline;
|
font-size: 1.125rem;
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
@media (max-width: 30rem) {
|
||||||
text-underline-offset: var(--space-0-75);
|
font-size: 1rem;
|
||||||
border-right: 2px solid var(--color-border);
|
padding-bottom: calc(var(--vertical-padding) + 4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 30rem) {
|
||||||
|
padding-left: 0.5rem;
|
||||||
|
padding-right: 0.5rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="right"] {
|
[data-slot="right"] {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
padding: var(--vertical-padding) 1rem;
|
||||||
align-items: center;
|
}
|
||||||
justify-content: center;
|
|
||||||
gap: var(--space-2-5);
|
@media (max-width: 50rem) {
|
||||||
padding: 0 var(--space-6);
|
flex-direction: column;
|
||||||
|
|
||||||
|
[data-slot="right"] {
|
||||||
|
border-left: none;
|
||||||
|
border-top: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="command"] {
|
[data-slot="command"] {
|
||||||
all: unset;
|
all: unset;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
font-size: var(--font-size-lg);
|
font-size: 1.125rem;
|
||||||
font-family: var(--font-mono);
|
font-family: var(--font-mono);
|
||||||
gap: var(--space-2);
|
gap: var(--space-2);
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
& > span {
|
||||||
|
@media (max-width: 24rem) {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
@media (max-width: 56rem) {
|
||||||
|
[data-slot="protocol"] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (max-width: 38rem) {
|
||||||
|
text-align: center;
|
||||||
|
span:first-child {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="highlight"] {
|
[data-slot="highlight"] {
|
||||||
@@ -85,8 +159,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
[data-component="features"] {
|
[data-component="features"] {
|
||||||
border-top: 2px solid var(--color-border);
|
border-top: 1px solid var(--color-border);
|
||||||
padding: var(--space-12);
|
padding: var(--padding);
|
||||||
|
|
||||||
[data-slot="list"] {
|
[data-slot="list"] {
|
||||||
padding-left: var(--space-4);
|
padding-left: var(--space-4);
|
||||||
@@ -95,11 +169,22 @@
|
|||||||
|
|
||||||
li {
|
li {
|
||||||
margin-bottom: var(--space-4);
|
margin-bottom: var(--space-4);
|
||||||
|
line-height: 1.6;
|
||||||
|
|
||||||
strong {
|
strong {
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
line-height: 1;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
letter-spacing: 0.03125rem;
|
||||||
|
background: var(--color-border);
|
||||||
|
padding: 0.125rem 0.375rem;
|
||||||
|
color: var(--color-text-inverted);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
li:last-child {
|
li:last-child {
|
||||||
@@ -109,7 +194,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
[data-component="install"] {
|
[data-component="install"] {
|
||||||
border-top: 2px solid var(--color-border);
|
border-top: 1px solid var(--color-border);
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
grid-template-rows: 1fr 1fr;
|
grid-template-rows: 1fr 1fr;
|
||||||
@@ -120,33 +205,54 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-component="title"] {
|
[data-component="method"] {
|
||||||
letter-spacing: -0.03125rem;
|
display: flex;
|
||||||
text-transform: uppercase;
|
padding: calc(var(--vertical-padding) / 2) calc(var(--padding) / 2) calc(var(--vertical-padding) / 2 + 0.125rem);
|
||||||
font-weight: 400;
|
flex-direction: column;
|
||||||
font-size: var(--font-size-md);
|
text-align: left;
|
||||||
flex-shrink: 0;
|
gap: var(--space-2-5);
|
||||||
color: oklch(0.55 0.02 269.87);
|
|
||||||
|
@media (max-width: 30rem) {
|
||||||
|
gap: 0.3125rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-component="method"] {
|
@media (max-width: 40rem) {
|
||||||
padding: var(--space-4) var(--space-6);
|
text-align: left;
|
||||||
display: flex;
|
}
|
||||||
flex-direction: column;
|
|
||||||
align-items: start;
|
|
||||||
gap: var(--space-3);
|
|
||||||
|
|
||||||
&:nth-child(2) {
|
&:nth-child(2) {
|
||||||
border-left: 2px solid var(--color-border);
|
border-left: 1px solid var(--color-border);
|
||||||
|
|
||||||
|
@media (max-width: 40rem) {
|
||||||
|
border-left: none;
|
||||||
|
border-top: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:nth-child(3) {
|
&:nth-child(3) {
|
||||||
border-top: 2px solid var(--color-border);
|
border-top: 1px solid var(--color-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:nth-child(4) {
|
&:nth-child(4) {
|
||||||
border-top: 2px solid var(--color-border);
|
border-top: 1px solid var(--color-border);
|
||||||
border-left: 2px solid var(--color-border);
|
border-left: 1px 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"] {
|
[data-slot="button"] {
|
||||||
@@ -155,63 +261,146 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
gap: var(--space-2);
|
gap: var(--space-2-5);
|
||||||
|
font-size: 1rem;
|
||||||
|
|
||||||
|
@media (max-width: 24rem) {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
strong {
|
strong {
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 40rem) {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 30rem) {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-component="screenshots"] {
|
[data-component="screenshots"] {
|
||||||
border-top: 2px solid var(--color-border);
|
border-top: 1px solid var(--color-border);
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
gap: 0;
|
|
||||||
|
|
||||||
[data-slot="left"] {
|
figure {
|
||||||
padding: var(--space-8) var(--space-6);
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
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;
|
||||||
|
|
||||||
img {
|
& > div,
|
||||||
width: 100%;
|
figcaption {
|
||||||
height: auto;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="right"] {
|
|
||||||
display: grid;
|
|
||||||
grid-template-rows: 1fr 1fr;
|
|
||||||
border-left: 2px solid var(--color-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="filler"] {
|
& > div {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-grow: 1;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="cell"] {
|
a {
|
||||||
padding: var(--space-8) var(--space-6);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex: 1;
|
||||||
gap: var(--space-4);
|
min-height: 0;
|
||||||
|
align-items: center;
|
||||||
&:nth-child(2) {
|
justify-content: center;
|
||||||
border-top: 2px solid var(--color-border);
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
figcaption {
|
||||||
width: 80%;
|
letter-spacing: -0.03125rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--color-text-dimmed);
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
@media (max-width: 30rem) {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& > [data-slot="left"] figure {
|
||||||
|
height: var(--images-height);
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > [data-slot="right"] figure {
|
||||||
|
height: calc(var(--images-height) / 2);
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > [data-slot="left"] img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > [data-slot="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;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > [data-slot="left"] {
|
||||||
|
grid-row: 1;
|
||||||
|
grid-column: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > [data-slot="right"] {
|
||||||
|
grid-row: 2;
|
||||||
|
grid-column: 1;
|
||||||
|
border-left: none;
|
||||||
|
border-top: 1px solid var(--color-border);
|
||||||
|
|
||||||
|
& > [data-slot="row1"],
|
||||||
|
& > [data-slot="row2"] {
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
& > [data-slot="left"] figure,
|
||||||
|
& > [data-slot="right"] figure {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > [data-slot="left"] img,
|
||||||
|
& > [data-slot="right"] img {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
max-height: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-component="copy-status"] {
|
[data-component="copy-status"] {
|
||||||
|
@media (max-width: 38rem) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
[data-slot="copy"] {
|
[data-slot="copy"] {
|
||||||
display: block;
|
display: block;
|
||||||
width: var(--space-4);
|
width: var(--space-4);
|
||||||
@@ -227,7 +416,7 @@
|
|||||||
display: none;
|
display: none;
|
||||||
width: var(--space-4);
|
width: var(--space-4);
|
||||||
height: var(--space-4);
|
height: var(--space-4);
|
||||||
color: white;
|
color: var(--color-text);
|
||||||
|
|
||||||
[data-copied] & {
|
[data-copied] & {
|
||||||
display: block;
|
display: block;
|
||||||
@@ -236,21 +425,48 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
[data-component="footer"] {
|
[data-component="footer"] {
|
||||||
border-top: 2px solid var(--color-border);
|
border-top: 1px solid var(--color-border);
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-template-columns: 1fr 1fr 1fr;
|
flex-direction: row;
|
||||||
font-size: var(--font-size-lg);
|
|
||||||
height: var(--space-20);
|
|
||||||
|
|
||||||
[data-slot="cell"] {
|
[data-slot="cell"] {
|
||||||
display: flex;
|
flex: 1;
|
||||||
align-items: center;
|
text-align: center;
|
||||||
justify-content: center;
|
|
||||||
border-right: 2px solid var(--color-border);
|
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
padding: var(--vertical-padding) 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
&:last-child {
|
[data-slot="cell"] + [data-slot="cell"] {
|
||||||
border-right: none;
|
border-left: 1px 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: 1px solid var(--color-border);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
import { Title } from "@solidjs/meta"
|
|
||||||
import { onCleanup, onMount } from "solid-js"
|
|
||||||
import "./index.css"
|
import "./index.css"
|
||||||
import logo from "../asset/logo-ornate-dark.svg"
|
import { Title } from "@solidjs/meta"
|
||||||
import IMG_SPLASH from "../asset/screenshot-splash.webp"
|
import { Match, onCleanup, onMount, Switch } from "solid-js"
|
||||||
import IMG_VSCODE from "../asset/screenshot-vscode.webp"
|
import logoLight from "../asset/logo-ornate-light.svg"
|
||||||
import IMG_GITHUB from "../asset/screenshot-github.webp"
|
import logoDark from "../asset/logo-ornate-dark.svg"
|
||||||
|
import IMG_SPLASH from "../asset/lander/screenshot-splash.png"
|
||||||
|
import IMG_VSCODE from "../asset/lander/screenshot-vscode.png"
|
||||||
|
import IMG_GITHUB from "../asset/lander/screenshot-github.png"
|
||||||
import { IconCopy, IconCheck } from "../component/icon"
|
import { IconCopy, IconCheck } from "../component/icon"
|
||||||
|
import { createAsync, query, redirect, A } from "@solidjs/router"
|
||||||
|
import { getActor } from "~/context/auth"
|
||||||
|
import { withActor } from "~/context/auth.withActor"
|
||||||
|
import { Account } from "@opencode/cloud-core/account.js"
|
||||||
|
|
||||||
function CopyStatus() {
|
function CopyStatus() {
|
||||||
return (
|
return (
|
||||||
@@ -16,7 +21,17 @@ function CopyStatus() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const defaultWorkspace = query(async () => {
|
||||||
|
"use server"
|
||||||
|
const actor = await getActor()
|
||||||
|
if (actor.type === "account") {
|
||||||
|
const workspaces = await withActor(() => Account.workspaces())
|
||||||
|
return workspaces[0].id
|
||||||
|
}
|
||||||
|
}, "defaultWorkspace")
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
|
const workspace = createAsync(() => defaultWorkspace())
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
const commands = document.querySelectorAll("[data-copy]")
|
const commands = document.querySelectorAll("[data-copy]")
|
||||||
for (const button of commands) {
|
for (const button of commands) {
|
||||||
@@ -42,21 +57,24 @@ export default function Home() {
|
|||||||
<Title>opencode | AI coding agent built for the terminal</Title>
|
<Title>opencode | AI coding agent built for the terminal</Title>
|
||||||
<div data-component="content">
|
<div data-component="content">
|
||||||
<section data-component="top">
|
<section data-component="top">
|
||||||
<img data-slot="logo" src={logo} alt="logo" />
|
<img data-slot="logo light" src={logoLight} alt="opencode logo light" />
|
||||||
<h1 data-slot="title">The AI coding agent built for the terminal.</h1>
|
<img data-slot="logo dark" src={logoDark} alt="opencode logo dark" />
|
||||||
|
<h1 data-slot="title">The AI coding agent built for the terminal</h1>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section data-component="cta">
|
<section data-component="cta">
|
||||||
<div data-slot="left">
|
<div data-slot="left">
|
||||||
<a href="/docs">Get Started</a>
|
<a href="/docs">
|
||||||
|
Get Started
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div data-slot="right">
|
<div data-slot="right">
|
||||||
<button data-copy data-slot="command" data-command="curl -fsSL https://opencode.ai/install | bash">
|
<button data-copy data-slot="command">
|
||||||
<span>
|
<span>
|
||||||
<span>curl -fsSL </span>
|
<span>curl -fsSL </span>
|
||||||
<span data-slot="protocol">https://</span>
|
<span data-slot="protocol">https://</span>
|
||||||
<span data-slot="highlight">opencode.ai/install</span>
|
<span data-slot="highlight">opencode.ai/install</span>
|
||||||
| bash
|
<span> | bash</span>
|
||||||
</span>
|
</span>
|
||||||
<CopyStatus />
|
<CopyStatus />
|
||||||
</button>
|
</button>
|
||||||
@@ -66,23 +84,26 @@ export default function Home() {
|
|||||||
<section data-component="features">
|
<section data-component="features">
|
||||||
<ul data-slot="list">
|
<ul data-slot="list">
|
||||||
<li>
|
<li>
|
||||||
<strong>Native TUI</strong>: A responsive, native, themeable terminal UI.
|
<strong>Native TUI</strong> A responsive, native, themeable terminal UI
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<strong>LSP enabled</strong>: Automatically loads the right LSPs for the LLM.
|
<strong>LSP enabled</strong> Automatically loads the right LSPs for the LLM
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<strong>Multi-session</strong>: Start multiple agents in parallel on the same project.
|
<strong>opencode zen</strong> A <a href="/docs/zen">curated list of models</a> provided by opencode <label>New</label>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<strong>Shareable links</strong>: Share a link to any sessions for reference or to debug.
|
<strong>Multi-session</strong> Start multiple agents in parallel on the same project
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<strong>Claude Pro</strong>: Log in with Anthropic to use your Claude Pro or Max account.
|
<strong>Shareable links</strong> Share a link to any sessions for reference or to debug
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<strong>Use any model</strong>: Supports 75+ LLM providers through{" "}
|
<strong>Claude Pro</strong> Log in with Anthropic to use your Claude Pro or Max account
|
||||||
<a href="https://models.dev">Models.dev</a>, including local models.
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Use any model</strong> Supports 75+ LLM providers through{" "}
|
||||||
|
<a href="https://models.dev">Models.dev</a>, including local models
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
@@ -127,26 +148,12 @@ export default function Home() {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section data-component="screenshots">
|
<section data-component="screenshots">
|
||||||
<div data-slot="left">
|
<figure>
|
||||||
<div data-component="title">opencode TUI with tokyonight theme</div>
|
<figcaption>opencode TUI with the tokyonight theme</figcaption>
|
||||||
<div data-slot="filler">
|
<a href="/docs/cli">
|
||||||
<img src={IMG_SPLASH} alt="opencode TUI with tokyonight theme" />
|
<img src={IMG_SPLASH} alt="opencode TUI with tokyonight theme" />
|
||||||
</div>
|
</a>
|
||||||
</div>
|
</figure>
|
||||||
<div data-slot="right">
|
|
||||||
<div data-slot="cell">
|
|
||||||
<div data-component="title">opencode in VS Code</div>
|
|
||||||
<div data-slot="filler">
|
|
||||||
<img src={IMG_VSCODE} alt="opencode in VS Code" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div data-slot="cell">
|
|
||||||
<div data-component="title">opencode in GitHub</div>
|
|
||||||
<div data-slot="filler">
|
|
||||||
<img src={IMG_GITHUB} alt="opencode in GitHub" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<footer data-component="footer">
|
<footer data-component="footer">
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import type { APIEvent } from "@solidjs/start/server"
|
||||||
|
|
||||||
|
async function handler(evt: APIEvent) {
|
||||||
|
const req = evt.request.clone()
|
||||||
|
const url = new URL(req.url)
|
||||||
|
const targetUrl = `https://docs.opencode.ai/docs${url.pathname}${url.search}`
|
||||||
|
const response = await fetch(targetUrl, {
|
||||||
|
method: req.method,
|
||||||
|
headers: req.headers,
|
||||||
|
body: req.body,
|
||||||
|
})
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GET = handler
|
||||||
|
export const POST = handler
|
||||||
|
export const PUT = handler
|
||||||
|
export const DELETE = handler
|
||||||
|
export const OPTIONS = handler
|
||||||
|
export const PATCH = handler
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
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")
|
||||||
|
|
||||||
|
const chargedAmount = 2000
|
||||||
|
|
||||||
|
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(chargedAmount)}`,
|
||||||
|
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(chargedAmount),
|
||||||
|
paymentID,
|
||||||
|
customerID,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("finished handling")
|
||||||
|
|
||||||
|
return Response.json("ok", { status: 200 })
|
||||||
|
}
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
[data-page="workspace"] {
|
||||||
|
line-height: 1;
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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: var(--space-4) var(--space-4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[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);
|
||||||
|
|
||||||
|
[data-slot="user"] {
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 30rem) {
|
||||||
|
[data-slot="user"] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import "./workspace.css"
|
||||||
|
import { useAuthSession } from "~/context/auth.session"
|
||||||
|
import { IconLogo } from "../component/icon"
|
||||||
|
import { withActor } from "~/context/auth.withActor"
|
||||||
|
import { query, action, redirect, createAsync, RouteSectionProps, Navigate, useNavigate, useParams, A } from "@solidjs/router"
|
||||||
|
import { User } from "@opencode/cloud-core/user.js"
|
||||||
|
import { Actor } from "@opencode/cloud-core/actor.js"
|
||||||
|
import { getRequestEvent } from "solid-js/web"
|
||||||
|
|
||||||
|
const getUserInfo = query(async (workspaceID: string) => {
|
||||||
|
"use server"
|
||||||
|
return withActor(async () => {
|
||||||
|
const actor = Actor.assert("user")
|
||||||
|
return await User.fromID(actor.properties.userID)
|
||||||
|
}, workspaceID)
|
||||||
|
}, "userInfo")
|
||||||
|
|
||||||
|
const logout = action(async () => {
|
||||||
|
"use server"
|
||||||
|
const auth = await useAuthSession()
|
||||||
|
const event = getRequestEvent()
|
||||||
|
const current = auth.data.current
|
||||||
|
if (current)
|
||||||
|
await auth.update((val) => {
|
||||||
|
delete val.account?.[current]
|
||||||
|
const first = Object.keys(val.account ?? {})[0]
|
||||||
|
val.current = first
|
||||||
|
event!.locals.actor = undefined
|
||||||
|
return val
|
||||||
|
})
|
||||||
|
throw redirect("/")
|
||||||
|
})
|
||||||
|
|
||||||
|
export default function WorkspaceLayout(props: RouteSectionProps) {
|
||||||
|
const params = useParams()
|
||||||
|
const userInfo = createAsync(() => getUserInfo(params.id))
|
||||||
|
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 data-slot="user">{userInfo()?.email}</span>
|
||||||
|
<form action={logout} method="post">
|
||||||
|
<button type="submit" formaction={logout}>
|
||||||
|
Logout
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<div>{props.children}</div>
|
||||||
|
</main>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,431 @@
|
|||||||
|
[data-page="workspace-[id]"] {
|
||||||
|
max-width: 64rem;
|
||||||
|
padding: var(--space-10) var(--space-4);
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-10);
|
||||||
|
|
||||||
|
@media (max-width: 30rem) {
|
||||||
|
padding-top: var(--space-4);
|
||||||
|
padding-bottom: var(--space-4);
|
||||||
|
|
||||||
|
gap: var(--space-8);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="sections"] {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-16);
|
||||||
|
|
||||||
|
@media (max-width: 30rem) {
|
||||||
|
gap: var(--space-8);
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-6);
|
||||||
|
|
||||||
|
/* 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-md);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
line-height: 1.4;
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
section:not(:last-child) {
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
padding-bottom: var(--space-16);
|
||||||
|
|
||||||
|
@media (max-width: 30rem) {
|
||||||
|
padding-bottom: var(--space-8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-component="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 {
|
||||||
|
line-height: 1.5;
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Title section */
|
||||||
|
[data-component="title-section"] {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-2);
|
||||||
|
padding-bottom: var(--space-8);
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
|
||||||
|
@media (max-width: 30rem) {
|
||||||
|
padding-bottom: var(--space-6);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
line-height: 1.5;
|
||||||
|
font-size: var(--font-size-md);
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* API Keys Section */
|
||||||
|
[data-component="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);
|
||||||
|
|
||||||
|
@media (max-width: 30rem) {
|
||||||
|
gap: var(--space-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
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-component="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;
|
||||||
|
|
||||||
|
&[data-state="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-component="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-component="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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,490 @@
|
|||||||
|
import "./[id].css"
|
||||||
|
import { Billing } from "@opencode/cloud-core/billing.js"
|
||||||
|
import { Key } from "@opencode/cloud-core/key.js"
|
||||||
|
import {
|
||||||
|
json,
|
||||||
|
query,
|
||||||
|
action,
|
||||||
|
useParams,
|
||||||
|
useAction,
|
||||||
|
createAsync,
|
||||||
|
useSubmission,
|
||||||
|
} from "@solidjs/router"
|
||||||
|
import { createMemo, createSignal, For, Show } from "solid-js"
|
||||||
|
import { withActor } from "~/context/auth.withActor"
|
||||||
|
import { IconCopy, IconCheck } from "~/component/icon"
|
||||||
|
|
||||||
|
function 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(",", ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
function 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////
|
||||||
|
// Keys related queries and actions
|
||||||
|
/////////////////////////////////////
|
||||||
|
|
||||||
|
const listKeys = query(async (workspaceID: string) => {
|
||||||
|
"use server"
|
||||||
|
return withActor(() => Key.list(), workspaceID)
|
||||||
|
}, "key.list")
|
||||||
|
|
||||||
|
const createKey = action(async (workspaceID: string, name: string) => {
|
||||||
|
"use server"
|
||||||
|
return json(
|
||||||
|
withActor(() => Key.create({ name }), workspaceID),
|
||||||
|
{ revalidate: listKeys.key },
|
||||||
|
)
|
||||||
|
}, "key.create")
|
||||||
|
|
||||||
|
const removeKey = action(async (workspaceID: string, id: string) => {
|
||||||
|
"use server"
|
||||||
|
return json(
|
||||||
|
withActor(() => Key.remove({ id }), workspaceID),
|
||||||
|
{ revalidate: listKeys.key },
|
||||||
|
)
|
||||||
|
}, "key.remove")
|
||||||
|
|
||||||
|
/////////////////////////////////////
|
||||||
|
// Billing related queries and actions
|
||||||
|
/////////////////////////////////////
|
||||||
|
|
||||||
|
const getBalanceInfo = query(async (workspaceID: string) => {
|
||||||
|
"use server"
|
||||||
|
return withActor(async () => {
|
||||||
|
return await Billing.get()
|
||||||
|
}, workspaceID)
|
||||||
|
}, "balanceInfo")
|
||||||
|
|
||||||
|
const getUsageInfo = query(async (workspaceID: string) => {
|
||||||
|
"use server"
|
||||||
|
return withActor(async () => {
|
||||||
|
return await Billing.usages()
|
||||||
|
}, workspaceID)
|
||||||
|
}, "usageInfo")
|
||||||
|
|
||||||
|
const getPaymentsInfo = query(async (workspaceID: string) => {
|
||||||
|
"use server"
|
||||||
|
return withActor(async () => {
|
||||||
|
return await Billing.payments()
|
||||||
|
}, workspaceID)
|
||||||
|
}, "paymentsInfo")
|
||||||
|
|
||||||
|
const createCheckoutUrl = action(async (workspaceID: string, successUrl: string, cancelUrl: string) => {
|
||||||
|
"use server"
|
||||||
|
return withActor(() => Billing.generateCheckoutUrl({ successUrl, cancelUrl }), workspaceID)
|
||||||
|
}, "checkoutUrl")
|
||||||
|
|
||||||
|
// const createPortalUrl = action(async (workspaceID: string, returnUrl: string) => {
|
||||||
|
// "use server"
|
||||||
|
// return withActor(() => Billing.generatePortalUrl({ returnUrl }), workspaceID)
|
||||||
|
// }, "portalUrl")
|
||||||
|
|
||||||
|
function KeysSection() {
|
||||||
|
// Dummy data for testing
|
||||||
|
const dummyKeys = [
|
||||||
|
{
|
||||||
|
id: "key_1",
|
||||||
|
name: "Development API Key",
|
||||||
|
key: "oc_dev_1234567890abcdef1234567890abcdef12345678",
|
||||||
|
timeCreated: new Date("2024-01-15T10:30:00Z"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "key_2",
|
||||||
|
name: "Production API Key",
|
||||||
|
key: "oc_prod_abcdef1234567890abcdef1234567890abcdef12",
|
||||||
|
timeCreated: new Date("2024-02-01T14:22:00Z"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "key_3",
|
||||||
|
name: "Testing Environment",
|
||||||
|
key: "oc_test_9876543210fedcba9876543210fedcba98765432",
|
||||||
|
timeCreated: new Date("2024-02-10T09:15:00Z"),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const params = useParams()
|
||||||
|
const keys = createAsync(() => listKeys(params.id))
|
||||||
|
// const keys = () => dummyKeys
|
||||||
|
const [showForm, setShowForm] = createSignal(false)
|
||||||
|
const [name, setName] = createSignal("")
|
||||||
|
const removeAction = useAction(removeKey)
|
||||||
|
const createAction = useAction(createKey)
|
||||||
|
const createSubmission = useSubmission(createKey)
|
||||||
|
const [copiedId, setCopiedId] = createSignal<string | null>(null)
|
||||||
|
|
||||||
|
function formatKey(key: string) {
|
||||||
|
if (key.length <= 11) return key
|
||||||
|
return `${key.slice(0, 7)}...${key.slice(-4)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleCreateKey() {
|
||||||
|
if (!name().trim()) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
await createAction(params.id, name().trim())
|
||||||
|
setName("")
|
||||||
|
setShowForm(false)
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to create API key:", error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function copyKeyToClipboard(text: string, keyId: string) {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(text)
|
||||||
|
setCopiedId(keyId)
|
||||||
|
setTimeout(() => setCopiedId(null), 1500)
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to copy to clipboard:", error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleDeleteKey(keyId: string) {
|
||||||
|
if (!confirm("Are you sure you want to delete this API key?")) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await removeAction(params.id, keyId)
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to delete API key:", error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section data-component="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={!showForm()}
|
||||||
|
fallback={
|
||||||
|
<div data-slot="create-form">
|
||||||
|
<input
|
||||||
|
data-component="input"
|
||||||
|
type="text"
|
||||||
|
placeholder="Enter key name"
|
||||||
|
value={name()}
|
||||||
|
onInput={(e) => setName(e.currentTarget.value)}
|
||||||
|
onKeyPress={(e) => e.key === "Enter" && handleCreateKey()}
|
||||||
|
/>
|
||||||
|
<div data-slot="form-actions">
|
||||||
|
<button
|
||||||
|
data-color="ghost"
|
||||||
|
onClick={() => {
|
||||||
|
setShowForm(false)
|
||||||
|
setName("")
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
data-color="primary"
|
||||||
|
disabled={createSubmission.pending || !name().trim()}
|
||||||
|
onClick={handleCreateKey}
|
||||||
|
>
|
||||||
|
{createSubmission.pending ? "Creating..." : "Create"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
data-color="primary"
|
||||||
|
onClick={() => {
|
||||||
|
console.log("clicked")
|
||||||
|
setShowForm(true)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Create API Key
|
||||||
|
</button>
|
||||||
|
</Show>
|
||||||
|
<div data-slot="api-keys-table">
|
||||||
|
<Show
|
||||||
|
when={keys()?.length}
|
||||||
|
fallback={
|
||||||
|
<div data-component="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={copiedId() === 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function BalanceSection() {
|
||||||
|
const params = useParams()
|
||||||
|
const dummyBalanceInfo = { balance: 2500000000 } // $25.00 in cents
|
||||||
|
|
||||||
|
const balanceInfo = createAsync(() => getBalanceInfo(params.id))
|
||||||
|
// const balanceInfo = () => dummyBalanceInfo
|
||||||
|
const createCheckoutUrlAction = useAction(createCheckoutUrl)
|
||||||
|
const createCheckoutUrlSubmission = useSubmission(createCheckoutUrl)
|
||||||
|
|
||||||
|
async function handleBuyCredits() {
|
||||||
|
try {
|
||||||
|
const baseUrl = window.location.href
|
||||||
|
const checkoutUrl = await createCheckoutUrlAction(params.id, baseUrl, baseUrl)
|
||||||
|
if (checkoutUrl) {
|
||||||
|
window.location.href = checkoutUrl
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to get checkout URL:", error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section data-component="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"
|
||||||
|
data-state={(() => {
|
||||||
|
const balanceStr = ((balanceInfo()?.balance ?? 0) / 100000000).toFixed(2)
|
||||||
|
return balanceStr === "0.00" || balanceStr === "-0.00" ? "danger" : undefined
|
||||||
|
})()}
|
||||||
|
>
|
||||||
|
<span data-slot="currency">$</span>
|
||||||
|
<span data-slot="value">
|
||||||
|
{(() => {
|
||||||
|
const balanceStr = ((balanceInfo()?.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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function UsageSection() {
|
||||||
|
const params = useParams()
|
||||||
|
const dummyUsage = [
|
||||||
|
{
|
||||||
|
id: "usage_1",
|
||||||
|
model: "claude-3-sonnet-20240229",
|
||||||
|
inputTokens: 1250,
|
||||||
|
outputTokens: 890,
|
||||||
|
cost: 125000000, // $1.25 in cents
|
||||||
|
timeCreated: "2024-02-10T15:30:00Z",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "usage_2",
|
||||||
|
model: "gpt-4-turbo-preview",
|
||||||
|
inputTokens: 2100,
|
||||||
|
outputTokens: 1456,
|
||||||
|
cost: 340000000, // $3.40 in cents
|
||||||
|
timeCreated: "2024-02-09T09:45:00Z",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "usage_3",
|
||||||
|
model: "claude-3-haiku-20240307",
|
||||||
|
inputTokens: 850,
|
||||||
|
outputTokens: 620,
|
||||||
|
cost: 45000000, // $0.45 in cents
|
||||||
|
timeCreated: "2024-02-08T13:22:00Z",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "usage_4",
|
||||||
|
model: "gpt-3.5-turbo",
|
||||||
|
inputTokens: 1800,
|
||||||
|
outputTokens: 1200,
|
||||||
|
cost: 85000000, // $0.85 in cents
|
||||||
|
timeCreated: "2024-02-07T11:15:00Z",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const usage = createAsync(() => getUsageInfo(params.id))
|
||||||
|
// const usage = () => dummyUsage
|
||||||
|
return (
|
||||||
|
<section data-component="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={usage() && usage()!.length > 0}
|
||||||
|
fallback={
|
||||||
|
<div data-component="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>Input</th>
|
||||||
|
<th>Output</th>
|
||||||
|
<th>Cost</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<For each={usage()!}>
|
||||||
|
{(usage) => {
|
||||||
|
const date = createMemo(() => 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">{usage.inputTokens}</td>
|
||||||
|
<td data-slot="usage-tokens">{usage.outputTokens}</td>
|
||||||
|
<td data-slot="usage-cost">${((usage.cost ?? 0) / 100000000).toFixed(4)}</td>
|
||||||
|
</tr>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</For>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function PaymentsSection() {
|
||||||
|
const params = useParams()
|
||||||
|
const dummyPayments = [
|
||||||
|
{
|
||||||
|
id: "pi_1234567890",
|
||||||
|
amount: 5000000000, // $50.00 in cents
|
||||||
|
timeCreated: "2024-02-01T10:00:00Z",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "pi_0987654321",
|
||||||
|
amount: 2500000000, // $25.00 in cents
|
||||||
|
timeCreated: "2024-01-15T14:30:00Z",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const payments = createAsync(() => getPaymentsInfo(params.id))
|
||||||
|
// const payments = () => dummyPayments
|
||||||
|
|
||||||
|
return payments() && payments()!.length > 0 && (
|
||||||
|
<section data-component="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={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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function() {
|
||||||
|
return (
|
||||||
|
<div data-page="workspace-[id]">
|
||||||
|
<section data-component="title-section">
|
||||||
|
<h1>Zen</h1>
|
||||||
|
<p>
|
||||||
|
Curated list of models provided by opencode. <a target="_blank" href="/docs/zen">Learn more</a>.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div data-slot="sections">
|
||||||
|
<KeysSection />
|
||||||
|
<BalanceSection />
|
||||||
|
<UsageSection />
|
||||||
|
<PaymentsSection />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,310 @@
|
|||||||
|
import { Resource } from "@opencode/cloud-resource"
|
||||||
|
import type { APIEvent } from "@solidjs/start/server"
|
||||||
|
import { Database, eq, sql } from "@opencode/cloud-core/drizzle/index.js"
|
||||||
|
import { KeyTable } from "@opencode/cloud-core/schema/key.sql.js"
|
||||||
|
import { BillingTable, UsageTable } from "@opencode/cloud-core/schema/billing.sql.js"
|
||||||
|
import { centsToMicroCents } from "@opencode/cloud-core/util/price.js"
|
||||||
|
import { Identifier } from "@opencode/cloud-core/identifier.js"
|
||||||
|
|
||||||
|
const MODELS = {
|
||||||
|
// "anthropic/claude-sonnet-4": {
|
||||||
|
// auth: true,
|
||||||
|
// api: "https://api.anthropic.com",
|
||||||
|
// apiKey: Resource.ANTHROPIC_API_KEY.value,
|
||||||
|
// model: "claude-sonnet-4-20250514",
|
||||||
|
// cost: {
|
||||||
|
// input: 0.0000015,
|
||||||
|
// output: 0.000006,
|
||||||
|
// reasoning: 0.0000015,
|
||||||
|
// cacheRead: 0.0000001,
|
||||||
|
// cacheWrite: 0.0000001,
|
||||||
|
// },
|
||||||
|
// headerMappings: {},
|
||||||
|
// },
|
||||||
|
"qwen/qwen3-coder": {
|
||||||
|
id: "qwen/qwen3-coder",
|
||||||
|
auth: true,
|
||||||
|
api: "https://inference.baseten.co",
|
||||||
|
apiKey: Resource.BASETEN_API_KEY.value,
|
||||||
|
model: "Qwen/Qwen3-Coder-480B-A35B-Instruct",
|
||||||
|
cost: {
|
||||||
|
input: 0.00000038,
|
||||||
|
output: 0.00000153,
|
||||||
|
reasoning: 0,
|
||||||
|
cacheRead: 0,
|
||||||
|
cacheWrite: 0,
|
||||||
|
},
|
||||||
|
headerMappings: {},
|
||||||
|
},
|
||||||
|
"grok-code": {
|
||||||
|
id: "x-ai/grok-code-fast-1",
|
||||||
|
auth: false,
|
||||||
|
api: "https://api.x.ai",
|
||||||
|
apiKey: Resource.XAI_API_KEY.value,
|
||||||
|
model: "grok-code",
|
||||||
|
cost: {
|
||||||
|
input: 0,
|
||||||
|
output: 0,
|
||||||
|
reasoning: 0,
|
||||||
|
cacheRead: 0,
|
||||||
|
cacheWrite: 0,
|
||||||
|
},
|
||||||
|
headerMappings: {
|
||||||
|
"x-grok-conv-id": "x-opencode-session",
|
||||||
|
"x-grok-req-id": "x-opencode-request",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const FREE_WORKSPACES = [
|
||||||
|
"wrk_01K46JDFR0E75SG2Q8K172KF3Y", // frank
|
||||||
|
]
|
||||||
|
|
||||||
|
class AuthError extends Error {}
|
||||||
|
class CreditsError extends Error {}
|
||||||
|
class ModelError extends Error {}
|
||||||
|
|
||||||
|
export async function POST(input: APIEvent) {
|
||||||
|
try {
|
||||||
|
const url = new URL(input.request.url)
|
||||||
|
const body = await input.request.json()
|
||||||
|
const MODEL = validateModel()
|
||||||
|
const apiKey = await authenticate()
|
||||||
|
const isFree = FREE_WORKSPACES.includes(apiKey?.workspaceID ?? "")
|
||||||
|
await checkCredits()
|
||||||
|
|
||||||
|
// Request to model provider
|
||||||
|
const res = await fetch(new URL(url.pathname.replace(/^\/zen/, "") + url.search, MODEL.api), {
|
||||||
|
method: "POST",
|
||||||
|
headers: (() => {
|
||||||
|
const headers = input.request.headers
|
||||||
|
headers.delete("host")
|
||||||
|
headers.delete("content-length")
|
||||||
|
headers.set("authorization", `Bearer ${MODEL.apiKey}`)
|
||||||
|
Object.entries(MODEL.headerMappings ?? {}).forEach(([k, v]) => {
|
||||||
|
headers.set(k, headers.get(v)!)
|
||||||
|
})
|
||||||
|
return headers
|
||||||
|
})(),
|
||||||
|
body: JSON.stringify({
|
||||||
|
...body,
|
||||||
|
model: MODEL.model,
|
||||||
|
stream_options: {
|
||||||
|
include_usage: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
// Scrub response headers
|
||||||
|
const resHeaders = new Headers()
|
||||||
|
const keepHeaders = ["content-type", "cache-control"]
|
||||||
|
for (const [k, v] of res.headers.entries()) {
|
||||||
|
if (keepHeaders.includes(k.toLowerCase())) {
|
||||||
|
resHeaders.set(k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle non-streaming response
|
||||||
|
if (!body.stream) {
|
||||||
|
const body = await res.json()
|
||||||
|
await trackUsage(body)
|
||||||
|
return new Response(JSON.stringify(body), {
|
||||||
|
status: res.status,
|
||||||
|
statusText: res.statusText,
|
||||||
|
headers: resHeaders,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle streaming response
|
||||||
|
const stream = new ReadableStream({
|
||||||
|
start(c) {
|
||||||
|
const reader = res.body?.getReader()
|
||||||
|
const decoder = new TextDecoder()
|
||||||
|
let buffer = ""
|
||||||
|
|
||||||
|
function pump(): Promise<void> {
|
||||||
|
return (
|
||||||
|
reader?.read().then(async ({ done, value }) => {
|
||||||
|
if (done) {
|
||||||
|
c.close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer += decoder.decode(value, { stream: true })
|
||||||
|
|
||||||
|
const parts = buffer.split("\n\n")
|
||||||
|
buffer = parts.pop() ?? ""
|
||||||
|
|
||||||
|
const usage = parts
|
||||||
|
.map((part) => part.trim())
|
||||||
|
.filter((part) => part.startsWith("data: "))
|
||||||
|
.map((part) => {
|
||||||
|
try {
|
||||||
|
return JSON.parse(part.slice(6))
|
||||||
|
} catch (e) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.find((part) => part.usage)
|
||||||
|
if (usage) await trackUsage(usage)
|
||||||
|
|
||||||
|
c.enqueue(value)
|
||||||
|
|
||||||
|
return pump()
|
||||||
|
}) || Promise.resolve()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return pump()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return new Response(stream, {
|
||||||
|
status: res.status,
|
||||||
|
statusText: res.statusText,
|
||||||
|
headers: resHeaders,
|
||||||
|
})
|
||||||
|
|
||||||
|
function validateModel() {
|
||||||
|
if (!(body.model in MODELS)) {
|
||||||
|
throw new ModelError(`Model ${body.model} not supported`)
|
||||||
|
}
|
||||||
|
return MODELS[body.model as keyof typeof MODELS]
|
||||||
|
}
|
||||||
|
|
||||||
|
async function authenticate() {
|
||||||
|
try {
|
||||||
|
const authHeader = input.request.headers.get("authorization")
|
||||||
|
if (!authHeader || !authHeader.startsWith("Bearer ")) throw new AuthError("Missing API key.")
|
||||||
|
|
||||||
|
const apiKey = authHeader.split(" ")[1]
|
||||||
|
const key = await Database.use((tx) =>
|
||||||
|
tx
|
||||||
|
.select({
|
||||||
|
id: KeyTable.id,
|
||||||
|
workspaceID: KeyTable.workspaceID,
|
||||||
|
})
|
||||||
|
.from(KeyTable)
|
||||||
|
.where(eq(KeyTable.key, apiKey))
|
||||||
|
.then((rows) => rows[0]),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!key) throw new AuthError("Invalid API key.")
|
||||||
|
return key
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
// ignore error if model does not require authentication
|
||||||
|
if (!MODEL.auth) return
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkCredits() {
|
||||||
|
if (!apiKey || !MODEL.auth || isFree) return
|
||||||
|
|
||||||
|
const billing = await Database.use((tx) =>
|
||||||
|
tx
|
||||||
|
.select({
|
||||||
|
balance: BillingTable.balance,
|
||||||
|
})
|
||||||
|
.from(BillingTable)
|
||||||
|
.where(eq(BillingTable.workspaceID, apiKey.workspaceID))
|
||||||
|
.then((rows) => rows[0]),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (billing.balance <= 0) throw new CreditsError("Insufficient balance")
|
||||||
|
}
|
||||||
|
|
||||||
|
async function trackUsage(chunk: any) {
|
||||||
|
console.log(`trackUsage ${apiKey}`)
|
||||||
|
|
||||||
|
if (!apiKey) return
|
||||||
|
|
||||||
|
const usage = chunk.usage
|
||||||
|
const inputTokens = usage.prompt_tokens ?? 0
|
||||||
|
const outputTokens = usage.completion_tokens ?? 0
|
||||||
|
const reasoningTokens = usage.completion_tokens_details?.reasoning_tokens ?? 0
|
||||||
|
const cacheReadTokens = usage.prompt_tokens_details?.cached_tokens ?? 0
|
||||||
|
//const cacheWriteTokens = providerMetadata?.["anthropic"]?.["cacheCreationInputTokens"] ?? 0
|
||||||
|
const cacheWriteTokens = 0
|
||||||
|
|
||||||
|
const inputCost = MODEL.cost.input * inputTokens
|
||||||
|
const outputCost = MODEL.cost.output * outputTokens
|
||||||
|
const reasoningCost = MODEL.cost.reasoning * reasoningTokens
|
||||||
|
const cacheReadCost = MODEL.cost.cacheRead * cacheReadTokens
|
||||||
|
const cacheWriteCost = MODEL.cost.cacheWrite * cacheWriteTokens
|
||||||
|
const costInCents = (inputCost + outputCost + reasoningCost + cacheReadCost + cacheWriteCost) * 100
|
||||||
|
const cost = isFree ? 0 : centsToMicroCents(costInCents)
|
||||||
|
|
||||||
|
await Database.transaction(async (tx) => {
|
||||||
|
await tx.insert(UsageTable).values({
|
||||||
|
workspaceID: apiKey.workspaceID,
|
||||||
|
id: Identifier.create("usage"),
|
||||||
|
model: MODEL.id,
|
||||||
|
inputTokens,
|
||||||
|
outputTokens,
|
||||||
|
reasoningTokens,
|
||||||
|
cacheReadTokens,
|
||||||
|
cacheWriteTokens,
|
||||||
|
cost,
|
||||||
|
})
|
||||||
|
await tx
|
||||||
|
.update(BillingTable)
|
||||||
|
.set({
|
||||||
|
balance: sql`${BillingTable.balance} - ${cost}`,
|
||||||
|
})
|
||||||
|
.where(eq(BillingTable.workspaceID, apiKey.workspaceID))
|
||||||
|
})
|
||||||
|
|
||||||
|
await Database.use((tx) =>
|
||||||
|
tx
|
||||||
|
.update(KeyTable)
|
||||||
|
.set({ timeUsed: sql`now()` })
|
||||||
|
.where(eq(KeyTable.id, apiKey.id)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error instanceof AuthError) {
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
error: {
|
||||||
|
message: error.message,
|
||||||
|
type: "invalid_request_error",
|
||||||
|
param: null,
|
||||||
|
code: "unauthorized",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
status: 401,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error instanceof CreditsError) {
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
error: {
|
||||||
|
message: error.message,
|
||||||
|
type: "insufficient_quota",
|
||||||
|
param: null,
|
||||||
|
code: "insufficient_quota",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
status: 401,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error instanceof ModelError) {
|
||||||
|
return new Response(JSON.stringify({ error: { message: error.message } }), {
|
||||||
|
status: 401,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(error)
|
||||||
|
return new Response(JSON.stringify({ error: { message: error.message } }), {
|
||||||
|
status: 500,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
html {
|
html {
|
||||||
color-scheme: dark;
|
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
|
background-color: var(--color-bg);
|
||||||
|
color: var(--color-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
|||||||
@@ -1,58 +1,14 @@
|
|||||||
body {
|
:root {
|
||||||
--color-white: #ffffff;
|
--color-white: #ffffff;
|
||||||
--color-black: #000000;
|
--color-black: #000000;
|
||||||
}
|
|
||||||
|
|
||||||
[data-color-mode="dark"] {
|
/* Default light theme colors */
|
||||||
/* OpenCode theme colors */
|
|
||||||
--color-bg: #0c0c0e;
|
|
||||||
--color-bg-surface: #161618;
|
|
||||||
--color-bg-elevated: #1c1c1f;
|
|
||||||
|
|
||||||
--color-text: #ffffff;
|
|
||||||
--color-text-muted: #a1a1a6;
|
|
||||||
--color-text-disabled: #68686f;
|
|
||||||
|
|
||||||
--color-accent: #007aff;
|
|
||||||
--color-accent-hover: #0056b3;
|
|
||||||
--color-accent-active: #004085;
|
|
||||||
|
|
||||||
--color-success: #30d158;
|
|
||||||
--color-warning: #ff9f0a;
|
|
||||||
--color-danger: #ff453a;
|
|
||||||
|
|
||||||
--color-border: #38383a;
|
|
||||||
--color-border-muted: #2c2c2e;
|
|
||||||
|
|
||||||
/* Button colors */
|
|
||||||
--color-primary: var(--color-accent);
|
|
||||||
--color-primary-hover: var(--color-accent-hover);
|
|
||||||
--color-primary-active: var(--color-accent-active);
|
|
||||||
--color-primary-text: #ffffff;
|
|
||||||
|
|
||||||
--color-danger: #ff453a;
|
|
||||||
--color-danger-hover: #d70015;
|
|
||||||
--color-danger-active: #a50011;
|
|
||||||
--color-danger-text: #ffffff;
|
|
||||||
|
|
||||||
--color-warning: #ff9f0a;
|
|
||||||
--color-warning-hover: #cc7f08;
|
|
||||||
--color-warning-active: #995f06;
|
|
||||||
--color-warning-text: #000000;
|
|
||||||
|
|
||||||
/* Surface colors */
|
|
||||||
--color-surface: var(--color-bg-surface);
|
|
||||||
--color-surface-hover: var(--color-bg-elevated);
|
|
||||||
--color-border: var(--color-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-color-mode="light"] {
|
|
||||||
/* OpenCode light theme colors */
|
|
||||||
--color-bg: #ffffff;
|
--color-bg: #ffffff;
|
||||||
--color-bg-surface: #f5f5f7;
|
--color-bg-surface: #f5f5f7;
|
||||||
--color-bg-elevated: #ffffff;
|
--color-bg-elevated: #ffffff;
|
||||||
|
|
||||||
--color-text: #1d1d1f;
|
--color-text: #1d1d1f;
|
||||||
|
--color-text-secondary: #424245;
|
||||||
--color-text-muted: #6e6e73;
|
--color-text-muted: #6e6e73;
|
||||||
--color-text-disabled: #86868b;
|
--color-text-disabled: #86868b;
|
||||||
|
|
||||||
@@ -86,5 +42,50 @@ body {
|
|||||||
/* Surface colors */
|
/* Surface colors */
|
||||||
--color-surface: var(--color-bg-surface);
|
--color-surface: var(--color-bg-surface);
|
||||||
--color-surface-hover: var(--color-bg-elevated);
|
--color-surface-hover: var(--color-bg-elevated);
|
||||||
--color-border: var(--color-border);
|
--color-surface-border: var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--color-bg: #0c0c0e;
|
||||||
|
--color-bg-surface: #161618;
|
||||||
|
--color-bg-elevated: #1c1c1f;
|
||||||
|
|
||||||
|
--color-text: #ffffff;
|
||||||
|
--color-text-secondary: #c7c7cc;
|
||||||
|
--color-text-muted: #a1a1a6;
|
||||||
|
--color-text-disabled: #68686f;
|
||||||
|
|
||||||
|
--color-accent: #007aff;
|
||||||
|
--color-accent-hover: #0056b3;
|
||||||
|
--color-accent-active: #004085;
|
||||||
|
|
||||||
|
--color-success: #30d158;
|
||||||
|
--color-warning: #ff9f0a;
|
||||||
|
--color-danger: #ff453a;
|
||||||
|
|
||||||
|
--color-border: #38383a;
|
||||||
|
--color-border-muted: #2c2c2e;
|
||||||
|
|
||||||
|
/* Button colors */
|
||||||
|
--color-primary: var(--color-accent);
|
||||||
|
--color-primary-hover: var(--color-accent-hover);
|
||||||
|
--color-primary-active: var(--color-accent-active);
|
||||||
|
--color-primary-text: #ffffff;
|
||||||
|
|
||||||
|
--color-danger: #ff453a;
|
||||||
|
--color-danger-hover: #d70015;
|
||||||
|
--color-danger-active: #a50011;
|
||||||
|
--color-danger-text: #ffffff;
|
||||||
|
|
||||||
|
--color-warning: #ff9f0a;
|
||||||
|
--color-warning-hover: #cc7f08;
|
||||||
|
--color-warning-active: #995f06;
|
||||||
|
--color-warning-text: #000000;
|
||||||
|
|
||||||
|
/* Surface colors */
|
||||||
|
--color-surface: var(--color-bg-surface);
|
||||||
|
--color-surface-hover: var(--color-bg-elevated);
|
||||||
|
--color-surface-border: var(--color-border);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ body {
|
|||||||
--font-size-7xl: 4.5rem;
|
--font-size-7xl: 4.5rem;
|
||||||
--font-size-8xl: 6rem;
|
--font-size-8xl: 6rem;
|
||||||
--font-size-9xl: 8rem;
|
--font-size-9xl: 8rem;
|
||||||
--font-mono: IBM Plex Mono;
|
|
||||||
--font-sans: Inter;
|
--font-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||||
|
--font-sans: var(--font-mono);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,4 +39,8 @@ body {
|
|||||||
--space-72: 18rem;
|
--space-72: 18rem;
|
||||||
--space-80: 20rem;
|
--space-80: 20rem;
|
||||||
--space-96: 24rem;
|
--space-96: 24rem;
|
||||||
|
|
||||||
|
--border-radius-sm: 0.1875rem;
|
||||||
|
--border-radius-md: 0.3125rem;
|
||||||
|
--border-radius-lg: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
{
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/tsconfig",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ESNext",
|
"target": "ESNext",
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
@@ -10,10 +12,14 @@
|
|||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"types": ["vinxi/types/client"],
|
"types": [
|
||||||
|
"vinxi/types/client"
|
||||||
|
],
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"~/*": ["./src/*"]
|
"~/*": [
|
||||||
|
"./src/*"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { defineConfig } from "drizzle-kit"
|
|
||||||
import { Resource } from "sst"
|
import { Resource } from "sst"
|
||||||
|
import { defineConfig } from "drizzle-kit"
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
out: "./migrations/",
|
out: "./migrations/",
|
||||||
strict: true,
|
strict: true,
|
||||||
schema: ["./src/**/*.sql.ts"],
|
schema: ["./src/**/*.sql.ts"],
|
||||||
verbose: true,
|
verbose: true,
|
||||||
dialect: "postgresql",
|
dialect: "mysql",
|
||||||
dbCredentials: {
|
dbCredentials: {
|
||||||
database: Resource.Database.database,
|
database: Resource.Database.database,
|
||||||
host: Resource.Database.host,
|
host: Resource.Database.host,
|
||||||
|
|||||||
@@ -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");
|
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
CREATE TABLE `account` (
|
||||||
|
`id` varchar(30) NOT NULL,
|
||||||
|
`time_created` timestamp(3) NOT NULL DEFAULT (now()),
|
||||||
|
`time_updated` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||||
|
`time_deleted` timestamp(3),
|
||||||
|
`email` varchar(255) NOT NULL,
|
||||||
|
CONSTRAINT `email` UNIQUE(`email`)
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE `billing` (
|
||||||
|
`id` varchar(30) NOT NULL,
|
||||||
|
`workspace_id` varchar(30) NOT NULL,
|
||||||
|
`time_created` timestamp(3) NOT NULL DEFAULT (now()),
|
||||||
|
`time_updated` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||||
|
`time_deleted` timestamp(3),
|
||||||
|
`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(3) NOT NULL DEFAULT (now()),
|
||||||
|
`time_updated` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||||
|
`time_deleted` timestamp(3),
|
||||||
|
`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(3) NOT NULL DEFAULT (now()),
|
||||||
|
`time_updated` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||||
|
`time_deleted` timestamp(3),
|
||||||
|
`model` varchar(255) NOT NULL,
|
||||||
|
`input_tokens` int NOT NULL,
|
||||||
|
`output_tokens` int NOT NULL,
|
||||||
|
`reasoning_tokens` int,
|
||||||
|
`cache_read_tokens` int,
|
||||||
|
`cache_write_tokens` int,
|
||||||
|
`cost` bigint NOT NULL,
|
||||||
|
CONSTRAINT `usage_workspace_id_id_pk` PRIMARY KEY(`workspace_id`,`id`)
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE `key` (
|
||||||
|
`id` varchar(30) NOT NULL,
|
||||||
|
`workspace_id` varchar(30) NOT NULL,
|
||||||
|
`time_created` timestamp(3) NOT NULL DEFAULT (now()),
|
||||||
|
`time_updated` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||||
|
`time_deleted` timestamp(3),
|
||||||
|
`user_id` text NOT NULL,
|
||||||
|
`name` varchar(255) NOT NULL,
|
||||||
|
`key` varchar(255) NOT NULL,
|
||||||
|
`time_used` timestamp(3),
|
||||||
|
CONSTRAINT `key_workspace_id_id_pk` PRIMARY KEY(`workspace_id`,`id`),
|
||||||
|
CONSTRAINT `global_key` UNIQUE(`key`)
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE `user` (
|
||||||
|
`id` varchar(30) NOT NULL,
|
||||||
|
`workspace_id` varchar(30) NOT NULL,
|
||||||
|
`time_created` timestamp(3) NOT NULL DEFAULT (now()),
|
||||||
|
`time_updated` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||||
|
`time_deleted` timestamp(3),
|
||||||
|
`email` varchar(255) NOT NULL,
|
||||||
|
`name` varchar(255) NOT NULL,
|
||||||
|
`time_seen` timestamp(3),
|
||||||
|
`color` int,
|
||||||
|
CONSTRAINT `user_workspace_id_id_pk` PRIMARY KEY(`workspace_id`,`id`),
|
||||||
|
CONSTRAINT `user_email` UNIQUE(`workspace_id`,`email`)
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE `workspace` (
|
||||||
|
`id` varchar(30) NOT NULL,
|
||||||
|
`slug` varchar(255),
|
||||||
|
`name` varchar(255),
|
||||||
|
`time_created` timestamp(3) NOT NULL DEFAULT (now()),
|
||||||
|
`time_updated` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||||
|
`time_deleted` timestamp(3),
|
||||||
|
CONSTRAINT `workspace_id` PRIMARY KEY(`id`),
|
||||||
|
CONSTRAINT `slug` UNIQUE(`slug`)
|
||||||
|
);
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE `key` ADD `actor` json;--> statement-breakpoint
|
||||||
|
ALTER TABLE `key` DROP COLUMN `user_id`;
|
||||||
@@ -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,85 +1,142 @@
|
|||||||
{
|
{
|
||||||
"id": "9b5cec8c-8b59-4d7a-bb5c-76ade1c83d6f",
|
"version": "5",
|
||||||
|
"dialect": "mysql",
|
||||||
|
"id": "aee779c5-db1d-4655-95ec-6451c18455be",
|
||||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||||
"version": "7",
|
|
||||||
"dialect": "postgresql",
|
|
||||||
"tables": {
|
"tables": {
|
||||||
"public.billing": {
|
"account": {
|
||||||
"name": "billing",
|
"name": "account",
|
||||||
"schema": "",
|
|
||||||
"columns": {
|
"columns": {
|
||||||
"id": {
|
"id": {
|
||||||
"name": "id",
|
"name": "id",
|
||||||
"type": "varchar(30)",
|
"type": "varchar(30)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"time_created": {
|
||||||
|
"name": "time_created",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(now())"
|
||||||
|
},
|
||||||
|
"time_updated": {
|
||||||
|
"name": "time_updated",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
|
||||||
|
},
|
||||||
|
"time_deleted": {
|
||||||
|
"name": "time_deleted",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"name": "email",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"email": {
|
||||||
|
"name": "email",
|
||||||
|
"columns": [
|
||||||
|
"email"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraint": {}
|
||||||
|
},
|
||||||
|
"billing": {
|
||||||
|
"name": "billing",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "varchar(30)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"workspace_id": {
|
"workspace_id": {
|
||||||
"name": "workspace_id",
|
"name": "workspace_id",
|
||||||
"type": "varchar(30)",
|
"type": "varchar(30)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"time_created": {
|
"time_created": {
|
||||||
"name": "time_created",
|
"name": "time_created",
|
||||||
"type": "timestamp with time zone",
|
"type": "timestamp(3)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"default": "now()"
|
"autoincrement": false,
|
||||||
|
"default": "(now())"
|
||||||
|
},
|
||||||
|
"time_updated": {
|
||||||
|
"name": "time_updated",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
|
||||||
},
|
},
|
||||||
"time_deleted": {
|
"time_deleted": {
|
||||||
"name": "time_deleted",
|
"name": "time_deleted",
|
||||||
"type": "timestamp with time zone",
|
"type": "timestamp(3)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"customer_id": {
|
"customer_id": {
|
||||||
"name": "customer_id",
|
"name": "customer_id",
|
||||||
"type": "varchar(255)",
|
"type": "varchar(255)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"payment_method_id": {
|
"payment_method_id": {
|
||||||
"name": "payment_method_id",
|
"name": "payment_method_id",
|
||||||
"type": "varchar(255)",
|
"type": "varchar(255)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"payment_method_last4": {
|
"payment_method_last4": {
|
||||||
"name": "payment_method_last4",
|
"name": "payment_method_last4",
|
||||||
"type": "varchar(4)",
|
"type": "varchar(4)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"balance": {
|
"balance": {
|
||||||
"name": "balance",
|
"name": "balance",
|
||||||
"type": "bigint",
|
"type": "bigint",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"reload": {
|
"reload": {
|
||||||
"name": "reload",
|
"name": "reload",
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {},
|
"indexes": {},
|
||||||
"foreignKeys": {
|
"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": {
|
"compositePrimaryKeys": {
|
||||||
"billing_workspace_id_id_pk": {
|
"billing_workspace_id_id_pk": {
|
||||||
"name": "billing_workspace_id_id_pk",
|
"name": "billing_workspace_id_id_pk",
|
||||||
@@ -90,74 +147,72 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"uniqueConstraints": {},
|
"uniqueConstraints": {},
|
||||||
"policies": {},
|
"checkConstraint": {}
|
||||||
"checkConstraints": {},
|
|
||||||
"isRLSEnabled": false
|
|
||||||
},
|
},
|
||||||
"public.payment": {
|
"payment": {
|
||||||
"name": "payment",
|
"name": "payment",
|
||||||
"schema": "",
|
|
||||||
"columns": {
|
"columns": {
|
||||||
"id": {
|
"id": {
|
||||||
"name": "id",
|
"name": "id",
|
||||||
"type": "varchar(30)",
|
"type": "varchar(30)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"workspace_id": {
|
"workspace_id": {
|
||||||
"name": "workspace_id",
|
"name": "workspace_id",
|
||||||
"type": "varchar(30)",
|
"type": "varchar(30)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"time_created": {
|
"time_created": {
|
||||||
"name": "time_created",
|
"name": "time_created",
|
||||||
"type": "timestamp with time zone",
|
"type": "timestamp(3)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"default": "now()"
|
"autoincrement": false,
|
||||||
|
"default": "(now())"
|
||||||
|
},
|
||||||
|
"time_updated": {
|
||||||
|
"name": "time_updated",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
|
||||||
},
|
},
|
||||||
"time_deleted": {
|
"time_deleted": {
|
||||||
"name": "time_deleted",
|
"name": "time_deleted",
|
||||||
"type": "timestamp with time zone",
|
"type": "timestamp(3)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"customer_id": {
|
"customer_id": {
|
||||||
"name": "customer_id",
|
"name": "customer_id",
|
||||||
"type": "varchar(255)",
|
"type": "varchar(255)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"payment_id": {
|
"payment_id": {
|
||||||
"name": "payment_id",
|
"name": "payment_id",
|
||||||
"type": "varchar(255)",
|
"type": "varchar(255)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"amount": {
|
"amount": {
|
||||||
"name": "amount",
|
"name": "amount",
|
||||||
"type": "bigint",
|
"type": "bigint",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {},
|
"indexes": {},
|
||||||
"foreignKeys": {
|
"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": {
|
"compositePrimaryKeys": {
|
||||||
"payment_workspace_id_id_pk": {
|
"payment_workspace_id_id_pk": {
|
||||||
"name": "payment_workspace_id_id_pk",
|
"name": "payment_workspace_id_id_pk",
|
||||||
@@ -168,104 +223,100 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"uniqueConstraints": {},
|
"uniqueConstraints": {},
|
||||||
"policies": {},
|
"checkConstraint": {}
|
||||||
"checkConstraints": {},
|
|
||||||
"isRLSEnabled": false
|
|
||||||
},
|
},
|
||||||
"public.usage": {
|
"usage": {
|
||||||
"name": "usage",
|
"name": "usage",
|
||||||
"schema": "",
|
|
||||||
"columns": {
|
"columns": {
|
||||||
"id": {
|
"id": {
|
||||||
"name": "id",
|
"name": "id",
|
||||||
"type": "varchar(30)",
|
"type": "varchar(30)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"workspace_id": {
|
"workspace_id": {
|
||||||
"name": "workspace_id",
|
"name": "workspace_id",
|
||||||
"type": "varchar(30)",
|
"type": "varchar(30)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"time_created": {
|
"time_created": {
|
||||||
"name": "time_created",
|
"name": "time_created",
|
||||||
"type": "timestamp with time zone",
|
"type": "timestamp(3)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"default": "now()"
|
"autoincrement": false,
|
||||||
|
"default": "(now())"
|
||||||
|
},
|
||||||
|
"time_updated": {
|
||||||
|
"name": "time_updated",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
|
||||||
},
|
},
|
||||||
"time_deleted": {
|
"time_deleted": {
|
||||||
"name": "time_deleted",
|
"name": "time_deleted",
|
||||||
"type": "timestamp with time zone",
|
"type": "timestamp(3)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false
|
"notNull": false,
|
||||||
},
|
"autoincrement": false
|
||||||
"request_id": {
|
|
||||||
"name": "request_id",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
},
|
||||||
"model": {
|
"model": {
|
||||||
"name": "model",
|
"name": "model",
|
||||||
"type": "varchar(255)",
|
"type": "varchar(255)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"input_tokens": {
|
"input_tokens": {
|
||||||
"name": "input_tokens",
|
"name": "input_tokens",
|
||||||
"type": "integer",
|
"type": "int",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"output_tokens": {
|
"output_tokens": {
|
||||||
"name": "output_tokens",
|
"name": "output_tokens",
|
||||||
"type": "integer",
|
"type": "int",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"reasoning_tokens": {
|
"reasoning_tokens": {
|
||||||
"name": "reasoning_tokens",
|
"name": "reasoning_tokens",
|
||||||
"type": "integer",
|
"type": "int",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"cache_read_tokens": {
|
"cache_read_tokens": {
|
||||||
"name": "cache_read_tokens",
|
"name": "cache_read_tokens",
|
||||||
"type": "integer",
|
"type": "int",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"cache_write_tokens": {
|
"cache_write_tokens": {
|
||||||
"name": "cache_write_tokens",
|
"name": "cache_write_tokens",
|
||||||
"type": "integer",
|
"type": "int",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"cost": {
|
"cost": {
|
||||||
"name": "cost",
|
"name": "cost",
|
||||||
"type": "bigint",
|
"type": "bigint",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {},
|
"indexes": {},
|
||||||
"foreignKeys": {
|
"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": {
|
"compositePrimaryKeys": {
|
||||||
"usage_workspace_id_id_pk": {
|
"usage_workspace_id_id_pk": {
|
||||||
"name": "usage_workspace_id_id_pk",
|
"name": "usage_workspace_id_id_pk",
|
||||||
@@ -276,102 +327,179 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"uniqueConstraints": {},
|
"uniqueConstraints": {},
|
||||||
"policies": {},
|
"checkConstraint": {}
|
||||||
"checkConstraints": {},
|
|
||||||
"isRLSEnabled": false
|
|
||||||
},
|
},
|
||||||
"public.user": {
|
"key": {
|
||||||
"name": "user",
|
"name": "key",
|
||||||
"schema": "",
|
|
||||||
"columns": {
|
"columns": {
|
||||||
"id": {
|
"id": {
|
||||||
"name": "id",
|
"name": "id",
|
||||||
"type": "varchar(30)",
|
"type": "varchar(30)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"workspace_id": {
|
"workspace_id": {
|
||||||
"name": "workspace_id",
|
"name": "workspace_id",
|
||||||
"type": "varchar(30)",
|
"type": "varchar(30)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"time_created": {
|
"time_created": {
|
||||||
"name": "time_created",
|
"name": "time_created",
|
||||||
"type": "timestamp with time zone",
|
"type": "timestamp(3)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"default": "now()"
|
"autoincrement": false,
|
||||||
|
"default": "(now())"
|
||||||
|
},
|
||||||
|
"time_updated": {
|
||||||
|
"name": "time_updated",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
|
||||||
},
|
},
|
||||||
"time_deleted": {
|
"time_deleted": {
|
||||||
"name": "time_deleted",
|
"name": "time_deleted",
|
||||||
"type": "timestamp with time zone",
|
"type": "timestamp(3)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"email": {
|
"user_id": {
|
||||||
"name": "email",
|
"name": "user_id",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"name": "name",
|
"name": "name",
|
||||||
"type": "varchar(255)",
|
"type": "varchar(255)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"key": {
|
||||||
|
"name": "key",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"time_used": {
|
||||||
|
"name": "time_used",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"global_key": {
|
||||||
|
"name": "global_key",
|
||||||
|
"columns": [
|
||||||
|
"key"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"key_workspace_id_id_pk": {
|
||||||
|
"name": "key_workspace_id_id_pk",
|
||||||
|
"columns": [
|
||||||
|
"workspace_id",
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraint": {}
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"name": "user",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "varchar(30)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"workspace_id": {
|
||||||
|
"name": "workspace_id",
|
||||||
|
"type": "varchar(30)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"time_created": {
|
||||||
|
"name": "time_created",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(now())"
|
||||||
|
},
|
||||||
|
"time_updated": {
|
||||||
|
"name": "time_updated",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
|
||||||
|
},
|
||||||
|
"time_deleted": {
|
||||||
|
"name": "time_deleted",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"name": "email",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"time_seen": {
|
"time_seen": {
|
||||||
"name": "time_seen",
|
"name": "time_seen",
|
||||||
"type": "timestamp with time zone",
|
"type": "timestamp(3)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"color": {
|
"color": {
|
||||||
"name": "color",
|
"name": "color",
|
||||||
"type": "integer",
|
"type": "int",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"user_email": {
|
"user_email": {
|
||||||
"name": "user_email",
|
"name": "user_email",
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
"workspace_id",
|
||||||
"expression": "workspace_id",
|
"email"
|
||||||
"isExpression": false,
|
|
||||||
"asc": true,
|
|
||||||
"nulls": "last"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"expression": "email",
|
|
||||||
"isExpression": false,
|
|
||||||
"asc": true,
|
|
||||||
"nulls": "last"
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
"isUnique": true,
|
"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"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
"compositePrimaryKeys": {
|
"compositePrimaryKeys": {
|
||||||
"user_workspace_id_id_pk": {
|
"user_workspace_id_id_pk": {
|
||||||
"name": "user_workspace_id_id_pk",
|
"name": "user_workspace_id_id_pk",
|
||||||
@@ -382,80 +510,86 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"uniqueConstraints": {},
|
"uniqueConstraints": {},
|
||||||
"policies": {},
|
"checkConstraint": {}
|
||||||
"checkConstraints": {},
|
|
||||||
"isRLSEnabled": false
|
|
||||||
},
|
},
|
||||||
"public.workspace": {
|
"workspace": {
|
||||||
"name": "workspace",
|
"name": "workspace",
|
||||||
"schema": "",
|
|
||||||
"columns": {
|
"columns": {
|
||||||
"id": {
|
"id": {
|
||||||
"name": "id",
|
"name": "id",
|
||||||
"type": "varchar(30)",
|
"type": "varchar(30)",
|
||||||
"primaryKey": true,
|
"primaryKey": false,
|
||||||
"notNull": true
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"slug": {
|
"slug": {
|
||||||
"name": "slug",
|
"name": "slug",
|
||||||
"type": "varchar(255)",
|
"type": "varchar(255)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"name": "name",
|
"name": "name",
|
||||||
"type": "varchar(255)",
|
"type": "varchar(255)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"time_created": {
|
"time_created": {
|
||||||
"name": "time_created",
|
"name": "time_created",
|
||||||
"type": "timestamp with time zone",
|
"type": "timestamp(3)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"default": "now()"
|
"autoincrement": false,
|
||||||
|
"default": "(now())"
|
||||||
|
},
|
||||||
|
"time_updated": {
|
||||||
|
"name": "time_updated",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
|
||||||
},
|
},
|
||||||
"time_deleted": {
|
"time_deleted": {
|
||||||
"name": "time_deleted",
|
"name": "time_deleted",
|
||||||
"type": "timestamp with time zone",
|
"type": "timestamp(3)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"slug": {
|
"slug": {
|
||||||
"name": "slug",
|
"name": "slug",
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
"slug"
|
||||||
"expression": "slug",
|
|
||||||
"isExpression": false,
|
|
||||||
"asc": true,
|
|
||||||
"nulls": "last"
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
"isUnique": true,
|
"isUnique": true
|
||||||
"concurrently": false,
|
|
||||||
"method": "btree",
|
|
||||||
"with": {}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"foreignKeys": {},
|
"foreignKeys": {},
|
||||||
"compositePrimaryKeys": {},
|
"compositePrimaryKeys": {
|
||||||
|
"workspace_id": {
|
||||||
|
"name": "workspace_id",
|
||||||
|
"columns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"uniqueConstraints": {},
|
"uniqueConstraints": {},
|
||||||
"policies": {},
|
"checkConstraint": {}
|
||||||
"checkConstraints": {},
|
|
||||||
"isRLSEnabled": false
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"enums": {},
|
|
||||||
"schemas": {},
|
|
||||||
"sequences": {},
|
|
||||||
"roles": {},
|
|
||||||
"policies": {},
|
|
||||||
"views": {},
|
"views": {},
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"columns": {},
|
|
||||||
"schemas": {},
|
"schemas": {},
|
||||||
"tables": {}
|
"tables": {},
|
||||||
|
"columns": {}
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"tables": {},
|
||||||
|
"indexes": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,139 +1,142 @@
|
|||||||
{
|
{
|
||||||
"id": "bf9e9084-4073-4ecb-8e56-5610816c9589",
|
"version": "5",
|
||||||
"prevId": "9b5cec8c-8b59-4d7a-bb5c-76ade1c83d6f",
|
"dialect": "mysql",
|
||||||
"version": "7",
|
"id": "79b7ee25-1c1c-41ff-9bbf-754af257102b",
|
||||||
"dialect": "postgresql",
|
"prevId": "aee779c5-db1d-4655-95ec-6451c18455be",
|
||||||
"tables": {
|
"tables": {
|
||||||
"public.account": {
|
"account": {
|
||||||
"name": "account",
|
"name": "account",
|
||||||
"schema": "",
|
|
||||||
"columns": {
|
"columns": {
|
||||||
"id": {
|
"id": {
|
||||||
"name": "id",
|
"name": "id",
|
||||||
"type": "varchar(30)",
|
"type": "varchar(30)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"time_created": {
|
"time_created": {
|
||||||
"name": "time_created",
|
"name": "time_created",
|
||||||
"type": "timestamp with time zone",
|
"type": "timestamp(3)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"default": "now()"
|
"autoincrement": false,
|
||||||
|
"default": "(now())"
|
||||||
|
},
|
||||||
|
"time_updated": {
|
||||||
|
"name": "time_updated",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
|
||||||
},
|
},
|
||||||
"time_deleted": {
|
"time_deleted": {
|
||||||
"name": "time_deleted",
|
"name": "time_deleted",
|
||||||
"type": "timestamp with time zone",
|
"type": "timestamp(3)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"email": {
|
"email": {
|
||||||
"name": "email",
|
"name": "email",
|
||||||
"type": "varchar(255)",
|
"type": "varchar(255)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"email": {
|
"email": {
|
||||||
"name": "email",
|
"name": "email",
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
"email"
|
||||||
"expression": "email",
|
|
||||||
"isExpression": false,
|
|
||||||
"asc": true,
|
|
||||||
"nulls": "last"
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
"isUnique": true,
|
"isUnique": true
|
||||||
"concurrently": false,
|
|
||||||
"method": "btree",
|
|
||||||
"with": {}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"foreignKeys": {},
|
"foreignKeys": {},
|
||||||
"compositePrimaryKeys": {},
|
"compositePrimaryKeys": {},
|
||||||
"uniqueConstraints": {},
|
"uniqueConstraints": {},
|
||||||
"policies": {},
|
"checkConstraint": {}
|
||||||
"checkConstraints": {},
|
|
||||||
"isRLSEnabled": false
|
|
||||||
},
|
},
|
||||||
"public.billing": {
|
"billing": {
|
||||||
"name": "billing",
|
"name": "billing",
|
||||||
"schema": "",
|
|
||||||
"columns": {
|
"columns": {
|
||||||
"id": {
|
"id": {
|
||||||
"name": "id",
|
"name": "id",
|
||||||
"type": "varchar(30)",
|
"type": "varchar(30)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"workspace_id": {
|
"workspace_id": {
|
||||||
"name": "workspace_id",
|
"name": "workspace_id",
|
||||||
"type": "varchar(30)",
|
"type": "varchar(30)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"time_created": {
|
"time_created": {
|
||||||
"name": "time_created",
|
"name": "time_created",
|
||||||
"type": "timestamp with time zone",
|
"type": "timestamp(3)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"default": "now()"
|
"autoincrement": false,
|
||||||
|
"default": "(now())"
|
||||||
|
},
|
||||||
|
"time_updated": {
|
||||||
|
"name": "time_updated",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
|
||||||
},
|
},
|
||||||
"time_deleted": {
|
"time_deleted": {
|
||||||
"name": "time_deleted",
|
"name": "time_deleted",
|
||||||
"type": "timestamp with time zone",
|
"type": "timestamp(3)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"customer_id": {
|
"customer_id": {
|
||||||
"name": "customer_id",
|
"name": "customer_id",
|
||||||
"type": "varchar(255)",
|
"type": "varchar(255)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"payment_method_id": {
|
"payment_method_id": {
|
||||||
"name": "payment_method_id",
|
"name": "payment_method_id",
|
||||||
"type": "varchar(255)",
|
"type": "varchar(255)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"payment_method_last4": {
|
"payment_method_last4": {
|
||||||
"name": "payment_method_last4",
|
"name": "payment_method_last4",
|
||||||
"type": "varchar(4)",
|
"type": "varchar(4)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"balance": {
|
"balance": {
|
||||||
"name": "balance",
|
"name": "balance",
|
||||||
"type": "bigint",
|
"type": "bigint",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"reload": {
|
"reload": {
|
||||||
"name": "reload",
|
"name": "reload",
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {},
|
"indexes": {},
|
||||||
"foreignKeys": {
|
"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": {
|
"compositePrimaryKeys": {
|
||||||
"billing_workspace_id_id_pk": {
|
"billing_workspace_id_id_pk": {
|
||||||
"name": "billing_workspace_id_id_pk",
|
"name": "billing_workspace_id_id_pk",
|
||||||
@@ -144,74 +147,72 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"uniqueConstraints": {},
|
"uniqueConstraints": {},
|
||||||
"policies": {},
|
"checkConstraint": {}
|
||||||
"checkConstraints": {},
|
|
||||||
"isRLSEnabled": false
|
|
||||||
},
|
},
|
||||||
"public.payment": {
|
"payment": {
|
||||||
"name": "payment",
|
"name": "payment",
|
||||||
"schema": "",
|
|
||||||
"columns": {
|
"columns": {
|
||||||
"id": {
|
"id": {
|
||||||
"name": "id",
|
"name": "id",
|
||||||
"type": "varchar(30)",
|
"type": "varchar(30)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"workspace_id": {
|
"workspace_id": {
|
||||||
"name": "workspace_id",
|
"name": "workspace_id",
|
||||||
"type": "varchar(30)",
|
"type": "varchar(30)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"time_created": {
|
"time_created": {
|
||||||
"name": "time_created",
|
"name": "time_created",
|
||||||
"type": "timestamp with time zone",
|
"type": "timestamp(3)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"default": "now()"
|
"autoincrement": false,
|
||||||
|
"default": "(now())"
|
||||||
|
},
|
||||||
|
"time_updated": {
|
||||||
|
"name": "time_updated",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
|
||||||
},
|
},
|
||||||
"time_deleted": {
|
"time_deleted": {
|
||||||
"name": "time_deleted",
|
"name": "time_deleted",
|
||||||
"type": "timestamp with time zone",
|
"type": "timestamp(3)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"customer_id": {
|
"customer_id": {
|
||||||
"name": "customer_id",
|
"name": "customer_id",
|
||||||
"type": "varchar(255)",
|
"type": "varchar(255)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"payment_id": {
|
"payment_id": {
|
||||||
"name": "payment_id",
|
"name": "payment_id",
|
||||||
"type": "varchar(255)",
|
"type": "varchar(255)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"amount": {
|
"amount": {
|
||||||
"name": "amount",
|
"name": "amount",
|
||||||
"type": "bigint",
|
"type": "bigint",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {},
|
"indexes": {},
|
||||||
"foreignKeys": {
|
"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": {
|
"compositePrimaryKeys": {
|
||||||
"payment_workspace_id_id_pk": {
|
"payment_workspace_id_id_pk": {
|
||||||
"name": "payment_workspace_id_id_pk",
|
"name": "payment_workspace_id_id_pk",
|
||||||
@@ -222,104 +223,100 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"uniqueConstraints": {},
|
"uniqueConstraints": {},
|
||||||
"policies": {},
|
"checkConstraint": {}
|
||||||
"checkConstraints": {},
|
|
||||||
"isRLSEnabled": false
|
|
||||||
},
|
},
|
||||||
"public.usage": {
|
"usage": {
|
||||||
"name": "usage",
|
"name": "usage",
|
||||||
"schema": "",
|
|
||||||
"columns": {
|
"columns": {
|
||||||
"id": {
|
"id": {
|
||||||
"name": "id",
|
"name": "id",
|
||||||
"type": "varchar(30)",
|
"type": "varchar(30)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"workspace_id": {
|
"workspace_id": {
|
||||||
"name": "workspace_id",
|
"name": "workspace_id",
|
||||||
"type": "varchar(30)",
|
"type": "varchar(30)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"time_created": {
|
"time_created": {
|
||||||
"name": "time_created",
|
"name": "time_created",
|
||||||
"type": "timestamp with time zone",
|
"type": "timestamp(3)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"default": "now()"
|
"autoincrement": false,
|
||||||
|
"default": "(now())"
|
||||||
|
},
|
||||||
|
"time_updated": {
|
||||||
|
"name": "time_updated",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
|
||||||
},
|
},
|
||||||
"time_deleted": {
|
"time_deleted": {
|
||||||
"name": "time_deleted",
|
"name": "time_deleted",
|
||||||
"type": "timestamp with time zone",
|
"type": "timestamp(3)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false
|
"notNull": false,
|
||||||
},
|
"autoincrement": false
|
||||||
"request_id": {
|
|
||||||
"name": "request_id",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
},
|
||||||
"model": {
|
"model": {
|
||||||
"name": "model",
|
"name": "model",
|
||||||
"type": "varchar(255)",
|
"type": "varchar(255)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"input_tokens": {
|
"input_tokens": {
|
||||||
"name": "input_tokens",
|
"name": "input_tokens",
|
||||||
"type": "integer",
|
"type": "int",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"output_tokens": {
|
"output_tokens": {
|
||||||
"name": "output_tokens",
|
"name": "output_tokens",
|
||||||
"type": "integer",
|
"type": "int",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"reasoning_tokens": {
|
"reasoning_tokens": {
|
||||||
"name": "reasoning_tokens",
|
"name": "reasoning_tokens",
|
||||||
"type": "integer",
|
"type": "int",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"cache_read_tokens": {
|
"cache_read_tokens": {
|
||||||
"name": "cache_read_tokens",
|
"name": "cache_read_tokens",
|
||||||
"type": "integer",
|
"type": "int",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"cache_write_tokens": {
|
"cache_write_tokens": {
|
||||||
"name": "cache_write_tokens",
|
"name": "cache_write_tokens",
|
||||||
"type": "integer",
|
"type": "int",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"cost": {
|
"cost": {
|
||||||
"name": "cost",
|
"name": "cost",
|
||||||
"type": "bigint",
|
"type": "bigint",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {},
|
"indexes": {},
|
||||||
"foreignKeys": {
|
"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": {
|
"compositePrimaryKeys": {
|
||||||
"usage_workspace_id_id_pk": {
|
"usage_workspace_id_id_pk": {
|
||||||
"name": "usage_workspace_id_id_pk",
|
"name": "usage_workspace_id_id_pk",
|
||||||
@@ -330,102 +327,179 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"uniqueConstraints": {},
|
"uniqueConstraints": {},
|
||||||
"policies": {},
|
"checkConstraint": {}
|
||||||
"checkConstraints": {},
|
|
||||||
"isRLSEnabled": false
|
|
||||||
},
|
},
|
||||||
"public.user": {
|
"key": {
|
||||||
"name": "user",
|
"name": "key",
|
||||||
"schema": "",
|
|
||||||
"columns": {
|
"columns": {
|
||||||
"id": {
|
"id": {
|
||||||
"name": "id",
|
"name": "id",
|
||||||
"type": "varchar(30)",
|
"type": "varchar(30)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"workspace_id": {
|
"workspace_id": {
|
||||||
"name": "workspace_id",
|
"name": "workspace_id",
|
||||||
"type": "varchar(30)",
|
"type": "varchar(30)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"time_created": {
|
"time_created": {
|
||||||
"name": "time_created",
|
"name": "time_created",
|
||||||
"type": "timestamp with time zone",
|
"type": "timestamp(3)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"default": "now()"
|
"autoincrement": false,
|
||||||
|
"default": "(now())"
|
||||||
|
},
|
||||||
|
"time_updated": {
|
||||||
|
"name": "time_updated",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
|
||||||
},
|
},
|
||||||
"time_deleted": {
|
"time_deleted": {
|
||||||
"name": "time_deleted",
|
"name": "time_deleted",
|
||||||
"type": "timestamp with time zone",
|
"type": "timestamp(3)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"email": {
|
"actor": {
|
||||||
"name": "email",
|
"name": "actor",
|
||||||
"type": "text",
|
"type": "json",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"name": "name",
|
"name": "name",
|
||||||
"type": "varchar(255)",
|
"type": "varchar(255)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"key": {
|
||||||
|
"name": "key",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"time_used": {
|
||||||
|
"name": "time_used",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"global_key": {
|
||||||
|
"name": "global_key",
|
||||||
|
"columns": [
|
||||||
|
"key"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"key_workspace_id_id_pk": {
|
||||||
|
"name": "key_workspace_id_id_pk",
|
||||||
|
"columns": [
|
||||||
|
"workspace_id",
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraint": {}
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"name": "user",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "varchar(30)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"workspace_id": {
|
||||||
|
"name": "workspace_id",
|
||||||
|
"type": "varchar(30)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"time_created": {
|
||||||
|
"name": "time_created",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(now())"
|
||||||
|
},
|
||||||
|
"time_updated": {
|
||||||
|
"name": "time_updated",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
|
||||||
|
},
|
||||||
|
"time_deleted": {
|
||||||
|
"name": "time_deleted",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"name": "email",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"time_seen": {
|
"time_seen": {
|
||||||
"name": "time_seen",
|
"name": "time_seen",
|
||||||
"type": "timestamp with time zone",
|
"type": "timestamp(3)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"color": {
|
"color": {
|
||||||
"name": "color",
|
"name": "color",
|
||||||
"type": "integer",
|
"type": "int",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"user_email": {
|
"user_email": {
|
||||||
"name": "user_email",
|
"name": "user_email",
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
"workspace_id",
|
||||||
"expression": "workspace_id",
|
"email"
|
||||||
"isExpression": false,
|
|
||||||
"asc": true,
|
|
||||||
"nulls": "last"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"expression": "email",
|
|
||||||
"isExpression": false,
|
|
||||||
"asc": true,
|
|
||||||
"nulls": "last"
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
"isUnique": true,
|
"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"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
"compositePrimaryKeys": {
|
"compositePrimaryKeys": {
|
||||||
"user_workspace_id_id_pk": {
|
"user_workspace_id_id_pk": {
|
||||||
"name": "user_workspace_id_id_pk",
|
"name": "user_workspace_id_id_pk",
|
||||||
@@ -436,80 +510,86 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"uniqueConstraints": {},
|
"uniqueConstraints": {},
|
||||||
"policies": {},
|
"checkConstraint": {}
|
||||||
"checkConstraints": {},
|
|
||||||
"isRLSEnabled": false
|
|
||||||
},
|
},
|
||||||
"public.workspace": {
|
"workspace": {
|
||||||
"name": "workspace",
|
"name": "workspace",
|
||||||
"schema": "",
|
|
||||||
"columns": {
|
"columns": {
|
||||||
"id": {
|
"id": {
|
||||||
"name": "id",
|
"name": "id",
|
||||||
"type": "varchar(30)",
|
"type": "varchar(30)",
|
||||||
"primaryKey": true,
|
"primaryKey": false,
|
||||||
"notNull": true
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"slug": {
|
"slug": {
|
||||||
"name": "slug",
|
"name": "slug",
|
||||||
"type": "varchar(255)",
|
"type": "varchar(255)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"name": "name",
|
"name": "name",
|
||||||
"type": "varchar(255)",
|
"type": "varchar(255)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"time_created": {
|
"time_created": {
|
||||||
"name": "time_created",
|
"name": "time_created",
|
||||||
"type": "timestamp with time zone",
|
"type": "timestamp(3)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"default": "now()"
|
"autoincrement": false,
|
||||||
|
"default": "(now())"
|
||||||
|
},
|
||||||
|
"time_updated": {
|
||||||
|
"name": "time_updated",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
|
||||||
},
|
},
|
||||||
"time_deleted": {
|
"time_deleted": {
|
||||||
"name": "time_deleted",
|
"name": "time_deleted",
|
||||||
"type": "timestamp with time zone",
|
"type": "timestamp(3)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"slug": {
|
"slug": {
|
||||||
"name": "slug",
|
"name": "slug",
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
"slug"
|
||||||
"expression": "slug",
|
|
||||||
"isExpression": false,
|
|
||||||
"asc": true,
|
|
||||||
"nulls": "last"
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
"isUnique": true,
|
"isUnique": true
|
||||||
"concurrently": false,
|
|
||||||
"method": "btree",
|
|
||||||
"with": {}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"foreignKeys": {},
|
"foreignKeys": {},
|
||||||
"compositePrimaryKeys": {},
|
"compositePrimaryKeys": {
|
||||||
|
"workspace_id": {
|
||||||
|
"name": "workspace_id",
|
||||||
|
"columns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"uniqueConstraints": {},
|
"uniqueConstraints": {},
|
||||||
"policies": {},
|
"checkConstraint": {}
|
||||||
"checkConstraints": {},
|
|
||||||
"isRLSEnabled": false
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"enums": {},
|
|
||||||
"schemas": {},
|
|
||||||
"sequences": {},
|
|
||||||
"roles": {},
|
|
||||||
"policies": {},
|
|
||||||
"views": {},
|
"views": {},
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"columns": {},
|
|
||||||
"schemas": {},
|
"schemas": {},
|
||||||
"tables": {}
|
"tables": {},
|
||||||
|
"columns": {}
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"tables": {},
|
||||||
|
"indexes": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,615 +0,0 @@
|
|||||||
{
|
|
||||||
"id": "351e4956-74e0-4282-a23b-02f1a73fa38c",
|
|
||||||
"prevId": "bf9e9084-4073-4ecb-8e56-5610816c9589",
|
|
||||||
"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.key": {
|
|
||||||
"name": "key",
|
|
||||||
"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
|
|
||||||
},
|
|
||||||
"user_id": {
|
|
||||||
"name": "user_id",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"name": "name",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
"key": {
|
|
||||||
"name": "key",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
"time_used": {
|
|
||||||
"name": "time_used",
|
|
||||||
"type": "timestamp with time zone",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"indexes": {
|
|
||||||
"global_key": {
|
|
||||||
"name": "global_key",
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"expression": "key",
|
|
||||||
"isExpression": false,
|
|
||||||
"asc": true,
|
|
||||||
"nulls": "last"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"isUnique": true,
|
|
||||||
"concurrently": false,
|
|
||||||
"method": "btree",
|
|
||||||
"with": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"foreignKeys": {
|
|
||||||
"key_workspace_id_workspace_id_fk": {
|
|
||||||
"name": "key_workspace_id_workspace_id_fk",
|
|
||||||
"tableFrom": "key",
|
|
||||||
"tableTo": "workspace",
|
|
||||||
"columnsFrom": [
|
|
||||||
"workspace_id"
|
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "no action",
|
|
||||||
"onUpdate": "no action"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"compositePrimaryKeys": {
|
|
||||||
"key_workspace_id_id_pk": {
|
|
||||||
"name": "key_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,609 +0,0 @@
|
|||||||
{
|
|
||||||
"id": "fa935883-9e51-4811-90c7-8967eefe458c",
|
|
||||||
"prevId": "351e4956-74e0-4282-a23b-02f1a73fa38c",
|
|
||||||
"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
|
|
||||||
},
|
|
||||||
"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.key": {
|
|
||||||
"name": "key",
|
|
||||||
"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
|
|
||||||
},
|
|
||||||
"user_id": {
|
|
||||||
"name": "user_id",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"name": "name",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
"key": {
|
|
||||||
"name": "key",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
"time_used": {
|
|
||||||
"name": "time_used",
|
|
||||||
"type": "timestamp with time zone",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"indexes": {
|
|
||||||
"global_key": {
|
|
||||||
"name": "global_key",
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"expression": "key",
|
|
||||||
"isExpression": false,
|
|
||||||
"asc": true,
|
|
||||||
"nulls": "last"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"isUnique": true,
|
|
||||||
"concurrently": false,
|
|
||||||
"method": "btree",
|
|
||||||
"with": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"foreignKeys": {
|
|
||||||
"key_workspace_id_workspace_id_fk": {
|
|
||||||
"name": "key_workspace_id_workspace_id_fk",
|
|
||||||
"tableFrom": "key",
|
|
||||||
"tableTo": "workspace",
|
|
||||||
"columnsFrom": [
|
|
||||||
"workspace_id"
|
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "no action",
|
|
||||||
"onUpdate": "no action"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"compositePrimaryKeys": {
|
|
||||||
"key_workspace_id_id_pk": {
|
|
||||||
"name": "key_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,33 +1,19 @@
|
|||||||
{
|
{
|
||||||
"version": "7",
|
"version": "7",
|
||||||
"dialect": "postgresql",
|
"dialect": "mysql",
|
||||||
"entries": [
|
"entries": [
|
||||||
{
|
{
|
||||||
"idx": 0,
|
"idx": 0,
|
||||||
"version": "7",
|
"version": "5",
|
||||||
"when": 1754518198186,
|
"when": 1756796050935,
|
||||||
"tag": "0000_amused_mojo",
|
"tag": "0000_fluffy_raza",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"version": "7",
|
"version": "5",
|
||||||
"when": 1754609655262,
|
"when": 1756871639102,
|
||||||
"tag": "0001_thankful_chat",
|
"tag": "0001_serious_whistler",
|
||||||
"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
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"name": "@opencode/cloud-core",
|
"name": "@opencode/cloud-core",
|
||||||
"version": "0.5.5",
|
"version": "0.6.5",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-sts": "3.782.0",
|
"@aws-sdk/client-sts": "3.782.0",
|
||||||
|
"@opencode/cloud-resource": "workspace:*",
|
||||||
|
"@planetscale/database": "1.19.0",
|
||||||
"drizzle-orm": "0.41.0",
|
"drizzle-orm": "0.41.0",
|
||||||
"postgres": "3.4.7",
|
"postgres": "3.4.7",
|
||||||
"stripe": "18.0.0",
|
"stripe": "18.0.0",
|
||||||
@@ -15,9 +17,11 @@
|
|||||||
"./*": "./src/*"
|
"./*": "./src/*"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"db": "sst shell drizzle-kit"
|
"db": "sst shell drizzle-kit",
|
||||||
|
"typecheck": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"drizzle-kit": "0.30.5"
|
"drizzle-kit": "0.30.5",
|
||||||
|
"mysql2": "3.14.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ export namespace Actor {
|
|||||||
properties: {
|
properties: {
|
||||||
userID: string
|
userID: string
|
||||||
workspaceID: string
|
workspaceID: string
|
||||||
email: string
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import { Resource } from "sst"
|
|
||||||
import { Stripe } from "stripe"
|
import { Stripe } from "stripe"
|
||||||
import { Database, eq, sql } from "./drizzle"
|
import { Database, eq, sql } from "./drizzle"
|
||||||
import { BillingTable, UsageTable } from "./schema/billing.sql"
|
import { BillingTable, PaymentTable, UsageTable } from "./schema/billing.sql"
|
||||||
import { Actor } from "./actor"
|
import { Actor } from "./actor"
|
||||||
import { fn } from "./util/fn"
|
import { fn } from "./util/fn"
|
||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
import { Identifier } from "./identifier"
|
import { User } from "./user"
|
||||||
import { centsToMicroCents } from "./util/price"
|
import { Resource } from "@opencode/cloud-resource"
|
||||||
|
|
||||||
export namespace Billing {
|
export namespace Billing {
|
||||||
export const stripe = () =>
|
export const stripe = () =>
|
||||||
@@ -29,43 +28,93 @@ export namespace Billing {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const consume = fn(
|
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 generateCheckoutUrl = fn(
|
||||||
z.object({
|
z.object({
|
||||||
requestID: z.string().optional(),
|
successUrl: z.string(),
|
||||||
model: z.string(),
|
cancelUrl: 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) => {
|
async (input) => {
|
||||||
const workspaceID = Actor.workspace()
|
const account = Actor.assert("user")
|
||||||
const cost = centsToMicroCents(input.costInCents)
|
const { successUrl, cancelUrl } = input
|
||||||
|
|
||||||
return await Database.transaction(async (tx) => {
|
const user = await User.fromID(account.properties.userID)
|
||||||
await tx.insert(UsageTable).values({
|
const customer = await Billing.get()
|
||||||
workspaceID,
|
const session = await Billing.stripe().checkout.sessions.create({
|
||||||
id: Identifier.create("usage"),
|
mode: "payment",
|
||||||
requestID: input.requestID,
|
line_items: [
|
||||||
model: input.model,
|
{
|
||||||
inputTokens: input.inputTokens,
|
price_data: {
|
||||||
outputTokens: input.outputTokens,
|
currency: "usd",
|
||||||
reasoningTokens: input.reasoningTokens,
|
product_data: {
|
||||||
cacheReadTokens: input.cacheReadTokens,
|
name: "opencode credits",
|
||||||
cacheWriteTokens: input.cacheWriteTokens,
|
},
|
||||||
cost,
|
unit_amount: 2123, // $20 minimum + Stripe fee 4.4% + $0.30
|
||||||
|
},
|
||||||
|
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,
|
||||||
})
|
})
|
||||||
const [updated] = await tx
|
|
||||||
.update(BillingTable)
|
return session.url
|
||||||
.set({
|
},
|
||||||
balance: sql`${BillingTable.balance} - ${cost}`,
|
)
|
||||||
})
|
|
||||||
.where(eq(BillingTable.workspaceID, workspaceID))
|
export const generatePortalUrl = fn(
|
||||||
.returning()
|
z.object({
|
||||||
return updated.balance
|
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,39 +1,33 @@
|
|||||||
import { drizzle } from "drizzle-orm/postgres-js"
|
import { drizzle } from "drizzle-orm/planetscale-serverless"
|
||||||
import { Resource } from "sst"
|
import { Resource } from "@opencode/cloud-resource"
|
||||||
export * from "drizzle-orm"
|
export * from "drizzle-orm"
|
||||||
import postgres from "postgres"
|
import { Client } from "@planetscale/database"
|
||||||
|
|
||||||
function createClient() {
|
import { MySqlTransaction, type MySqlTransactionConfig } from "drizzle-orm/mysql-core"
|
||||||
const client = postgres({
|
|
||||||
idle_timeout: 30000,
|
|
||||||
connect_timeout: 30000,
|
|
||||||
host: Resource.Database.host,
|
|
||||||
database: Resource.Database.database,
|
|
||||||
user: Resource.Database.username,
|
|
||||||
password: Resource.Database.password,
|
|
||||||
port: Resource.Database.port,
|
|
||||||
ssl: {
|
|
||||||
rejectUnauthorized: false,
|
|
||||||
},
|
|
||||||
max: 1,
|
|
||||||
})
|
|
||||||
|
|
||||||
return drizzle(client, {})
|
|
||||||
}
|
|
||||||
|
|
||||||
import { PgTransaction, type PgTransactionConfig } from "drizzle-orm/pg-core"
|
|
||||||
import type { ExtractTablesWithRelations } from "drizzle-orm"
|
import type { ExtractTablesWithRelations } from "drizzle-orm"
|
||||||
import type { PostgresJsQueryResultHKT } from "drizzle-orm/postgres-js"
|
import type { PlanetScalePreparedQueryHKT, PlanetscaleQueryResultHKT } from "drizzle-orm/planetscale-serverless"
|
||||||
import { Context } from "../context"
|
import { Context } from "../context"
|
||||||
|
import { memo } from "../util/memo"
|
||||||
|
|
||||||
export namespace Database {
|
export namespace Database {
|
||||||
export type Transaction = PgTransaction<
|
export type Transaction = MySqlTransaction<
|
||||||
PostgresJsQueryResultHKT,
|
PlanetscaleQueryResultHKT,
|
||||||
Record<string, unknown>,
|
PlanetScalePreparedQueryHKT,
|
||||||
ExtractTablesWithRelations<Record<string, unknown>>
|
Record<string, never>,
|
||||||
|
ExtractTablesWithRelations<Record<string, never>>
|
||||||
>
|
>
|
||||||
|
|
||||||
export type TxOrDb = Transaction | ReturnType<typeof createClient>
|
const client = memo(() => {
|
||||||
|
const result = new Client({
|
||||||
|
host: Resource.Database.host,
|
||||||
|
username: Resource.Database.username,
|
||||||
|
password: Resource.Database.password,
|
||||||
|
})
|
||||||
|
const db = drizzle(result, {})
|
||||||
|
return db
|
||||||
|
})
|
||||||
|
|
||||||
|
export type TxOrDb = Transaction | ReturnType<typeof client>
|
||||||
|
|
||||||
const TransactionContext = Context.create<{
|
const TransactionContext = Context.create<{
|
||||||
tx: TxOrDb
|
tx: TxOrDb
|
||||||
@@ -46,14 +40,13 @@ export namespace Database {
|
|||||||
return tx.transaction(callback)
|
return tx.transaction(callback)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof Context.NotFound) {
|
if (err instanceof Context.NotFound) {
|
||||||
const client = createClient()
|
|
||||||
const effects: (() => void | Promise<void>)[] = []
|
const effects: (() => void | Promise<void>)[] = []
|
||||||
const result = await TransactionContext.provide(
|
const result = await TransactionContext.provide(
|
||||||
{
|
{
|
||||||
effects,
|
effects,
|
||||||
tx: client,
|
tx: client(),
|
||||||
},
|
},
|
||||||
() => callback(client),
|
() => callback(client()),
|
||||||
)
|
)
|
||||||
await Promise.all(effects.map((x) => x()))
|
await Promise.all(effects.map((x) => x()))
|
||||||
return result
|
return result
|
||||||
@@ -74,15 +67,14 @@ export namespace Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function transaction<T>(callback: (tx: TxOrDb) => Promise<T>, config?: PgTransactionConfig) {
|
export async function transaction<T>(callback: (tx: TxOrDb) => Promise<T>, config?: MySqlTransactionConfig) {
|
||||||
try {
|
try {
|
||||||
const { tx } = TransactionContext.use()
|
const { tx } = TransactionContext.use()
|
||||||
return callback(tx)
|
return callback(tx)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof Context.NotFound) {
|
if (err instanceof Context.NotFound) {
|
||||||
const client = createClient()
|
|
||||||
const effects: (() => void | Promise<void>)[] = []
|
const effects: (() => void | Promise<void>)[] = []
|
||||||
const result = await client.transaction(async (tx) => {
|
const result = await client().transaction(async (tx) => {
|
||||||
return TransactionContext.provide({ tx, effects }, () => callback(tx))
|
return TransactionContext.provide({ tx, effects }, () => callback(tx))
|
||||||
}, config)
|
}, config)
|
||||||
await Promise.all(effects.map((x) => x()))
|
await Promise.all(effects.map((x) => x()))
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { bigint, timestamp, varchar } from "drizzle-orm/pg-core"
|
import { sql } from "drizzle-orm"
|
||||||
|
import { bigint, timestamp, varchar } from "drizzle-orm/mysql-core"
|
||||||
|
|
||||||
export const ulid = (name: string) => varchar(name, { length: 30 })
|
export const ulid = (name: string) => varchar(name, { length: 30 })
|
||||||
|
|
||||||
@@ -15,7 +16,7 @@ export const id = () => ulid("id").notNull()
|
|||||||
|
|
||||||
export const utc = (name: string) =>
|
export const utc = (name: string) =>
|
||||||
timestamp(name, {
|
timestamp(name, {
|
||||||
withTimezone: true,
|
fsp: 3,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const currency = (name: string) =>
|
export const currency = (name: string) =>
|
||||||
@@ -25,5 +26,8 @@ export const currency = (name: string) =>
|
|||||||
|
|
||||||
export const timestamps = {
|
export const timestamps = {
|
||||||
timeCreated: utc("time_created").notNull().defaultNow(),
|
timeCreated: utc("time_created").notNull().defaultNow(),
|
||||||
|
timeUpdated: utc("time_updated")
|
||||||
|
.notNull()
|
||||||
|
.default(sql`CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)`),
|
||||||
timeDeleted: utc("time_deleted"),
|
timeDeleted: utc("time_deleted"),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
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 workspace = Actor.workspace()
|
||||||
|
const keys = await Database.use((tx) =>
|
||||||
|
tx
|
||||||
|
.select()
|
||||||
|
.from(KeyTable)
|
||||||
|
.where(eq(KeyTable.workspaceID, workspace))
|
||||||
|
.orderBy(sql`${KeyTable.timeCreated} DESC`),
|
||||||
|
)
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
export const create = fn(z.object({ name: z.string().min(1).max(255) }), async (input) => {
|
||||||
|
const workspaceID = Actor.workspace()
|
||||||
|
const { name } = input
|
||||||
|
|
||||||
|
// Generate secret key: sk- + 64 random characters (upper, lower, numbers)
|
||||||
|
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
||||||
|
let secretKey = "sk-"
|
||||||
|
const array = new Uint32Array(64)
|
||||||
|
crypto.getRandomValues(array)
|
||||||
|
for (let i = 0, l = array.length; i < l; i++) {
|
||||||
|
secretKey += chars[array[i] % chars.length]
|
||||||
|
}
|
||||||
|
const keyID = Identifier.create("key")
|
||||||
|
|
||||||
|
await Database.use((tx) =>
|
||||||
|
tx.insert(KeyTable).values({
|
||||||
|
id: keyID,
|
||||||
|
workspaceID,
|
||||||
|
actor: Actor.use(),
|
||||||
|
name,
|
||||||
|
key: secretKey,
|
||||||
|
timeUsed: null,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
return keyID
|
||||||
|
})
|
||||||
|
|
||||||
|
export const remove = fn(z.object({ id: z.string() }), async (input) => {
|
||||||
|
const workspace = Actor.workspace()
|
||||||
|
await Database.use((tx) =>
|
||||||
|
tx.delete(KeyTable).where(and(eq(KeyTable.id, input.id), eq(KeyTable.workspaceID, workspace))),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { pgTable, uniqueIndex, varchar } from "drizzle-orm/pg-core"
|
import { mysqlTable, uniqueIndex, varchar } from "drizzle-orm/mysql-core"
|
||||||
import { id, timestamps } from "../drizzle/types"
|
import { id, timestamps } from "../drizzle/types"
|
||||||
|
|
||||||
export const AccountTable = pgTable(
|
export const AccountTable = mysqlTable(
|
||||||
"account",
|
"account",
|
||||||
{
|
{
|
||||||
id: id(),
|
id: id(),
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { bigint, boolean, integer, pgTable, varchar } from "drizzle-orm/pg-core"
|
import { bigint, boolean, int, mysqlTable, varchar } from "drizzle-orm/mysql-core"
|
||||||
import { timestamps, workspaceColumns } from "../drizzle/types"
|
import { timestamps, workspaceColumns } from "../drizzle/types"
|
||||||
import { workspaceIndexes } from "./workspace.sql"
|
import { workspaceIndexes } from "./workspace.sql"
|
||||||
|
|
||||||
export const BillingTable = pgTable(
|
export const BillingTable = mysqlTable(
|
||||||
"billing",
|
"billing",
|
||||||
{
|
{
|
||||||
...workspaceColumns,
|
...workspaceColumns,
|
||||||
@@ -16,7 +16,7 @@ export const BillingTable = pgTable(
|
|||||||
(table) => [...workspaceIndexes(table)],
|
(table) => [...workspaceIndexes(table)],
|
||||||
)
|
)
|
||||||
|
|
||||||
export const PaymentTable = pgTable(
|
export const PaymentTable = mysqlTable(
|
||||||
"payment",
|
"payment",
|
||||||
{
|
{
|
||||||
...workspaceColumns,
|
...workspaceColumns,
|
||||||
@@ -28,17 +28,17 @@ export const PaymentTable = pgTable(
|
|||||||
(table) => [...workspaceIndexes(table)],
|
(table) => [...workspaceIndexes(table)],
|
||||||
)
|
)
|
||||||
|
|
||||||
export const UsageTable = pgTable(
|
export const UsageTable = mysqlTable(
|
||||||
"usage",
|
"usage",
|
||||||
{
|
{
|
||||||
...workspaceColumns,
|
...workspaceColumns,
|
||||||
...timestamps,
|
...timestamps,
|
||||||
model: varchar("model", { length: 255 }).notNull(),
|
model: varchar("model", { length: 255 }).notNull(),
|
||||||
inputTokens: integer("input_tokens").notNull(),
|
inputTokens: int("input_tokens").notNull(),
|
||||||
outputTokens: integer("output_tokens").notNull(),
|
outputTokens: int("output_tokens").notNull(),
|
||||||
reasoningTokens: integer("reasoning_tokens"),
|
reasoningTokens: int("reasoning_tokens"),
|
||||||
cacheReadTokens: integer("cache_read_tokens"),
|
cacheReadTokens: int("cache_read_tokens"),
|
||||||
cacheWriteTokens: integer("cache_write_tokens"),
|
cacheWriteTokens: int("cache_write_tokens"),
|
||||||
cost: bigint("cost", { mode: "number" }).notNull(),
|
cost: bigint("cost", { mode: "number" }).notNull(),
|
||||||
},
|
},
|
||||||
(table) => [...workspaceIndexes(table)],
|
(table) => [...workspaceIndexes(table)],
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import { text, pgTable, varchar, uniqueIndex } from "drizzle-orm/pg-core"
|
import { mysqlTable, varchar, uniqueIndex, json } from "drizzle-orm/mysql-core"
|
||||||
import { timestamps, utc, workspaceColumns } from "../drizzle/types"
|
import { timestamps, utc, workspaceColumns } from "../drizzle/types"
|
||||||
import { workspaceIndexes } from "./workspace.sql"
|
import { workspaceIndexes } from "./workspace.sql"
|
||||||
|
import { Actor } from "../actor"
|
||||||
|
|
||||||
export const KeyTable = pgTable(
|
export const KeyTable = mysqlTable(
|
||||||
"key",
|
"key",
|
||||||
{
|
{
|
||||||
...workspaceColumns,
|
...workspaceColumns,
|
||||||
...timestamps,
|
...timestamps,
|
||||||
userID: text("user_id").notNull(),
|
actor: json("actor").$type<Actor.Info>(),
|
||||||
name: varchar("name", { length: 255 }).notNull(),
|
name: varchar("name", { length: 255 }).notNull(),
|
||||||
key: varchar("key", { length: 255 }).notNull(),
|
key: varchar("key", { length: 255 }).notNull(),
|
||||||
timeUsed: utc("time_used"),
|
timeUsed: utc("time_used"),
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import { text, pgTable, uniqueIndex, varchar, integer } from "drizzle-orm/pg-core"
|
import { text, mysqlTable, uniqueIndex, varchar, int } from "drizzle-orm/mysql-core"
|
||||||
import { timestamps, utc, workspaceColumns } from "../drizzle/types"
|
import { timestamps, utc, workspaceColumns } from "../drizzle/types"
|
||||||
import { workspaceIndexes } from "./workspace.sql"
|
import { workspaceIndexes } from "./workspace.sql"
|
||||||
|
|
||||||
export const UserTable = pgTable(
|
export const UserTable = mysqlTable(
|
||||||
"user",
|
"user",
|
||||||
{
|
{
|
||||||
...workspaceColumns,
|
...workspaceColumns,
|
||||||
...timestamps,
|
...timestamps,
|
||||||
email: text("email").notNull(),
|
email: varchar("email", { length: 255 }).notNull(),
|
||||||
name: varchar("name", { length: 255 }).notNull(),
|
name: varchar("name", { length: 255 }).notNull(),
|
||||||
timeSeen: utc("time_seen"),
|
timeSeen: utc("time_seen"),
|
||||||
color: integer("color"),
|
color: int("color"),
|
||||||
},
|
},
|
||||||
(table) => [...workspaceIndexes(table), uniqueIndex("user_email").on(table.workspaceID, table.email)],
|
(table) => [...workspaceIndexes(table), uniqueIndex("user_email").on(table.workspaceID, table.email)],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { primaryKey, foreignKey, pgTable, uniqueIndex, varchar } from "drizzle-orm/pg-core"
|
import { primaryKey, mysqlTable, uniqueIndex, varchar } from "drizzle-orm/mysql-core"
|
||||||
import { timestamps, ulid } from "../drizzle/types"
|
import { timestamps, ulid } from "../drizzle/types"
|
||||||
|
|
||||||
export const WorkspaceTable = pgTable(
|
export const WorkspaceTable = mysqlTable(
|
||||||
"workspace",
|
"workspace",
|
||||||
{
|
{
|
||||||
id: ulid("id").notNull().primaryKey(),
|
id: ulid("id").notNull().primaryKey(),
|
||||||
@@ -17,9 +17,5 @@ export function workspaceIndexes(table: any) {
|
|||||||
primaryKey({
|
primaryKey({
|
||||||
columns: [table.workspaceID, table.id],
|
columns: [table.workspaceID, table.id],
|
||||||
}),
|
}),
|
||||||
foreignKey({
|
|
||||||
foreignColumns: [WorkspaceTable.id],
|
|
||||||
columns: [table.workspaceID],
|
|
||||||
}),
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
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])
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
export function memo<T>(fn: () => T, cleanup?: (input: T) => Promise<void>) {
|
||||||
|
let value: T | undefined
|
||||||
|
let loaded = false
|
||||||
|
|
||||||
|
const result = (): T => {
|
||||||
|
if (loaded) return value as T
|
||||||
|
loaded = true
|
||||||
|
value = fn()
|
||||||
|
return value as T
|
||||||
|
}
|
||||||
|
result.reset = async () => {
|
||||||
|
if (cleanup && value) await cleanup(value)
|
||||||
|
loaded = false
|
||||||
|
value = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import { Identifier } from "./identifier"
|
|||||||
import { UserTable } from "./schema/user.sql"
|
import { UserTable } from "./schema/user.sql"
|
||||||
import { BillingTable } from "./schema/billing.sql"
|
import { BillingTable } from "./schema/billing.sql"
|
||||||
import { WorkspaceTable } from "./schema/workspace.sql"
|
import { WorkspaceTable } from "./schema/workspace.sql"
|
||||||
|
import { Key } from "./key"
|
||||||
|
|
||||||
export namespace Workspace {
|
export namespace Workspace {
|
||||||
export const create = fn(z.void(), async () => {
|
export const create = fn(z.void(), async () => {
|
||||||
@@ -25,9 +26,18 @@ export namespace Workspace {
|
|||||||
await tx.insert(BillingTable).values({
|
await tx.insert(BillingTable).values({
|
||||||
workspaceID,
|
workspaceID,
|
||||||
id: Identifier.create("billing"),
|
id: Identifier.create("billing"),
|
||||||
balance: centsToMicroCents(100),
|
balance: 0,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
await Actor.provide(
|
||||||
|
"system",
|
||||||
|
{
|
||||||
|
workspaceID,
|
||||||
|
},
|
||||||
|
async () => {
|
||||||
|
await Key.create({ name: "Default API Key" })
|
||||||
|
},
|
||||||
|
)
|
||||||
return workspaceID
|
return workspaceID
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode/cloud-function",
|
"name": "@opencode/cloud-function",
|
||||||
"version": "0.5.5",
|
"version": "0.6.5",
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"typecheck": "tsc --noEmit"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@cloudflare/workers-types": "4.20250522.0",
|
"@cloudflare/workers-types": "4.20250522.0",
|
||||||
"@types/node": "catalog:",
|
"@types/node": "catalog:",
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
import { Resource } from "sst"
|
|
||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
import { issuer } from "@openauthjs/openauth"
|
import { issuer } from "@openauthjs/openauth"
|
||||||
|
import type { Theme } from "@openauthjs/openauth/ui/theme"
|
||||||
import { createSubjects } from "@openauthjs/openauth/subject"
|
import { createSubjects } from "@openauthjs/openauth/subject"
|
||||||
import { CodeProvider } from "@openauthjs/openauth/provider/code"
|
import { THEME_OPENAUTH } from "@openauthjs/openauth/ui/theme"
|
||||||
import { GithubProvider } from "@openauthjs/openauth/provider/github"
|
import { GithubProvider } from "@openauthjs/openauth/provider/github"
|
||||||
import { GoogleOidcProvider } from "@openauthjs/openauth/provider/google"
|
import { GoogleOidcProvider } from "@openauthjs/openauth/provider/google"
|
||||||
import { CloudflareStorage } from "@openauthjs/openauth/storage/cloudflare"
|
import { CloudflareStorage } from "@openauthjs/openauth/storage/cloudflare"
|
||||||
import { Account } from "@opencode/cloud-core/account.js"
|
import { Account } from "@opencode/cloud-core/account.js"
|
||||||
|
import { Workspace } from "@opencode/cloud-core/workspace.js"
|
||||||
|
import { Actor } from "@opencode/cloud-core/actor.js"
|
||||||
|
import { Resource } from "@opencode/cloud-resource"
|
||||||
|
import { Database } from "@opencode/cloud-core/drizzle/index.js"
|
||||||
|
|
||||||
type Env = {
|
type Env = {
|
||||||
AuthStorage: KVNamespace
|
AuthStorage: KVNamespace
|
||||||
@@ -23,9 +27,15 @@ export const subjects = createSubjects({
|
|||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const MY_THEME: Theme = {
|
||||||
|
...THEME_OPENAUTH,
|
||||||
|
logo: "https://opencode.ai/favicon.svg",
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
async fetch(request: Request, env: Env, ctx: ExecutionContext) {
|
async fetch(request: Request, env: Env, ctx: ExecutionContext) {
|
||||||
return issuer({
|
const result = await issuer({
|
||||||
|
theme: MY_THEME,
|
||||||
providers: {
|
providers: {
|
||||||
github: GithubProvider({
|
github: GithubProvider({
|
||||||
clientID: Resource.GITHUB_CLIENT_ID_CONSOLE.value,
|
clientID: Resource.GITHUB_CLIENT_ID_CONSOLE.value,
|
||||||
@@ -90,15 +100,14 @@ export default {
|
|||||||
let email: string | undefined
|
let email: string | undefined
|
||||||
|
|
||||||
if (response.provider === "github") {
|
if (response.provider === "github") {
|
||||||
const userResponse = await fetch("https://api.github.com/user", {
|
const emails = (await fetch("https://api.github.com/user/emails", {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${response.tokenset.access}`,
|
Authorization: `Bearer ${response.tokenset.access}`,
|
||||||
"User-Agent": "opencode",
|
"User-Agent": "opencode",
|
||||||
Accept: "application/vnd.github+json",
|
Accept: "application/vnd.github+json",
|
||||||
},
|
},
|
||||||
})
|
}).then((x) => x.json())) as any
|
||||||
const user = (await userResponse.json()) as { email: string }
|
email = emails.find((x: any) => x.primary && x.verified)?.email
|
||||||
email = user.email
|
|
||||||
} else if (response.provider === "google") {
|
} else if (response.provider === "google") {
|
||||||
if (!response.id.email_verified) throw new Error("Google email not verified")
|
if (!response.id.email_verified) throw new Error("Google email not verified")
|
||||||
email = response.id.email as string
|
email = response.id.email as string
|
||||||
@@ -117,8 +126,15 @@ export default {
|
|||||||
email: email!,
|
email: email!,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
await Actor.provide("account", { accountID, email }, async () => {
|
||||||
|
const workspaces = await Account.workspaces()
|
||||||
|
if (workspaces.length === 0) {
|
||||||
|
await Workspace.create()
|
||||||
|
}
|
||||||
|
})
|
||||||
return ctx.subject("account", accountID, { accountID, email })
|
return ctx.subject("account", accountID, { accountID, email })
|
||||||
},
|
},
|
||||||
}).fetch(request, env, ctx)
|
}).fetch(request, env, ctx)
|
||||||
|
return result
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,909 +0,0 @@
|
|||||||
import { z } from "zod"
|
|
||||||
import { Hono, MiddlewareHandler } from "hono"
|
|
||||||
import { cors } from "hono/cors"
|
|
||||||
import { HTTPException } from "hono/http-exception"
|
|
||||||
import { zValidator } from "@hono/zod-validator"
|
|
||||||
import { Resource } from "sst"
|
|
||||||
import { type ProviderMetadata, type LanguageModelUsage, generateText, streamText } 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 { and, Database, eq, sql } from "@opencode/cloud-core/drizzle/index.js"
|
|
||||||
import { UserTable } from "@opencode/cloud-core/schema/user.sql.js"
|
|
||||||
import { KeyTable } from "@opencode/cloud-core/schema/key.sql.js"
|
|
||||||
import { createClient } from "@openauthjs/openauth/client"
|
|
||||||
import { Log } from "@opencode/cloud-core/util/log.js"
|
|
||||||
import { Billing } from "@opencode/cloud-core/billing.js"
|
|
||||||
import { Workspace } from "@opencode/cloud-core/workspace.js"
|
|
||||||
import { BillingTable, PaymentTable, UsageTable } from "@opencode/cloud-core/schema/billing.sql.js"
|
|
||||||
import { centsToMicroCents } from "@opencode/cloud-core/util/price.js"
|
|
||||||
import { Identifier } from "../../core/src/identifier"
|
|
||||||
|
|
||||||
type Env = {}
|
|
||||||
|
|
||||||
let _client: ReturnType<typeof createClient>
|
|
||||||
const client = () => {
|
|
||||||
if (_client) return _client
|
|
||||||
_client = createClient({
|
|
||||||
clientID: "api",
|
|
||||||
issuer: Resource.AUTH_API_URL.value,
|
|
||||||
})
|
|
||||||
return _client
|
|
||||||
}
|
|
||||||
|
|
||||||
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 log = Log.create({
|
|
||||||
namespace: "api",
|
|
||||||
})
|
|
||||||
|
|
||||||
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 RestAuth: MiddlewareHandler = async (c, next) => {
|
|
||||||
const authorization = c.req.header("authorization")
|
|
||||||
if (!authorization) {
|
|
||||||
return Actor.provide("public", {}, next)
|
|
||||||
}
|
|
||||||
const token = authorization.split(" ")[1]
|
|
||||||
if (!token)
|
|
||||||
throw new HTTPException(403, {
|
|
||||||
message: "Bearer token is required.",
|
|
||||||
})
|
|
||||||
|
|
||||||
const verified = await client().verify(token)
|
|
||||||
if (verified.err) {
|
|
||||||
throw new HTTPException(403, {
|
|
||||||
message: "Invalid token.",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
let subject = verified.subject as Actor.Info
|
|
||||||
if (subject.type === "account") {
|
|
||||||
const workspaceID = c.req.header("x-opencode-workspace")
|
|
||||||
const email = subject.properties.email
|
|
||||||
if (workspaceID) {
|
|
||||||
const user = await Database.use((tx) =>
|
|
||||||
tx
|
|
||||||
.select({
|
|
||||||
id: UserTable.id,
|
|
||||||
workspaceID: UserTable.workspaceID,
|
|
||||||
email: UserTable.email,
|
|
||||||
})
|
|
||||||
.from(UserTable)
|
|
||||||
.where(and(eq(UserTable.email, email), eq(UserTable.workspaceID, workspaceID)))
|
|
||||||
.then((rows) => rows[0]),
|
|
||||||
)
|
|
||||||
if (!user)
|
|
||||||
throw new HTTPException(403, {
|
|
||||||
message: "You do not have access to this workspace.",
|
|
||||||
})
|
|
||||||
subject = {
|
|
||||||
type: "user",
|
|
||||||
properties: {
|
|
||||||
userID: user.id,
|
|
||||||
workspaceID: workspaceID,
|
|
||||||
email: user.email,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await Actor.provide(subject.type, subject.properties, 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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.use("/*", cors())
|
|
||||||
.use(RestAuth)
|
|
||||||
.get("/rest/account", async (c) => {
|
|
||||||
const account = Actor.assert("account")
|
|
||||||
let workspaces = await Workspace.list()
|
|
||||||
if (workspaces.length === 0) {
|
|
||||||
await Workspace.create()
|
|
||||||
workspaces = await Workspace.list()
|
|
||||||
}
|
|
||||||
return c.json({
|
|
||||||
id: account.properties.accountID,
|
|
||||||
email: account.properties.email,
|
|
||||||
workspaces,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.get("/billing/info", async (c) => {
|
|
||||||
const billing = await Billing.get()
|
|
||||||
const payments = await Database.use((tx) =>
|
|
||||||
tx
|
|
||||||
.select()
|
|
||||||
.from(PaymentTable)
|
|
||||||
.where(eq(PaymentTable.workspaceID, Actor.workspace()))
|
|
||||||
.orderBy(sql`${PaymentTable.timeCreated} DESC`)
|
|
||||||
.limit(100),
|
|
||||||
)
|
|
||||||
const usage = await Database.use((tx) =>
|
|
||||||
tx
|
|
||||||
.select()
|
|
||||||
.from(UsageTable)
|
|
||||||
.where(eq(UsageTable.workspaceID, Actor.workspace()))
|
|
||||||
.orderBy(sql`${UsageTable.timeCreated} DESC`)
|
|
||||||
.limit(100),
|
|
||||||
)
|
|
||||||
return c.json({ billing, payments, usage })
|
|
||||||
})
|
|
||||||
.post(
|
|
||||||
"/billing/checkout",
|
|
||||||
zValidator(
|
|
||||||
"json",
|
|
||||||
z.custom<{
|
|
||||||
success_url: string
|
|
||||||
cancel_url: string
|
|
||||||
}>(),
|
|
||||||
),
|
|
||||||
async (c) => {
|
|
||||||
const account = Actor.assert("user")
|
|
||||||
|
|
||||||
const body = await c.req.json()
|
|
||||||
|
|
||||||
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: account.properties.email,
|
|
||||||
customer_creation: "always",
|
|
||||||
}),
|
|
||||||
metadata: {
|
|
||||||
workspaceID: Actor.workspace(),
|
|
||||||
},
|
|
||||||
currency: "usd",
|
|
||||||
payment_method_types: ["card"],
|
|
||||||
success_url: body.success_url,
|
|
||||||
cancel_url: body.cancel_url,
|
|
||||||
})
|
|
||||||
|
|
||||||
return c.json({
|
|
||||||
url: session.url,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.post("/billing/portal", async (c) => {
|
|
||||||
const body = await c.req.json()
|
|
||||||
|
|
||||||
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: body.return_url,
|
|
||||||
})
|
|
||||||
|
|
||||||
return c.json({
|
|
||||||
url: session.url,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.post("/stripe/webhook", async (c) => {
|
|
||||||
const body = await Billing.stripe().webhooks.constructEventAsync(
|
|
||||||
await c.req.text(),
|
|
||||||
c.req.header("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 c.json("ok", 200)
|
|
||||||
})
|
|
||||||
.get("/keys", async (c) => {
|
|
||||||
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 c.json({ keys })
|
|
||||||
})
|
|
||||||
.post("/keys", zValidator("json", z.object({ name: z.string().min(1).max(255) })), async (c) => {
|
|
||||||
const user = Actor.assert("user")
|
|
||||||
const { name } = c.req.valid("json")
|
|
||||||
|
|
||||||
// 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 c.json({
|
|
||||||
key: secretKey,
|
|
||||||
id: keyRecord[0].id,
|
|
||||||
name: keyRecord[0].name,
|
|
||||||
created: keyRecord[0].timeCreated,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.delete("/keys/:id", async (c) => {
|
|
||||||
const user = Actor.assert("user")
|
|
||||||
const keyId = c.req.param("id")
|
|
||||||
|
|
||||||
const result = await Database.use((tx) =>
|
|
||||||
tx
|
|
||||||
.delete(KeyTable)
|
|
||||||
.where(and(eq(KeyTable.id, keyId), eq(KeyTable.workspaceID, user.properties.workspaceID)))
|
|
||||||
.returning({ id: KeyTable.id }),
|
|
||||||
)
|
|
||||||
|
|
||||||
if (result.length === 0) {
|
|
||||||
return c.json({ error: "Key not found" }, 404)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.json({ success: true, id: result[0].id })
|
|
||||||
})
|
|
||||||
.all("*", (c) => c.text("Not Found"))
|
|
||||||
|
|
||||||
export type ApiType = typeof app
|
|
||||||
|
|
||||||
export default app
|
|
||||||
@@ -14,18 +14,14 @@ declare module "sst" {
|
|||||||
"type": "sst.sst.Linkable"
|
"type": "sst.sst.Linkable"
|
||||||
"value": string
|
"value": string
|
||||||
}
|
}
|
||||||
|
"BASETEN_API_KEY": {
|
||||||
|
"type": "sst.sst.Secret"
|
||||||
|
"value": string
|
||||||
|
}
|
||||||
"Console": {
|
"Console": {
|
||||||
"type": "sst.cloudflare.StaticSite"
|
"type": "sst.cloudflare.SolidStart"
|
||||||
"url": string
|
"url": string
|
||||||
}
|
}
|
||||||
"DATABASE_PASSWORD": {
|
|
||||||
"type": "sst.sst.Secret"
|
|
||||||
"value": string
|
|
||||||
}
|
|
||||||
"DATABASE_USERNAME": {
|
|
||||||
"type": "sst.sst.Secret"
|
|
||||||
"value": string
|
|
||||||
}
|
|
||||||
"Database": {
|
"Database": {
|
||||||
"database": string
|
"database": string
|
||||||
"host": string
|
"host": string
|
||||||
@@ -54,10 +50,6 @@ declare module "sst" {
|
|||||||
"type": "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
"value": string
|
"value": string
|
||||||
}
|
}
|
||||||
"OPENAI_API_KEY": {
|
|
||||||
"type": "sst.sst.Secret"
|
|
||||||
"value": string
|
|
||||||
}
|
|
||||||
"STRIPE_SECRET_KEY": {
|
"STRIPE_SECRET_KEY": {
|
||||||
"type": "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
"value": string
|
"value": string
|
||||||
@@ -70,7 +62,7 @@ declare module "sst" {
|
|||||||
"type": "sst.cloudflare.Astro"
|
"type": "sst.cloudflare.Astro"
|
||||||
"url": string
|
"url": string
|
||||||
}
|
}
|
||||||
"ZHIPU_API_KEY": {
|
"XAI_API_KEY": {
|
||||||
"type": "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
"value": string
|
"value": string
|
||||||
}
|
}
|
||||||
@@ -84,7 +76,6 @@ declare module "sst" {
|
|||||||
"AuthApi": cloudflare.Service
|
"AuthApi": cloudflare.Service
|
||||||
"AuthStorage": cloudflare.KVNamespace
|
"AuthStorage": cloudflare.KVNamespace
|
||||||
"Bucket": cloudflare.R2Bucket
|
"Bucket": cloudflare.R2Bucket
|
||||||
"GatewayApi": cloudflare.Service
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"lockfileVersion": 1,
|
||||||
|
"workspaces": {
|
||||||
|
"": {
|
||||||
|
"dependencies": {
|
||||||
|
"@cloudflare/workers-types": "^4.20250830.0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"packages": {
|
||||||
|
"@cloudflare/workers-types": ["@cloudflare/workers-types@4.20250830.0", "", {}, "sha512-uAGZFqEBFnCiwIokxMnrrtjIkT8qyGT1LACSScEUyW7nKmtD0Viykp9QZWrIlssyEp/MDB6XsdALF8y6upxpcg=="],
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
|
"name": "@opencode/cloud-resource",
|
||||||
|
"dependencies": {
|
||||||
|
"@cloudflare/workers-types": "^4.20250830.0"
|
||||||
|
},
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"production": {
|
||||||
|
"import": "./resource.cloudflare.ts"
|
||||||
|
},
|
||||||
|
"import": "./resource.node.ts"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { env } from "cloudflare:workers"
|
||||||
|
|
||||||
|
export const Resource = new Proxy(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
get(_target, prop: string) {
|
||||||
|
if (prop in env) {
|
||||||
|
// @ts-expect-error
|
||||||
|
const value = env[prop]
|
||||||
|
return typeof value === "string" ? JSON.parse(value) : value
|
||||||
|
}
|
||||||
|
throw new Error(`"${prop}" is not linked in your sst.config.ts (cloudflare)`)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
) as Record<string, any>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { Resource } from "sst"
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
/* This file is auto-generated by SST. Do not edit. */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
/* deno-fmt-ignore-file */
|
||||||
|
|
||||||
|
import "sst"
|
||||||
|
declare module "sst" {
|
||||||
|
export interface Resource {
|
||||||
|
"ANTHROPIC_API_KEY": {
|
||||||
|
"type": "sst.sst.Secret"
|
||||||
|
"value": string
|
||||||
|
}
|
||||||
|
"AUTH_API_URL": {
|
||||||
|
"type": "sst.sst.Linkable"
|
||||||
|
"value": string
|
||||||
|
}
|
||||||
|
"BASETEN_API_KEY": {
|
||||||
|
"type": "sst.sst.Secret"
|
||||||
|
"value": string
|
||||||
|
}
|
||||||
|
"Console": {
|
||||||
|
"type": "sst.cloudflare.SolidStart"
|
||||||
|
"url": string
|
||||||
|
}
|
||||||
|
"Database": {
|
||||||
|
"database": string
|
||||||
|
"host": string
|
||||||
|
"password": string
|
||||||
|
"port": number
|
||||||
|
"type": "sst.sst.Linkable"
|
||||||
|
"username": string
|
||||||
|
}
|
||||||
|
"GITHUB_APP_ID": {
|
||||||
|
"type": "sst.sst.Secret"
|
||||||
|
"value": string
|
||||||
|
}
|
||||||
|
"GITHUB_APP_PRIVATE_KEY": {
|
||||||
|
"type": "sst.sst.Secret"
|
||||||
|
"value": string
|
||||||
|
}
|
||||||
|
"GITHUB_CLIENT_ID_CONSOLE": {
|
||||||
|
"type": "sst.sst.Secret"
|
||||||
|
"value": string
|
||||||
|
}
|
||||||
|
"GITHUB_CLIENT_SECRET_CONSOLE": {
|
||||||
|
"type": "sst.sst.Secret"
|
||||||
|
"value": string
|
||||||
|
}
|
||||||
|
"GOOGLE_CLIENT_ID": {
|
||||||
|
"type": "sst.sst.Secret"
|
||||||
|
"value": string
|
||||||
|
}
|
||||||
|
"STRIPE_SECRET_KEY": {
|
||||||
|
"type": "sst.sst.Secret"
|
||||||
|
"value": string
|
||||||
|
}
|
||||||
|
"STRIPE_WEBHOOK_SECRET": {
|
||||||
|
"type": "sst.sst.Linkable"
|
||||||
|
"value": string
|
||||||
|
}
|
||||||
|
"Web": {
|
||||||
|
"type": "sst.cloudflare.Astro"
|
||||||
|
"url": string
|
||||||
|
}
|
||||||
|
"XAI_API_KEY": {
|
||||||
|
"type": "sst.sst.Secret"
|
||||||
|
"value": string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// cloudflare
|
||||||
|
import * as cloudflare from "@cloudflare/workers-types";
|
||||||
|
declare module "sst" {
|
||||||
|
export interface Resource {
|
||||||
|
"Api": cloudflare.Service
|
||||||
|
"AuthApi": cloudflare.Service
|
||||||
|
"AuthStorage": cloudflare.KVNamespace
|
||||||
|
"Bucket": cloudflare.R2Bucket
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
import "sst"
|
||||||
|
export {}
|
||||||
@@ -2,11 +2,11 @@
|
|||||||
"$schema": "https://json.schemastore.org/tsconfig",
|
"$schema": "https://json.schemastore.org/tsconfig",
|
||||||
"extends": "@tsconfig/node22/tsconfig.json",
|
"extends": "@tsconfig/node22/tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"jsx": "preserve",
|
"module": "ESNext",
|
||||||
"jsxImportSource": "solid-js",
|
|
||||||
"module": "esnext",
|
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
"types": [
|
||||||
"types": ["vite/client"]
|
"@cloudflare/workers-types",
|
||||||
|
"node"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
src/scrap.ts
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "@opencode/cloud-scripts",
|
||||||
|
"version": "0.6.5",
|
||||||
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"start": "tsx",
|
||||||
|
"shell": "sst shell"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@opencode/cloud-core": "workspace:*",
|
||||||
|
"tsx": "4.20.5"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "catalog:",
|
||||||
|
"typescript": "catalog:"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
// placeholder
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
/* This file is auto-generated by SST. Do not edit. */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
/* deno-fmt-ignore-file */
|
||||||
|
|
||||||
|
/// <reference path="../../sst-env.d.ts" />
|
||||||
|
|
||||||
|
import "sst"
|
||||||
|
export {}
|
||||||