Merge branch 'dev' into sqlite2
This commit is contained in:
commit
fcc903489b
5 changed files with 123 additions and 3 deletions
|
|
@ -79,7 +79,7 @@ export async function handler(
|
||||||
const dataDumper = createDataDumper(sessionId, requestId, projectId)
|
const dataDumper = createDataDumper(sessionId, requestId, projectId)
|
||||||
const trialLimiter = createTrialLimiter(modelInfo.trial, ip, ocClient)
|
const trialLimiter = createTrialLimiter(modelInfo.trial, ip, ocClient)
|
||||||
const isTrial = await trialLimiter?.isTrial()
|
const isTrial = await trialLimiter?.isTrial()
|
||||||
const rateLimiter = createRateLimiter(modelInfo.rateLimit, ip)
|
const rateLimiter = createRateLimiter(modelInfo.rateLimit, ip, input.request.headers)
|
||||||
await rateLimiter?.check()
|
await rateLimiter?.check()
|
||||||
const stickyTracker = createStickyTracker(modelInfo.stickyProvider, sessionId)
|
const stickyTracker = createStickyTracker(modelInfo.stickyProvider, sessionId)
|
||||||
const stickyProvider = await stickyTracker?.get()
|
const stickyProvider = await stickyTracker?.get()
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,11 @@ import { RateLimitError } from "./error"
|
||||||
import { logger } from "./logger"
|
import { logger } from "./logger"
|
||||||
import { ZenData } from "@opencode-ai/console-core/model.js"
|
import { ZenData } from "@opencode-ai/console-core/model.js"
|
||||||
|
|
||||||
export function createRateLimiter(limit: ZenData.RateLimit | undefined, rawIp: string) {
|
export function createRateLimiter(limit: ZenData.RateLimit | undefined, rawIp: string, headers: Headers) {
|
||||||
if (!limit) return
|
if (!limit) return
|
||||||
|
|
||||||
|
const limitValue = limit.checkHeader && !headers.get(limit.checkHeader) ? limit.fallbackValue! : limit.value
|
||||||
|
|
||||||
const ip = !rawIp.length ? "unknown" : rawIp
|
const ip = !rawIp.length ? "unknown" : rawIp
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
const intervals =
|
const intervals =
|
||||||
|
|
@ -32,7 +34,7 @@ export function createRateLimiter(limit: ZenData.RateLimit | undefined, rawIp: s
|
||||||
)
|
)
|
||||||
const total = rows.reduce((sum, r) => sum + r.count, 0)
|
const total = rows.reduce((sum, r) => sum + r.count, 0)
|
||||||
logger.debug(`rate limit total: ${total}`)
|
logger.debug(`rate limit total: ${total}`)
|
||||||
if (total >= limit.value) throw new RateLimitError(`Rate limit exceeded. Please try again later.`)
|
if (total >= limitValue) throw new RateLimitError(`Rate limit exceeded. Please try again later.`)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ export namespace ZenData {
|
||||||
const RateLimitSchema = z.object({
|
const RateLimitSchema = z.object({
|
||||||
period: z.enum(["day", "rolling"]),
|
period: z.enum(["day", "rolling"]),
|
||||||
value: z.number().int(),
|
value: z.number().int(),
|
||||||
|
checkHeader: z.string().optional(),
|
||||||
|
fallbackValue: z.number().int().optional(),
|
||||||
})
|
})
|
||||||
export type Format = z.infer<typeof FormatSchema>
|
export type Format = z.infer<typeof FormatSchema>
|
||||||
export type Trial = z.infer<typeof TrialSchema>
|
export type Trial = z.infer<typeof TrialSchema>
|
||||||
|
|
|
||||||
|
|
@ -772,6 +772,12 @@ export namespace ProviderTransform {
|
||||||
result.items = {}
|
result.items = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove properties/required from non-object types (Gemini rejects these)
|
||||||
|
if (result.type && result.type !== "object") {
|
||||||
|
delete result.properties
|
||||||
|
delete result.required
|
||||||
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -293,6 +293,116 @@ describe("ProviderTransform.schema - gemini array items", () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("ProviderTransform.schema - gemini non-object properties removal", () => {
|
||||||
|
const geminiModel = {
|
||||||
|
providerID: "google",
|
||||||
|
api: {
|
||||||
|
id: "gemini-3-pro",
|
||||||
|
},
|
||||||
|
} as any
|
||||||
|
|
||||||
|
test("removes properties from non-object types", () => {
|
||||||
|
const schema = {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
data: {
|
||||||
|
type: "string",
|
||||||
|
properties: { invalid: { type: "string" } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as any
|
||||||
|
|
||||||
|
const result = ProviderTransform.schema(geminiModel, schema) as any
|
||||||
|
|
||||||
|
expect(result.properties.data.type).toBe("string")
|
||||||
|
expect(result.properties.data.properties).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
test("removes required from non-object types", () => {
|
||||||
|
const schema = {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
data: {
|
||||||
|
type: "array",
|
||||||
|
items: { type: "string" },
|
||||||
|
required: ["invalid"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as any
|
||||||
|
|
||||||
|
const result = ProviderTransform.schema(geminiModel, schema) as any
|
||||||
|
|
||||||
|
expect(result.properties.data.type).toBe("array")
|
||||||
|
expect(result.properties.data.required).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
test("removes properties and required from nested non-object types", () => {
|
||||||
|
const schema = {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
outer: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
inner: {
|
||||||
|
type: "number",
|
||||||
|
properties: { bad: { type: "string" } },
|
||||||
|
required: ["bad"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as any
|
||||||
|
|
||||||
|
const result = ProviderTransform.schema(geminiModel, schema) as any
|
||||||
|
|
||||||
|
expect(result.properties.outer.properties.inner.type).toBe("number")
|
||||||
|
expect(result.properties.outer.properties.inner.properties).toBeUndefined()
|
||||||
|
expect(result.properties.outer.properties.inner.required).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
test("keeps properties and required on object types", () => {
|
||||||
|
const schema = {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
data: {
|
||||||
|
type: "object",
|
||||||
|
properties: { name: { type: "string" } },
|
||||||
|
required: ["name"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as any
|
||||||
|
|
||||||
|
const result = ProviderTransform.schema(geminiModel, schema) as any
|
||||||
|
|
||||||
|
expect(result.properties.data.type).toBe("object")
|
||||||
|
expect(result.properties.data.properties).toBeDefined()
|
||||||
|
expect(result.properties.data.required).toEqual(["name"])
|
||||||
|
})
|
||||||
|
|
||||||
|
test("does not affect non-gemini providers", () => {
|
||||||
|
const openaiModel = {
|
||||||
|
providerID: "openai",
|
||||||
|
api: {
|
||||||
|
id: "gpt-4",
|
||||||
|
},
|
||||||
|
} as any
|
||||||
|
|
||||||
|
const schema = {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
data: {
|
||||||
|
type: "string",
|
||||||
|
properties: { invalid: { type: "string" } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as any
|
||||||
|
|
||||||
|
const result = ProviderTransform.schema(openaiModel, schema) as any
|
||||||
|
|
||||||
|
expect(result.properties.data.properties).toBeDefined()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe("ProviderTransform.message - DeepSeek reasoning content", () => {
|
describe("ProviderTransform.message - DeepSeek reasoning content", () => {
|
||||||
test("DeepSeek with tool calls includes reasoning_content in providerOptions", () => {
|
test("DeepSeek with tool calls includes reasoning_content in providerOptions", () => {
|
||||||
const msgs = [
|
const msgs = [
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue