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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ Run `/feedback` to open the feedback form at `fx.sh/feedback`. It does not creat

Run `/trace` to create a private Markdown diagnostic with logs, session context, runtime state, permissions, and recent activity. On macOS, fx copies the `.md` file to the clipboard; on other platforms, it saves the file and prints its path. Review and redact the trace before sharing it.

fx automatically summarizes a long session into a fresh context window when the active model request reaches 80% of its usable input capacity, then continues the same turn. Run `/compact` to create the same durable handoff immediately and wait for your next prompt.

Use `fx ask` for a single request:

```bash
Expand Down
12 changes: 11 additions & 1 deletion src/acp/prompt.zig
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,10 @@ const AcpContext = struct {
.max_tool_result_bytes = session.max_tool_result_bytes,
.api_key = session.api_key,
.agent_stream_provider = server.streamProviderFor(self.state, session.provider),
.compaction_route = self.state.cfg.provider_set.compactionRoute(
session.provider,
session.credential_source,
),
.credential_source = session.credential_source,
.account_id = session.account_id,
.provider = session.provider,
Expand Down Expand Up @@ -773,7 +777,7 @@ pub fn handlePrompt(
if (explicit_skills.diagnostic_notice) |notice| try pushContextNotice(@ptrCast(&ctx), notice);

session.session_rt.setConversationLanguageFromUserMessage(owned_prompt);
const context_history = try session.session_rt.snapshotContextHistory(alloc);
const context_history = try session.session_rt.snapshotHistory(alloc);
defer types.freeHistoryTurnSlice(alloc, context_history);
var context_snapshot = try state.context_snapshot.dupe(alloc);
defer context_snapshot.deinit(alloc);
Expand Down Expand Up @@ -801,6 +805,8 @@ pub fn handlePrompt(
.gateway_team = state.gateway_team,
.permission_mode = captured_permission_mode,
.history = context_history,
.context_history_start = session.session_rt.contextHistoryStart(),
.unversioned_history_count = session.session_rt.unversionedHistoryEnd(),
.root_user_intent_context = root_user_intent_context,
.grants = session.session_grants,
.context_snapshot = context_snapshot,
Expand Down Expand Up @@ -1311,6 +1317,10 @@ fn agentRuntimeDeps(ctx: *AcpContext) agent_runtime.AgentRuntimeDeps {
return .{
.ctx = @ptrCast(ctx),
.agent_stream_provider = server.streamProviderFor(ctx.state, ctx.state.active_session.?.provider),
.compaction_route = ctx.state.cfg.provider_set.compactionRoute(
ctx.state.active_session.?.provider,
ctx.state.active_session.?.credential_source,
),
.flush_assistant_stream_per_content_chunk = host_target.is_wasm,
.tool_registry = ctx.toolRegistry(),
.context_registry = ctx.state.cfg.context_registry,
Expand Down
2 changes: 1 addition & 1 deletion src/builtins/commands.zig
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,7 @@ pub const slash_specs = [_]SlashSpec{
.{ .kind = .copy, .command = "/copy", .help_entry = "/copy", .completion_description = "copy the last assistant response", .presentation_category = .session },
.{ .kind = .feedback, .command = "/feedback", .help_entry = "/feedback", .completion_description = "open the fx feedback form", .presentation_category = .product, .show_in_welcome = true },
.{ .kind = .trace, .command = "/trace", .help_entry = "/trace", .completion_description = "copy a private diagnostic trace", .presentation_category = .product },
.{ .kind = .compact, .command = "/compact", .help_entry = "/compact", .completion_description = "compact older conversation turns", .presentation_category = .session },
.{ .kind = .compact, .command = "/compact", .help_entry = "/compact", .completion_description = "summarize context into a fresh window", .presentation_category = .session },
.{ .kind = .settings, .command = "/settings", .help_entry = "/settings [startup-scrollback [on|off]]", .completion_description = "browse and update settings", .presentation_category = .appearance, .has_args = true, .accepts_payload = true },
.{ .kind = .alias, .command = "/alias", .aliases = &.{}, .help_entry = "/alias [name] [command]", .completion_description = "show alias availability", .presentation_category = .extensions, .has_args = true, .accepts_payload = true },
.{ .kind = .credits, .command = "/credits", .aliases = &.{"/balance"}, .help_entry = "/credits (/balance)", .completion_description = "show gateway credits balance", .presentation_category = .account, .requires_prompt_credential = true },
Expand Down
77 changes: 50 additions & 27 deletions src/builtins/gateway.zig
Original file line number Diff line number Diff line change
Expand Up @@ -148,10 +148,12 @@ pub const generation_usage_provider = gateway_generation_usage.provider;

pub const agent_stream_provider = agent_stream_provider_contract.Provider{
.stream_fn = streamAgentCompletion,
.build_request_fn = buildAgentRequestForProvider,
};

pub const provider_bundle = provider_set.Bundle{
.capabilities = .{ .fx_search = true, .vision_fallback = true },
.compaction_model = "openai/gpt-5.6-luna",
.presentation = provider_catalog.find(.gateway),
.auth_strategy = .vercel,
.fallback_model_capabilities_fn = vercel_model_policy.capabilitiesForModel,
Expand Down Expand Up @@ -250,6 +252,14 @@ pub fn buildAgentRequest(
unreachable;
}

fn buildAgentRequestForProvider(
_: ?*anyopaque,
alloc: Allocator,
request: agent_stream_provider_contract.RequestData,
) anyerror![]u8 {
return buildAgentRequest(alloc, request);
}

fn resolveGatewayProviderOptions(
model: []const u8,
effort: shared_types.ReasoningEffort,
Expand Down Expand Up @@ -522,35 +532,48 @@ fn streamAgentCompletion(
error.SubscriptionCredentialCannotAuthorizeGateway,
);
}
const payload = try buildAgentRequest(alloc, request.data());
defer alloc.free(payload);
const payload = request.prepared_request_body orelse
try buildAgentRequest(alloc, request.data());
defer if (request.prepared_request_body == null) alloc.free(payload);
var events = request.events;
const result = gateway_client.streamGatewayCompletion(
alloc,
.{
.api_key = request.credential.secret,
.team = request.credential.tenant,
.session_id = request.session_id,
.model = request.model,
.retry_count = request.retry_count,
.chat_url = agentChatUrl(),
.payload = payload,
.trace_ctx = request.trace_ctx,
.content_capture_limit = request.content_capture_limit,
.delivery = request.delivery,
.admission = request.admission,
.on_reasoning_chunk = EventBridge.reasoning,
.on_tool_input_chunk = EventBridge.toolInput,
.provider_attempt_owner = switch (request.provider_attempt_owner) {
.transport => .transport,
.agent => .agent,
},
const stream_request = gateway_client.StreamRequest{
.api_key = request.credential.secret,
.team = request.credential.tenant,
.session_id = request.session_id,
.model = request.model,
.retry_count = request.retry_count,
.chat_url = agentChatUrl(),
.payload = payload,
.trace_ctx = request.trace_ctx,
.content_capture_limit = request.content_capture_limit,
.delivery = request.delivery,
.admission = request.admission,
.on_reasoning_chunk = EventBridge.reasoning,
.on_tool_input_chunk = EventBridge.toolInput,
.provider_attempt_owner = switch (request.provider_attempt_owner) {
.transport => .transport,
.agent => .agent,
},
&events,
EventBridge.content,
EventBridge.toolStart,
request.cancel_flag,
) catch |err| {
};
const result = (if (request.deadline) |deadline|
gateway_client.streamGatewayCompletionBounded(
alloc,
stream_request,
&events,
EventBridge.content,
EventBridge.toolStart,
deadline,
request.cancel_flag,
)
else
gateway_client.streamGatewayCompletion(
alloc,
stream_request,
&events,
EventBridge.content,
EventBridge.toolStart,
request.cancel_flag,
)) catch |err| {
request.attempt_evidence.network_failure = gateway_client.networkFailureEvidence(
err,
request.delivery.load(),
Expand Down
2 changes: 2 additions & 0 deletions src/builtins/providers.zig
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const provider_catalog = @import("../core/auth/provider_catalog.zig");
pub const native = provider_set.Set{
.gateway = gateway.provider_bundle,
.codex = .{
.compaction_model = "gpt-5.6-luna",
.presentation = provider_catalog.find(.codex),
.auth_strategy = .chatgpt,
.agent_stream = openai_codex.agent_stream_provider,
Expand All @@ -19,6 +20,7 @@ pub const native = provider_set.Set{
.permission_reviewer = openai_codex_permission_reviewer.provider,
},
.grok = .{
.compaction_model = "grok-4.5",
.presentation = provider_catalog.find(.grok),
.auth_strategy = .grok,
.agent_stream = xai_grok.agent_stream_provider,
Expand Down
2 changes: 2 additions & 0 deletions src/core/agent/agent_runtime.zig
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,14 @@ pub const dispatchAttentionRequiredCheckpoint = runtime_lifecycle.dispatchAttent
pub const TurnFinalizationGuard = runtime_finalization.TurnFinalizationGuard;
pub const Config = runtime_config.Config;
pub const processAgentPrompt = runtime_orchestrator.processAgentPrompt;
pub const compactContextTransaction = runtime_orchestrator.compactContextTransaction;
pub const persistedStatusForCurrentFxLocalResult = runtime_execution_memory.persistedStatusForCurrentFxLocalResult;
pub const classifyProviderExecutedResultStatus = runtime_execution_memory.classifyProviderExecutedResultStatus;
pub const normalizeAssistantTextForDisplay = runtime_assistant_stream.normalizeAssistantTextForDisplay;

test {
_ = @import("stream_provider.zig");
_ = @import("runtime/context_compaction.zig");
_ = @import("runtime/tests/gateway_flow.zig");
_ = @import("runtime/tests/tool_flow.zig");
_ = @import("runtime/tests/interruption_flow.zig");
Expand Down
3 changes: 0 additions & 3 deletions src/core/agent/runtime/config.zig
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@ const stream_provider = @import("../stream_provider.zig");

const ReasoningEffort = types.ReasoningEffort;

pub const default_history_context_budget_tokens: usize = 24_000;
pub const history_context_budget_window_divisor: usize = 4;

/// Who owns the prompt driving this run. A root turn's prompt is real user
/// input; a subagent turn's prompt is assistant-authored delegation and can
/// never establish user authorization for the automatic reviewer.
Expand Down
Loading
Loading