summaryrefslogtreecommitdiffhomepage
path: root/packages/function/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/function/src')
-rw-r--r--packages/function/src/gateway.ts499
1 files changed, 0 insertions, 499 deletions
diff --git a/packages/function/src/gateway.ts b/packages/function/src/gateway.ts
deleted file mode 100644
index 17e9f5094..000000000
--- a/packages/function/src/gateway.ts
+++ /dev/null
@@ -1,499 +0,0 @@
-import { Hono, Context, Next } from "hono"
-import { Resource } from "sst"
-import { 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"
-
-type Env = {}
-
-const auth = async (c: Context, next: 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]
-
- // Replace with your validation logic
- if (apiKey !== Resource.OPENCODE_API_KEY.value) {
- return c.json(
- {
- error: {
- message: "Invalid API key.",
- type: "invalid_request_error",
- param: null,
- code: "unauthorized",
- },
- },
- 401,
- )
- }
-
- await next()
-}
-export default new Hono<{ Bindings: Env }>()
- .get("/", (c) => c.text("Hello, world!"))
- .post("/v1/chat/completions", auth, async (c) => {
- try {
- const body = await c.req.json<ChatCompletionCreateParamsBase>()
-
- console.log(body)
-
- const model = (() => {
- const [provider, ...parts] = body.model.split("/")
- const model = parts.join("/")
- if (provider === "anthropic" && model === "claude-sonnet-4") {
- return createAnthropic({
- apiKey: Resource.ANTHROPIC_API_KEY.value,
- })("claude-sonnet-4-20250514")
- }
- if (provider === "openai" && model === "gpt-4.1") {
- return createOpenAI({
- apiKey: Resource.OPENAI_API_KEY.value,
- })("gpt-4.1")
- }
- if (provider === "zhipuai" && model === "glm-4.5-flash") {
- return createOpenAICompatible({
- name: "Zhipu AI",
- baseURL: "https://api.z.ai/api/paas/v4",
- apiKey: Resource.ZHIPU_API_KEY.value,
- })("glm-4.5-flash")
- }
- throw new Error(`Unsupported provider: ${provider}`)
- })()
-
- const requestBody = transformOpenAIRequestToAiSDK()
-
- return body.stream ? await handleStream() : await handleGenerate()
-
- async function handleStream() {
- const result = await streamText({
- model,
- ...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.fullStream) {
- // TODO
- //console.log("!!! CHUCK !!!", chunk);
- switch (chunk.type) {
- case "text-delta": {
- const data = {
- id,
- object: "chat.completion.chunk",
- created,
- model: body.model,
- choices: [
- {
- index: 0,
- delta: {
- content: chunk.text,
- },
- 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.text,
- },
- 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: [
- {
- id: chunk.toolCallId,
- type: "function",
- function: {
- name: chunk.toolName,
- arguments: JSON.stringify(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,
- error: {
- message: 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 finishReason =
- {
- stop: "stop",
- length: "length",
- "content-filter": "content_filter",
- "tool-calls": "tool_calls",
- error: "stop",
- other: "stop",
- unknown: "stop",
- }[chunk.finishReason] || "stop"
-
- const data = {
- id,
- object: "chat.completion.chunk",
- created,
- model: body.model,
- choices: [
- {
- index: 0,
- delta: {},
- finish_reason: finishReason,
- },
- ],
- usage: {
- prompt_tokens: chunk.totalUsage.inputTokens,
- completion_tokens: chunk.totalUsage.outputTokens,
- total_tokens: chunk.totalUsage.totalTokens,
- completion_tokens_details: {
- reasoning_tokens: chunk.totalUsage.reasoningTokens,
- },
- prompt_tokens_details: {
- cached_tokens: chunk.totalUsage.cachedInputTokens,
- },
- },
- }
- 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 "start-step":
- case "finish-step":
- 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 generateText({
- model,
- ...requestBody,
- })
- 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()
-
- 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" }
- if (body.response_format.type === "json_schema")
- return {
- type: "json",
- 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" }
- throw new Error("Unsupported response format")
- })(),
- seed: body.seed ?? undefined,
- }
-
- function transformTools() {
- const { tools, tool_choice } = body
-
- if (!tools || tools.length === 0) {
- return { tools: undefined, toolChoice: undefined }
- }
-
- const aiSdkTools = tools.reduce(
- (acc, tool) => {
- acc[tool.function.name] = {
- type: "function" as const,
- name: tool.function.name,
- description: tool.function.description,
- inputSchema: tool.function.parameters,
- }
- return acc
- },
- {} as Record<string, any>,
- )
-
- let aiSdkToolChoice
- if (tool_choice == null) {
- aiSdkToolChoice = undefined
- } else if (tool_choice === "auto") {
- aiSdkToolChoice = "auto"
- } else if (tool_choice === "none") {
- aiSdkToolChoice = "none"
- } else if (tool_choice === "required") {
- aiSdkToolChoice = "required"
- } else if (tool_choice.type === "function") {
- aiSdkToolChoice = {
- type: "tool",
- 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
- }
- }
- } catch (error: any) {
- return c.json({ error: { message: error.message } }, 500)
- }
- })
- .all("*", (c) => c.text("Not Found"))