镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/contracts/src/providerUsageLimits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ export const ServerProviderUsageWindow = Schema.Struct({
id: TrimmedNonEmptyString,
kind: Schema.Literals(["session", "weekly", "monthly", "other"]),
label: TrimmedNonEmptyString,
/** When present, this limit applies only to these provider model IDs. */
modelIds: Schema.optional(Schema.Array(TrimmedNonEmptyString)),
providerIds: Schema.optional(Schema.Array(TrimmedNonEmptyString)),
usedPercent: Schema.Number.check(Schema.isBetween({ minimum: 0, maximum: 100 })),
resetsAt: Schema.optional(IsoDateTime),
windowDurationMins: Schema.optional(NonNegativeInt),
Expand Down
23 changes: 23 additions & 0 deletions packages/provider-core/src/server/usageLimits.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,29 @@ describe("applyUsageLimitsUpdate", () => {
},
});
expect(next).toBe(published);
const scoped = { checkedAt, windows: [{ ...weekly, modelIds: ["opencode/a"] }] };
const providerScoped = { checkedAt, windows: [{ ...weekly, providerIds: ["opencode"] }] };
expect(
applyUsageLimitsUpdate({
previous: providerScoped,
checkedAt,
update: { windows: [{ ...weekly, providerIds: ["console-custom"] }] },
})?.windows[0]?.providerIds,
).toEqual(["console-custom"]);
expect(
applyUsageLimitsUpdate({
previous: scoped,
checkedAt,
update: { windows: [{ ...weekly, modelIds: ["opencode/a"] }] },
}),
).toBe(scoped);
expect(
applyUsageLimitsUpdate({
previous: scoped,
checkedAt,
update: { windows: [{ ...weekly, modelIds: ["opencode/b"] }] },
})?.windows[0]?.modelIds,
).toEqual(["opencode/b"]);
});

it("upserts by id and keeps the reset a percent-only update omits", () => {
Expand Down
10 changes: 10 additions & 0 deletions packages/provider-core/src/server/usageLimits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,16 @@ function usageWindowEquals(a: ServerProviderUsageWindow, b: ServerProviderUsageW
a.kind === b.kind &&
a.label === b.label &&
a.usedPercent === b.usedPercent &&
(a.modelIds === b.modelIds ||
(a.modelIds !== undefined &&
b.modelIds !== undefined &&
a.modelIds.length === b.modelIds.length &&
a.modelIds.every((id, index) => id === b.modelIds?.[index]))) &&
(a.providerIds === b.providerIds ||
(a.providerIds !== undefined &&
b.providerIds !== undefined &&
a.providerIds.length === b.providerIds.length &&
a.providerIds.every((id, index) => id === b.providerIds?.[index]))) &&
a.resetsAt === b.resetsAt &&
a.windowDurationMins === b.windowDurationMins
);
Expand Down
79 changes: 79 additions & 0 deletions packages/provider-opencode/src/server/driver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -737,6 +737,81 @@ it.layer(layer)("OpenCodeDriver runtime selection", (it) => {
}).pipe(Effect.scoped, Effect.provide(TestClock.layer())),
);

it.effect.each([
{ status: 200, data: null, reason: "unsupported" },
{ status: 404, data: null, reason: "unsupported" },
{ status: 503, data: null, reason: "probeFailed" },
{
status: 200,
data: {
workspace: {
id: "remote-workspace",
name: "Remote workspace",
usageURL: "https://opencode.ai/console/remote-workspace",
},
budgets: [
{
id: "all",
limit: 20,
spent: 15,
scope: "combined",
per: "set",
coverage: "all",
models: [],
shared: true,
resetsAt: 1793491200000,
},
],
},
reason: undefined,
},
])(
"reads external workspace usage without local credentials, HTTP $status, $reason",
(scenario) =>
Effect.gen(function* () {
const requested: string[] = [];
const replay = replayOpenCodeServer(OPENCODE_2_RESPONSES, "secret", requested);
const server = HttpClient.make((request) => {
assert.notInclude(request.url, "opencode.ai/zen");
if (request.url.endsWith("/api/experimental/usage/limits")) {
assert.strictEqual(request.headers.authorization, "Basic b3BlbmNvZGU6c2VjcmV0");
return Effect.succeed(
HttpClientResponse.fromWeb(
request,
Response.json(
{
location: { directory: "/work" },
data: scenario.data,
},
{ status: scenario.status },
),
),
);
}
return replay.execute(request);
});
const instance = yield* create(
{ serverUrl: "https://remote.example", serverPassword: "secret" },
server,
);
const [snapshot] = Array.from(
yield* instance.snapshot.streamChanges.pipe(
Stream.filter((snapshot) => snapshot.usageLimits !== undefined),
Stream.take(1),
Stream.runCollect,
),
);
assert.strictEqual(snapshot?.usageLimits?.unavailable?.reason, scenario.reason);
if (scenario.reason === undefined) {
assert.strictEqual(snapshot?.usageLimits?.windows[0]?.usedPercent, 75);
assert.strictEqual(snapshot?.usageLimits?.externalUsage?.label, "Remote workspace");
assert.deepStrictEqual(snapshot?.usageLimits?.windows[0]?.providerIds, []);
} else {
assert.deepStrictEqual(snapshot?.usageLimits?.windows, []);
}
}).pipe(Effect.scoped),
);

it.effect.each(
[
{ name: "recovers" },
Expand Down Expand Up @@ -776,6 +851,10 @@ it.layer(layer)("OpenCodeDriver runtime selection", (it) => {
);
}
const path = new URL(request.url).pathname;
if (path === "/api/experimental/usage/limits")
return Effect.succeed(
HttpClientResponse.fromWeb(request, new Response(null, { status: 404 })),
);
if (path === "/api/model") modelReads += 1;
const body =
path === "/api/info"
Expand Down
27 changes: 26 additions & 1 deletion packages/provider-opencode/src/server/driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import * as Digest from "effect/crypto/Digest";
import * as SecureRandom from "effect/crypto/SecureRandom";
import * as Deferred from "effect/Deferred";
import * as Effect from "effect/Effect";
import * as DateTime from "effect/DateTime";
import * as Equal from "effect/Equal";
import * as Option from "effect/Option";
import * as FileSystem from "effect/FileSystem";
Expand Down Expand Up @@ -454,7 +455,7 @@ export const OpenCodeDriver: ProviderDriver<
),
);

const checkUsageLimits = readOpenCodeGoUsageLimits({
const legacyUsageLimits = readOpenCodeGoUsageLimits({
enabled: effectiveConfig.enabled,
serverUrl: effectiveConfig.serverUrl,
environment: processEnv,
Expand All @@ -464,6 +465,30 @@ export const OpenCodeDriver: ProviderDriver<
Effect.provideService(HttpClient.HttpClient, httpClient),
Effect.provideService(Digest.Digest, digest),
);
let unsupportedUsageVersion: string | undefined;
const checkUsageLimits = Effect.gen(function* () {
if (!effectiveConfig.enabled) return yield* legacyUsageLimits;
const runtime = yield* runtimeProbe.get;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
if (runtime.generation === "v1" || runtime.version === unsupportedUsageVersion)
return yield* legacyUsageLimits;
const limits = yield* openCode2Server.withConnection((connection) =>
connection.usageLimits(host.paths.cwd),
);
if (limits === undefined) unsupportedUsageVersion = runtime.version;
return limits ?? (yield* legacyUsageLimits);
}).pipe(
Effect.timeout("10 seconds"),
Effect.catch(() =>
Effect.map(DateTime.now, (now) => ({
checkedAt: DateTime.formatIso(now),
windows: [],
unavailable: {
reason: "probeFailed" as const,
message: "OpenCode could not read workspace usage.",
},
})),
),
);
const checkProvider = Effect.all(
{
// A configured loopback server reports its running version. Probe the local
Expand Down
Loading
Loading