feat: init

This commit is contained in:
Travis Fischer
2023-11-09 00:44:12 -06:00
commit 671610bc4e
16 changed files with 6489 additions and 0 deletions
+13
View File
@@ -0,0 +1,13 @@
root = true
[*]
indent_style = space
indent_size = 2
tab_width = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.js]
quote_type = single
+8
View File
@@ -0,0 +1,8 @@
# ------------------------------------------------------------------------------
# This is an example .env file.
#
# All of these environment vars must be defined either in your environment or in
# a local .env file in order to run the examples for this project.
# ------------------------------------------------------------------------------
OPENAI_API_KEY=
+9
View File
@@ -0,0 +1,9 @@
{
"root": true,
"extends": ["@dexaai/eslint-config", "@dexaai/eslint-config/node"],
"ignorePatterns": ["node_modules", "dist", ".next"],
"rules": {
"no-console": "off",
"import/order": "off"
}
}
+55
View File
@@ -0,0 +1,55 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
*.swp
.idea
# ignore editor workspace files
.vscode/
.idea/
*.sublime-workspace
*.sublime-project
# dependencies
node_modules/
.pnp/
.pnp.js
# testing
/coverage
# next.js
.next/
out/
# production
build/
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
# local env files
.env
.env.local
.env.build
.env.development.local
.env.test.local
.env.production.local
.turbo
+3
View File
@@ -0,0 +1,3 @@
[submodule "openai-openapi"]
path = openai-openapi
url = git@github.com:openai/openai-openapi.git
+4
View File
@@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npm test
+1
View File
@@ -0,0 +1 @@
enable-pre-post-scripts=true
+2
View File
@@ -0,0 +1,2 @@
dist/
.next/
+16
View File
@@ -0,0 +1,16 @@
module.exports = {
plugins: [require('@trivago/prettier-plugin-sort-imports')],
singleQuote: true,
jsxSingleQuote: true,
semi: false,
useTabs: false,
tabWidth: 2,
bracketSpacing: true,
bracketSameLine: false,
arrowParens: 'always',
trailingComma: 'none',
importOrder: ['^node:.*', '<THIRD_PARTY_MODULES>', '^(@/(.*)$)', '^[./]'],
importOrderSeparation: true,
importOrderSortSpecifiers: true,
importOrderGroupNamespaceSpecifiers: true
}
+272
View File
@@ -0,0 +1,272 @@
import fs from 'node:fs/promises'
import * as prettier from 'prettier'
import SwaggerParser from '@apidevtools/swagger-parser'
import type { OpenAPIV3 } from 'openapi-types'
import {
FetchingJSONSchemaStore,
InputData,
JSONSchemaInput,
quicktype
} from 'quicktype-core'
const jsonContentType = 'application/json'
// Notes:
// `json-schema-to-zod` doesn't seem to output subtypes
// `quicktype` doesn't seem to output zod .describes
// The subset of API paths that we care about, which determines the subset of types
// that we need to generate.
const openaiOpenAPIPaths: Record<string, boolean> = {
'/chat/completions': false,
'/completions': false,
'/edits': false,
'/images/generations': false,
'/images/edits': false,
'/images/variations': false,
'/embeddings': false,
'/audio/speech': false,
'/audio/transcriptions': false,
'/audio/translations': false,
'/files': true,
'/files/{file_id}': true,
'/files/{file_id}/content': true,
'/fine_tuning/jobs': false,
'/fine_tuning/jobs/{fine_tuning_job_id}': false,
'/fine_tuning/jobs/{fine_tuning_job_id}/events': false,
'/fine_tuning/jobs/{fine_tuning_job_id}/cancel': false,
'/fine-tunes': false,
'/fine-tunes/{fine_tune_id}': false,
'/fine-tunes/{fine_tune_id}/cancel': false,
'/fine-tunes/{fine_tune_id}/events': false,
'/models': false,
'/models/{model}': false,
'/moderations': false,
'/assistants': true,
'/assistants/{assistant_id}': true,
'/threads': true,
'/threads/{thread_id}': true,
'/threads/{thread_id}/messages': true,
'/threads/{thread_id}/messages/{message_id}': true,
'/threads/runs': true,
'/threads/{thread_id}/runs': true,
'/threads/{thread_id}/runs/{run_id}': true,
'/threads/{thread_id}/runs/{run_id}/submit_tool_outputs': true,
'/threads/{thread_id}/runs/{run_id}/cancel': true,
'/threads/{thread_id}/runs/{run_id}/steps': true,
'/threads/{thread_id}/runs/{run_id}/steps/{step_id}': true,
'/assistants/{assistant_id}/files': true,
'/assistants/{assistant_id}/files/{file_id}': true,
'/threads/{thread_id}/messages/{message_id}/files': true,
'/threads/{thread_id}/messages/{message_id}/files/{file_id}': true
}
async function main() {
const srcFile = './openai-openapi/openapi.yaml'
const destFile = './src/generated-types.ts'
const parser = new SwaggerParser()
const spec = (await parser.bundle(srcFile)) as OpenAPIV3.Document
if (spec.openapi !== '3.0.0') {
console.error(`Unexpected OpenAI OpenAPI version "${spec.openapi}"`)
console.error('The OpenAI API likely received a major update.')
process.exit(1)
}
const pathsToProcess: string[] = []
for (const path in spec.paths) {
const openaiOpenAPIPath = openaiOpenAPIPaths[path]
if (openaiOpenAPIPath === undefined) {
console.error(`Unexpected OpenAI OpenAPI path: ${path}`)
console.error('The OpenAI API likely received a major update.')
process.exit(1)
}
if (openaiOpenAPIPath) {
pathsToProcess.push(path)
}
}
for (const path of Object.keys(openaiOpenAPIPaths)) {
if (!spec.paths[path]) {
console.error(`Missing expected OpenAI OpenAPI path: ${path}`)
console.error('The OpenAI API likely received a major update.')
process.exit(1)
}
}
const componentsToProcess = new Set<string>()
for (const path of pathsToProcess) {
const pathItem = spec.paths[path]
if (!pathItem) {
throw new Error()
}
// console.log(JSON.stringify(pathItem, null, 2))
const httpMethods = Object.keys(pathItem)
for (const httpMethod of httpMethods) {
const operation = pathItem[httpMethod]
const paths = [
['responses', '200', 'content', jsonContentType, 'schema'],
['requestBody', 'content', jsonContentType, 'schema'],
['requestBody', 'content', 'multipart/form-data', 'schema']
]
for (const path of paths) {
const resolved = new Set<string>()
getAndResolve(operation, path, parser.$refs, resolved)
for (const ref of resolved) {
componentsToProcess.add(ref)
}
}
}
}
const componentNames = Array.from(componentsToProcess)
.map((ref) => ref.split('/').pop())
.sort()
console.log(componentNames)
console.log()
const proccessedComponents = new Set<string>()
const componentToRefs: Record<
string,
{ dereferenced: any; refs: Set<string> }
> = {}
for (const ref of componentsToProcess) {
const component = parser.$refs.get(ref)
if (!component) continue // TODO
const resolved = new Set<string>()
const dereferenced = dereference(component, parser.$refs, resolved)
if (!dereferenced) continue // TODO
componentToRefs[ref] = { dereferenced, refs: resolved }
}
const sortedComponents = Object.keys(componentToRefs).sort(
(a, b) => componentToRefs[b].refs.size - componentToRefs[a].refs.size
)
const schemaInput = new JSONSchemaInput(new FetchingJSONSchemaStore())
for (const ref of sortedComponents) {
const name = ref.split('/').pop()!
if (!name) throw new Error()
const { dereferenced, refs } = componentToRefs[ref]
if (proccessedComponents.has(ref)) {
continue
}
for (const r of refs) {
if (proccessedComponents.has(r)) {
continue
}
proccessedComponents.add(r)
}
proccessedComponents.add(ref)
await schemaInput.addSource({
name,
schema: JSON.stringify(dereferenced, null, 2)
})
}
const inputData = new InputData()
inputData.addInput(schemaInput)
const res = await quicktype({
inputData,
lang: 'TypeScript Zod'
})
const output = res.lines
.join('\n')
.replaceAll(/OpenAi/g, 'OpenAI')
.trim()
const prettyOutput = prettier.format(output, {
parser: 'typescript',
semi: false,
singleQuote: true,
jsxSingleQuote: true,
bracketSpacing: true,
bracketSameLine: false,
arrowParens: 'always',
trailingComma: 'none'
})
await fs.writeFile(destFile, prettyOutput)
}
function getAndResolve<T extends any = any>(
obj: any,
keys: string[],
refs: SwaggerParser.$Refs,
resolved?: Set<string>
): T | null {
if (obj === undefined) return null
if (typeof obj !== 'object') return null
if (obj.$ref) {
const derefed = refs.get(obj.$ref)
resolved?.add(obj.$ref)
if (!derefed) {
return null
}
obj = derefed
}
if (!keys.length) {
return dereference(obj, refs, resolved) as T
}
const key = keys[0]
const value = obj[key]
keys = keys.slice(1)
if (value === undefined) {
return null
}
return getAndResolve(value, keys, refs, resolved)
}
function dereference<T extends any = any>(
obj: T,
refs: SwaggerParser.$Refs,
resolved?: Set<string>
): T {
if (!obj) return obj
if (Array.isArray(obj)) {
return obj.map((item) => dereference(item, refs, resolved)) as T
} else if (typeof obj === 'object') {
if ('$ref' in obj) {
const derefed = refs.get(obj.$ref as string)
if (!derefed) {
return obj
}
resolved?.add(obj.$ref as string)
return dereference(derefed, refs, resolved)
} else {
return Object.fromEntries(
Object.entries(obj).map(([key, value]) => [
key,
dereference(value, refs, resolved)
])
) as T
}
} else {
return obj
}
}
main()
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Travis Fischer
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+1
Submodule openai-openapi added at 5c1857ea86
+67
View File
@@ -0,0 +1,67 @@
{
"name": "openopenai",
"private": true,
"description": "Self-hostable version of OpenAI's new stateful Assistant APIs.",
"author": "Travis Fischer <travis@transitivebullsh.it>",
"repository": {
"type": "git",
"url": "transitive-bullshit/OpenOpenAI"
},
"license": "MIT",
"type": "module",
"packageManager": "pnpm@8.10.2",
"engines": {
"node": ">=18"
},
"scripts": {
"build": "tsc --project tsconfig.dist.json",
"prebuild": "run-s clean",
"clean": "del dist",
"dev": "tsc --watch",
"prepare": "husky install",
"release": "np",
"test": "run-p test:*",
"test:lint": "eslint .",
"test:format": "prettier --check \"**/*.{js,ts,tsx}\"",
"test:typecheck": "tsc --noEmit"
},
"devDependencies": {
"@apidevtools/swagger-parser": "^10.1.0",
"@dexaai/eslint-config": "^0.4.0",
"@trivago/prettier-plugin-sort-imports": "^4.2.1",
"@types/node": "^20.9.0",
"del-cli": "^5.1.0",
"eslint": "^8.53.0",
"husky": "^8.0.3",
"lint-staged": "^15.0.2",
"np": "^8.0.4",
"npm-run-all": "^4.1.5",
"openapi-types": "^12.1.3",
"prettier": "^2.8.8",
"quicktype-core": "^23.0.77",
"tsx": "^3.14.0",
"typescript": "^5.2.2"
},
"lint-staged": {
"*.{ts,tsx}": [
"eslint --fix",
"prettier --ignore-unknown --write"
]
},
"keywords": [
"openai",
"ai",
"assistant",
"gpts",
"agents",
"agi",
"llms",
"chatgpt"
],
"dependencies": {
"@hono/node-server": "^1.2.0",
"@hono/zod-openapi": "^0.8.3",
"hono": "^3.9.2",
"zod": "^3.22.4"
}
}
+5982
View File
File diff suppressed because it is too large Load Diff
+7
View File
@@ -0,0 +1,7 @@
import { serve } from '@hono/node-server'
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('Hello Hono!'))
serve(app)
+28
View File
@@ -0,0 +1,28 @@
{
"include": ["src", "bin"],
"compilerOptions": {
"target": "es2020",
"lib": ["ESNext", "DOM"],
"module": "esnext",
"moduleResolution": "node",
"jsx": "preserve",
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noImplicitAny": false,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"forceConsistentCasingInFileNames": true,
"importHelpers": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"allowSyntheticDefaultImports": true,
"isolatedModules": true,
"declaration": true,
"sourceMap": true,
"noEmit": false,
"outDir": "dist"
}
}