feat: init
This commit is contained in:
@@ -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
|
||||
@@ -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=
|
||||
@@ -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
@@ -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
|
||||
@@ -0,0 +1,3 @@
|
||||
[submodule "openai-openapi"]
|
||||
path = openai-openapi
|
||||
url = git@github.com:openai/openai-openapi.git
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npm test
|
||||
@@ -0,0 +1,2 @@
|
||||
dist/
|
||||
.next/
|
||||
@@ -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
@@ -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()
|
||||
@@ -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.
|
||||
Submodule
+1
Submodule openai-openapi added at 5c1857ea86
@@ -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"
|
||||
}
|
||||
}
|
||||
Generated
+5982
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user