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
24 changes: 6 additions & 18 deletions apps/mobile/src/features/threads/ThreadComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,8 @@ import {
type ModelSelection,
type ProviderInteractionMode,
type ServerConfig as T3ServerConfig,
type UsageLimitsReport,
} from "@t3tools/contracts";
import {
collectProviderUsageLimits,
hasProviderUsageLimits,
isUsageLimitsCommand,
} from "@t3tools/shared/usageLimits";
import { hasProviderUsageLimits, isUsageLimitsCommand } from "@t3tools/shared/usageLimits";
import { StackActions, useFocusEffect, useNavigation } from "@react-navigation/native";
import type { ReactNode } from "react";
import { useEffect, useEffectEvent, useRef, useState, type RefObject } from "react";
Expand Down Expand Up @@ -258,7 +253,8 @@ export interface ThreadComposerProps {
followUp?: ActiveTurnComposerAction,
stillSendable?: () => boolean,
) => Promise<MessageId | null>;
readonly onShowUsageLimits: (report: UsageLimitsReport | null) => void;
/** Opens the limits panel; false when the provider reports none. */
readonly onShowUsageLimits: () => boolean;
readonly canSwitchProvider: boolean;
readonly onUpdateInteractionMode?: (interactionMode: ProviderInteractionMode) => void;
readonly onHeightChange?: (height: number) => void;
Expand Down Expand Up @@ -533,19 +529,11 @@ export const ThreadComposer = function ThreadComposer(props: ThreadComposerProps
props.serverConfig?.usageLimitSources ?? [],
);
const openUsageLimits = () => {
const report = collectProviderUsageLimits(
props.selectedThread.runtime?.providerInstanceId ??
props.selectedThread.modelSelection.instanceId,
props.serverConfig?.providers ?? [],
props.serverConfig?.usageLimitSources ?? [],
// oxlint-disable-next-line react/purity -- The submit and menu handlers read usage limits only after a user action.
Date.now(),
);
onShowUsageLimits(report);
if (!report) {
const shown = onShowUsageLimits();
if (!shown) {
Dialog.alert("Usage limits unavailable", "This provider does not currently report limits.");
}
return report !== null;
return shown;
};

const lockedProviderInstanceId = props.canSwitchProvider
Expand Down
27 changes: 16 additions & 11 deletions apps/mobile/src/features/threads/ThreadDetailScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ import type {
RuntimeRequestId,
ScopedThreadRef,
ServerConfig as T3ServerConfig,
UsageLimitsReport,
RunId,
} from "@t3tools/contracts";
import {
Expand Down Expand Up @@ -1025,9 +1024,12 @@ function ThreadDetailBody(
readonly now: number;
} | null>(null);
// Approval/question identity invalidates quota: answering on any client resumes spending.
const usageLimitsInstanceId =
props.selectedThread.runtime?.providerInstanceId ??
props.selectedThread.modelSelection.instanceId;
const usageLimitsKey = [
selectedThreadKey,
props.selectedThread.modelSelection.instanceId,
usageLimitsInstanceId,
props.selectedThread.latestRun?.runId ?? "",
props.activePendingApproval?.requestId ?? props.activePendingUserInput?.requestId ?? "",
].join(":");
Expand All @@ -1037,22 +1039,25 @@ function ThreadDetailBody(
const usageLimitsReport =
usageLimitsPanel !== null && usageLimitsPanel.key === usageLimitsKey
? collectProviderUsageLimits(
props.selectedThread.modelSelection.instanceId,
usageLimitsInstanceId,
props.serverConfig?.providers ?? [],
props.serverConfig?.usageLimitSources ?? [],
usageLimitsPanel.now,
)
: null;
const showUsageLimits = (report: UsageLimitsReport | null) =>
const showUsageLimits = () => {
const now = Date.now();
const report = collectProviderUsageLimits(
usageLimitsInstanceId,
props.serverConfig?.providers ?? [],
props.serverConfig?.usageLimitSources ?? [],
now,
);
setUsageLimitsPanel(
report === null
? null
: {
key: usageLimitsKey,
threadKey: selectedThreadKey,
now: Date.parse(report.createdAt),
},
report === null ? null : { key: usageLimitsKey, threadKey: selectedThreadKey, now },
);
return report !== null;
};
const dismissUsageLimits = () => setUsageLimitsPanel(null);
// Late sends clear only their originating thread's limits panel.
const clearUsageLimitsFor = (threadKey: string) =>
Expand Down
7 changes: 3 additions & 4 deletions apps/server/src/peer/PeerMcpClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -309,12 +309,11 @@ const make = Effect.gen(function* () {
}),
);
}
const success = input.success;
const decoded =
body.result.structuredContent === undefined
? // oxlint-disable-next-line t3code/no-inline-schema-compile -- Each caller supplies its tool's result schema.
Schema.decodeEffect(Schema.fromJsonString(input.success))(text)
: // oxlint-disable-next-line t3code/no-inline-schema-compile -- Each caller supplies its tool's result schema.
Schema.decodeEffect(input.success)(body.result.structuredContent);
? Schema.decodeEffect(Schema.fromJsonString(success))(text)
: Schema.decodeEffect(success)(body.result.structuredContent);
return yield* decoded.pipe(
Effect.map(Result.succeed),
Effect.mapError(
Expand Down
9 changes: 5 additions & 4 deletions apps/server/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import "vite-plus/test/config";
// @effect-diagnostics-next-line nodeBuiltinImport:off -- tsdown needs the asset path when it reads this config.
import * as NodePath from "node:path";
import * as HostProcess from "@t3tools/shared/HostProcess";
import * as Effect from "effect/Effect";
import { defineConfig, mergeConfig } from "vite-plus";

import baseConfig from "../../vite.config.ts";
Expand Down Expand Up @@ -46,10 +48,9 @@ const packExecutableTarget = resolveCliExecutableTarget({
target: process.env.T3CODE_PACK_EXE_TARGET,
nodeVersion: rootPackageJson.engines.node,
hostNodeVersion: process.versions.node,
// oxlint-disable-next-line t3code/no-global-process-runtime -- Build config selects the running Node binary, including its emulated architecture, before the Effect runtime exists.
hostPlatform: process.platform,
// oxlint-disable-next-line t3code/no-global-process-runtime -- Build config must match the Node binary's architecture rather than the machine's physical architecture.
hostArch: process.arch,
// The running Node is the one tsdown embeds for a native build.
hostPlatform: Effect.runSync(HostProcess.Platform),
hostArch: Effect.runSync(HostProcess.Architecture),
});

// The root config's `projects` route repo-root runs to package configs,
Expand Down
19 changes: 7 additions & 12 deletions packages/client-runtime/src/state/providerAccountSync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,15 @@ import {
EnvironmentRpcUnavailableError,
getInitialServerConfig,
requestGuarded,
RpcPermissionGuard,
} from "../rpc/client.ts";
import { createCommandPermissions } from "./commandPermissions.ts";
import { resolveProviderInstanceDisplayName } from "./providerInstanceDisplay.ts";
import { type AtomCommand, createAtomCommandScheduler, createRuntimeCommand } from "./runtime.ts";
import {
type AtomCommand,
createAtomCommandScheduler,
createRuntimeCommand,
withEnvironmentRpcPermissions,
} from "./runtime.ts";

export interface ProviderAccountSyncRequest {
readonly sourceEnvironmentId: EnvironmentId;
Expand Down Expand Up @@ -242,16 +246,7 @@ export function createProviderAccountSyncCommand<E>(
]),
},
execute: (request, atomRegistry) =>
syncAccounts(request).pipe(
Effect.provideService(RpcPermissionGuard, {
authorize: (environmentId, method, payload) =>
createCommandPermissions(runtime, method).authorize(
atomRegistry,
environmentId,
payload,
),
}),
),
syncAccounts(request).pipe(withEnvironmentRpcPermissions(runtime, atomRegistry)),
});
return {
...command,
Expand Down
1 change: 0 additions & 1 deletion vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,6 @@ export default defineConfig({
files: [
"packages/client-runtime/src/state/runtime.ts",
"packages/client-runtime/src/state/vcsAction.ts",
"packages/client-runtime/src/state/providerAccountSync.ts",
],
rules: {
"t3code/no-rpc-permission-bypass": [
Expand Down
Loading