|
@@ -2,7 +2,7 @@
|
|
|
import { createHash } from 'node:crypto'
|
|
import { createHash } from 'node:crypto'
|
|
|
import fs from 'node:fs'
|
|
import fs from 'node:fs'
|
|
|
import path from 'node:path'
|
|
import path from 'node:path'
|
|
|
-import type { AppSettings, ProviderStatusDto, UsagePayload } from '@agent-server/shared'
|
|
|
|
|
|
|
+import type { AppSettings, ProviderQuotaDto, ProviderStatusDto, QuotaWindowDto, UsagePayload } from '@agent-server/shared'
|
|
|
import { communitySkillsDir } from '../../config'
|
|
import { communitySkillsDir } from '../../config'
|
|
|
import { checkAccess } from '../../services/access-policy'
|
|
import { checkAccess } from '../../services/access-policy'
|
|
|
import { ProviderRunError, type AgentProvider, type RunContext, type RunResult } from '../types'
|
|
import { ProviderRunError, type AgentProvider, type RunContext, type RunResult } from '../types'
|
|
@@ -11,7 +11,7 @@ import { GATEWAY_SERVER } from '../../mcp-gateway/server'
|
|
|
import { buildThreadConfig } from './permissions'
|
|
import { buildThreadConfig } from './permissions'
|
|
|
import { ALWAYS_DISABLED, skillDisplayId, skillsToDisable } from './skills'
|
|
import { ALWAYS_DISABLED, skillDisplayId, skillsToDisable } from './skills'
|
|
|
import type {
|
|
import type {
|
|
|
- AskForApproval, CommandExecutionRequestApprovalParams, FileChangeRequestApprovalParams, GetAccountResponse,
|
|
|
|
|
|
|
+ AskForApproval, CommandExecutionRequestApprovalParams, FileChangeRequestApprovalParams, GetAccountRateLimitsResponse, GetAccountResponse, RateLimitSnapshot,
|
|
|
ItemCompletedNotification, ItemStartedNotification, LoginAccountResponse, ModelListResponse, SkillMetadata, SkillsListResponse,
|
|
ItemCompletedNotification, ItemStartedNotification, LoginAccountResponse, ModelListResponse, SkillMetadata, SkillsListResponse,
|
|
|
McpServerElicitationRequestParams, PermissionsRequestApprovalParams, ThreadForkResponse, ThreadItem, ThreadStartResponse, ThreadTokenUsageUpdatedNotification,
|
|
McpServerElicitationRequestParams, PermissionsRequestApprovalParams, ThreadForkResponse, ThreadItem, ThreadStartResponse, ThreadTokenUsageUpdatedNotification,
|
|
|
ToolRequestUserInputParams, TurnCompletedNotification, TurnStartResponse,
|
|
ToolRequestUserInputParams, TurnCompletedNotification, TurnStartResponse,
|
|
@@ -37,6 +37,7 @@ export class CodexProvider implements AgentProvider {
|
|
|
private starting: Promise<AppServerClient> | null = null
|
|
private starting: Promise<AppServerClient> | null = null
|
|
|
private clientKey = ''
|
|
private clientKey = ''
|
|
|
private runs = new Map<string, ThreadRun>()
|
|
private runs = new Map<string, ThreadRun>()
|
|
|
|
|
+ private quotaCache: { at: number; value: ProviderQuotaDto } | null = null
|
|
|
/**
|
|
/**
|
|
|
* 当前 app-server 进程中已加载的会话及其加载时的配置指纹。已加载会话的配置不会被 thread/resume 更新
|
|
* 当前 app-server 进程中已加载的会话及其加载时的配置指纹。已加载会话的配置不会被 thread/resume 更新
|
|
|
* (已验证,codex 0.164),配置变化时改为 thread/fork:保留历史、采用新配置(ADR-0020)
|
|
* (已验证,codex 0.164),配置变化时改为 thread/fork:保留历史、采用新配置(ADR-0020)
|
|
@@ -210,6 +211,22 @@ export class CodexProvider implements AgentProvider {
|
|
|
.map(s => ({ id: skillDisplayId(s, codexHome()), name: s.name, description: s.shortDescription ?? s.description, scope: s.scope }))
|
|
.map(s => ({ id: skillDisplayId(s, codexHome()), name: s.name, description: s.shortDescription ?? s.description, scope: s.scope }))
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /** ChatGPT 订阅额度(account/rateLimits/read),缓存 1 分钟 */
|
|
|
|
|
+ async quota(settings: AppSettings): Promise<ProviderQuotaDto> {
|
|
|
|
|
+ if (this.quotaCache && Date.now() - this.quotaCache.at < 60_000) return this.quotaCache.value
|
|
|
|
|
+ const client = await this.ensure(settings)
|
|
|
|
|
+ const r = await client.request<GetAccountRateLimitsResponse>('account/rateLimits/read', {}, 30_000)
|
|
|
|
|
+ const snapshots = Object.values(r.rateLimitsByLimitId ?? {}).filter((s): s is RateLimitSnapshot => !!s)
|
|
|
|
|
+ const list = snapshots.length ? snapshots : [r.rateLimits]
|
|
|
|
|
+ const windows = list.flatMap(s => quotaWindows(s, list.length > 1))
|
|
|
|
|
+ const value: ProviderQuotaDto = {
|
|
|
|
|
+ provider: 'codex', plan: list.find(s => s.planType)?.planType ?? null, windows, source: 'live', fetchedAt: new Date().toISOString(),
|
|
|
|
|
+ message: r.ordinaryUsageAllowed === false ? '当前账户的额度已用完或不可用' : windows.length ? null : '账户没有返回额度信息',
|
|
|
|
|
+ }
|
|
|
|
|
+ this.quotaCache = { at: Date.now(), value }
|
|
|
|
|
+ return value
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
/** 直接调用 app-server(插件目录等后台功能使用) */
|
|
/** 直接调用 app-server(插件目录等后台功能使用) */
|
|
|
async request<T>(settings: AppSettings, method: string, params: unknown, timeoutMs = 60_000): Promise<T> {
|
|
async request<T>(settings: AppSettings, method: string, params: unknown, timeoutMs = 60_000): Promise<T> {
|
|
|
const client = await this.ensure(settings)
|
|
const client = await this.ensure(settings)
|
|
@@ -528,6 +545,15 @@ class ThreadRun {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+function quotaWindows(s: RateLimitSnapshot, named: boolean): QuotaWindowDto[] {
|
|
|
|
|
+ const label = (mins: number | null) => (mins === null ? '窗口' : mins % 1440 === 0 ? `${mins / 1440} 天` : mins % 60 === 0 ? `${mins / 60} 小时` : `${mins} 分钟`)
|
|
|
|
|
+ const prefix = named && (s.limitName ?? s.limitId) ? `${s.limitName ?? s.limitId} · ` : ''
|
|
|
|
|
+ return ([['primary', s.primary], ['secondary', s.secondary]] as const).flatMap(([k, w]) => (w ? [{
|
|
|
|
|
+ key: `${s.limitId ?? 'default'}:${k}`, label: prefix + label(w.windowDurationMins), usedPercent: w.usedPercent,
|
|
|
|
|
+ resetsAt: w.resetsAt ? new Date(w.resetsAt * 1000).toISOString() : null,
|
|
|
|
|
+ }] : []))
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
/** Codex 把命令包成 `/usr/bin/bash -lc "..."`,显示时去掉外层,只保留第一行 */
|
|
/** Codex 把命令包成 `/usr/bin/bash -lc "..."`,显示时去掉外层,只保留第一行 */
|
|
|
export function commandTitle(command: string): string {
|
|
export function commandTitle(command: string): string {
|
|
|
let c = command.trim()
|
|
let c = command.trim()
|