mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-07-26 09:34:47 +00:00
refactor(agent-core-v2): drop the @moonshot-ai/protocol dependency (#1745)
This commit is contained in:
parent
481b28b8f4
commit
27236bd75f
82 changed files with 1389 additions and 225 deletions
|
|
@ -62,7 +62,6 @@
|
|||
"@modelcontextprotocol/sdk": "^1.29.0",
|
||||
"@moonshot-ai/kimi-code-oauth": "workspace:^",
|
||||
"@moonshot-ai/minidb": "workspace:^",
|
||||
"@moonshot-ai/protocol": "workspace:^",
|
||||
"@mozilla/readability": "^0.6.0",
|
||||
"ajv": "^8.18.0",
|
||||
"ajv-formats": "^3.0.1",
|
||||
|
|
|
|||
|
|
@ -304,6 +304,11 @@ const ALLOWED_EXCEPTIONS = new Set([
|
|||
'auth>tool',
|
||||
'auth>toolRegistry',
|
||||
'permissionGate>approval',
|
||||
// `permissionRules` (L3) persists the approval broker's `ApprovalResponse`
|
||||
// (Session, L7) verbatim in its wire-logged `PermissionApprovalResultRecord`
|
||||
// — a real cross-scope dependency, surfaced here rather than hidden behind a
|
||||
// re-declared copy of the shape.
|
||||
'permissionRules>approval',
|
||||
'userTool>interaction',
|
||||
'permissionPolicy>plan',
|
||||
'permissionPolicy>swarm',
|
||||
|
|
|
|||
|
|
@ -6,13 +6,12 @@
|
|||
* codes, the registry (`registerErrorDomain` / `errorInfo` / `isErrorCode`) the
|
||||
* serializer reads, and the domain-independent core codes (`internal`,
|
||||
* `not_implemented`). Domain-owned codes live next to their owning domain and
|
||||
* are aggregated into the public `ErrorCodes` const by `#/errors`.
|
||||
* are aggregated into the public `ErrorCodes` const by `#/errors`, which also
|
||||
* derives the `ErrorCode` union type from that aggregate — so each domain's
|
||||
* `errors.ts` is the single source of truth and there is no central
|
||||
* hand-maintained list to keep in sync.
|
||||
*/
|
||||
|
||||
import type { KimiErrorCode } from '@moonshot-ai/protocol';
|
||||
|
||||
export type ErrorCode = KimiErrorCode;
|
||||
|
||||
export interface ErrorInfo {
|
||||
readonly title: string;
|
||||
readonly retryable: boolean;
|
||||
|
|
@ -21,18 +20,25 @@ export interface ErrorInfo {
|
|||
}
|
||||
|
||||
export interface ErrorDomain {
|
||||
readonly codes: { readonly [name: string]: ErrorCode };
|
||||
readonly retryable?: ReadonlyArray<ErrorCode>;
|
||||
readonly codes: { readonly [name: string]: string };
|
||||
readonly retryable?: ReadonlyArray<string>;
|
||||
readonly info?: { readonly [code: string]: ErrorInfo };
|
||||
}
|
||||
|
||||
const registeredCodes = new Set<ErrorCode>();
|
||||
const retryableCodes = new Set<ErrorCode>();
|
||||
// Maps each registered code to the `codes` object that contributed it: a
|
||||
// domain re-registering itself stays idempotent, while two different domains
|
||||
// claiming the same code fail loudly at registration time.
|
||||
const registeredCodes = new Map<string, object>();
|
||||
const retryableCodes = new Set<string>();
|
||||
const infoOverrides: { [code: string]: ErrorInfo } = {};
|
||||
|
||||
export function registerErrorDomain(domain: ErrorDomain): void {
|
||||
for (const code of Object.values(domain.codes)) {
|
||||
registeredCodes.add(code);
|
||||
const owner = registeredCodes.get(code);
|
||||
if (owner !== undefined && owner !== domain.codes) {
|
||||
throw new Error(`error code '${code}' is registered by two different domains`);
|
||||
}
|
||||
registeredCodes.set(code, domain.codes);
|
||||
}
|
||||
for (const code of domain.retryable ?? []) {
|
||||
retryableCodes.add(code);
|
||||
|
|
@ -42,11 +48,11 @@ export function registerErrorDomain(domain: ErrorDomain): void {
|
|||
}
|
||||
}
|
||||
|
||||
export function isErrorCode(code: unknown): code is ErrorCode {
|
||||
return typeof code === 'string' && registeredCodes.has(code as ErrorCode);
|
||||
export function isErrorCode(code: unknown): code is string {
|
||||
return typeof code === 'string' && registeredCodes.has(code);
|
||||
}
|
||||
|
||||
export function errorInfo(code: ErrorCode): ErrorInfo {
|
||||
export function errorInfo(code: string): ErrorInfo {
|
||||
const override = infoOverrides[code];
|
||||
if (override !== undefined) return override;
|
||||
return {
|
||||
|
|
@ -60,6 +66,7 @@ export const CoreErrors = {
|
|||
codes: {
|
||||
INTERNAL: 'internal',
|
||||
NOT_IMPLEMENTED: 'not_implemented',
|
||||
VALIDATION_FAILED: 'validation.failed',
|
||||
},
|
||||
info: {
|
||||
internal: {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
*/
|
||||
|
||||
import { CoreErrors } from './codes';
|
||||
import type { ErrorCode } from './codes';
|
||||
import type { ErrorCode } from '#/errors';
|
||||
|
||||
export class ExpectedError extends Error {
|
||||
readonly isExpected = true;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
*/
|
||||
|
||||
import { CoreErrors, errorInfo, isErrorCode } from './codes';
|
||||
import type { ErrorCode } from './codes';
|
||||
import type { ErrorCode } from '#/errors';
|
||||
import { Error2 } from './errors';
|
||||
|
||||
export interface ErrorPayload {
|
||||
|
|
|
|||
26
packages/agent-core-v2/src/_base/utils/isoDateTime.ts
Normal file
26
packages/agent-core-v2/src/_base/utils/isoDateTime.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
const ISO_8601_REGEX =
|
||||
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?(?:Z|[+-]\d{2}(?::?\d{2})?)$/;
|
||||
|
||||
/**
|
||||
* Wire-schema primitive for ISO 8601 datetime strings: validates the shape and
|
||||
* normalizes to `Date#toISOString()` output. Shared by the edge DTO schemas
|
||||
* (`sessionFs`, `file`, `terminal`, `auth`, …) that expose timestamps.
|
||||
*/
|
||||
export const isoDateTimeSchema = z
|
||||
.string()
|
||||
.refine((value) => ISO_8601_REGEX.test(value), {
|
||||
message: 'must be an ISO 8601 datetime string',
|
||||
})
|
||||
.transform((value, ctx) => {
|
||||
const ms = Date.parse(value);
|
||||
if (Number.isNaN(ms)) {
|
||||
ctx.addIssue({
|
||||
code: 'custom',
|
||||
message: 'invalid ISO 8601 datetime',
|
||||
});
|
||||
return z.NEVER;
|
||||
}
|
||||
return new Date(ms).toISOString();
|
||||
});
|
||||
|
|
@ -18,7 +18,8 @@
|
|||
import { createDecorator, type ServiceIdentifier } from '#/_base/di/instantiation';
|
||||
import type { IDisposable } from '#/_base/di/lifecycle';
|
||||
import type { PromptOrigin } from '#/agent/contextMemory/types';
|
||||
import type { TurnEndReason } from '@moonshot-ai/protocol';
|
||||
|
||||
export type TurnEndReason = 'completed' | 'cancelled' | 'failed' | 'blocked';
|
||||
|
||||
export type AgentLifecycleState = 'initializing' | 'ready' | 'disposing' | 'disposed';
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
* so REST consumers can still render the media after reload/resume.
|
||||
*/
|
||||
|
||||
import type { Message, MessageContent, MessageRole, ToolUseContent } from '@moonshot-ai/protocol';
|
||||
import type { Message, MessageContent, MessageRole, ToolUseContent } from './wireMessage';
|
||||
|
||||
import type { ContextMessage } from './types';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import type { ContentPart, Message } from '#/app/llmProtocol/message';
|
||||
|
||||
import type { AgentTaskStatus } from '#/agent/task/task';
|
||||
import type { CronJobOrigin, CronMissedOrigin, ShellCommandOrigin } from '@moonshot-ai/protocol';
|
||||
|
||||
export type SkillSource = 'project' | 'user' | 'extra' | 'builtin';
|
||||
|
||||
|
|
@ -36,6 +35,14 @@ export interface InjectionOrigin {
|
|||
readonly variant: string;
|
||||
}
|
||||
|
||||
export interface ShellCommandOrigin {
|
||||
readonly kind: 'shell_command';
|
||||
readonly phase: 'input' | 'output';
|
||||
/** Only present on `phase: 'output'` — whether the command failed, so replay
|
||||
* can colour stderr red only for actual failures (not warnings). */
|
||||
readonly isError?: boolean;
|
||||
}
|
||||
|
||||
export interface CompactionSummaryOrigin {
|
||||
readonly kind: 'compaction_summary';
|
||||
}
|
||||
|
|
@ -52,6 +59,20 @@ export interface TaskOrigin {
|
|||
readonly notificationId: string;
|
||||
}
|
||||
|
||||
export interface CronJobOrigin {
|
||||
readonly kind: 'cron_job';
|
||||
readonly jobId: string;
|
||||
readonly cron: string;
|
||||
readonly recurring: boolean;
|
||||
readonly coalescedCount: number;
|
||||
readonly stale: boolean;
|
||||
}
|
||||
|
||||
export interface CronMissedOrigin {
|
||||
readonly kind: 'cron_missed';
|
||||
readonly count: number;
|
||||
}
|
||||
|
||||
export interface HookResultOrigin {
|
||||
readonly kind: 'hook_result';
|
||||
readonly event: string;
|
||||
|
|
|
|||
100
packages/agent-core-v2/src/agent/contextMemory/wireMessage.ts
Normal file
100
packages/agent-core-v2/src/agent/contextMemory/wireMessage.ts
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
/**
|
||||
* The wire `Message` shape — the legacy REST/streaming message format served
|
||||
* on the `messages`, `snapshot`, and `sessions` (`:undo`) edge surfaces.
|
||||
* Defined next to `messageProjection.ts`, which projects `ContextMessage`
|
||||
* into this shape; consumed by the `messageLegacy` edge adapter and the
|
||||
* transports.
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
import { isoDateTimeSchema } from '#/_base/utils/isoDateTime';
|
||||
|
||||
export const messageRoleSchema = z.enum(['user', 'assistant', 'tool', 'system']);
|
||||
export type MessageRole = z.infer<typeof messageRoleSchema>;
|
||||
|
||||
export const textContentSchema = z.object({
|
||||
type: z.literal('text'),
|
||||
text: z.string(),
|
||||
});
|
||||
export type TextContent = z.infer<typeof textContentSchema>;
|
||||
|
||||
export const toolUseContentSchema = z.object({
|
||||
type: z.literal('tool_use'),
|
||||
tool_call_id: z.string().min(1),
|
||||
tool_name: z.string().min(1),
|
||||
input: z.unknown(),
|
||||
});
|
||||
export type ToolUseContent = z.infer<typeof toolUseContentSchema>;
|
||||
|
||||
export const toolResultContentSchema = z.object({
|
||||
type: z.literal('tool_result'),
|
||||
tool_call_id: z.string().min(1),
|
||||
output: z.unknown(),
|
||||
is_error: z.boolean().optional(),
|
||||
});
|
||||
export type ToolResultContent = z.infer<typeof toolResultContentSchema>;
|
||||
|
||||
export const imageSourceSchema = z.discriminatedUnion('kind', [
|
||||
z.object({ kind: z.literal('url'), url: z.string().min(1) }),
|
||||
z.object({
|
||||
kind: z.literal('base64'),
|
||||
media_type: z.string().min(1),
|
||||
data: z.string().min(1),
|
||||
}),
|
||||
z.object({ kind: z.literal('file'), file_id: z.string().min(1) }),
|
||||
]);
|
||||
export type ImageSource = z.infer<typeof imageSourceSchema>;
|
||||
|
||||
export const imageContentSchema = z.object({
|
||||
type: z.literal('image'),
|
||||
source: imageSourceSchema,
|
||||
});
|
||||
export type ImageContent = z.infer<typeof imageContentSchema>;
|
||||
|
||||
// Video uses the same source shape as image (url / base64 / uploaded file id).
|
||||
export const videoContentSchema = z.object({
|
||||
type: z.literal('video'),
|
||||
source: imageSourceSchema,
|
||||
});
|
||||
export type VideoContent = z.infer<typeof videoContentSchema>;
|
||||
|
||||
export const fileContentSchema = z.object({
|
||||
type: z.literal('file'),
|
||||
file_id: z.string().min(1),
|
||||
name: z.string(),
|
||||
media_type: z.string().min(1),
|
||||
size: z.number().int().nonnegative(),
|
||||
});
|
||||
export type FileContent = z.infer<typeof fileContentSchema>;
|
||||
|
||||
export const thinkingContentSchema = z.object({
|
||||
type: z.literal('thinking'),
|
||||
thinking: z.string(),
|
||||
signature: z.string().optional(),
|
||||
});
|
||||
export type ThinkingContent = z.infer<typeof thinkingContentSchema>;
|
||||
|
||||
export const messageContentSchema = z.discriminatedUnion('type', [
|
||||
textContentSchema,
|
||||
toolUseContentSchema,
|
||||
toolResultContentSchema,
|
||||
imageContentSchema,
|
||||
videoContentSchema,
|
||||
fileContentSchema,
|
||||
thinkingContentSchema,
|
||||
]);
|
||||
export type MessageContent = z.infer<typeof messageContentSchema>;
|
||||
|
||||
export const messageSchema = z.object({
|
||||
id: z.string().min(1),
|
||||
session_id: z.string().min(1),
|
||||
role: messageRoleSchema,
|
||||
content: z.array(messageContentSchema),
|
||||
created_at: isoDateTimeSchema,
|
||||
prompt_id: z.string().min(1).optional(),
|
||||
parent_message_id: z.string().min(1).optional(),
|
||||
metadata: z.record(z.string(), z.unknown()).optional(),
|
||||
});
|
||||
|
||||
export type Message = z.infer<typeof messageSchema>;
|
||||
|
|
@ -38,7 +38,7 @@ import {
|
|||
IAgentPromptService,
|
||||
type PromptSubmitContext,
|
||||
} from '#/agent/prompt/prompt';
|
||||
import type { HookResultEvent, TurnEndedEvent } from '@moonshot-ai/protocol';
|
||||
import type { TurnEndedEvent } from '#/agent/loop/turnEvents';
|
||||
import { IEventBus } from '#/app/event/eventBus';
|
||||
import type { ExecutableToolResult } from '#/tool/toolContract';
|
||||
import type { ToolDidExecuteContext, ToolBeforeExecuteContext } from '#/agent/toolExecutor/toolHooks';
|
||||
|
|
@ -53,6 +53,14 @@ import {
|
|||
renderUserPromptHookResult,
|
||||
} from './user-prompt';
|
||||
|
||||
export interface HookResultEvent {
|
||||
readonly type: 'hook.result';
|
||||
readonly turnId?: number;
|
||||
readonly hookEvent: string;
|
||||
readonly content: string;
|
||||
readonly blocked?: boolean;
|
||||
}
|
||||
|
||||
declare module '#/app/event/eventBus' {
|
||||
interface DomainEventMap {
|
||||
'hook.result': HookResultEvent;
|
||||
|
|
|
|||
|
|
@ -38,14 +38,28 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
import { defineModel } from '#/wire/model';
|
||||
import type {
|
||||
CompactionBlockedEvent,
|
||||
CompactionCancelledEvent,
|
||||
CompactionCompletedEvent,
|
||||
CompactionStartedEvent,
|
||||
} from '@moonshot-ai/protocol';
|
||||
|
||||
import type { CompactionBeginData } from './types';
|
||||
import type { CompactionBeginData, CompactionResult } from './types';
|
||||
|
||||
export interface CompactionStartedEvent {
|
||||
readonly type: 'compaction.started';
|
||||
readonly trigger: 'manual' | 'auto';
|
||||
readonly instruction?: string;
|
||||
}
|
||||
|
||||
export interface CompactionBlockedEvent {
|
||||
readonly type: 'compaction.blocked';
|
||||
readonly turnId?: number;
|
||||
}
|
||||
|
||||
export interface CompactionCancelledEvent {
|
||||
readonly type: 'compaction.cancelled';
|
||||
}
|
||||
|
||||
export interface CompactionCompletedEvent {
|
||||
readonly type: 'compaction.completed';
|
||||
readonly result: CompactionResult;
|
||||
}
|
||||
|
||||
export type CompactionPhase = 'idle' | 'running' | 'cancelled' | 'completed';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,32 @@
|
|||
import type { CompactionResult as ProtocolCompactionResult } from '@moonshot-ai/protocol';
|
||||
|
||||
export interface CompactionResult extends ProtocolCompactionResult {
|
||||
export interface CompactionResult {
|
||||
summary: string;
|
||||
contextSummary?: string;
|
||||
compactedCount: number;
|
||||
tokensBefore: number;
|
||||
tokensAfter: number;
|
||||
/**
|
||||
* Number of real user messages kept verbatim ahead of the summary in the
|
||||
* post-compaction live context. Recorded so the wire-transcript reducer can
|
||||
* reproduce the live folded length without re-deriving it from the full
|
||||
* transcript (which still holds the untruncated originals of messages the
|
||||
* live context may have truncated, so the two would otherwise diverge).
|
||||
* Optional for backward compatibility with older wire records.
|
||||
*/
|
||||
keptUserMessageCount?: number;
|
||||
/**
|
||||
* Of `keptUserMessageCount`, how many messages form the head segment (the
|
||||
* oldest user input kept when the pool overflowed the budget). Present iff
|
||||
* the selection split into head + tail, in which case the live context also
|
||||
* holds one elision-marker message between the segments. Optional for
|
||||
* backward compatibility with older wire records.
|
||||
*/
|
||||
keptHeadUserMessageCount?: number;
|
||||
/**
|
||||
* Oldest messages trimmed from the summarizer input when the compaction
|
||||
* request overflowed the model window; not covered by the produced summary.
|
||||
* Mirrors agent-core's `CompactionResult.droppedCount`; optional for backward
|
||||
* compatibility.
|
||||
*/
|
||||
droppedCount?: number;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
import { randomUUID } from 'node:crypto';
|
||||
|
||||
import type { TurnEndedEvent, TurnStartedEvent } from '@moonshot-ai/protocol';
|
||||
import type { TurnEndedEvent, TurnStartedEvent } from '#/agent/loop/turnEvents';
|
||||
import { Disposable, MutableDisposable, type IDisposable } from '#/_base/di/lifecycle';
|
||||
import { InstantiationType } from '#/_base/di/extensions';
|
||||
import { LifecycleScope, registerScopedService } from '#/_base/di/scope';
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import { z } from 'zod';
|
||||
|
||||
import type { ToolInputDisplay } from '@moonshot-ai/protocol';
|
||||
import type { ToolInputDisplay } from '#/tool/toolInputDisplay';
|
||||
|
||||
import { toInputJsonSchema } from '#/tool/input-schema';
|
||||
import { IAgentPermissionModeService } from '#/agent/permissionMode/permissionMode';
|
||||
|
|
|
|||
|
|
@ -36,16 +36,6 @@ import { Disposable, toDisposable, type IDisposable } from '#/_base/di/lifecycle
|
|||
import { LifecycleScope, registerScopedService } from '#/_base/di/scope';
|
||||
import { abortError, isAbortError, isUserCancellation, userCancellationReason } from '#/_base/utils/abort';
|
||||
import { toErrorMessage } from '#/_base/errors/errorMessage';
|
||||
import type {
|
||||
AssistantDeltaEvent,
|
||||
ThinkingDeltaEvent,
|
||||
ToolCallDeltaEvent,
|
||||
TurnEndedEvent,
|
||||
TurnStartedEvent,
|
||||
TurnStepCompletedEvent,
|
||||
TurnStepInterruptedEvent,
|
||||
TurnStepStartedEvent,
|
||||
} from '@moonshot-ai/protocol';
|
||||
import { IAgentLLMRequesterService, type LLMRequestFinish } from '#/agent/llmRequester/llmRequester';
|
||||
import { IAgentToolExecutorService } from '#/agent/toolExecutor/toolExecutor';
|
||||
import { IConfigService } from '#/app/config/config';
|
||||
|
|
@ -92,19 +82,10 @@ import {
|
|||
} from './stepRequest';
|
||||
import { StepRequestQueue, type StepRequestBatch } from './stepRequestQueue';
|
||||
import { cancelTurn, promptTurn, TurnModel } from './turnOps';
|
||||
|
||||
declare module '#/app/event/eventBus' {
|
||||
interface DomainEventMap {
|
||||
'turn.started': TurnStartedEvent;
|
||||
'turn.ended': TurnEndedEvent;
|
||||
'turn.step.started': TurnStepStartedEvent;
|
||||
'turn.step.completed': TurnStepCompletedEvent;
|
||||
'turn.step.interrupted': TurnStepInterruptedEvent;
|
||||
'assistant.delta': AssistantDeltaEvent;
|
||||
'thinking.delta': ThinkingDeltaEvent;
|
||||
'tool.call.delta': ToolCallDeltaEvent;
|
||||
}
|
||||
}
|
||||
// Loads the `DomainEventMap` augmentation for the `turn.*` / delta events this
|
||||
// service publishes (the augmentation lives with the event definitions;
|
||||
// without an import it would not enter every consumer's program).
|
||||
import './turnEvents';
|
||||
|
||||
export type LoopInterruptReason = 'aborted' | 'max_steps' | 'error';
|
||||
|
||||
|
|
|
|||
101
packages/agent-core-v2/src/agent/loop/turnEvents.ts
Normal file
101
packages/agent-core-v2/src/agent/loop/turnEvents.ts
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
/**
|
||||
* `loop` domain — the `turn.*` / delta event payloads published through
|
||||
* `IEventBus` as a turn runs. These are the loop's share of the agent event
|
||||
* stream; consumers (transports, replay, telemetry) subscribe by `type`.
|
||||
*/
|
||||
|
||||
import type { KimiErrorPayload } from '#/_base/errors/serialize';
|
||||
import type { PromptOrigin } from '#/agent/contextMemory/types';
|
||||
import type { FinishReason } from '#/app/llmProtocol/finishReason';
|
||||
import type { TokenUsage } from '#/app/llmProtocol/usage';
|
||||
import type { TurnEndReason } from '#/activity/activity';
|
||||
|
||||
export interface TurnStartedEvent {
|
||||
readonly type: 'turn.started';
|
||||
readonly turnId: number;
|
||||
readonly origin: PromptOrigin;
|
||||
}
|
||||
|
||||
export interface TurnEndedEvent {
|
||||
readonly type: 'turn.ended';
|
||||
readonly turnId: number;
|
||||
readonly reason: TurnEndReason;
|
||||
readonly error?: KimiErrorPayload;
|
||||
readonly durationMs?: number;
|
||||
}
|
||||
|
||||
export interface TurnStepStartedEvent {
|
||||
readonly type: 'turn.step.started';
|
||||
readonly turnId: number;
|
||||
readonly step: number;
|
||||
readonly stepId?: string;
|
||||
}
|
||||
|
||||
export interface TurnStepCompletedEvent {
|
||||
readonly type: 'turn.step.completed';
|
||||
readonly turnId: number;
|
||||
readonly step: number;
|
||||
readonly stepId?: string;
|
||||
readonly usage?: TokenUsage;
|
||||
readonly finishReason?: string;
|
||||
readonly llmFirstTokenLatencyMs?: number;
|
||||
readonly llmStreamDurationMs?: number;
|
||||
/**
|
||||
* Split of `llmFirstTokenLatencyMs`: in-process request-building time on the
|
||||
* client vs. network + API-server time to the first token. Both omitted when
|
||||
* the provider does not report the client/server boundary.
|
||||
*/
|
||||
readonly llmRequestBuildMs?: number;
|
||||
readonly llmServerFirstTokenMs?: number;
|
||||
/**
|
||||
* Split of `llmStreamDurationMs` (the decode window): time awaiting parts from
|
||||
* the provider vs. time processing parts in-process. Both omitted when the
|
||||
* provider stream did not report decode accounting.
|
||||
*/
|
||||
readonly llmServerDecodeMs?: number;
|
||||
readonly llmClientConsumeMs?: number;
|
||||
readonly providerFinishReason?: FinishReason;
|
||||
readonly rawFinishReason?: string;
|
||||
}
|
||||
|
||||
export interface TurnStepInterruptedEvent {
|
||||
readonly type: 'turn.step.interrupted';
|
||||
readonly turnId: number;
|
||||
readonly step: number;
|
||||
readonly stepId?: string;
|
||||
readonly reason: string;
|
||||
readonly message?: string;
|
||||
}
|
||||
|
||||
export interface AssistantDeltaEvent {
|
||||
readonly type: 'assistant.delta';
|
||||
readonly turnId: number;
|
||||
readonly delta: string;
|
||||
}
|
||||
|
||||
export interface ThinkingDeltaEvent {
|
||||
readonly type: 'thinking.delta';
|
||||
readonly turnId: number;
|
||||
readonly delta: string;
|
||||
}
|
||||
|
||||
export interface ToolCallDeltaEvent {
|
||||
readonly type: 'tool.call.delta';
|
||||
readonly turnId: number;
|
||||
readonly toolCallId: string;
|
||||
readonly name?: string;
|
||||
readonly argumentsPart?: string;
|
||||
}
|
||||
|
||||
declare module '#/app/event/eventBus' {
|
||||
interface DomainEventMap {
|
||||
'turn.started': TurnStartedEvent;
|
||||
'turn.ended': TurnEndedEvent;
|
||||
'turn.step.started': TurnStepStartedEvent;
|
||||
'turn.step.completed': TurnStepCompletedEvent;
|
||||
'turn.step.interrupted': TurnStepInterruptedEvent;
|
||||
'assistant.delta': AssistantDeltaEvent;
|
||||
'thinking.delta': ThinkingDeltaEvent;
|
||||
'tool.call.delta': ToolCallDeltaEvent;
|
||||
}
|
||||
}
|
||||
|
|
@ -5,12 +5,8 @@ import { LifecycleScope, registerScopedService } from '#/_base/di/scope';
|
|||
import type { Tool as KosongTool } from '#/app/llmProtocol/tool';
|
||||
|
||||
import { Disposable, type IDisposable } from "#/_base/di/lifecycle";
|
||||
import type { KimiErrorPayload } from '#/_base/errors/serialize';
|
||||
import { ErrorCodes, makeErrorPayload } from "#/errors";
|
||||
import type {
|
||||
ErrorEvent,
|
||||
McpServerStatusEvent,
|
||||
ToolListUpdatedEvent,
|
||||
} from '@moonshot-ai/protocol';
|
||||
import { IEventBus } from '#/app/event/eventBus';
|
||||
import { ITelemetryService } from '#/app/telemetry/telemetry';
|
||||
import { sessionMediaOriginalsDir } from '#/agent/media/image-originals';
|
||||
|
|
@ -31,6 +27,31 @@ import {
|
|||
type McpToolCollision,
|
||||
} from './mcpDiscoveryOps';
|
||||
|
||||
export interface ErrorEvent extends KimiErrorPayload {
|
||||
readonly type: 'error';
|
||||
}
|
||||
|
||||
export interface McpServerStatusPayload {
|
||||
readonly name: string;
|
||||
readonly transport: 'stdio' | 'http' | 'sse';
|
||||
readonly status: 'pending' | 'connected' | 'failed' | 'disabled' | 'needs-auth';
|
||||
readonly toolCount: number;
|
||||
readonly error?: string;
|
||||
}
|
||||
|
||||
export interface McpServerStatusEvent {
|
||||
readonly type: 'mcp.server.status';
|
||||
readonly server: McpServerStatusPayload;
|
||||
}
|
||||
|
||||
export type ToolListUpdatedReason = 'mcp.connected' | 'mcp.disconnected' | 'mcp.failed';
|
||||
|
||||
export interface ToolListUpdatedEvent {
|
||||
readonly type: 'tool.list.updated';
|
||||
readonly reason: ToolListUpdatedReason;
|
||||
readonly serverName: string;
|
||||
}
|
||||
|
||||
declare module '#/app/event/eventBus' {
|
||||
interface DomainEventMap {
|
||||
'mcp.server.status': McpServerStatusEvent;
|
||||
|
|
|
|||
|
|
@ -32,13 +32,20 @@ import {
|
|||
type ExecutableToolResult,
|
||||
} from '#/tool/toolContract';
|
||||
import { toInputJsonSchema } from '#/tool/input-schema';
|
||||
import {
|
||||
MCP_OAUTH_AUTHORIZATION_URL_TOOL_UPDATE,
|
||||
type McpOAuthAuthorizationUrlUpdateData,
|
||||
} from '@moonshot-ai/protocol';
|
||||
import { AlreadyAuthorizedError, type McpOAuthService } from '#/agent/mcp/oauth/service';
|
||||
import { qualifyMcpToolName } from '#/agent/mcp/tool-naming';
|
||||
|
||||
/**
|
||||
* `ToolUpdate.customKind` emitted by the MCP auth tool when the OAuth
|
||||
* authorization URL is ready; clients render it as an actionable login link.
|
||||
*/
|
||||
export const MCP_OAUTH_AUTHORIZATION_URL_TOOL_UPDATE = 'mcp.oauth.authorization_url';
|
||||
|
||||
export interface McpOAuthAuthorizationUrlUpdateData {
|
||||
readonly serverName: string;
|
||||
readonly authorizationUrl: string;
|
||||
}
|
||||
|
||||
const DEFAULT_AUTH_TIMEOUT_MS = 15 * 60 * 1000;
|
||||
|
||||
const AUTH_TOOL_TOOL_NAME = 'authenticate';
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import { IEventBus } from '#/app/event/eventBus';
|
|||
import { ITelemetryService } from '#/app/telemetry/telemetry';
|
||||
import { ISessionApprovalService } from "#/session/approval/approval";
|
||||
import { ISessionContext } from '#/session/sessionContext/sessionContext';
|
||||
import type { ToolInputDisplay } from '@moonshot-ai/protocol';
|
||||
import type { ToolInputDisplay } from '#/tool/toolInputDisplay';
|
||||
import {
|
||||
IAgentPermissionGate,
|
||||
} from './permissionGate';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { PrepareToolExecutionResult, ResolvedToolExecutionHookContext } from '#/agent/toolExecutor/toolHooks';
|
||||
import type { ToolInputDisplay } from '@moonshot-ai/protocol';
|
||||
import type { ToolInputDisplay } from '#/tool/toolInputDisplay';
|
||||
import type { PermissionRule } from '#/agent/permissionRules/permissionRules';
|
||||
|
||||
export type PermissionMode = 'manual' | 'yolo' | 'auto';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { createDecorator } from "#/_base/di/instantiation";
|
||||
import type { ApprovalResponse } from "@moonshot-ai/protocol";
|
||||
import type { ApprovalResponse } from "#/session/approval/approval";
|
||||
|
||||
export interface PermissionApprovalResultRecord {
|
||||
readonly turnId: number;
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
* keeps the user-approved output and the `approved` outcome.
|
||||
*/
|
||||
|
||||
import type { ToolInputDisplay } from '@moonshot-ai/protocol';
|
||||
import type { ToolInputDisplay } from '#/tool/toolInputDisplay';
|
||||
import { z } from 'zod';
|
||||
|
||||
import type { BuiltinTool, ExecutableToolResult, ToolExecution } from '#/tool/toolContract';
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import type { ThinkingEffort } from '#/app/llmProtocol/thinkingEffort';
|
|||
import type { Model } from '#/app/model/modelInstance';
|
||||
|
||||
import { createDecorator } from "#/_base/di/instantiation";
|
||||
import type { ErrorCode } from '#/_base/errors/codes';
|
||||
import type { ErrorCode } from '#/errors';
|
||||
import { Error2 } from '#/_base/errors/errors';
|
||||
import type { ToolSource } from '#/tool/toolContract';
|
||||
|
||||
|
|
|
|||
|
|
@ -51,7 +51,6 @@ import { ISessionWorkspaceContext } from '#/session/workspaceContext/workspaceCo
|
|||
import { ISessionSkillCatalog } from '#/session/sessionSkillCatalog/skillCatalog';
|
||||
import type { ResolvedAgentProfile, SystemPromptContext } from '#/agent/profile/profile';
|
||||
|
||||
import type { WarningEvent } from '@moonshot-ai/protocol';
|
||||
import { ITelemetryService } from '#/app/telemetry/telemetry';
|
||||
import { IAgentTelemetryContextService } from '#/app/telemetry/agentTelemetryContext';
|
||||
import { IWireService } from '#/wire/wire';
|
||||
|
|
@ -81,6 +80,12 @@ import {
|
|||
type ProfileModelState,
|
||||
} from './profileOps';
|
||||
|
||||
export interface WarningEvent {
|
||||
readonly type: 'warning';
|
||||
readonly message: string;
|
||||
readonly code?: string;
|
||||
}
|
||||
|
||||
declare module '#/app/event/eventBus' {
|
||||
interface DomainEventMap {
|
||||
warning: WarningEvent;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import type { PermissionData, PermissionMode } from '#/agent/permissionPolicy/ty
|
|||
import type { PlanData } from '#/agent/plan/plan';
|
||||
import type { ToolInfo } from '#/tool/toolContract';
|
||||
import type { SessionSummary } from '#/agent/rpc/core-api';
|
||||
import type { UsageStatus } from '@moonshot-ai/protocol';
|
||||
import type { UsageStatus } from '#/agent/usage/usage';
|
||||
import type { SessionMeta } from '#/session/sessionMetadata/sessionMetadata';
|
||||
|
||||
type AgentType = 'main' | 'sub';
|
||||
|
|
|
|||
|
|
@ -20,7 +20,8 @@ import type { ExperimentalFeatureState } from '#/app/flag/flag';
|
|||
import type { ResumeSessionResult } from '#/agent/replayBuilder/types';
|
||||
import type { SessionMeta } from '#/session/sessionMetadata/sessionMetadata';
|
||||
import type { ContentPart } from '#/app/llmProtocol/message';
|
||||
import type { SessionWarning, UsageStatus } from '@moonshot-ai/protocol';
|
||||
import type { SessionWarning } from '#/app/sessionLegacy/sessionWire';
|
||||
import type { UsageStatus } from '#/agent/usage/usage';
|
||||
|
||||
import type { ExportSessionPayload, ExportSessionResult } from '#/app/sessionExport/sessionExport';
|
||||
import type { PluginCommandDef, PluginInfo, PluginSummary, ReloadSummary } from '#/app/plugin/types';
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import { IAgentContextMemoryService } from '#/agent/contextMemory/contextMemory'
|
|||
import { IAgentContextSizeService } from '#/agent/contextSize/contextSize';
|
||||
import { IAgentFullCompactionService } from '#/agent/fullCompaction/fullCompaction';
|
||||
import { IAgentGoalService } from '#/agent/goal/goal';
|
||||
import type { PluginCommandActivatedEvent } from '@moonshot-ai/protocol';
|
||||
import { IEventBus } from '#/app/event/eventBus';
|
||||
import { IEventService } from '#/app/event/event';
|
||||
import { ErrorCodes, Error2 } from '#/errors';
|
||||
|
|
@ -65,6 +64,15 @@ import {
|
|||
promptMetadataTextFromSkill,
|
||||
} from './prompt-metadata';
|
||||
|
||||
export interface PluginCommandActivatedEvent {
|
||||
readonly type: 'plugin_command.activated';
|
||||
readonly activationId: string;
|
||||
readonly pluginId: string;
|
||||
readonly commandName: string;
|
||||
readonly commandArgs?: string;
|
||||
readonly trigger: 'user-slash';
|
||||
}
|
||||
|
||||
declare module '#/app/event/eventBus' {
|
||||
interface DomainEventMap {
|
||||
'plugin_command.activated': PluginCommandActivatedEvent;
|
||||
|
|
|
|||
|
|
@ -9,8 +9,6 @@
|
|||
* scope.
|
||||
*/
|
||||
|
||||
import type { ShellOutputEvent, ShellStartedEvent } from '@moonshot-ai/protocol';
|
||||
|
||||
import { InstantiationType } from '#/_base/di/extensions';
|
||||
import { LifecycleScope, registerScopedService } from '#/_base/di/scope';
|
||||
import { userCancellationReason } from '#/_base/utils/abort';
|
||||
|
|
@ -27,6 +25,28 @@ import {
|
|||
type RunShellCommandResult,
|
||||
} from './shellCommand';
|
||||
|
||||
/**
|
||||
* Live stdout/stderr chunk from a user-initiated `!` shell command. Transient
|
||||
* (never persisted, never replayed) — the final output is still recorded once
|
||||
* via `context.append_message` on completion. `commandId` lets the TUI route
|
||||
* chunks to the matching live entry and drop stale events from a prior run.
|
||||
*/
|
||||
export interface ShellOutputEvent {
|
||||
readonly type: 'shell.output';
|
||||
readonly commandId: string;
|
||||
readonly update: ToolUpdate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired once when a `!` shell command's foreground process task is registered,
|
||||
* carrying the task id so the client can detach (ctrl+b) it. Transient.
|
||||
*/
|
||||
export interface ShellStartedEvent {
|
||||
readonly type: 'shell.started';
|
||||
readonly commandId: string;
|
||||
readonly taskId: string;
|
||||
}
|
||||
|
||||
declare module '#/app/event/eventBus' {
|
||||
interface DomainEventMap {
|
||||
'shell.output': ShellOutputEvent;
|
||||
|
|
|
|||
|
|
@ -13,8 +13,6 @@
|
|||
* before the first turn runs (same rationale as `fullCompaction`).
|
||||
*/
|
||||
|
||||
import type { TurnStepRetryingEvent } from '@moonshot-ai/protocol';
|
||||
|
||||
import { Disposable } from '#/_base/di/lifecycle';
|
||||
import { InstantiationType } from '#/_base/di/extensions';
|
||||
import { LifecycleScope, registerScopedService } from '#/_base/di/scope';
|
||||
|
|
@ -37,6 +35,20 @@ import { LOOP_CONTROL_SECTION, type LoopControl } from '#/agent/loop/configSecti
|
|||
|
||||
import { IAgentStepRetryService } from './stepRetry';
|
||||
|
||||
export interface TurnStepRetryingEvent {
|
||||
readonly type: 'turn.step.retrying';
|
||||
readonly turnId: number;
|
||||
readonly step: number;
|
||||
readonly stepId?: string;
|
||||
readonly failedAttempt: number;
|
||||
readonly nextAttempt: number;
|
||||
readonly maxAttempts: number;
|
||||
readonly delayMs: number;
|
||||
readonly errorName: string;
|
||||
readonly errorMessage: string;
|
||||
readonly statusCode?: number;
|
||||
}
|
||||
|
||||
declare module '#/app/event/eventBus' {
|
||||
interface DomainEventMap {
|
||||
'turn.step.retrying': TurnStepRetryingEvent;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
/**
|
||||
* `toolExecutor` domain — the `tool.call.*` / `tool.progress` / `tool.result`
|
||||
* event payloads published through `IEventBus` as tool calls execute.
|
||||
*/
|
||||
|
||||
import type { ToolUpdate } from '#/tool/toolContract';
|
||||
import type { ToolInputDisplay } from '#/tool/toolInputDisplay';
|
||||
|
||||
export interface ToolCallStartedEvent {
|
||||
readonly type: 'tool.call.started';
|
||||
readonly turnId: number;
|
||||
readonly toolCallId: string;
|
||||
readonly name: string;
|
||||
readonly args: unknown;
|
||||
readonly description?: string;
|
||||
readonly display?: ToolInputDisplay;
|
||||
}
|
||||
|
||||
export interface ToolProgressEvent {
|
||||
readonly type: 'tool.progress';
|
||||
readonly turnId: number;
|
||||
readonly toolCallId: string;
|
||||
readonly update: ToolUpdate;
|
||||
}
|
||||
|
||||
export interface ToolResultEvent {
|
||||
readonly type: 'tool.result';
|
||||
readonly turnId: number;
|
||||
readonly toolCallId: string;
|
||||
readonly output: unknown;
|
||||
readonly isError?: boolean;
|
||||
readonly synthetic?: boolean;
|
||||
}
|
||||
|
||||
declare module '#/app/event/eventBus' {
|
||||
interface DomainEventMap {
|
||||
'tool.call.started': ToolCallStartedEvent;
|
||||
'tool.result': ToolResultEvent;
|
||||
'tool.progress': ToolProgressEvent;
|
||||
}
|
||||
}
|
||||
|
|
@ -11,12 +11,7 @@ import { InstantiationType } from '#/_base/di/extensions';
|
|||
import { toDisposable } from '#/_base/di/lifecycle';
|
||||
import { LifecycleScope, registerScopedService } from '#/_base/di/scope';
|
||||
import type { ContentPart } from '#/app/llmProtocol/message';
|
||||
import type {
|
||||
ToolCallStartedEvent,
|
||||
ToolInputDisplay,
|
||||
ToolProgressEvent,
|
||||
ToolResultEvent,
|
||||
} from '@moonshot-ai/protocol';
|
||||
import type { ToolInputDisplay } from '#/tool/toolInputDisplay';
|
||||
|
||||
import {
|
||||
compileToolArgsValidator,
|
||||
|
|
@ -54,14 +49,10 @@ import {
|
|||
type UnavailableToolDescriber,
|
||||
} from './toolExecutor';
|
||||
import { ToolScheduler } from './toolScheduler';
|
||||
|
||||
declare module '#/app/event/eventBus' {
|
||||
interface DomainEventMap {
|
||||
'tool.call.started': ToolCallStartedEvent;
|
||||
'tool.result': ToolResultEvent;
|
||||
'tool.progress': ToolProgressEvent;
|
||||
}
|
||||
}
|
||||
// Loads the `DomainEventMap` augmentation for the `tool.call.*` / `tool.result`
|
||||
// events this service publishes (the augmentation lives with the event
|
||||
// definitions; without an import it would not enter every consumer's program).
|
||||
import './toolExecutorEvents';
|
||||
|
||||
const ABORT_GRACE_MS = 2_000;
|
||||
const TOOL_OUTPUT_EMPTY = 'Tool output is empty.';
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import type { TokenUsage } from '#/app/llmProtocol/usage';
|
|||
|
||||
import { createDecorator } from '#/_base/di/instantiation';
|
||||
import type { Event } from '#/_base/event';
|
||||
import type { ErrorCode } from '#/_base/errors/codes';
|
||||
import type { ErrorCode } from '#/errors';
|
||||
import { Error2 } from '#/_base/errors/errors';
|
||||
|
||||
import { UsageErrors } from './errors';
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@
|
|||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
import type { AgentPhase } from '@moonshot-ai/protocol';
|
||||
|
||||
import { addUsage, type TokenUsage } from '#/app/llmProtocol/usage';
|
||||
import { defineModel } from '#/wire/model';
|
||||
|
|
@ -34,7 +33,6 @@ declare module '#/app/event/eventBus' {
|
|||
thinkingEffort?: string;
|
||||
maxContextTokens?: number;
|
||||
contextTokens?: number;
|
||||
phase?: AgentPhase;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,20 +17,19 @@ import type {
|
|||
KimiOAuthLogoutResult,
|
||||
KimiOAuthTokenRef,
|
||||
} from '@moonshot-ai/kimi-code-oauth';
|
||||
import type {
|
||||
OAuthFlowSnapshot,
|
||||
OAuthFlowStart,
|
||||
OAuthLoginCancelResponse,
|
||||
OAuthLogoutResponse,
|
||||
RefreshOAuthProviderModelsResponse,
|
||||
} from '@moonshot-ai/protocol';
|
||||
|
||||
import { createDecorator, type ServiceIdentifier } from '#/_base/di/instantiation';
|
||||
import { Error2 } from '#/_base/errors/errors';
|
||||
|
||||
import type { OAuthRef } from '#/app/provider/provider';
|
||||
|
||||
import { AuthErrors } from './errors';
|
||||
import type {
|
||||
OAuthFlowSnapshot,
|
||||
OAuthFlowStart,
|
||||
OAuthLoginCancelResponse,
|
||||
OAuthLogoutResponse,
|
||||
RefreshOAuthProviderModelsResponse,
|
||||
} from './oauthWire';
|
||||
|
||||
export interface AuthStatus {
|
||||
readonly loggedIn: boolean;
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ import type {
|
|||
OAuthLoginCancelResponse,
|
||||
OAuthLogoutResponse,
|
||||
RefreshOAuthProviderModelsResponse,
|
||||
} from '@moonshot-ai/protocol';
|
||||
} from './oauthWire';
|
||||
|
||||
import { InstantiationType } from '#/_base/di/extensions';
|
||||
import { Disposable } from '#/_base/di/lifecycle';
|
||||
|
|
|
|||
109
packages/agent-core-v2/src/app/auth/oauthWire.ts
Normal file
109
packages/agent-core-v2/src/app/auth/oauthWire.ts
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
/**
|
||||
* `auth` domain — the v1 OAuth wire DTO schemas.
|
||||
*
|
||||
* Request/response shapes of the v1 `/oauth/*` endpoints plus the managed
|
||||
* OAuth provider model-refresh response, defined as zod schemas so the
|
||||
* transports validate against the same contract the `IOAuthService` returns.
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
import { isoDateTimeSchema } from '#/_base/utils/isoDateTime';
|
||||
|
||||
export const oauthFlowStatusEnum = z.enum([
|
||||
'pending',
|
||||
'authenticated',
|
||||
'denied',
|
||||
'expired',
|
||||
'cancelled',
|
||||
]);
|
||||
export type OAuthFlowStatus = z.infer<typeof oauthFlowStatusEnum>;
|
||||
|
||||
/**
|
||||
* Result of `POST /v1/oauth/login`.
|
||||
*
|
||||
* Two shapes, discriminated by `status`:
|
||||
* - `pending`: a real device-code flow was started; the `verification_*`,
|
||||
* `user_code`, `expires_*`, and `interval` fields are populated so the
|
||||
* client can render the device-code step and start polling.
|
||||
* - `authenticated`: the toolkit already had a usable token and short-
|
||||
* circuited via its `ensureFresh` fast path, so no device code was
|
||||
* issued. The client can skip the device-code step and treat the login
|
||||
* as already complete.
|
||||
*/
|
||||
export const oauthFlowStartPendingSchema = z.object({
|
||||
flow_id: z.string().min(1),
|
||||
provider: z.string().min(1),
|
||||
status: z.literal('pending'),
|
||||
verification_uri: z.string().url(),
|
||||
verification_uri_complete: z.string().url(),
|
||||
user_code: z.string().min(1),
|
||||
expires_in: z.number().int().positive(),
|
||||
interval: z.number().int().positive(),
|
||||
expires_at: isoDateTimeSchema,
|
||||
});
|
||||
export type OAuthFlowStartPending = z.infer<typeof oauthFlowStartPendingSchema>;
|
||||
|
||||
export const oauthFlowStartAuthenticatedSchema = z.object({
|
||||
flow_id: z.string().min(1),
|
||||
provider: z.string().min(1),
|
||||
status: z.literal('authenticated'),
|
||||
});
|
||||
export type OAuthFlowStartAuthenticated = z.infer<typeof oauthFlowStartAuthenticatedSchema>;
|
||||
|
||||
export const oauthFlowStartSchema = z.discriminatedUnion('status', [
|
||||
oauthFlowStartPendingSchema,
|
||||
oauthFlowStartAuthenticatedSchema,
|
||||
]);
|
||||
export type OAuthFlowStart = z.infer<typeof oauthFlowStartSchema>;
|
||||
|
||||
export const oauthFlowSnapshotSchema = z.object({
|
||||
flow_id: z.string().min(1),
|
||||
provider: z.string().min(1),
|
||||
status: oauthFlowStatusEnum,
|
||||
verification_uri: z.string().url(),
|
||||
verification_uri_complete: z.string().url(),
|
||||
user_code: z.string().min(1),
|
||||
expires_in: z.number().int().positive(),
|
||||
expires_at: isoDateTimeSchema,
|
||||
interval: z.number().int().positive(),
|
||||
resolved_at: isoDateTimeSchema.optional(),
|
||||
error_message: z.string().optional(),
|
||||
});
|
||||
export type OAuthFlowSnapshot = z.infer<typeof oauthFlowSnapshotSchema>;
|
||||
|
||||
export const oauthLoginCancelResponseSchema = z.object({
|
||||
cancelled: z.boolean(),
|
||||
status: oauthFlowStatusEnum,
|
||||
});
|
||||
export type OAuthLoginCancelResponse = z.infer<typeof oauthLoginCancelResponseSchema>;
|
||||
|
||||
export const oauthLogoutResponseSchema = z.object({
|
||||
logged_out: z.literal(true),
|
||||
provider: z.string().min(1),
|
||||
});
|
||||
export type OAuthLogoutResponse = z.infer<typeof oauthLogoutResponseSchema>;
|
||||
|
||||
const providerRefreshChangeSchema = z.object({
|
||||
provider_id: z.string().min(1),
|
||||
provider_name: z.string().min(1),
|
||||
added: z.number().int().min(0),
|
||||
removed: z.number().int().min(0),
|
||||
});
|
||||
|
||||
const providerRefreshFailureSchema = z.object({
|
||||
provider: z.string().min(1),
|
||||
reason: z.string().min(1),
|
||||
});
|
||||
|
||||
// Same response shape as the modelCatalog refresh endpoint; defined
|
||||
// self-contained here because the two domains sit at different layers and the
|
||||
// `auth` domain owns the OAuth-provider refresh operation.
|
||||
export const refreshOAuthProviderModelsResponseSchema = z.object({
|
||||
changed: z.array(providerRefreshChangeSchema),
|
||||
unchanged: z.array(z.string().min(1)),
|
||||
failed: z.array(providerRefreshFailureSchema),
|
||||
});
|
||||
export type RefreshOAuthProviderModelsResponse = z.infer<
|
||||
typeof refreshOAuthProviderModelsResponseSchema
|
||||
>;
|
||||
|
|
@ -9,10 +9,32 @@
|
|||
* stateless projector over the global provider / model / credential state.
|
||||
*/
|
||||
|
||||
import type { AuthSummary } from '@moonshot-ai/protocol';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { createDecorator, type ServiceIdentifier } from '#/_base/di/instantiation';
|
||||
|
||||
export const managedProviderStatusSchema = z.enum([
|
||||
'authenticated',
|
||||
'expired',
|
||||
'revoked',
|
||||
'unauthenticated',
|
||||
]);
|
||||
export type ManagedProviderStatus = z.infer<typeof managedProviderStatusSchema>;
|
||||
|
||||
export const managedProviderSummarySchema = z.object({
|
||||
name: z.string().min(1),
|
||||
status: managedProviderStatusSchema,
|
||||
});
|
||||
export type ManagedProviderSummary = z.infer<typeof managedProviderSummarySchema>;
|
||||
|
||||
export const authSummarySchema = z.object({
|
||||
ready: z.boolean(),
|
||||
providers_count: z.number().int().nonnegative(),
|
||||
default_model: z.string().nullable(),
|
||||
managed_provider: managedProviderSummarySchema.nullable(),
|
||||
});
|
||||
export type AuthSummary = z.infer<typeof authSummarySchema>;
|
||||
|
||||
export interface IAuthLegacyService {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
*/
|
||||
|
||||
import { KIMI_CODE_PROVIDER_NAME } from '@moonshot-ai/kimi-code-oauth';
|
||||
import type { AuthSummary } from '@moonshot-ai/protocol';
|
||||
import type { AuthSummary } from './authLegacy';
|
||||
|
||||
import { InstantiationType } from '#/_base/di/extensions';
|
||||
import { LifecycleScope, registerScopedService } from '#/_base/di/scope';
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
* them without pulling in the rest of the cron stack.
|
||||
*/
|
||||
|
||||
import type { CronJobOrigin } from '@moonshot-ai/protocol';
|
||||
import type { CronJobOrigin } from '#/agent/contextMemory/types';
|
||||
|
||||
export function formatLocalIsoWithOffset(ms: number): string {
|
||||
const date = new Date(ms);
|
||||
|
|
|
|||
|
|
@ -8,11 +8,22 @@
|
|||
|
||||
import type { Readable } from 'node:stream';
|
||||
|
||||
import type { FileMeta } from '@moonshot-ai/protocol';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { createDecorator, type ServiceIdentifier } from '#/_base/di/instantiation';
|
||||
import { registerErrorDomain, type ErrorDomain } from '#/_base/errors/codes';
|
||||
import { Error2 } from '#/_base/errors/errors';
|
||||
import { isoDateTimeSchema } from '#/_base/utils/isoDateTime';
|
||||
|
||||
export const fileMetaSchema = z.object({
|
||||
id: z.string().min(1),
|
||||
name: z.string().min(1),
|
||||
media_type: z.string().min(1),
|
||||
size: z.number().int().nonnegative(),
|
||||
created_at: isoDateTimeSchema,
|
||||
expires_at: isoDateTimeSchema.optional(),
|
||||
});
|
||||
export type FileMeta = z.infer<typeof fileMetaSchema>;
|
||||
|
||||
export const DEFAULT_MAX_UPLOAD_BYTES = 50 * 1024 * 1024;
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
import { randomUUID } from 'node:crypto';
|
||||
import { Readable } from 'node:stream';
|
||||
|
||||
import type { FileMeta } from '@moonshot-ai/protocol';
|
||||
import type { FileMeta } from './fileService';
|
||||
|
||||
import { InstantiationType } from '#/_base/di/extensions';
|
||||
import { LifecycleScope, registerScopedService } from '#/_base/di/scope';
|
||||
|
|
|
|||
|
|
@ -9,8 +9,62 @@
|
|||
* already-resolved absolute `cwd` and repo-relative paths.
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
import { createDecorator, type ServiceIdentifier } from '#/_base/di/instantiation';
|
||||
import type { FsDiffResponse, FsGitStatusResponse } from '@moonshot-ai/protocol';
|
||||
|
||||
export const fsGitStatusSchema = z.enum([
|
||||
'clean',
|
||||
'modified',
|
||||
'added',
|
||||
'deleted',
|
||||
'renamed',
|
||||
'untracked',
|
||||
'ignored',
|
||||
'conflicted',
|
||||
]);
|
||||
export type FsGitStatus = z.infer<typeof fsGitStatusSchema>;
|
||||
|
||||
export const fsPullRequestSchema = z.object({
|
||||
number: z.number().int().positive(),
|
||||
state: z.enum(['open', 'merged', 'closed', 'draft']),
|
||||
url: z.string().url(),
|
||||
});
|
||||
export type FsPullRequest = z.infer<typeof fsPullRequestSchema>;
|
||||
|
||||
export const fsGitStatusRequestSchema = z.object({
|
||||
paths: z.array(z.string().min(1)).optional(),
|
||||
});
|
||||
export type FsGitStatusRequest = z.infer<typeof fsGitStatusRequestSchema>;
|
||||
|
||||
export const fsGitStatusResponseSchema = z.object({
|
||||
branch: z.string(),
|
||||
ahead: z.number().int().nonnegative(),
|
||||
behind: z.number().int().nonnegative(),
|
||||
entries: z.record(z.string(), fsGitStatusSchema),
|
||||
// Aggregate working-tree diff against HEAD (`git diff --numstat HEAD`):
|
||||
// summed added/deleted lines across all changed files. Binary files (numstat
|
||||
// `-`) contribute 0. Both 0 for a clean tree or a repo with no commits yet.
|
||||
additions: z.number().int().nonnegative(),
|
||||
deletions: z.number().int().nonnegative(),
|
||||
// GitHub pull request for the current branch, looked up via `gh pr view`.
|
||||
// Null when not a GitHub repo, `gh` is unavailable/unauthenticated, the
|
||||
// branch has no PR, or the lookup failed/timed out. Never fails the request.
|
||||
pullRequest: fsPullRequestSchema.nullable(),
|
||||
});
|
||||
export type FsGitStatusResponse = z.infer<typeof fsGitStatusResponseSchema>;
|
||||
|
||||
export const fsDiffRequestSchema = z.object({
|
||||
path: z.string().min(1),
|
||||
});
|
||||
export type FsDiffRequest = z.infer<typeof fsDiffRequestSchema>;
|
||||
|
||||
export const fsDiffResponseSchema = z.object({
|
||||
path: z.string(),
|
||||
diff: z.string(),
|
||||
truncated: z.boolean(),
|
||||
});
|
||||
export type FsDiffResponse = z.infer<typeof fsDiffResponseSchema>;
|
||||
|
||||
export interface IGitService {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
* `services/fs/fsGit.ts`).
|
||||
*/
|
||||
|
||||
import type { FsGitStatus, FsGitStatusResponse, FsPullRequest } from '@moonshot-ai/protocol';
|
||||
import type { FsGitStatus, FsGitStatusResponse, FsPullRequest } from './git';
|
||||
|
||||
export function parsePorcelain(
|
||||
stdout: string,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
* paths.
|
||||
*/
|
||||
|
||||
import type { FsDiffResponse, FsGitStatusResponse, FsPullRequest } from '@moonshot-ai/protocol';
|
||||
import type { FsDiffResponse, FsGitStatusResponse, FsPullRequest } from './git';
|
||||
|
||||
import { InstantiationType } from '#/_base/di/extensions';
|
||||
import { LifecycleScope, registerScopedService } from '#/_base/di/scope';
|
||||
|
|
|
|||
|
|
@ -6,17 +6,42 @@
|
|||
* folder. Distinct from the Session-side `sessionFs`, which is sandboxed and may
|
||||
* be remote. App-scoped.
|
||||
*
|
||||
* The wire shapes (`FsBrowseResponse` / `FsHomeResponse`) are sourced from
|
||||
* `@moonshot-ai/protocol` so the `/api/v1` and `/api/v2` transports share one
|
||||
* contract. Domain errors (`HostFolder*Error`) carry the failing path and are
|
||||
* translated to protocol error codes at the transport boundary.
|
||||
* The wire shapes (`FsBrowseResponse` / `FsHomeResponse`) are defined here as
|
||||
* zod schemas so the `/api/v1` and `/api/v2` transports share one contract.
|
||||
* Domain errors (`HostFolder*Error`) carry the failing path and are
|
||||
* translated to wire error codes at the transport boundary.
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
import { createDecorator, type ServiceIdentifier } from '#/_base/di/instantiation';
|
||||
|
||||
import type { FsBrowseResponse, FsHomeResponse } from '@moonshot-ai/protocol';
|
||||
export const fsBrowseQuerySchema = z.object({
|
||||
path: z.string().min(1).optional(),
|
||||
});
|
||||
export type FsBrowseQuery = z.infer<typeof fsBrowseQuerySchema>;
|
||||
|
||||
export type { FsBrowseResponse, FsHomeResponse };
|
||||
export const fsBrowseEntrySchema = z.object({
|
||||
name: z.string().min(1),
|
||||
path: z.string().min(1),
|
||||
is_dir: z.literal(true),
|
||||
is_git_repo: z.boolean(),
|
||||
branch: z.string().optional(),
|
||||
});
|
||||
export type FsBrowseEntry = z.infer<typeof fsBrowseEntrySchema>;
|
||||
|
||||
export const fsBrowseResponseSchema = z.object({
|
||||
path: z.string().min(1),
|
||||
parent: z.string().min(1).nullable(),
|
||||
entries: z.array(fsBrowseEntrySchema),
|
||||
});
|
||||
export type FsBrowseResponse = z.infer<typeof fsBrowseResponseSchema>;
|
||||
|
||||
export const fsHomeResponseSchema = z.object({
|
||||
home: z.string().min(1),
|
||||
recent_roots: z.array(z.string().min(1)),
|
||||
});
|
||||
export type FsHomeResponse = z.infer<typeof fsHomeResponseSchema>;
|
||||
|
||||
export class HostFolderNotAbsoluteError extends Error {
|
||||
readonly path: string;
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import { lstat, readFile, readdir, realpath } from 'node:fs/promises';
|
|||
import { homedir } from 'node:os';
|
||||
import { dirname, isAbsolute, join } from 'node:path';
|
||||
|
||||
import type { FsBrowseEntry, FsBrowseResponse, FsHomeResponse } from '@moonshot-ai/protocol';
|
||||
import type { FsBrowseEntry, FsBrowseResponse, FsHomeResponse } from './hostFolderBrowser';
|
||||
|
||||
import { InstantiationType } from '#/_base/di/extensions';
|
||||
import { LifecycleScope, registerScopedService } from '#/_base/di/scope';
|
||||
|
|
|
|||
|
|
@ -19,15 +19,22 @@
|
|||
* - `message.not_found` → 40403
|
||||
*/
|
||||
|
||||
import type {
|
||||
CursorQuery,
|
||||
Message,
|
||||
MessageRole,
|
||||
PageResponse,
|
||||
} from '@moonshot-ai/protocol';
|
||||
import type { Message, MessageRole } from '#/agent/contextMemory/wireMessage';
|
||||
|
||||
import { createDecorator, type ServiceIdentifier } from '#/_base/di/instantiation';
|
||||
|
||||
/** Cursor pagination query shared by the v1 history/list endpoints. */
|
||||
export interface CursorQuery {
|
||||
before_id?: string | undefined;
|
||||
after_id?: string | undefined;
|
||||
page_size?: number | undefined;
|
||||
}
|
||||
|
||||
export interface PageResponse<T> {
|
||||
items: T[];
|
||||
has_more: boolean;
|
||||
}
|
||||
|
||||
export interface MessageListQuery extends CursorQuery {
|
||||
readonly role?: MessageRole;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,9 @@
|
|||
* (`packages/agent-core/src/services/message/messageService.ts`).
|
||||
*/
|
||||
|
||||
import type { Message, PageResponse } from '@moonshot-ai/protocol';
|
||||
import type { Message } from '#/agent/contextMemory/wireMessage';
|
||||
|
||||
import type { PageResponse } from './messageLegacy';
|
||||
|
||||
import { InstantiationType } from '#/_base/di/extensions';
|
||||
import { type IAgentScopeHandle, LifecycleScope, registerScopedService } from '#/_base/di/scope';
|
||||
|
|
|
|||
|
|
@ -3,20 +3,15 @@
|
|||
* model aliases, plus the global default-model selection.
|
||||
*
|
||||
* Projects the `provider` / `model` configuration registries into the
|
||||
* protocol `ProviderCatalogItem` / `ModelCatalogItem` wire shapes that the
|
||||
* edge (`server-v2` `/api/v1` routes) serves. App-scoped — provider and
|
||||
* `ProviderCatalogItem` / `ModelCatalogItem` wire shapes (defined below as zod
|
||||
* schemas) that the edge (`server-v2` `/api/v1` routes) serves. App-scoped — provider and
|
||||
* model configuration is global and shared across sessions. This domain is a
|
||||
* thin facade over `provider`, `model`, `config`, and `auth`; it owns no
|
||||
* persistence of its own. The OAuth-provider model refresh lives in
|
||||
* The OAuth-provider model refresh lives in `auth` (`IOAuthService`), not here.
|
||||
*/
|
||||
|
||||
import type {
|
||||
ModelCatalogItem,
|
||||
ProviderCatalogItem,
|
||||
RefreshProviderModelsResponse,
|
||||
SetDefaultModelResponse,
|
||||
} from '@moonshot-ai/protocol';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { createDecorator, type ServiceIdentifier } from '#/_base/di/instantiation';
|
||||
|
||||
|
|
@ -24,6 +19,64 @@ import type { ModelAlias } from '#/app/model/model';
|
|||
import { effectiveModelConfig } from '#/app/model/modelAuth';
|
||||
import type { ProviderConfig } from '#/app/provider/provider';
|
||||
|
||||
export const modelCatalogItemSchema = z.object({
|
||||
provider: z.string().min(1),
|
||||
model: z.string().min(1),
|
||||
display_name: z.string().min(1).optional(),
|
||||
max_context_size: z.number().int().min(1),
|
||||
capabilities: z.array(z.string()).optional(),
|
||||
support_efforts: z.array(z.string()).optional(),
|
||||
default_effort: z.string().optional(),
|
||||
});
|
||||
export type ModelCatalogItem = z.infer<typeof modelCatalogItemSchema>;
|
||||
|
||||
export const providerCatalogStatusSchema = z.enum([
|
||||
'connected',
|
||||
'error',
|
||||
'unconfigured',
|
||||
]);
|
||||
export type ProviderCatalogStatus = z.infer<typeof providerCatalogStatusSchema>;
|
||||
|
||||
export const providerCatalogItemSchema = z.object({
|
||||
id: z.string().min(1),
|
||||
type: z.string().min(1),
|
||||
base_url: z.string().min(1).optional(),
|
||||
default_model: z.string().min(1).optional(),
|
||||
has_api_key: z.boolean(),
|
||||
status: providerCatalogStatusSchema,
|
||||
models: z.array(z.string().min(1)).optional(),
|
||||
});
|
||||
export type ProviderCatalogItem = z.infer<typeof providerCatalogItemSchema>;
|
||||
|
||||
export const providerRefreshChangeSchema = z.object({
|
||||
provider_id: z.string().min(1),
|
||||
provider_name: z.string().min(1),
|
||||
added: z.number().int().min(0),
|
||||
removed: z.number().int().min(0),
|
||||
});
|
||||
export type ProviderRefreshChange = z.infer<typeof providerRefreshChangeSchema>;
|
||||
|
||||
export const providerRefreshFailureSchema = z.object({
|
||||
provider: z.string().min(1),
|
||||
reason: z.string().min(1),
|
||||
});
|
||||
export type ProviderRefreshFailure = z.infer<typeof providerRefreshFailureSchema>;
|
||||
|
||||
export const setDefaultModelResponseSchema = z.object({
|
||||
default_model: z.string().min(1),
|
||||
model: modelCatalogItemSchema,
|
||||
});
|
||||
export type SetDefaultModelResponse = z.infer<typeof setDefaultModelResponseSchema>;
|
||||
|
||||
export const refreshProviderModelsResponseSchema = z.object({
|
||||
changed: z.array(providerRefreshChangeSchema),
|
||||
unchanged: z.array(z.string().min(1)),
|
||||
failed: z.array(providerRefreshFailureSchema),
|
||||
});
|
||||
export type RefreshProviderModelsResponse = z.infer<
|
||||
typeof refreshProviderModelsResponseSchema
|
||||
>;
|
||||
|
||||
export type RefreshProviderModelsScope = 'all' | 'oauth';
|
||||
|
||||
export interface RefreshProviderModelsOptions {
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import type {
|
|||
ProviderCatalogItem,
|
||||
RefreshProviderModelsResponse,
|
||||
SetDefaultModelResponse,
|
||||
} from '@moonshot-ai/protocol';
|
||||
} from './modelCatalog';
|
||||
|
||||
import { InstantiationType } from '#/_base/di/extensions';
|
||||
import { LifecycleScope, registerScopedService } from '#/_base/di/scope';
|
||||
|
|
|
|||
|
|
@ -22,14 +22,12 @@
|
|||
* it is a stateless dispatcher that resolves the target session/agent per call.
|
||||
*/
|
||||
|
||||
import type {
|
||||
GoalSnapshot,
|
||||
SessionStatusResponse,
|
||||
UpdateSessionProfileRequest,
|
||||
} from '@moonshot-ai/protocol';
|
||||
import type { GoalSnapshot } from '#/agent/goal/types';
|
||||
|
||||
import { createDecorator, type ServiceIdentifier } from '#/_base/di/instantiation';
|
||||
|
||||
import type { SessionStatusResponse, UpdateSessionProfileRequest } from './sessionWire';
|
||||
|
||||
export interface SessionWireFields {
|
||||
readonly id: string;
|
||||
readonly workspaceId: string;
|
||||
|
|
|
|||
|
|
@ -13,11 +13,9 @@
|
|||
* the real work stays in the native services.
|
||||
*/
|
||||
|
||||
import type {
|
||||
GoalSnapshot,
|
||||
SessionStatusResponse,
|
||||
UpdateSessionProfileRequest,
|
||||
} from '@moonshot-ai/protocol';
|
||||
import type { GoalSnapshot } from '#/agent/goal/types';
|
||||
|
||||
import type { SessionStatusResponse, UpdateSessionProfileRequest } from './sessionWire';
|
||||
|
||||
import { InstantiationType } from '#/_base/di/extensions';
|
||||
import { type IAgentScopeHandle, LifecycleScope, registerScopedService } from '#/_base/di/scope';
|
||||
|
|
|
|||
102
packages/agent-core-v2/src/app/sessionLegacy/sessionWire.ts
Normal file
102
packages/agent-core-v2/src/app/sessionLegacy/sessionWire.ts
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
/**
|
||||
* `sessionLegacy` domain — the v1 session wire DTO schemas.
|
||||
*
|
||||
* These zod schemas define the request/response shapes of the v1 session
|
||||
* endpoints this adapter backs (`POST /sessions/{id}/profile`,
|
||||
* `GET /sessions/{id}/status`, session warnings); the transports validate
|
||||
* against them and the adapter's contract consumes the inferred types.
|
||||
* Field-level changes here are wire breaks — see the schema-fidelity rule in
|
||||
* `server-align.md`.
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
import { isoDateTimeSchema } from '#/_base/utils/isoDateTime';
|
||||
|
||||
export const sessionStatusSchema = z.enum([
|
||||
'idle',
|
||||
'running',
|
||||
'awaiting_approval',
|
||||
'awaiting_question',
|
||||
'aborted',
|
||||
]);
|
||||
export type SessionStatus = z.infer<typeof sessionStatusSchema>;
|
||||
|
||||
export const sessionWarningSchema = z.object({
|
||||
code: z.string(),
|
||||
message: z.string(),
|
||||
severity: z.enum(['info', 'warning', 'error']),
|
||||
});
|
||||
export type SessionWarning = z.infer<typeof sessionWarningSchema>;
|
||||
|
||||
export const sessionWarningsResponseSchema = z.object({
|
||||
warnings: z.array(sessionWarningSchema),
|
||||
});
|
||||
export type SessionWarningsResponse = z.infer<typeof sessionWarningsResponseSchema>;
|
||||
|
||||
export const promptThinkingSchema = z.string().min(1);
|
||||
export type PromptThinking = z.infer<typeof promptThinkingSchema>;
|
||||
|
||||
export const promptPermissionModeSchema = z.enum(['manual', 'yolo', 'auto']);
|
||||
export type PromptPermissionMode = z.infer<typeof promptPermissionModeSchema>;
|
||||
|
||||
export const sessionMetadataSchema = z
|
||||
.object({
|
||||
cwd: z.string().min(1),
|
||||
})
|
||||
.catchall(z.unknown());
|
||||
export type SessionMetadata = z.infer<typeof sessionMetadataSchema>;
|
||||
|
||||
export const sessionAgentConfigSchema = z.object({
|
||||
model: z.string(),
|
||||
system_prompt: z.string().optional(),
|
||||
tools: z.array(z.string()).optional(),
|
||||
mcp_servers: z.array(z.string()).optional(),
|
||||
thinking: promptThinkingSchema.optional(),
|
||||
permission_mode: promptPermissionModeSchema.optional(),
|
||||
plan_mode: z.boolean().optional(),
|
||||
swarm_mode: z.boolean().optional(),
|
||||
goal_objective: z.string().optional(),
|
||||
goal_control: z.enum(['pause', 'resume', 'cancel']).optional(),
|
||||
});
|
||||
export type SessionAgentConfig = z.infer<typeof sessionAgentConfigSchema>;
|
||||
|
||||
export const sessionAgentConfigPartialSchema = sessionAgentConfigSchema.partial();
|
||||
export type SessionAgentConfigPartial = z.infer<typeof sessionAgentConfigPartialSchema>;
|
||||
|
||||
export const permissionRuleMatcherSchema = z.object({
|
||||
kind: z.enum(['command_prefix', 'path_glob', 'exact_input', 'always']),
|
||||
value: z.string().optional(),
|
||||
});
|
||||
export type PermissionRuleMatcher = z.infer<typeof permissionRuleMatcherSchema>;
|
||||
|
||||
export const permissionRuleSchema = z.object({
|
||||
id: z.string().min(1),
|
||||
tool_name: z.string().min(1),
|
||||
matcher: permissionRuleMatcherSchema.optional(),
|
||||
decision: z.literal('approved'),
|
||||
created_at: isoDateTimeSchema,
|
||||
created_by: z.enum(['user', 'agent']),
|
||||
});
|
||||
export type PermissionRule = z.infer<typeof permissionRuleSchema>;
|
||||
|
||||
export const updateSessionProfileRequestSchema = z.object({
|
||||
title: z.string().min(1).optional(),
|
||||
metadata: sessionMetadataSchema.partial().optional(),
|
||||
agent_config: sessionAgentConfigPartialSchema.optional(),
|
||||
permission_rules: z.array(permissionRuleSchema).optional(),
|
||||
});
|
||||
export type UpdateSessionProfileRequest = z.infer<typeof updateSessionProfileRequestSchema>;
|
||||
|
||||
export const sessionStatusResponseSchema = z.object({
|
||||
status: sessionStatusSchema,
|
||||
model: z.string().optional(),
|
||||
thinking_level: z.string(),
|
||||
permission: z.string(),
|
||||
plan_mode: z.boolean(),
|
||||
swarm_mode: z.boolean(),
|
||||
context_tokens: z.number().int().nonnegative(),
|
||||
max_context_tokens: z.number().int().nonnegative(),
|
||||
context_usage: z.number().min(0).max(1),
|
||||
});
|
||||
export type SessionStatusResponse = z.infer<typeof sessionStatusResponseSchema>;
|
||||
|
|
@ -333,7 +333,7 @@ export class SessionLifecycleService extends Disposable implements ISessionLifec
|
|||
try {
|
||||
const workspace = await this.workspaceRegistry.get(workspaceId);
|
||||
if (workspace === undefined) {
|
||||
throw new Error2('workspace.not_found', `workspace ${workspaceId} does not exist`);
|
||||
throw new Error2(ErrorCodes.WORKSPACE_NOT_FOUND, `workspace ${workspaceId} does not exist`);
|
||||
}
|
||||
|
||||
const sourceMeta =
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
import { registerErrorDomain, type ErrorDomain } from '#/_base/errors/codes';
|
||||
|
||||
export const WorkspaceErrors = {
|
||||
codes: {
|
||||
WORKSPACE_NOT_FOUND: 'workspace.not_found',
|
||||
},
|
||||
} as const satisfies ErrorDomain;
|
||||
|
||||
registerErrorDomain(WorkspaceErrors);
|
||||
|
|
@ -34,6 +34,7 @@ import { StorageErrors } from '#/persistence/interface/storage';
|
|||
import { TerminalErrors } from '#/os/interface/terminalErrors';
|
||||
import { UsageErrors } from '#/agent/usage/errors';
|
||||
import { WireErrors } from '#/wire/errors';
|
||||
import { WorkspaceErrors } from '#/app/workspaceRegistry/errors';
|
||||
|
||||
export * from '#/_base/errors/codes';
|
||||
export * from '#/_base/errors/errorMessage';
|
||||
|
|
@ -66,6 +67,7 @@ export { StorageErrors } from '#/persistence/interface/storage';
|
|||
export { TerminalErrors } from '#/os/interface/terminalErrors';
|
||||
export { UsageErrors } from '#/agent/usage/errors';
|
||||
export { WireErrors } from '#/wire/errors';
|
||||
export { WorkspaceErrors } from '#/app/workspaceRegistry/errors';
|
||||
|
||||
export const ErrorCodes = {
|
||||
...CoreErrors.codes,
|
||||
|
|
@ -95,4 +97,13 @@ export const ErrorCodes = {
|
|||
...TerminalErrors.codes,
|
||||
...UsageErrors.codes,
|
||||
...WireErrors.codes,
|
||||
...WorkspaceErrors.codes,
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* The closed union of every error code a Kimi domain may throw — derived from
|
||||
* the `ErrorCodes` aggregate rather than declared centrally, so each domain's
|
||||
* `errors.ts` is the single source of truth: adding or renaming a code is a
|
||||
* domain-local change with no central list to keep in sync.
|
||||
*/
|
||||
export type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes];
|
||||
|
|
|
|||
|
|
@ -8,24 +8,74 @@
|
|||
* (`ISessionTerminalService`) lives in `src/session/terminal` and is the
|
||||
* surface most business code and the edge consume.
|
||||
*
|
||||
* Wire types (`Terminal`, `CreateTerminalRequest`, frame messages) are sourced
|
||||
* from `@moonshot-ai/protocol`.
|
||||
* Wire types (`Terminal`, `CreateTerminalRequest`, frame messages) are defined
|
||||
* here — the terminal REST schemas as zod, the attach-frame messages as plain
|
||||
* types.
|
||||
*/
|
||||
|
||||
import type {
|
||||
CreateTerminalRequest,
|
||||
Terminal,
|
||||
TerminalExitMessage,
|
||||
TerminalOutputMessage,
|
||||
} from '@moonshot-ai/protocol';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { createDecorator, type ServiceIdentifier } from '#/_base/di/instantiation';
|
||||
import type { Event } from '#/_base/event';
|
||||
import { isoDateTimeSchema } from '#/_base/utils/isoDateTime';
|
||||
|
||||
export type { CreateTerminalRequest, Terminal, TerminalExitMessage, TerminalOutputMessage };
|
||||
const relativeCwdSchema = z
|
||||
.string()
|
||||
.min(1)
|
||||
.refine((value) => !isAbsolutePath(value), 'cwd must be relative to the session workspace');
|
||||
|
||||
export const terminalStatusSchema = z.enum(['running', 'exited']);
|
||||
export type TerminalStatus = z.infer<typeof terminalStatusSchema>;
|
||||
|
||||
export const terminalSchema = z.object({
|
||||
id: z.string().min(1),
|
||||
session_id: z.string().min(1),
|
||||
cwd: z.string().min(1),
|
||||
shell: z.string().min(1),
|
||||
cols: z.number().int().positive(),
|
||||
rows: z.number().int().positive(),
|
||||
status: terminalStatusSchema,
|
||||
created_at: isoDateTimeSchema,
|
||||
exited_at: isoDateTimeSchema.optional(),
|
||||
exit_code: z.number().int().nullable().optional(),
|
||||
});
|
||||
export type Terminal = z.infer<typeof terminalSchema>;
|
||||
|
||||
export const createTerminalRequestSchema = z.object({
|
||||
cwd: relativeCwdSchema.optional(),
|
||||
shell: z.string().min(1).optional(),
|
||||
cols: z.number().int().positive().optional(),
|
||||
rows: z.number().int().positive().optional(),
|
||||
});
|
||||
export type CreateTerminalRequest = z.infer<typeof createTerminalRequestSchema>;
|
||||
|
||||
export interface TerminalOutputMessage {
|
||||
type: 'terminal_output';
|
||||
seq: number;
|
||||
session_id: string;
|
||||
terminal_id: string;
|
||||
timestamp: string;
|
||||
payload: { data: string };
|
||||
}
|
||||
|
||||
export interface TerminalExitMessage {
|
||||
type: 'terminal_exit';
|
||||
session_id: string;
|
||||
terminal_id: string;
|
||||
timestamp: string;
|
||||
payload: { exit_code?: number | null | undefined };
|
||||
}
|
||||
|
||||
export type TerminalFrame = TerminalOutputMessage | TerminalExitMessage;
|
||||
|
||||
function isAbsolutePath(value: string): boolean {
|
||||
return (
|
||||
value.startsWith('/') ||
|
||||
value.startsWith('\\') ||
|
||||
/^[A-Za-z]:[\\/]/.test(value)
|
||||
);
|
||||
}
|
||||
|
||||
export interface TerminalAttachSink {
|
||||
readonly id: string;
|
||||
send(frame: TerminalFrame): void;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
*/
|
||||
|
||||
import { createDecorator, type ServiceIdentifier } from '#/_base/di/instantiation';
|
||||
import type { ToolInputDisplay } from '@moonshot-ai/protocol';
|
||||
import type { ToolInputDisplay } from '#/tool/toolInputDisplay';
|
||||
|
||||
export interface ApprovalRequest {
|
||||
readonly id?: string;
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
* `OP_REGISTRY` at import time.
|
||||
*/
|
||||
|
||||
import type { CronJobOrigin } from '@moonshot-ai/protocol';
|
||||
import type { CronJobOrigin } from '#/agent/contextMemory/types';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { defineModel } from '#/wire/model';
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
import { ulid } from 'ulid';
|
||||
|
||||
import type { ContentPart } from '#/app/llmProtocol/message';
|
||||
import type { CronJobOrigin, CronMissedOrigin } from '@moonshot-ai/protocol';
|
||||
import type { CronJobOrigin, CronMissedOrigin } from '#/agent/contextMemory/types';
|
||||
|
||||
import { Disposable, toDisposable } from '#/_base/di/lifecycle';
|
||||
import { InstantiationType } from '#/_base/di/extensions';
|
||||
|
|
|
|||
|
|
@ -2,36 +2,219 @@
|
|||
* `sessionFs` domain (L2) — wire-shaped filesystem operations.
|
||||
*
|
||||
* Defines the `ISessionFsService` that backs the fs REST surface: content search,
|
||||
* content grep, and git status/diff. It orchestrates the os `IHostFileSystem`
|
||||
* content grep, and git status/diff, together with the zod DTO schemas the
|
||||
* transports validate against. It orchestrates the os `IHostFileSystem`
|
||||
* (file IO, resolved against the workspace root) plus `ISessionProcessRunner`
|
||||
* (for `rg` / `git` / `gh`) and returns protocol-shaped responses.
|
||||
* (for `rg` / `git` / `gh`). Git status/diff DTOs live in the `git` domain.
|
||||
* Session-scoped — the scope itself is the session, so no `sessionId` is
|
||||
* threaded through.
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
import { createDecorator, type ServiceIdentifier } from '#/_base/di/instantiation';
|
||||
import type {
|
||||
FsDiffRequest,
|
||||
FsDiffResponse,
|
||||
FsGitStatusRequest,
|
||||
FsGitStatusResponse,
|
||||
FsGrepRequest,
|
||||
FsGrepResponse,
|
||||
FsListManyRequest,
|
||||
FsListManyResponse,
|
||||
FsListRequest,
|
||||
FsListResponse,
|
||||
FsMkdirRequest,
|
||||
FsMkdirResponse,
|
||||
FsReadRequest,
|
||||
FsReadResponse,
|
||||
FsSearchRequest,
|
||||
FsSearchResponse,
|
||||
FsStatManyRequest,
|
||||
FsStatManyResponse,
|
||||
FsStatRequest,
|
||||
FsStatResponse,
|
||||
} from '@moonshot-ai/protocol';
|
||||
import { isoDateTimeSchema } from '#/_base/utils/isoDateTime';
|
||||
import {
|
||||
fsGitStatusSchema,
|
||||
type FsDiffRequest,
|
||||
type FsDiffResponse,
|
||||
type FsGitStatusRequest,
|
||||
type FsGitStatusResponse,
|
||||
} from '#/app/git/git';
|
||||
|
||||
export {
|
||||
fsDiffRequestSchema,
|
||||
fsDiffResponseSchema,
|
||||
fsGitStatusRequestSchema,
|
||||
fsGitStatusResponseSchema,
|
||||
} from '#/app/git/git';
|
||||
export type { FsDiffRequest, FsDiffResponse, FsGitStatusRequest, FsGitStatusResponse };
|
||||
|
||||
export const fsKindSchema = z.enum(['file', 'directory', 'symlink']);
|
||||
export type FsKind = z.infer<typeof fsKindSchema>;
|
||||
|
||||
export const fsEntrySchema = z.object({
|
||||
path: z.string(),
|
||||
name: z.string(),
|
||||
kind: fsKindSchema,
|
||||
size: z.number().int().nonnegative().optional(),
|
||||
modified_at: isoDateTimeSchema,
|
||||
etag: z.string().optional(),
|
||||
mime: z.string().optional(),
|
||||
language_id: z.string().optional(),
|
||||
is_binary: z.boolean().optional(),
|
||||
is_symlink_to: z.string().optional(),
|
||||
git_status: fsGitStatusSchema.optional(),
|
||||
child_count: z.number().int().nonnegative().optional(),
|
||||
});
|
||||
export type FsEntry = z.infer<typeof fsEntrySchema>;
|
||||
|
||||
export const fsSearchHitSchema = z.object({
|
||||
path: z.string(),
|
||||
name: z.string(),
|
||||
kind: fsKindSchema,
|
||||
score: z.number().min(0).max(1),
|
||||
match_positions: z.array(z.number().int().nonnegative()),
|
||||
});
|
||||
export type FsSearchHit = z.infer<typeof fsSearchHitSchema>;
|
||||
|
||||
export const fsGrepMatchSchema = z.object({
|
||||
line: z.number().int().positive(),
|
||||
col: z.number().int().positive(),
|
||||
text: z.string(),
|
||||
before: z.array(z.string()),
|
||||
after: z.array(z.string()),
|
||||
});
|
||||
export type FsGrepMatch = z.infer<typeof fsGrepMatchSchema>;
|
||||
|
||||
export const fsGrepFileHitSchema = z.object({
|
||||
path: z.string(),
|
||||
matches: z.array(fsGrepMatchSchema),
|
||||
});
|
||||
export type FsGrepFileHit = z.infer<typeof fsGrepFileHitSchema>;
|
||||
|
||||
export const fsListSortSchema = z.enum([
|
||||
'type_first',
|
||||
'name_asc',
|
||||
'name_desc',
|
||||
'mtime_desc',
|
||||
'size_desc',
|
||||
]);
|
||||
export type FsListSort = z.infer<typeof fsListSortSchema>;
|
||||
|
||||
export const fsListRequestSchema = z.object({
|
||||
path: z.string().default('.'),
|
||||
depth: z.number().int().min(1).max(10).default(1),
|
||||
limit: z.number().int().min(1).max(1000).default(200),
|
||||
show_hidden: z.boolean().default(false),
|
||||
follow_gitignore: z.boolean().default(true),
|
||||
exclude_globs: z.array(z.string()).optional(),
|
||||
sort: fsListSortSchema.default('type_first'),
|
||||
include_git_status: z.boolean().default(false),
|
||||
});
|
||||
export type FsListRequest = z.infer<typeof fsListRequestSchema>;
|
||||
|
||||
export const fsListResponseSchema = z.object({
|
||||
items: z.array(fsEntrySchema),
|
||||
children_by_path: z.record(z.string(), z.array(fsEntrySchema)).optional(),
|
||||
truncated: z.boolean(),
|
||||
});
|
||||
export type FsListResponse = z.infer<typeof fsListResponseSchema>;
|
||||
|
||||
export const fsReadEncodingRequestSchema = z.enum(['auto', 'utf-8', 'base64']);
|
||||
export const fsReadEncodingResponseSchema = z.enum(['utf-8', 'base64']);
|
||||
export type FsReadEncoding = z.infer<typeof fsReadEncodingResponseSchema>;
|
||||
|
||||
export const fsReadRequestSchema = z.object({
|
||||
path: z.string().min(1),
|
||||
offset: z.number().int().nonnegative().default(0),
|
||||
length: z.number().int().min(1).max(10_485_760).default(1_048_576),
|
||||
encoding: fsReadEncodingRequestSchema.default('auto'),
|
||||
});
|
||||
export type FsReadRequest = z.infer<typeof fsReadRequestSchema>;
|
||||
|
||||
export const fsReadResponseSchema = z.object({
|
||||
path: z.string(),
|
||||
content: z.string(),
|
||||
encoding: fsReadEncodingResponseSchema,
|
||||
size: z.number().int().nonnegative(),
|
||||
truncated: z.boolean(),
|
||||
etag: z.string(),
|
||||
mime: z.string(),
|
||||
language_id: z.string().optional(),
|
||||
line_count: z.number().int().nonnegative().optional(),
|
||||
is_binary: z.boolean(),
|
||||
});
|
||||
export type FsReadResponse = z.infer<typeof fsReadResponseSchema>;
|
||||
|
||||
export const fsMkdirRequestSchema = z.object({
|
||||
path: z.string().min(1),
|
||||
recursive: z.boolean().default(false),
|
||||
});
|
||||
export type FsMkdirRequest = z.infer<typeof fsMkdirRequestSchema>;
|
||||
|
||||
export const fsMkdirResponseSchema = fsEntrySchema;
|
||||
export type FsMkdirResponse = z.infer<typeof fsMkdirResponseSchema>;
|
||||
|
||||
export const fsListManyRequestSchema = z.object({
|
||||
paths: z.array(z.string().min(1)).min(1).max(100),
|
||||
depth: z.number().int().min(1).max(10).default(1),
|
||||
limit: z.number().int().min(1).max(1000).default(200),
|
||||
show_hidden: z.boolean().default(false),
|
||||
follow_gitignore: z.boolean().default(true),
|
||||
exclude_globs: z.array(z.string()).optional(),
|
||||
sort: fsListSortSchema.default('type_first'),
|
||||
include_git_status: z.boolean().default(false),
|
||||
});
|
||||
export type FsListManyRequest = z.infer<typeof fsListManyRequestSchema>;
|
||||
|
||||
export const fsListManyPartialErrorSchema = z.object({
|
||||
code: z.number().int(),
|
||||
msg: z.string(),
|
||||
});
|
||||
export type FsListManyPartialError = z.infer<typeof fsListManyPartialErrorSchema>;
|
||||
|
||||
export const fsListManyResponseSchema = z.object({
|
||||
results: z.record(z.string(), z.array(fsEntrySchema)),
|
||||
truncated_paths: z.array(z.string()).optional(),
|
||||
partial_errors: z.record(z.string(), fsListManyPartialErrorSchema).optional(),
|
||||
});
|
||||
export type FsListManyResponse = z.infer<typeof fsListManyResponseSchema>;
|
||||
|
||||
export const fsStatRequestSchema = z.object({
|
||||
path: z.string().min(1),
|
||||
});
|
||||
export type FsStatRequest = z.infer<typeof fsStatRequestSchema>;
|
||||
|
||||
export const fsStatResponseSchema = fsEntrySchema;
|
||||
export type FsStatResponse = z.infer<typeof fsStatResponseSchema>;
|
||||
|
||||
export const fsStatManyRequestSchema = z.object({
|
||||
paths: z.array(z.string().min(1)).min(1).max(1000),
|
||||
});
|
||||
export type FsStatManyRequest = z.infer<typeof fsStatManyRequestSchema>;
|
||||
|
||||
export const fsStatManyResponseSchema = z.object({
|
||||
entries: z.record(z.string(), fsEntrySchema.nullable()),
|
||||
});
|
||||
export type FsStatManyResponse = z.infer<typeof fsStatManyResponseSchema>;
|
||||
|
||||
export const fsSearchRequestSchema = z.object({
|
||||
query: z.string().min(1),
|
||||
limit: z.number().int().min(1).max(200).default(50),
|
||||
include_globs: z.array(z.string()).optional(),
|
||||
exclude_globs: z.array(z.string()).optional(),
|
||||
follow_gitignore: z.boolean().default(true),
|
||||
});
|
||||
export type FsSearchRequest = z.infer<typeof fsSearchRequestSchema>;
|
||||
|
||||
export const fsSearchResponseSchema = z.object({
|
||||
items: z.array(fsSearchHitSchema),
|
||||
truncated: z.boolean(),
|
||||
});
|
||||
export type FsSearchResponse = z.infer<typeof fsSearchResponseSchema>;
|
||||
|
||||
export const fsGrepRequestSchema = z.object({
|
||||
pattern: z.string().min(1),
|
||||
regex: z.boolean().default(false),
|
||||
case_sensitive: z.boolean().default(true),
|
||||
include_globs: z.array(z.string()).optional(),
|
||||
exclude_globs: z.array(z.string()).optional(),
|
||||
follow_gitignore: z.boolean().default(true),
|
||||
max_files: z.number().int().min(1).max(10_000).default(200),
|
||||
max_matches_per_file: z.number().int().min(1).max(10_000).default(50),
|
||||
max_total_matches: z.number().int().min(1).max(100_000).default(5000),
|
||||
context_lines: z.number().int().min(0).max(10).default(2),
|
||||
});
|
||||
export type FsGrepRequest = z.infer<typeof fsGrepRequestSchema>;
|
||||
|
||||
export const fsGrepResponseSchema = z.object({
|
||||
files: z.array(fsGrepFileHitSchema),
|
||||
files_scanned: z.number().int().nonnegative(),
|
||||
truncated: z.boolean(),
|
||||
elapsed_ms: z.number().int().nonnegative(),
|
||||
});
|
||||
export type FsGrepResponse = z.infer<typeof fsGrepResponseSchema>;
|
||||
|
||||
export interface FsPathResolved {
|
||||
readonly absolute: string;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
* be unit-tested directly. Ported from v1 `services/fs/fsSearchService.ts`.
|
||||
*/
|
||||
|
||||
import type { FsGrepRequest } from '@moonshot-ai/protocol';
|
||||
import type { FsGrepRequest } from './fs';
|
||||
|
||||
export function computeFuzzyScore(name: string, queryLower: string): number {
|
||||
if (queryLower.length === 0) return 0;
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@
|
|||
import { basename, extname, isAbsolute, join, relative, sep } from 'node:path';
|
||||
|
||||
import {
|
||||
ErrorCode,
|
||||
type FsDiffRequest,
|
||||
type FsDiffResponse,
|
||||
type FsEntry,
|
||||
|
|
@ -41,7 +40,22 @@ import {
|
|||
type FsStatManyResponse,
|
||||
type FsStatRequest,
|
||||
type FsStatResponse,
|
||||
} from '@moonshot-ai/protocol';
|
||||
} from './fs';
|
||||
|
||||
/**
|
||||
* The v1 numeric wire codes this edge surface throws inside its
|
||||
* `{ code, msg }` wire errors (`toWireError`). Mirrors the envelope error
|
||||
* table owned by the transport (kap-server); kept as local literals because
|
||||
* they are part of this service's v1 edge contract.
|
||||
*/
|
||||
const FsWireErrorCode = {
|
||||
FS_PATH_NOT_FOUND: 40409,
|
||||
FS_IS_DIRECTORY: 40906,
|
||||
FS_IS_BINARY: 40907,
|
||||
FS_TOO_LARGE: 41302,
|
||||
FS_TOO_MANY_RESULTS: 41303,
|
||||
INTERNAL_ERROR: 50001,
|
||||
} as const;
|
||||
import ignore, { type Ignore } from 'ignore';
|
||||
|
||||
import { InstantiationType } from '#/_base/di/extensions';
|
||||
|
|
@ -919,19 +933,19 @@ function toWireError(err: unknown): { code: number; msg: string } {
|
|||
if (err instanceof Error2) {
|
||||
switch (err.code) {
|
||||
case ErrorCodes.FS_PATH_NOT_FOUND:
|
||||
return { code: ErrorCode.FS_PATH_NOT_FOUND, msg: err.message };
|
||||
return { code: FsWireErrorCode.FS_PATH_NOT_FOUND, msg: err.message };
|
||||
case ErrorCodes.FS_IS_DIRECTORY:
|
||||
return { code: ErrorCode.FS_IS_DIRECTORY, msg: err.message };
|
||||
return { code: FsWireErrorCode.FS_IS_DIRECTORY, msg: err.message };
|
||||
case ErrorCodes.FS_IS_BINARY:
|
||||
return { code: ErrorCode.FS_IS_BINARY, msg: err.message };
|
||||
return { code: FsWireErrorCode.FS_IS_BINARY, msg: err.message };
|
||||
case ErrorCodes.FS_TOO_LARGE:
|
||||
return { code: ErrorCode.FS_TOO_LARGE, msg: err.message };
|
||||
return { code: FsWireErrorCode.FS_TOO_LARGE, msg: err.message };
|
||||
case ErrorCodes.FS_TOO_MANY_RESULTS:
|
||||
return { code: ErrorCode.FS_TOO_MANY_RESULTS, msg: err.message };
|
||||
return { code: FsWireErrorCode.FS_TOO_MANY_RESULTS, msg: err.message };
|
||||
}
|
||||
}
|
||||
return {
|
||||
code: ErrorCode.INTERNAL_ERROR,
|
||||
code: FsWireErrorCode.INTERNAL_ERROR,
|
||||
msg: err instanceof Error ? err.message : 'internal error',
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,25 @@
|
|||
|
||||
import { createDecorator, type ServiceIdentifier } from '#/_base/di/instantiation';
|
||||
import type { Event } from '#/_base/event';
|
||||
import type { FsChangeEvent } from '@moonshot-ai/protocol';
|
||||
|
||||
export type FsChangeKind = 'file' | 'directory' | 'symlink';
|
||||
|
||||
export type FsChangeAction = 'created' | 'modified' | 'deleted';
|
||||
|
||||
export interface FsChangeEntry {
|
||||
path: string;
|
||||
change: FsChangeAction;
|
||||
kind: FsChangeKind;
|
||||
size_delta?: number | undefined;
|
||||
etag?: string | undefined;
|
||||
}
|
||||
|
||||
export interface FsChangeEvent {
|
||||
changes: FsChangeEntry[];
|
||||
coalesced_window_ms: number;
|
||||
truncated?: boolean | undefined;
|
||||
count?: number | undefined;
|
||||
}
|
||||
|
||||
export interface ISessionFsWatchService {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import {
|
|||
IHostFsWatchService,
|
||||
} from '#/os/interface/hostFsWatch';
|
||||
import { ISessionWorkspaceContext } from '#/session/workspaceContext/workspaceContext';
|
||||
import type { FsChangeEntry, FsChangeEvent } from '@moonshot-ai/protocol';
|
||||
import type { FsChangeEntry, FsChangeEvent } from './fsWatch';
|
||||
|
||||
import { ISessionFsWatchService } from './fsWatch';
|
||||
|
||||
|
|
|
|||
|
|
@ -27,18 +27,44 @@ import { IAgentContextSizeService } from '#/agent/contextSize/contextSize';
|
|||
import { isProviderRateLimitError } from '#/app/llmProtocol/errors';
|
||||
import { type TokenUsage } from '#/app/llmProtocol/usage';
|
||||
import { ITelemetryService } from '#/app/telemetry/telemetry';
|
||||
import type {
|
||||
SubagentCompletedEvent,
|
||||
SubagentFailedEvent,
|
||||
SubagentSpawnedEvent,
|
||||
SubagentStartedEvent,
|
||||
} from '@moonshot-ai/protocol';
|
||||
import { IEventBus } from '#/app/event/eventBus';
|
||||
import { isAbortError } from '#/_base/utils/abort';
|
||||
import { IAgentLifecycleService } from '#/session/agentLifecycle/agentLifecycle';
|
||||
|
||||
import { type AgentRunHandle, ISessionSubagentService } from './subagent';
|
||||
|
||||
export interface SubagentSpawnedEvent {
|
||||
readonly type: 'subagent.spawned';
|
||||
readonly subagentId: string;
|
||||
readonly subagentName: string;
|
||||
readonly parentToolCallId: string;
|
||||
readonly parentToolCallUuid?: string;
|
||||
readonly parentAgentId?: string;
|
||||
readonly callerAgentId?: string;
|
||||
readonly description?: string;
|
||||
readonly swarmIndex?: number;
|
||||
readonly runInBackground: boolean;
|
||||
}
|
||||
|
||||
export interface SubagentStartedEvent {
|
||||
readonly type: 'subagent.started';
|
||||
readonly subagentId: string;
|
||||
}
|
||||
|
||||
export interface SubagentCompletedEvent {
|
||||
readonly type: 'subagent.completed';
|
||||
readonly subagentId: string;
|
||||
readonly resultSummary: string;
|
||||
readonly usage?: TokenUsage;
|
||||
readonly contextTokens?: number;
|
||||
}
|
||||
|
||||
export interface SubagentFailedEvent {
|
||||
readonly type: 'subagent.failed';
|
||||
readonly subagentId: string;
|
||||
readonly error: string;
|
||||
}
|
||||
|
||||
declare module '#/app/event/eventBus' {
|
||||
interface DomainEventMap {
|
||||
'subagent.spawned': SubagentSpawnedEvent;
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ import { IAgentProfileService } from '#/agent/profile/profile';
|
|||
import { IAgentPermissionModeService } from '#/agent/permissionMode/permissionMode';
|
||||
import { IAgentLoopService } from '#/agent/loop/loop';
|
||||
import { IAgentUserToolService } from '#/agent/userTool/userTool';
|
||||
import type { SubagentSuspendedEvent } from '@moonshot-ai/protocol';
|
||||
import { IEventBus } from '#/app/event/eventBus';
|
||||
import { IAgentProfileCatalogService } from '#/app/agentProfileCatalog/agentProfileCatalog';
|
||||
import { applyProfilePromptPrefix } from '#/app/agentProfileCatalog/promptPrefix';
|
||||
|
|
@ -56,6 +55,12 @@ import {
|
|||
type AgentRunAttemptHandle,
|
||||
} from './agentRunBatch';
|
||||
|
||||
export interface SubagentSuspendedEvent {
|
||||
readonly type: 'subagent.suspended';
|
||||
readonly subagentId: string;
|
||||
readonly reason: string;
|
||||
}
|
||||
|
||||
declare module '#/app/event/eventBus' {
|
||||
interface DomainEventMap {
|
||||
'subagent.suspended': SubagentSuspendedEvent;
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
import type { ContentPart, ToolCall } from '#/app/llmProtocol/message';
|
||||
import type { Tool } from '#/app/llmProtocol/tool';
|
||||
import type { ToolInputDisplay } from '@moonshot-ai/protocol';
|
||||
import type { ToolInputDisplay } from '#/tool/toolInputDisplay';
|
||||
|
||||
export type ExecutableToolOutput = string | ContentPart[];
|
||||
|
||||
|
|
|
|||
87
packages/agent-core-v2/src/tool/toolInputDisplay.ts
Normal file
87
packages/agent-core-v2/src/tool/toolInputDisplay.ts
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
/**
|
||||
* `ToolInputDisplay` — structured UI hint describing a tool call's input, so
|
||||
* approval panels and tool renderers can present it without re-deriving it
|
||||
* from raw arguments. Carried by `RunnableToolExecution.display`,
|
||||
* `ToolResult.display`, and the `tool.call.started` event.
|
||||
*/
|
||||
export type ToolInputDisplay =
|
||||
| {
|
||||
kind: 'command';
|
||||
command: string;
|
||||
cwd?: string | undefined;
|
||||
description?: string | undefined;
|
||||
language?: 'bash' | undefined;
|
||||
}
|
||||
| {
|
||||
kind: 'file_io';
|
||||
operation: 'read' | 'write' | 'edit' | 'glob' | 'grep';
|
||||
path: string;
|
||||
detail?: string | undefined;
|
||||
content?: string | undefined;
|
||||
before?: string | undefined;
|
||||
after?: string | undefined;
|
||||
}
|
||||
| {
|
||||
kind: 'diff';
|
||||
path: string;
|
||||
before: string;
|
||||
after: string;
|
||||
hunks?: number | undefined;
|
||||
}
|
||||
| {
|
||||
kind: 'search';
|
||||
query: string;
|
||||
scope?: string | undefined;
|
||||
}
|
||||
| {
|
||||
kind: 'url_fetch';
|
||||
url: string;
|
||||
method?: string | undefined;
|
||||
}
|
||||
| {
|
||||
kind: 'agent_call';
|
||||
agent_name: string;
|
||||
prompt: string;
|
||||
background?: boolean | undefined;
|
||||
}
|
||||
| {
|
||||
kind: 'skill_call';
|
||||
skill_name: string;
|
||||
args?: string | undefined;
|
||||
}
|
||||
| {
|
||||
kind: 'todo_list';
|
||||
items: { title: string; status: string }[];
|
||||
}
|
||||
| {
|
||||
kind: 'task';
|
||||
task_id: string;
|
||||
status: string;
|
||||
description: string;
|
||||
task_kind?: string | undefined;
|
||||
}
|
||||
| {
|
||||
kind: 'task_stop';
|
||||
task_id: string;
|
||||
task_description: string;
|
||||
}
|
||||
| {
|
||||
kind: 'plan_review';
|
||||
plan: string;
|
||||
path?: string | undefined;
|
||||
options?: readonly { label: string; description: string }[] | undefined;
|
||||
}
|
||||
| {
|
||||
kind: 'goal_start';
|
||||
objective: string;
|
||||
completionCriterion?: string | undefined;
|
||||
// Current permission mode at approval time. The client uses it to pick the
|
||||
// start menu (manual vs yolo); `auto` never reaches this display because it
|
||||
// auto-approves the goal without a prompt.
|
||||
mode: 'manual' | 'yolo';
|
||||
}
|
||||
| {
|
||||
kind: 'generic';
|
||||
summary: string;
|
||||
detail?: unknown;
|
||||
};
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import type { TurnEndedEvent } from '@moonshot-ai/protocol';
|
||||
import type { TurnEndedEvent } from '#/agent/loop/turnEvents';
|
||||
|
||||
import type { IDisposable } from '#/_base/di/lifecycle';
|
||||
import { IAgentContextMemoryService } from '#/agent/contextMemory/contextMemory';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { MCP_OAUTH_AUTHORIZATION_URL_TOOL_UPDATE } from '@moonshot-ai/protocol';
|
||||
import { MCP_OAUTH_AUTHORIZATION_URL_TOOL_UPDATE } from '#/agent/mcp/tools/auth';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { AlreadyAuthorizedError, type BeginAuthorizationResult, type McpOAuthService } from '#/agent/mcp/oauth/service';
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ import { ISessionWorkspaceContext } from '#/session/workspaceContext/workspaceCo
|
|||
import type { ToolCall } from '#/app/llmProtocol/message';
|
||||
import { IEventBus } from '#/app/event/eventBus';
|
||||
import { EventBusService } from '#/app/event/eventBusService';
|
||||
import type { ToolInputDisplay } from '@moonshot-ai/protocol';
|
||||
import type { ToolInputDisplay } from '#/tool/toolInputDisplay';
|
||||
|
||||
import { stubApprovalService } from '../../session/approval/stubs';
|
||||
import { stubPermissionModeService } from '../permissionMode/stubs';
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { tmpdir } from 'node:os';
|
|||
import { join } from 'node:path';
|
||||
|
||||
import type { ToolCall } from '#/app/llmProtocol/message';
|
||||
import type { ToolInputDisplay } from '@moonshot-ai/protocol';
|
||||
import type { ToolInputDisplay } from '#/tool/toolInputDisplay';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { DisposableStore } from '#/_base/di/lifecycle';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import type { ToolCall } from '#/app/llmProtocol/message';
|
||||
import type { ApprovalResponse, ToolInputDisplay } from '@moonshot-ai/protocol';
|
||||
import type { ToolInputDisplay } from '#/tool/toolInputDisplay';
|
||||
import type { ApprovalResponse } from '#/session/approval/approval';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { DisposableStore } from '#/_base/di/lifecycle';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { ToolCall } from '#/app/llmProtocol/message';
|
||||
import type { ToolInputDisplay } from '@moonshot-ai/protocol';
|
||||
import type { ToolInputDisplay } from '#/tool/toolInputDisplay';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import type { ResolvedToolExecutionHookContext } from '#/agent/toolExecutor/toolHooks';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import type { ToolCall } from '#/app/llmProtocol/message';
|
||||
import type { AgentEvent, ToolInputDisplay } from '@moonshot-ai/protocol';
|
||||
import type { DomainEvent } from '#/app/event/eventBus';
|
||||
import type { ToolInputDisplay } from '#/tool/toolInputDisplay';
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
|
||||
import { SyncDescriptor } from '#/_base/di/descriptors';
|
||||
|
|
@ -33,7 +34,7 @@ let ix: TestInstantiationService;
|
|||
let executor: IAgentToolExecutorService;
|
||||
let registry: IAgentToolRegistryService;
|
||||
let events: ToolExecutorEvent[];
|
||||
let protocolEvents: AgentEvent[];
|
||||
let protocolEvents: DomainEvent[];
|
||||
let telemetryEvents: TelemetryRecord[];
|
||||
let truncateForModel: IAgentToolResultTruncationService['truncateForModel'];
|
||||
|
||||
|
|
@ -56,7 +57,7 @@ beforeEach(() => {
|
|||
reg.defineInstance(IEventBus, {
|
||||
publish: (event: { type: string }) => {
|
||||
if (event.type.startsWith('tool.')) {
|
||||
protocolEvents.push(event as unknown as AgentEvent);
|
||||
protocolEvents.push(event as unknown as DomainEvent);
|
||||
}
|
||||
},
|
||||
subscribe: (..._args: unknown[]) => ({ dispose: () => {} }),
|
||||
|
|
@ -178,7 +179,7 @@ describe('AgentToolExecutorService', () => {
|
|||
note: '<system>Image compressed.</system>',
|
||||
});
|
||||
const protocolResult = protocolEvents.find(
|
||||
(event): event is Extract<AgentEvent, { type: 'tool.result' }> =>
|
||||
(event): event is Extract<DomainEvent, { type: 'tool.result' }> =>
|
||||
event.type === 'tool.result',
|
||||
);
|
||||
expect(protocolResult).toMatchObject({
|
||||
|
|
@ -334,7 +335,7 @@ describe('AgentToolExecutorService', () => {
|
|||
}),
|
||||
]);
|
||||
const toolCallEvent = protocolEvents.find(
|
||||
(event): event is Extract<AgentEvent, { type: 'tool.call.started' }> =>
|
||||
(event): event is Extract<DomainEvent, { type: 'tool.call.started' }> =>
|
||||
event.type === 'tool.call.started',
|
||||
);
|
||||
expect(toolCallEvent?.args).toEqual({ x: 1 });
|
||||
|
|
@ -753,7 +754,7 @@ function eventTypes(): ToolExecutorEvent['type'][] {
|
|||
return events.map((event) => event.type);
|
||||
}
|
||||
|
||||
function protocolEventTypes(): AgentEvent['type'][] {
|
||||
function protocolEventTypes(): DomainEvent['type'][] {
|
||||
return protocolEvents.map((event) => event.type);
|
||||
}
|
||||
|
||||
|
|
@ -761,13 +762,13 @@ function pairedToolCallIds(): { readonly calls: string[]; readonly results: stri
|
|||
return {
|
||||
calls: protocolEvents
|
||||
.filter(
|
||||
(event): event is Extract<AgentEvent, { type: 'tool.call.started' }> =>
|
||||
(event): event is Extract<DomainEvent, { type: 'tool.call.started' }> =>
|
||||
event.type === 'tool.call.started',
|
||||
)
|
||||
.map((event) => event.toolCallId),
|
||||
results: protocolEvents
|
||||
.filter(
|
||||
(event): event is Extract<AgentEvent, { type: 'tool.result' }> =>
|
||||
(event): event is Extract<DomainEvent, { type: 'tool.result' }> =>
|
||||
event.type === 'tool.result',
|
||||
)
|
||||
.map((event) => event.toolCallId),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { ToolInputDisplay } from '@moonshot-ai/protocol';
|
||||
import type { ToolInputDisplay } from '#/tool/toolInputDisplay';
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
|
||||
import { SyncDescriptor } from '#/_base/di/descriptors';
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import type { CronJobOrigin } from '@moonshot-ai/protocol';
|
||||
import type { CronJobOrigin } from '#/agent/contextMemory/types';
|
||||
|
||||
import type {
|
||||
ExecutableTool,
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import {
|
|||
IHostFsWatchService,
|
||||
} from '#/os/interface/hostFsWatch';
|
||||
import { ISessionWorkspaceContext } from '#/session/workspaceContext/workspaceContext';
|
||||
import type { FsChangeEvent } from '@moonshot-ai/protocol';
|
||||
import type { FsChangeEvent } from '#/session/sessionFs/fsWatch';
|
||||
|
||||
import { ISessionFsWatchService } from '#/session/sessionFs/fsWatch';
|
||||
import { SessionFsWatchService } from '#/session/sessionFs/fsWatchService';
|
||||
|
|
|
|||
|
|
@ -476,11 +476,7 @@ export class SessionEventBroadcaster {
|
|||
const disposables: IDisposable[] = [
|
||||
eventBus.subscribe((event) => {
|
||||
let projected = event;
|
||||
if (
|
||||
handle.id === MAIN_AGENT_ID &&
|
||||
event.type === 'agent.status.updated' &&
|
||||
event.phase === undefined
|
||||
) {
|
||||
if (handle.id === MAIN_AGENT_ID && event.type === 'agent.status.updated') {
|
||||
const snapshot = readLegacyStatus(handle);
|
||||
if (snapshot !== undefined) {
|
||||
lastLegacyStatus = JSON.stringify(snapshot);
|
||||
|
|
|
|||
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
|
|
@ -508,9 +508,6 @@ importers:
|
|||
'@moonshot-ai/minidb':
|
||||
specifier: workspace:^
|
||||
version: link:../minidb
|
||||
'@moonshot-ai/protocol':
|
||||
specifier: workspace:^
|
||||
version: link:../protocol
|
||||
'@mozilla/readability':
|
||||
specifier: ^0.6.0
|
||||
version: 0.6.0
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue