Skip to content

Commit 035c7d3

Browse files
committed
Merge branch 'dev'
2 parents 733405c + 149c832 commit 035c7d3

8 files changed

Lines changed: 95 additions & 33 deletions

File tree

packages/cyberstrike/src/config/markdown.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export namespace ConfigMarkdown {
2121
if (!match) return content
2222

2323
const frontmatter = match[1]
24-
const lines = frontmatter.split("\n")
24+
const lines = frontmatter.split(/\r?\n/)
2525
const result: string[] = []
2626

2727
for (const line of lines) {

packages/cyberstrike/src/file/index.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -167,13 +167,6 @@ export namespace File {
167167
"efi",
168168
"rom",
169169
"com",
170-
"bat",
171-
"cmd",
172-
"ps1",
173-
"sh",
174-
"bash",
175-
"zsh",
176-
"fish",
177170
])
178171

179172
const imageExtensions = new Set([

packages/cyberstrike/src/mcp/index.ts

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -161,17 +161,33 @@ export namespace MCP {
161161
description: mcpTool.description ?? "",
162162
inputSchema: jsonSchema(schema),
163163
execute: async (args: unknown) => {
164-
return client.callTool(
165-
{
166-
name: mcpTool.name,
167-
arguments: (args || {}) as Record<string, unknown>,
168-
},
169-
CallToolResultSchema,
170-
{
171-
resetTimeoutOnProgress: true,
172-
timeout,
173-
},
174-
)
164+
try {
165+
return await client.callTool(
166+
{
167+
name: mcpTool.name,
168+
arguments: (args || {}) as Record<string, unknown>,
169+
},
170+
CallToolResultSchema,
171+
{
172+
resetTimeoutOnProgress: true,
173+
timeout,
174+
},
175+
)
176+
} catch (error) {
177+
log.error("MCP tool execution failed", {
178+
tool: mcpTool.name,
179+
error: error instanceof Error ? error.message : String(error),
180+
})
181+
return {
182+
content: [
183+
{
184+
type: "text" as const,
185+
text: `Error calling MCP tool "${mcpTool.name}": ${error instanceof Error ? error.message : String(error)}`,
186+
},
187+
],
188+
isError: true,
189+
}
190+
}
175191
},
176192
})
177193
}
@@ -300,7 +316,7 @@ export namespace MCP {
300316

301317
async function fetchResourcesForClient(clientName: string, client: Client) {
302318
const resources = await client.listResources().catch((e) => {
303-
log.error("failed to get prompts", { clientName, error: e.message })
319+
log.error("failed to get resources", { clientName, error: e.message })
304320
return undefined
305321
})
306322

@@ -368,6 +384,17 @@ export namespace MCP {
368384
let status: Status | undefined = undefined
369385

370386
if (mcp.type === "remote") {
387+
// Validate URL before attempting to connect
388+
try {
389+
new URL(mcp.url)
390+
} catch {
391+
log.error("invalid MCP URL", { key, url: mcp.url })
392+
return {
393+
mcpClient: undefined,
394+
status: { status: "failed" as const, error: `Invalid URL: ${mcp.url}` },
395+
}
396+
}
397+
371398
// OAuth is enabled by default for remote servers unless explicitly disabled with oauth: false
372399
const oauthDisabled = mcp.oauth === false
373400
const oauthConfig = typeof mcp.oauth === "object" ? mcp.oauth : undefined
@@ -1066,7 +1093,7 @@ export namespace MCP {
10661093
const client = clientsSnapshot[clientName]
10671094

10681095
if (!client) {
1069-
log.warn("client not found for prompt", {
1096+
log.warn("client not found for resource", {
10701097
clientName: clientName,
10711098
})
10721099
return undefined
@@ -1077,7 +1104,7 @@ export namespace MCP {
10771104
uri: resourceUri,
10781105
})
10791106
.catch((e) => {
1080-
log.error("failed to get prompt from MCP server", {
1107+
log.error("failed to read resource from MCP server", {
10811108
clientName: clientName,
10821109
resourceUri: resourceUri,
10831110
error: e.message,

packages/cyberstrike/src/provider/error.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ export namespace ProviderError {
1919
/context window exceeds limit/i, // MiniMax
2020
/exceeded model token limit/i, // Kimi For Coding, Moonshot
2121
/tokens in request more than max tokens allowed/i, // xAI / zAI
22+
/input.?length exceeds/i, // Mistral
23+
/request payload size exceeds/i, // Cerebras
24+
/total number of tokens.*exceeded/i, // Cohere
25+
/input tokens exceed/i, // Venice AI
26+
/exceeds the model's maximum/i, // Together AI
2227
/context[_ ]length[_ ]exceeded/i, // Generic fallback
2328
]
2429

@@ -74,8 +79,12 @@ export namespace ProviderError {
7479

7580
try {
7681
const body = JSON.parse(e.responseBody)
77-
// try to extract common error message fields
78-
const errMsg = body.message || body.error || body.error?.message
82+
// try to extract common error message fields across providers
83+
const errMsg =
84+
body.message ||
85+
(typeof body.error === "string" ? body.error : body.error?.message) ||
86+
body.detail ||
87+
body.errors?.[0]?.message
7988
if (errMsg && typeof errMsg === "string") {
8089
return `${msg}: ${errMsg}`
8190
}

packages/cyberstrike/src/provider/transform.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ export namespace ProviderTransform {
9090
if (
9191
model.providerID === "mistral" ||
9292
model.api.id.toLowerCase().includes("mistral") ||
93-
model.api.id.toLocaleLowerCase().includes("devstral")
93+
model.api.id.toLowerCase().includes("devstral")
9494
) {
9595
const result: ModelMessage[] = []
9696
for (let i = 0; i < msgs.length; i++) {
@@ -296,7 +296,7 @@ export namespace ProviderTransform {
296296
if (id.includes("gemini")) return 1.0
297297
if (id.includes("glm-4.6")) return 1.0
298298
if (id.includes("glm-4.7")) return 1.0
299-
if (id.includes("minimax-m2")) return 1.0
299+
if (id.includes("minimax-m2") || id.includes("minimax-m3")) return 1.0
300300
if (id.includes("kimi-k2")) {
301301
// kimi-k2-thinking & kimi-k2.5 && kimi-k2p5
302302
if (id.includes("thinking") || id.includes("k2.") || id.includes("k2p")) {
@@ -310,16 +310,16 @@ export namespace ProviderTransform {
310310
export function topP(model: Provider.Model) {
311311
const id = model.id.toLowerCase()
312312
if (id.includes("qwen")) return 1
313-
if (id.includes("minimax-m2") || id.includes("kimi-k2.5") || id.includes("kimi-k2p5") || id.includes("gemini")) {
313+
if (id.includes("minimax-m2") || id.includes("minimax-m3") || id.includes("kimi-k2.5") || id.includes("kimi-k2p5") || id.includes("gemini")) {
314314
return 0.95
315315
}
316316
return undefined
317317
}
318318

319319
export function topK(model: Provider.Model) {
320320
const id = model.id.toLowerCase()
321-
if (id.includes("minimax-m2")) {
322-
if (id.includes("m2.1")) return 40
321+
if (id.includes("minimax-m2") || id.includes("minimax-m3")) {
322+
if (id.includes("m2.1") || id.includes("m3")) return 40
323323
return 20
324324
}
325325
if (id.includes("gemini")) return 64
@@ -1011,8 +1011,8 @@ export namespace ProviderTransform {
10111011
}
10121012

10131013
export function schema(model: Provider.Model, schema: JSONSchema.BaseSchema | JSONSchema7): JSONSchema7 {
1014-
// Sanitize MCP tool schemas for OpenAI/Azure compatibility
1015-
if (model.api.npm === "@ai-sdk/openai" || model.api.npm === "@ai-sdk/azure") {
1014+
// Sanitize MCP tool schemas for OpenAI/Azure/Copilot compatibility
1015+
if (model.api.npm === "@ai-sdk/openai" || model.api.npm === "@ai-sdk/azure" || model.api.npm === "@ai-sdk/github-copilot") {
10161016
schema = sanitizeOpenAISchema(schema) as JSONSchema7
10171017
}
10181018

@@ -1059,12 +1059,25 @@ export namespace ProviderTransform {
10591059
}
10601060
}
10611061

1062+
// Gemini does not support type arrays like ["string", "null"].
1063+
// Flatten to the first non-null type, or fall back to "string".
1064+
if (Array.isArray(result.type)) {
1065+
const nonNull = (result.type as string[]).filter((t) => t !== "null")
1066+
result.type = nonNull[0] ?? "string"
1067+
result.nullable = true
1068+
}
1069+
10621070
// Remove properties/required from non-object types (Gemini rejects these)
10631071
if (result.type && result.type !== "object") {
10641072
delete result.properties
10651073
delete result.required
1074+
delete result.additionalProperties
10661075
}
10671076

1077+
// Gemini does not support $defs / definitions
1078+
delete result.$defs
1079+
delete result.definitions
1080+
10681081
return result
10691082
}
10701083

packages/cyberstrike/src/session/compaction.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,8 @@ Rules:
233233
model,
234234
})
235235

236-
if (result === "continue" && input.auto) {
236+
const config = await Config.get()
237+
if (result === "continue" && input.auto && config.compaction?.auto !== false) {
237238
const continueMsg = await Session.updateMessage({
238239
id: Identifier.ascending("message"),
239240
role: "user",

packages/cyberstrike/src/session/processor.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,17 @@ export namespace SessionProcessor {
254254
input.assistantMessage.finish = value.finishReason
255255
input.assistantMessage.cost += usage.cost
256256
input.assistantMessage.tokens = usage.tokens
257+
// Surface content-filter blocks so the user understands why
258+
// the response was empty or truncated
259+
if (value.finishReason === "content-filter") {
260+
await Session.updatePart({
261+
id: Identifier.ascending("part"),
262+
messageID: input.assistantMessage.id,
263+
sessionID: input.assistantMessage.sessionID,
264+
type: "text",
265+
text: "The model's response was blocked by a content filter. Try rephrasing your request.",
266+
})
267+
}
257268
await Session.updatePart({
258269
id: Identifier.ascending("part"),
259270
reason: value.finishReason,

packages/cyberstrike/src/tool/grep.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,15 @@ export const GrepTool = Tool.define("grep", {
3737
await assertExternalDirectory(ctx, searchPath, { kind: "directory" })
3838

3939
const rgPath = await Ripgrep.filepath()
40-
const args = ["-nH", "--hidden", "--no-messages", "--field-match-separator=|", "--regexp", params.pattern]
40+
const args = [
41+
"-nH",
42+
"--hidden",
43+
"--follow",
44+
"--no-messages",
45+
"--field-match-separator=|",
46+
"--regexp",
47+
params.pattern,
48+
]
4149
if (params.include) {
4250
args.push("--glob", params.include)
4351
}

0 commit comments

Comments
 (0)