repositories
loading repo index
repositories
loading repo index
repository
loading code, commits, and activity
public Clawd ADK gateway launch mirror
stars
latest
clone command
git clone gitlawb://did:key:z6Mkq5mY...iFZ5/my-project-publ...git clone gitlawb://did:key:z6Mkq5mY.../my-project-publ...2fa351d6docs: add automaton and perps launch sources16d ago| #1 | /** |
| #2 | * Conway Inference Client |
| #3 | * |
| #4 | * Wraps Conway's /v1/chat/completions endpoint (OpenAI-compatible). |
| #5 | * The automaton pays for its own thinking through Conway credits. |
| #6 | */ |
| #7 | |
| #8 | import type { |
| #9 | InferenceClient, |
| #10 | ChatMessage, |
| #11 | InferenceOptions, |
| #12 | InferenceResponse, |
| #13 | InferenceToolCall, |
| #14 | TokenUsage, |
| #15 | InferenceToolDefinition, |
| #16 | } from "../types.js"; |
| #17 | |
| #18 | interface InferenceClientOptions { |
| #19 | apiUrl: string; |
| #20 | apiKey: string; |
| #21 | defaultModel: string; |
| #22 | maxTokens: number; |
| #23 | lowComputeModel?: string; |
| #24 | } |
| #25 | |
| #26 | export function createInferenceClient( |
| #27 | options: InferenceClientOptions, |
| #28 | ): InferenceClient { |
| #29 | const { apiUrl, apiKey } = options; |
| #30 | let currentModel = options.defaultModel; |
| #31 | let maxTokens = options.maxTokens; |
| #32 | |
| #33 | const chat = async ( |
| #34 | messages: ChatMessage[], |
| #35 | opts?: InferenceOptions, |
| #36 | ): Promise<InferenceResponse> => { |
| #37 | const model = opts?.model || currentModel; |
| #38 | const tools = opts?.tools; |
| #39 | |
| #40 | // Newer models (o-series, gpt-5.x) require max_completion_tokens |
| #41 | // GPT-4.1 and similar models also use max_completion_tokens |
| #42 | const usesCompletionTokens = |
| #43 | /^(o[1-9]|gpt-5)/.test(model) || |
| #44 | model.includes("4.1") || |
| #45 | model.includes("4o-mini"); |
| #46 | const tokenLimit = opts?.maxTokens || maxTokens; |
| #47 | |
| #48 | const body: Record<string, unknown> = { |
| #49 | model, |
| #50 | messages: messages.map(formatMessage), |
| #51 | stream: false, |
| #52 | }; |
| #53 | |
| #54 | if (usesCompletionTokens) { |
| #55 | body.max_completion_tokens = tokenLimit; |
| #56 | } else { |
| #57 | body.max_tokens = tokenLimit; |
| #58 | } |
| #59 | |
| #60 | if (opts?.temperature !== undefined) { |
| #61 | body.temperature = opts.temperature; |
| #62 | } |
| #63 | |
| #64 | if (tools && tools.length > 0) { |
| #65 | body.tools = tools; |
| #66 | body.tool_choice = "auto"; |
| #67 | } |
| #68 | |
| #69 | const resp = await fetch(`${apiUrl}/v1/chat/completions`, { |
| #70 | method: "POST", |
| #71 | headers: { |
| #72 | "Content-Type": "application/json", |
| #73 | Authorization: apiKey, |
| #74 | }, |
| #75 | body: JSON.stringify(body), |
| #76 | }); |
| #77 | |
| #78 | if (!resp.ok) { |
| #79 | const text = await resp.text(); |
| #80 | throw new Error( |
| #81 | `Inference error: ${resp.status}: ${text}`, |
| #82 | ); |
| #83 | } |
| #84 | |
| #85 | const data = await resp.json() as any; |
| #86 | const choice = data.choices?.[0]; |
| #87 | |
| #88 | if (!choice) { |
| #89 | throw new Error("No completion choice returned from inference"); |
| #90 | } |
| #91 | |
| #92 | const message = choice.message; |
| #93 | const usage: TokenUsage = { |
| #94 | promptTokens: data.usage?.prompt_tokens || 0, |
| #95 | completionTokens: data.usage?.completion_tokens || 0, |
| #96 | totalTokens: data.usage?.total_tokens || 0, |
| #97 | }; |
| #98 | |
| #99 | const toolCalls: InferenceToolCall[] | undefined = |
| #100 | message.tool_calls?.map((tc: any) => ({ |
| #101 | id: tc.id, |
| #102 | type: "function" as const, |
| #103 | function: { |
| #104 | name: tc.function.name, |
| #105 | arguments: tc.function.arguments, |
| #106 | }, |
| #107 | })); |
| #108 | |
| #109 | return { |
| #110 | id: data.id || "", |
| #111 | model: data.model || model, |
| #112 | message: { |
| #113 | role: message.role, |
| #114 | content: message.content || "", |
| #115 | tool_calls: toolCalls, |
| #116 | }, |
| #117 | toolCalls, |
| #118 | usage, |
| #119 | finishReason: choice.finish_reason || "stop", |
| #120 | }; |
| #121 | }; |
| #122 | |
| #123 | const setLowComputeMode = (enabled: boolean): void => { |
| #124 | if (enabled) { |
| #125 | currentModel = "gpt-4o-mini"; |
| #126 | maxTokens = 4096; |
| #127 | } else { |
| #128 | currentModel = options.defaultModel; |
| #129 | maxTokens = options.maxTokens; |
| #130 | } |
| #131 | }; |
| #132 | |
| #133 | const getDefaultModel = (): string => { |
| #134 | return currentModel; |
| #135 | }; |
| #136 | |
| #137 | return { |
| #138 | chat, |
| #139 | setLowComputeMode, |
| #140 | getDefaultModel, |
| #141 | }; |
| #142 | } |
| #143 | |
| #144 | function formatMessage( |
| #145 | msg: ChatMessage, |
| #146 | ): Record<string, unknown> { |
| #147 | const formatted: Record<string, unknown> = { |
| #148 | role: msg.role, |
| #149 | content: msg.content, |
| #150 | }; |
| #151 | |
| #152 | if (msg.name) formatted.name = msg.name; |
| #153 | if (msg.tool_calls) formatted.tool_calls = msg.tool_calls; |
| #154 | if (msg.tool_call_id) formatted.tool_call_id = msg.tool_call_id; |
| #155 | |
| #156 | return formatted; |
| #157 | } |
| #158 |