Skip to content
Merged
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
5 changes: 0 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -365,17 +365,12 @@ jobs:
name: Test Web ${{ matrix.shard }}
needs: changes
if: ${{ !cancelled() && needs.changes.outputs.test_web != 'false' }}
<<<<<<< origin/main
runs-on: ubuntu-24.04
timeout-minutes: 20
=======
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
shard: [1, 2]
>>>>>>> upstream/main
steps:
- name: Checkout
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
Expand Down
4 changes: 0 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
<<<<<<< origin/main
# About this fork

This is a fork of T3 Code that integrates a few additional features, including:
Expand All @@ -21,14 +20,11 @@ This is a fork of T3 Code that integrates a few additional features, including:

The following is the README from upstream.

# T3 Code
=======
<p align="center">
<a href="https://t3.codes">
<img src="./assets/prod/t3-black-web-icon-192x192.png" width="80" height="80" alt="T3 Code logo" />
</a>
</p>
>>>>>>> upstream/main

<h1 align="center">T3 Code</h1>

Expand Down
8 changes: 6 additions & 2 deletions apps/server/integration/transferBudgetV2.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,9 @@ const CLIENT_BOUNDED_OPT_INS = {
dedupeBodies: "1",
compactIds: "1",
deferWork: "1",
} satisfies Required<typeof EnvironmentOrchestrationThreadBoundedSnapshotQuery.Type>;
} satisfies Required<
Omit<typeof EnvironmentOrchestrationThreadBoundedSnapshotQuery.Type, "shownOnly">
>;
const CLIENT_HISTORY_OPT_INS = { compactIds: "1", deferWork: "1" } satisfies Required<
Omit<typeof EnvironmentOrchestrationThreadHistoryQuery.Type, "cursor" | "throughEntryId" | "view">
>;
Expand All @@ -117,7 +119,9 @@ const CLIENT_THREAD_SUBSCRIPTION = {
acceptCompactTurnItems: true,
acceptCompactIds: true,
acceptDedupedBodies: true,
} satisfies Required<Omit<OrchestrationV2SubscribeThreadInput, "threadId" | "afterSequence">>;
} satisfies Required<
Omit<OrchestrationV2SubscribeThreadInput, "threadId" | "afterSequence" | "shownOnly">
>;
type ShellResumeField = "afterSequence" | "httpSnapshotFallback" | "knownProjectIdentities";
const CLIENT_SHELL_SUBSCRIPTION = {
requestCompletionMarker: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,13 @@ import {
makeAntigravityAdapterV2,
} from "../../orchestration-v2/Adapters/AntigravityAdapterV2.ts";
import { antigravitySessionMeta } from "../../provider/acp/AntigravityAcpSupport.ts";
import {
antigravityRuntimeState,
type AntigravityRuntimeState,
profileAntigravityRuntimeState,
withAntigravityCliCompaction,
withUserAgyState,
} from "./antigravityCliRuntime.ts";
import { makeAntigravityUsageProbe } from "../../provider/antigravityUsageLimits.ts";
import {
type AntigravityTextGenerationOptions,
Expand Down Expand Up @@ -76,14 +83,6 @@ const cliRuntimeState = (path: Path.Path, userHome: string) =>
tokenFile: "antigravity-oauth-token",
});

const managedRuntimeState = (profileDirectory: string) =>
Effect.succeed({
runtime: "managed" as const,
root: profileDirectory,
appFolder: "antigravity-acp",
tokenFile: "acp_token.json",
});

const ERROR_MESSAGE = 17;
// An error step trimmed from `agy acp` 1.5.5: length-prefixed fields, so a length byte leads the status.
const CLI_API_ERROR = Buffer.concat([
Expand Down Expand Up @@ -256,13 +255,48 @@ const cliHelperTurn = Effect.fn("cliHelperTurn")(function* (
});

describe("Antigravity CLI runtime", () => {
it.effect("runs a user's own agy on its own state and a managed release on the profile", () =>
Effect.gen(function* () {
const path = yield* Path.Path;
const input = { profileDirectory: "/profile", userHome: "/home/user", path };
const profileArgs = ["acp", "--gemini-dir", "/profile", "--app-data-dir", "antigravity-acp"];
const spawn = { command: "agy", args: profileArgs, cwd: "/project" };
for (const [found, managedVersionDirectory, runtime, args] of [
["the user's own", null, "cli", ["acp"]],
["a managed release", "/managed/versions/release", "managed", profileArgs],
] as const) {
const executable = { harnessPath: null, managedVersionDirectory };
assert.equal(antigravityRuntimeState(executable, input).runtime, runtime, found);
assert.deepEqual(withUserAgyState(executable, spawn).args, args, found);
}
const harness = { harnessPath: "/release/harness", managedVersionDirectory: null };
assert.equal(antigravityRuntimeState(harness, input).runtime, "harness");
assert.deepEqual(withUserAgyState(harness, spawn).args, profileArgs);
}).pipe(Effect.provide(NodeServices.layer)),
);

it.effect("offers /compact on every agy and not on the harness runtime", () =>
Effect.gen(function* () {
const session = { compactThread: () => Effect.void };
for (const [runtime, offered] of [
["cli", true],
["managed", true],
["harness", false],
] as const) {
const open = withAntigravityCliCompaction(() => Effect.succeed(session), {
profileDirectory: "profile",
runtimeState: Effect.succeed({ ...profileAntigravityRuntimeState("profile"), runtime }),
});
assert.equal("compactThread" in (yield* open(undefined)), offered, runtime);
}
}),
);

it.effect("opens CLI sessions without additional directories", () =>
Effect.gen(function* () {
const path = yield* Path.Path;
const attachmentsDir = path.join("state", "attachments");
const sent = (
runtimeState: ReturnType<typeof cliRuntimeState | typeof managedRuntimeState>,
) =>
const sent = (runtimeState: Effect.Effect<AntigravityRuntimeState>) =>
Effect.gen(function* () {
let directories: RuntimeInput["additionalDirectories"];
const options = {
Expand All @@ -281,8 +315,12 @@ describe("Antigravity CLI runtime", () => {
return directories ?? [];
});

assert.deepEqual(yield* sent(managedRuntimeState("profile")), [attachmentsDir]);
assert.deepEqual(yield* sent(Effect.succeed(profileAntigravityRuntimeState("profile"))), [
attachmentsDir,
]);
assert.deepEqual(yield* sent(cliRuntimeState(path, "home")), []);
const managedAgy = Effect.succeed(profileAntigravityRuntimeState("profile", "managed"));
assert.deepEqual(yield* sent(managedAgy), [], "T3's managed agy");
}).pipe(Effect.scoped, Effect.provide(layer)),
);

Expand Down
76 changes: 47 additions & 29 deletions apps/server/src/fork/antigravityCliRuntime/antigravityCliRuntime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,67 +10,95 @@ import * as Option from "effect/Option";
import * as Path from "effect/Path";
import * as Schema from "effect/Schema";
import * as SqlClient from "effect/sql/SqlClient";
import type * as EffectAcpSchema from "effect-acp/compat";

import type * as AcpSessionRuntime from "@t3tools/provider-acp/server/AcpSessionRuntime";
import { removeAntigravitySessionFiles } from "../../provider/acp/AntigravitySessionFiles.ts";
import type { AntigravityExecutable } from "../../provider/AntigravityInstallation.ts";

/**
* Where a runtime keeps its sessions and its login. The managed runtime uses the profile T3
* gives it as GEMINI_HOME. The agy CLI ignores GEMINI_HOME and uses the user's own folder.
* Which runtime runs and where it keeps its sessions and its login. `harness` is the older 1.4.5
* runtime and `managed` is T3's own `agy`; both use the profile T3 gives them. `cli` is a user's own
* `agy`, which uses the user's folder.
*/
export interface AntigravityRuntimeState {
readonly runtime: "managed" | "cli";
readonly runtime: "harness" | "managed" | "cli";
readonly root: string;
readonly appFolder: string;
readonly tokenFile: string;
}

export const managedAntigravityRuntimeState = (
export const profileAntigravityRuntimeState = (
profileDirectory: string,
runtime: "harness" | "managed" = "harness",
): AntigravityRuntimeState => ({
runtime: "managed",
runtime,
root: profileDirectory,
appFolder: "antigravity-acp",
tokenFile: "acp_token.json",
});

/** An `agy` outside T3's managed releases; a managed release keeps its state in the profile. */
const isUserAgy = (
executable: Pick<AntigravityExecutable, "harnessPath" | "managedVersionDirectory">,
) => executable.harnessPath === null && executable.managedVersionDirectory === null;

export function antigravityRuntimeState(
executable: { readonly harnessPath: string | null },
executable: Pick<AntigravityExecutable, "harnessPath" | "managedVersionDirectory">,
input: {
readonly profileDirectory: string;
readonly userHome: string;
readonly path: Path.Path;
},
): AntigravityRuntimeState {
return executable.harnessPath === null
return isUserAgy(executable)
? {
runtime: "cli",
root: input.path.join(input.userHome, ".gemini"),
appFolder: "antigravity-cli",
tokenFile: "antigravity-oauth-token",
}
: managedAntigravityRuntimeState(input.profileDirectory);
: profileAntigravityRuntimeState(
input.profileDirectory,
executable.harnessPath === null ? "managed" : "harness",
);
}

const PROFILE_FLAGS = new Set(["--gemini-dir", "--app-data-dir"]);

/** Upstream points every `agy` at T3's profile; the user's own `agy` keeps its own login and state. */
export const withUserAgyState = (
executable: Pick<AntigravityExecutable, "harnessPath" | "managedVersionDirectory">,
spawn: AcpSessionRuntime.AcpSpawnInput,
): AcpSessionRuntime.AcpSpawnInput =>
isUserAgy(executable)
? {
...spawn,
args: spawn.args.filter(
(arg, index) =>
!PROFILE_FLAGS.has(arg) && !PROFILE_FLAGS.has(spawn.args[index - 1] ?? ""),
),
}
: spawn;

/** The state a caller was given, or the managed profile's when it was given none. */
export const antigravityRuntimeStateOf = (options: {
readonly profileDirectory: string;
readonly runtimeState?: Effect.Effect<AntigravityRuntimeState> | undefined;
}) =>
options.runtimeState ?? Effect.succeed(managedAntigravityRuntimeState(options.profileDirectory));
options.runtimeState ?? Effect.succeed(profileAntigravityRuntimeState(options.profileDirectory));

export const antigravityTokenPath = (state: AntigravityRuntimeState, path: Path.Path) =>
path.join(state.root, state.appFolder, state.tokenFile);

/** The CLI runs shell commands in an additional directory instead of the session's cwd. */
/** `agy` runs shell commands in an additional directory instead of the session's cwd. */
export const antigravitySessionDirectories = (
state: AntigravityRuntimeState,
attachmentsDir: string,
): ReadonlyArray<string> => (state.runtime === "cli" ? [] : [attachmentsDir]);
): ReadonlyArray<string> => (state.runtime === "harness" ? [attachmentsDir] : []);

/**
* `agy acp` runs a `/compact` prompt as its own command. The managed runtime reads it as chat,
* so its sessions offer no compaction. The runtime is read as each session opens.
* `agy acp` runs a `/compact` prompt as its own command. The older harness runtime reads it as
* chat, so its sessions offer no compaction. The runtime is read as each session opens.
*/
export const withAntigravityCliCompaction =
<Session extends { readonly compactThread?: unknown }, Input, E, R>(
Expand All @@ -79,9 +107,9 @@ export const withAntigravityCliCompaction =
) =>
(input: Input): Effect.Effect<Session, E, R> =>
Effect.zipWith(openSession(input), antigravityRuntimeStateOf(options), (session, state) => {
if (state.runtime === "cli") return session;
const { compactThread: _, ...managed } = session;
return managed as Session;
if (state.runtime !== "harness") return session;
const { compactThread: _, ...harness } = session;
return harness as Session;
});

const CLI_T3_MCP_TOOL_NAME = /^mcp_t3-code_[A-Za-z0-9_]+$/;
Expand Down Expand Up @@ -135,16 +163,6 @@ export function antigravityCliApiError(text: string) {
return match === null ? null : ([match[0], match[2], match[3], match[1]] as const);
}

const advertisesSystemPromptAppend = Schema.is(
Schema.Struct({
antigravity: Schema.Struct({ systemPrompt: Schema.Struct({ append: Schema.Literal(true) }) }),
}),
);

export const antigravityAdvertisesSystemPromptAppend = (
initializeResult: EffectAcpSchema.InitializeResponse,
) => advertisesSystemPromptAppend(initializeResult.agentCapabilities?._meta);

const CliLogin = Schema.Struct({
token: Schema.Struct({
access_token: Schema.NonEmptyString,
Expand All @@ -160,7 +178,7 @@ interface AntigravityAccess {
}

/**
* Undefined for the managed runtime. The CLI's login holds no OAuth client, so T3 cannot
* Undefined unless the runtime is a user's own `agy`. The CLI's login holds no OAuth client, so T3 cannot
* refresh it. The access token the CLI last saved stands in for a refresh until it expires.
*/
export const readAntigravityCliUsageLogin = Effect.fn("readAntigravityCliUsageLogin")(function* (
Expand Down Expand Up @@ -276,7 +294,7 @@ export const removeAntigravityRuntimeSessionFiles = Effect.fn(
}) {
const state = yield* input.runtimeState;
const { sessionId, cwd } = input;
if (state.runtime === "managed") {
if (state.runtime !== "cli") {
return yield* removeAntigravitySessionFiles({ profileDirectory: state.root, sessionId, cwd });
}
if (sessionId === undefined || !isNativeSessionId(sessionId)) return;
Expand Down
1 change: 1 addition & 0 deletions apps/server/src/fork/claudeSteerQueue.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ const startSteeredTurn = Effect.gen(function* () {
}),
forkSession: () => Effect.die("unused"),
subagentLaunchToolUseId: () => Effect.succeed(null),
subagentModel: () => Effect.succeed(null),
assertComplete: Effect.void,
},
});
Expand Down
28 changes: 0 additions & 28 deletions apps/server/src/fork/claudeSteerQueue.ts

This file was deleted.

12 changes: 12 additions & 0 deletions apps/server/src/fork/claudeUsageSource/claudeHub.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import * as TestClock from "effect/testing/TestClock";
import { HttpClient, HttpClientError, HttpClientResponse } from "effect/http";

import { makeCliproxyApi } from "../../usage/cliproxyApi.ts";
import { managementTimeout } from "./claudeHub.ts";

const config = {
kind: "cliproxy",
Expand Down Expand Up @@ -110,6 +111,15 @@ describe("Claude through a CLIProxyAPI hub", () => {
availableCount: 3,
nextCreditId: "grant_a",
nextExpiresAt: "2099-01-01T00:00:00.000Z",
options: [
{
availableCount: 3,
creditId: "grant_a",
description: "Refills your 5-hour and weekly limits.",
expiresAt: "2099-01-01T00:00:00.000Z",
label: "Full reset",
},
],
});
expect(test.requests.find((request) => request.body?.url)?.body?.url).toBe(
"https://api.anthropic.com/api/oauth/usage?cedar_ember=1",
Expand Down Expand Up @@ -273,6 +283,8 @@ describe("Claude through a CLIProxyAPI hub", () => {
yield* TestClock.adjust("29 seconds");
expect((yield* Fiber.join(redeem)).outcome).toBe("reset");
}
const postedRead = { auth_index: "a", url: "https://example.com/quota", data: "{}" };
expect(managementTimeout(postedRead)).toBe("15 seconds");
}),
);

Expand Down
Loading
Loading