-
Notifications
You must be signed in to change notification settings - Fork 11
feat: update supercode-cli version to 0.1.73 and enhance AI provider functionality: #213
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -812,6 +812,152 @@ app.post("/api/ai/chat", async (req, res) => { | |
| res.end() | ||
| break | ||
| } | ||
| case "supercode": { | ||
| const apiKey = process.env.CONCENTRATEAI_API_KEY | ||
| if (!apiKey) { res.status(500).json({ error: "Supercode Cloud not configured on server" }); return } | ||
| const modelName = modelParam || "deepseek-v4-flash" | ||
| const scStart = Date.now() | ||
| const bodyObj: any = { | ||
| model: modelName, | ||
| messages: nonSystemMessages.map((m: any) => { | ||
| const msg: any = { | ||
| role: m.role, | ||
| content: m.content !== null && m.content !== undefined ? String(m.content) : "", | ||
| } | ||
| if (m.tool_calls) msg.tool_calls = m.tool_calls | ||
| if (m.tool_call_id) msg.tool_call_id = m.tool_call_id | ||
| return msg | ||
| }), | ||
| max_tokens: getModelMaxTokens(modelName), | ||
| temperature: 0.7, | ||
| stream: true, | ||
| } | ||
| if (system && nonSystemMessages.length > 0) { | ||
| bodyObj.messages = [{ role: "system", content: system }, ...bodyObj.messages] | ||
| } | ||
| if (tools) { | ||
| bodyObj.tools = Object.entries(tools).map(([name, fn]: [string, any]) => ({ | ||
| type: "function", | ||
| function: { name, description: fn.description || "", parameters: toolParams(fn) }, | ||
| })) | ||
| } | ||
| const response = await fetch("https://api.concentrate.ai/v1/chat/completions", { | ||
| method: "POST", | ||
| headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" }, | ||
| body: JSON.stringify(bodyObj), | ||
| }) | ||
| if (!response.ok) { | ||
| const errText = await response.text().catch(() => "unknown error") | ||
| res.status(response.status).json({ error: `Supercode Cloud API ${response.status}: ${errText}` }) | ||
| return | ||
| } | ||
| const reader = response.body?.getReader() | ||
| if (!reader) { res.status(500).json({ error: "No response body" }); return } | ||
| const decoder = new TextDecoder() | ||
| let buffer = "" | ||
| let inputTokens = 0 | ||
| let outputTokens = 0 | ||
| let fullContent = "" | ||
| let sawToolCalls = false | ||
| let pendingToolCalls: Record<number, { id: string; name: string; args: string }> = {} | ||
| while (true) { | ||
| const { done, value } = await reader.read() | ||
| if (done) break | ||
| buffer += decoder.decode(value, { stream: true }) | ||
| const lines = buffer.split("\n") | ||
| buffer = lines.pop() || "" | ||
| for (const line of lines) { | ||
| const trimmed = line.trim() | ||
| if (!trimmed || !trimmed.startsWith("data: ")) continue | ||
| const jsonStr = trimmed.slice(6) | ||
| if (jsonStr === "[DONE]") break | ||
| try { | ||
| const data = JSON.parse(jsonStr) | ||
| const delta = data.choices?.[0]?.delta | ||
| if (delta?.content) { | ||
| fullContent += delta.content | ||
| res.write(JSON.stringify({ type: "text", content: delta.content }) + "\n") | ||
| } | ||
| if (delta?.tool_calls) { | ||
| sawToolCalls = true | ||
| for (const tc of delta.tool_calls) { | ||
| const index = tc.index ?? 0 | ||
| if (!pendingToolCalls[index]) { | ||
| pendingToolCalls[index] = { id: "", name: "", args: "" } | ||
| } | ||
| if (tc.id) pendingToolCalls[index].id = tc.id | ||
| if (tc.function?.name) pendingToolCalls[index].name = tc.function.name | ||
| if (tc.function?.arguments) pendingToolCalls[index].args += tc.function.arguments | ||
| } | ||
| } | ||
| const finishReason = data.choices?.[0]?.finish_reason | ||
| if (finishReason === "tool_calls") { | ||
| sawToolCalls = true | ||
| for (const [, call] of Object.entries(pendingToolCalls)) { | ||
| if (call.name && call.args) { | ||
| try { | ||
| const parsed = JSON.parse(call.args) | ||
| res.write(JSON.stringify({ type: "tool-call", toolName: call.name, args: parsed, toolCallId: call.id }) + "\n") | ||
| } catch { /* skip malformed args */ } | ||
| } | ||
| } | ||
| pendingToolCalls = {} | ||
| } | ||
| if (data.usage) { | ||
| inputTokens = data.usage.prompt_tokens ?? 0 | ||
| outputTokens = data.usage.completion_tokens ?? 0 | ||
| } | ||
| } catch { /* skip malformed */ } | ||
| } | ||
| } | ||
| if (!fullContent.trim() && !sawToolCalls) { | ||
| const fbBody: any = { | ||
| model: modelName, | ||
| messages: nonSystemMessages.map((m: any) => { | ||
| const msg: any = { | ||
| role: m.role, | ||
| content: m.content !== null && m.content !== undefined ? String(m.content) : "", | ||
| } | ||
| if (m.tool_calls) msg.tool_calls = m.tool_calls | ||
| if (m.tool_call_id) msg.tool_call_id = m.tool_call_id | ||
| return msg | ||
| }), | ||
| max_tokens: getModelMaxTokens(modelName), | ||
| temperature: 0.7, | ||
| stream: false, | ||
| } | ||
| if (system && nonSystemMessages.length > 0) { | ||
| fbBody.messages = [{ role: "system", content: system }, ...fbBody.messages] | ||
| } | ||
| const fbRes = await fetch("https://api.concentrate.ai/v1/chat/completions", { | ||
| method: "POST", | ||
| headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" }, | ||
| body: JSON.stringify(fbBody), | ||
| }) | ||
| if (fbRes.ok) { | ||
| const fbData: any = await fbRes.json() | ||
| const fbContent = fbData?.choices?.[0]?.message?.content ?? "" | ||
| if (fbContent) { | ||
| res.write(JSON.stringify({ type: "text", content: fbContent }) + "\n") | ||
| } | ||
| inputTokens = fbData?.usage?.prompt_tokens ?? 0 | ||
| outputTokens = fbData?.usage?.completion_tokens ?? 0 | ||
| } | ||
|
Comment on lines
+913
to
+945
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Preserve tools and propagate fallback failures. The fallback drops Proposed fix- const fbBody: any = {
- model: modelName,
- messages: nonSystemMessages.map(/* ... */),
- max_tokens: getModelMaxTokens(modelName),
- temperature: 0.7,
- stream: false,
- }
+ const fbBody = {
+ ...bodyObj,
+ stream: false,
+ }Also return the fallback upstream error when 🤖 Prompt for AI Agents |
||
| } | ||
| recordUsage({ | ||
| provider: "supercode", model: modelName, | ||
| inputTokens, outputTokens, cachedInputTokens: 0, | ||
| totalTokens: inputTokens + outputTokens, | ||
| costUsd: computeCost(modelName, inputTokens, outputTokens, 0), | ||
| durationMs: Date.now() - scStart, | ||
| }) | ||
| res.write(JSON.stringify({ | ||
| type: "finish", reason: "stop", | ||
| usage: { inputTokens, outputTokenDetails: { textTokens: outputTokens, reasoningTokens: 0 }, outputTokens, inputTokenDetails: { noCacheTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 }, totalTokens: inputTokens + outputTokens } | ||
| }) + "\n") | ||
| res.end() | ||
| break | ||
| } | ||
| default: { | ||
| res.status(400).json({ error: `Unknown provider: ${provider}` }) | ||
| } | ||
|
|
@@ -935,6 +1081,30 @@ app.post("/api/ai/generate-object", async (req, res) => { | |
| res.json({ object: { content: data.choices?.[0]?.message?.content || "" } }) | ||
| break | ||
| } | ||
| case "supercode": { | ||
| const apiKey = process.env.CONCENTRATEAI_API_KEY | ||
| if (!apiKey) { res.status(500).json({ error: "Supercode Cloud not configured on server" }); return } | ||
| const modelName = modelParam || "deepseek-v4-flash" | ||
| const response = await fetch("https://api.concentrate.ai/v1/chat/completions", { | ||
| method: "POST", | ||
| headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" }, | ||
| body: JSON.stringify({ | ||
| model: modelName, | ||
| messages: [{ role: "user", content: prompt }], | ||
| max_tokens: getModelMaxTokens(modelName), | ||
| temperature: 0.7, | ||
| stream: false, | ||
| }), | ||
| }) | ||
| if (!response.ok) { | ||
| const errText = await response.text().catch(() => "unknown error") | ||
| res.status(response.status).json({ error: `Supercode Cloud API ${response.status}: ${errText}` }) | ||
| return | ||
| } | ||
| const data: any = await response.json() | ||
| res.json({ object: { content: data.choices?.[0]?.message?.content || "" } }) | ||
|
Comment on lines
+1091
to
+1105
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
FILE="apps/supercode-cli/server/src/index.ts"
echo "== line count =="
wc -l "$FILE"
echo
echo "== outline (if available) =="
ast-grep outline "$FILE" --view expanded || true
echo
echo "== surrounding lines 1040-1135 =="
sed -n '1040,1135p' "$FILE"
echo
echo "== search for schema usage in file =="
rg -n '\bschema\b|response_format|structured|json_schema|json_object' "$FILE"Repository: yashdev9274/supercli Length of output: 5247 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Find the route/function that contains the snippet and inspect nearby context
FILE="apps/supercode-cli/server/src/index.ts"
python3 - <<'PY'
from pathlib import Path
p = Path("apps/supercode-cli/server/src/index.ts")
text = p.read_text()
needle = 'res.json({ object: { content: data.choices?.[0]?.message?.content || "" } })'
idx = text.find(needle)
print("needle_index", idx)
if idx != -1:
start = max(0, text.rfind('\n', 0, idx - 1500))
end = min(len(text), idx + 700)
snippet = text[start:end]
# show with line numbers
base_line = text[:start].count('\n') + 1
for i, line in enumerate(snippet.splitlines(), start=base_line):
print(f"{i}: {line}")
PYRepository: yashdev9274/supercli Length of output: 2656 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Inspect whether the API client or downstream parser expects a structured object
rg -n 'object\.content|structured generation|schema-based|response_format|message\.content|choices\?\.\[0\]\?\.message' apps/supercode-cli/server/srcRepository: yashdev9274/supercli Length of output: 1340 🏁 Script executed: #!/bin/bash
set -euo pipefail
FILE="apps/supercode-cli/server/src/index.ts"
sed -n '1000,1135p' "$FILE"Repository: yashdev9274/supercli Length of output: 6066 Use the schema-driven path for the Supercode provider 🤖 Prompt for AI Agents |
||
| break | ||
| } | ||
| default: { | ||
| res.status(400).json({ error: `Provider ${provider} not supported for structured generation` }) | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,152 @@ | ||
| import { z } from "zod" | ||
| import chalk from "chalk" | ||
| import boxen from "boxen" | ||
| import * as readline from "readline" | ||
| import { theme } from "../../cli/utils/tui" | ||
| import { serialize, ok, fail } from "../../cli/ai/tool-result" | ||
|
|
||
| const questionOptionSchema = z.object({ | ||
| label: z.string().describe("Display text (1-5 words, concise)"), | ||
| description: z.string().optional().describe("Explanation of the choice"), | ||
| }) | ||
|
|
||
| const questionItemSchema = z.object({ | ||
| question: z.string().describe("Complete question to ask the user"), | ||
| header: z.string().optional().describe("Very short label (max 30 chars) for the question category"), | ||
| options: z | ||
| .array(questionOptionSchema) | ||
| .optional() | ||
| .describe("Available choices for the user to select from"), | ||
| multiple: z | ||
| .boolean() | ||
| .optional() | ||
| .default(false) | ||
| .describe("Allow selecting multiple choices. When enabled, user can pick several options."), | ||
| }) | ||
|
|
||
| const questionSchema = z.object({ | ||
| questions: z | ||
| .array(questionItemSchema) | ||
| .min(1) | ||
| .max(5) | ||
| .describe("Questions to ask the user"), | ||
| }) | ||
|
|
||
| export type QuestionArgs = z.infer<typeof questionSchema> | ||
|
|
||
| function styledLabel(num: number, label: string): string { | ||
| const numTag = chalk.hex(theme.green)(` ${num}.`) | ||
| return `${numTag} ${chalk.bold(label)}` | ||
| } | ||
|
|
||
| function styledDesc(desc: string): string { | ||
| return chalk.hex(theme.muted)(` ${desc}`) | ||
| } | ||
|
|
||
| function askOne(item: z.infer<typeof questionItemSchema>): Promise<string | string[]> { | ||
| return new Promise((resolve) => { | ||
| const stdin = process.stdin | ||
| const wasRaw = stdin.isRaw | ||
|
|
||
| if (stdin.isTTY) { | ||
| stdin.setRawMode(false) | ||
| } | ||
|
|
||
| console.log() | ||
|
|
||
| const header = item.header | ||
| ? chalk.hex(theme.green)(` ${item.header} `) | ||
| : "" | ||
|
|
||
| const boxContent = item.header | ||
| ? chalk.bold(item.question) | ||
| : chalk.bold(item.question) | ||
|
|
||
| console.log( | ||
| boxen(boxContent, { | ||
| title: item.header || undefined, | ||
| titleAlignment: "left", | ||
| borderColor: theme.green, | ||
| padding: { top: 0, bottom: 0, left: 1, right: 1 }, | ||
| margin: 0, | ||
| borderStyle: "round", | ||
| }), | ||
| ) | ||
|
|
||
| const rl = readline.createInterface({ input: stdin, output: process.stdout, terminal: true }) | ||
|
|
||
| if (!item.options || item.options.length === 0) { | ||
| rl.question(chalk.hex(theme.green)(" Your answer: "), (answer) => { | ||
| rl.close() | ||
| if (stdin.isTTY && wasRaw) stdin.setRawMode(true) | ||
| resolve(answer.trim()) | ||
| }) | ||
| return | ||
| } | ||
|
|
||
| console.log() | ||
| item.options.forEach((opt, i) => { | ||
| console.log(styledLabel(i + 1, opt.label)) | ||
| if (opt.description) { | ||
| console.log(styledDesc(opt.description)) | ||
| } | ||
| }) | ||
| console.log() | ||
|
|
||
| const promptText = item.multiple | ||
| ? chalk.hex(theme.green)(" Select options (comma-separated numbers, e.g. 1,3): ") | ||
| : chalk.hex(theme.green)(" Your choice (number): ") | ||
|
|
||
| rl.question(promptText, (raw) => { | ||
| rl.close() | ||
| if (stdin.isTTY && wasRaw) stdin.setRawMode(true) | ||
|
|
||
| const trimmed = raw.trim() | ||
| if (!trimmed) { | ||
| resolve([]) | ||
| return | ||
| } | ||
|
|
||
| if (item.multiple) { | ||
| const indices = trimmed | ||
| .split(",") | ||
| .map((s) => parseInt(s.trim(), 10)) | ||
| .filter((n) => !isNaN(n) && n >= 1 && n <= item.options!.length) | ||
| if (indices.length === 0) { | ||
| resolve([]) | ||
| return | ||
| } | ||
| resolve(indices.map((i) => item.options![i - 1].label)) | ||
| } else { | ||
| const num = parseInt(trimmed, 10) | ||
| if (isNaN(num) || num < 1 || num > item.options.length) { | ||
| resolve("(invalid selection)") | ||
| return | ||
| } | ||
| resolve(item.options[num - 1].label) | ||
|
Comment on lines
+100
to
+126
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Re-prompt until a valid choice is entered.
🤖 Prompt for AI Agents |
||
| } | ||
| }) | ||
| }) | ||
| } | ||
|
|
||
| export const questionTool = { | ||
| description: | ||
| "Ask the user questions to gather context, clarify requirements, " + | ||
| "or get decisions on implementation choices. Renders numbered MCQ-style " + | ||
| "options for structured feedback. Supports single-choice, multi-select, " + | ||
| "and free-form answers. Use before complex tasks to ensure you understand " + | ||
| "what the user needs. When asking for preferences or decisions, provide " + | ||
| "options with clear labels and optional descriptions.", | ||
| parameters: questionSchema, | ||
| execute: async (args: QuestionArgs) => | ||
| serialize(async () => { | ||
| const answers: Array<{ question: string; answer: string | string[] }> = [] | ||
|
|
||
| for (const item of args.questions) { | ||
| const answer = await askOne(item) | ||
| answers.push({ question: item.question, answer }) | ||
| } | ||
|
|
||
| return ok({ answers }) | ||
| }), | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: yashdev9274/supercli
Length of output: 13524
🏁 Script executed:
Repository: yashdev9274/supercli
Length of output: 226
🏁 Script executed:
Repository: yashdev9274/supercli
Length of output: 34747
🌐 Web query:
OpenAI Chat Completions streaming include_usage prompt_tokens_details.cached_tokens usage event docs💡 Result:
To include token usage statistics in your OpenAI Chat Completions streaming response, you must set the stream_options parameter to {"include_usage": true} in your request [1][2][3]. When this option is enabled: 1. Every streaming chunk will contain a usage field, which is set to null for all chunks except the final one [4][1][3]. 2. An additional, final chunk is streamed before the data: [DONE] message [4][2][3]. This final chunk contains the total token usage statistics for the entire request in its usage field [4][1][2]. 3. The choices array in this final usage-only chunk will be empty [4][1][2]. Regarding prompt_tokens_details and cached_tokens: When usage statistics are included, the usage object may contain a prompt_tokens_details field, which provides a breakdown of tokens used in the prompt [5]. This includes the cached_tokens field, which indicates how many input tokens were read from cache [5]. If prompt caching is not applicable or the request is small (typically under 1024 tokens), cached_tokens is zero [5][6]. The structure and presence of these fields in the final streaming usage chunk follow the same schema as non-streaming response usage objects [5]. Note: If the stream is interrupted, you may not receive this final usage chunk [4][2]. Additionally, ensure your code handles the final usage chunk by checking for the presence of usage data and an empty choices array, as this differs from standard content chunks [1][3].
Citations:
Request streamed usage metadata.
Add
stream_options: { include_usage: true }to the streamed request, and carry cached prompt-token counts throughrecordUsage,computeCost, and the emittedinputTokenDetails; otherwise streamed runs persist zero/incorrect token and cost totals.🤖 Prompt for AI Agents