Skip to content

Commit 27236bd

Browse files
authored
refactor(agent-core-v2): drop the @moonshot-ai/protocol dependency (#1745)
1 parent 481b28b commit 27236bd

82 files changed

Lines changed: 1389 additions & 225 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

packages/agent-core-v2/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@
6262
"@modelcontextprotocol/sdk": "^1.29.0",
6363
"@moonshot-ai/kimi-code-oauth": "workspace:^",
6464
"@moonshot-ai/minidb": "workspace:^",
65-
"@moonshot-ai/protocol": "workspace:^",
6665
"@mozilla/readability": "^0.6.0",
6766
"ajv": "^8.18.0",
6867
"ajv-formats": "^3.0.1",

packages/agent-core-v2/scripts/check-domain-layers.mjs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,11 @@ const ALLOWED_EXCEPTIONS = new Set([
304304
'auth>tool',
305305
'auth>toolRegistry',
306306
'permissionGate>approval',
307+
// `permissionRules` (L3) persists the approval broker's `ApprovalResponse`
308+
// (Session, L7) verbatim in its wire-logged `PermissionApprovalResultRecord`
309+
// — a real cross-scope dependency, surfaced here rather than hidden behind a
310+
// re-declared copy of the shape.
311+
'permissionRules>approval',
307312
'userTool>interaction',
308313
'permissionPolicy>plan',
309314
'permissionPolicy>swarm',

packages/agent-core-v2/src/_base/errors/codes.ts

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,12 @@
66
* codes, the registry (`registerErrorDomain` / `errorInfo` / `isErrorCode`) the
77
* serializer reads, and the domain-independent core codes (`internal`,
88
* `not_implemented`). Domain-owned codes live next to their owning domain and
9-
* are aggregated into the public `ErrorCodes` const by `#/errors`.
9+
* are aggregated into the public `ErrorCodes` const by `#/errors`, which also
10+
* derives the `ErrorCode` union type from that aggregate — so each domain's
11+
* `errors.ts` is the single source of truth and there is no central
12+
* hand-maintained list to keep in sync.
1013
*/
1114

12-
import type { KimiErrorCode } from '@moonshot-ai/protocol';
13-
14-
export type ErrorCode = KimiErrorCode;
15-
1615
export interface ErrorInfo {
1716
readonly title: string;
1817
readonly retryable: boolean;
@@ -21,18 +20,25 @@ export interface ErrorInfo {
2120
}
2221

2322
export interface ErrorDomain {
24-
readonly codes: { readonly [name: string]: ErrorCode };
25-
readonly retryable?: ReadonlyArray<ErrorCode>;
23+
readonly codes: { readonly [name: string]: string };
24+
readonly retryable?: ReadonlyArray<string>;
2625
readonly info?: { readonly [code: string]: ErrorInfo };
2726
}
2827

29-
const registeredCodes = new Set<ErrorCode>();
30-
const retryableCodes = new Set<ErrorCode>();
28+
// Maps each registered code to the `codes` object that contributed it: a
29+
// domain re-registering itself stays idempotent, while two different domains
30+
// claiming the same code fail loudly at registration time.
31+
const registeredCodes = new Map<string, object>();
32+
const retryableCodes = new Set<string>();
3133
const infoOverrides: { [code: string]: ErrorInfo } = {};
3234

3335
export function registerErrorDomain(domain: ErrorDomain): void {
3436
for (const code of Object.values(domain.codes)) {
35-
registeredCodes.add(code);
37+
const owner = registeredCodes.get(code);
38+
if (owner !== undefined && owner !== domain.codes) {
39+
throw new Error(`error code '${code}' is registered by two different domains`);
40+
}
41+
registeredCodes.set(code, domain.codes);
3642
}
3743
for (const code of domain.retryable ?? []) {
3844
retryableCodes.add(code);
@@ -42,11 +48,11 @@ export function registerErrorDomain(domain: ErrorDomain): void {
4248
}
4349
}
4450

45-
export function isErrorCode(code: unknown): code is ErrorCode {
46-
return typeof code === 'string' && registeredCodes.has(code as ErrorCode);
51+
export function isErrorCode(code: unknown): code is string {
52+
return typeof code === 'string' && registeredCodes.has(code);
4753
}
4854

49-
export function errorInfo(code: ErrorCode): ErrorInfo {
55+
export function errorInfo(code: string): ErrorInfo {
5056
const override = infoOverrides[code];
5157
if (override !== undefined) return override;
5258
return {
@@ -60,6 +66,7 @@ export const CoreErrors = {
6066
codes: {
6167
INTERNAL: 'internal',
6268
NOT_IMPLEMENTED: 'not_implemented',
69+
VALIDATION_FAILED: 'validation.failed',
6370
},
6471
info: {
6572
internal: {

packages/agent-core-v2/src/_base/errors/errors.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*/
55

66
import { CoreErrors } from './codes';
7-
import type { ErrorCode } from './codes';
7+
import type { ErrorCode } from '#/errors';
88

99
export class ExpectedError extends Error {
1010
readonly isExpected = true;

packages/agent-core-v2/src/_base/errors/serialize.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
*/
1010

1111
import { CoreErrors, errorInfo, isErrorCode } from './codes';
12-
import type { ErrorCode } from './codes';
12+
import type { ErrorCode } from '#/errors';
1313
import { Error2 } from './errors';
1414

1515
export interface ErrorPayload {
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { z } from 'zod';
2+
3+
const ISO_8601_REGEX =
4+
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?(?:Z|[+-]\d{2}(?::?\d{2})?)$/;
5+
6+
/**
7+
* Wire-schema primitive for ISO 8601 datetime strings: validates the shape and
8+
* normalizes to `Date#toISOString()` output. Shared by the edge DTO schemas
9+
* (`sessionFs`, `file`, `terminal`, `auth`, …) that expose timestamps.
10+
*/
11+
export const isoDateTimeSchema = z
12+
.string()
13+
.refine((value) => ISO_8601_REGEX.test(value), {
14+
message: 'must be an ISO 8601 datetime string',
15+
})
16+
.transform((value, ctx) => {
17+
const ms = Date.parse(value);
18+
if (Number.isNaN(ms)) {
19+
ctx.addIssue({
20+
code: 'custom',
21+
message: 'invalid ISO 8601 datetime',
22+
});
23+
return z.NEVER;
24+
}
25+
return new Date(ms).toISOString();
26+
});

packages/agent-core-v2/src/activity/activity.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
import { createDecorator, type ServiceIdentifier } from '#/_base/di/instantiation';
1919
import type { IDisposable } from '#/_base/di/lifecycle';
2020
import type { PromptOrigin } from '#/agent/contextMemory/types';
21-
import type { TurnEndReason } from '@moonshot-ai/protocol';
21+
22+
export type TurnEndReason = 'completed' | 'cancelled' | 'failed' | 'blocked';
2223

2324
export type AgentLifecycleState = 'initializing' | 'ready' | 'disposing' | 'disposed';
2425

packages/agent-core-v2/src/agent/contextMemory/messageProjection.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
* so REST consumers can still render the media after reload/resume.
1616
*/
1717

18-
import type { Message, MessageContent, MessageRole, ToolUseContent } from '@moonshot-ai/protocol';
18+
import type { Message, MessageContent, MessageRole, ToolUseContent } from './wireMessage';
1919

2020
import type { ContextMessage } from './types';
2121

packages/agent-core-v2/src/agent/contextMemory/types.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import type { ContentPart, Message } from '#/app/llmProtocol/message';
22

33
import type { AgentTaskStatus } from '#/agent/task/task';
4-
import type { CronJobOrigin, CronMissedOrigin, ShellCommandOrigin } from '@moonshot-ai/protocol';
54

65
export type SkillSource = 'project' | 'user' | 'extra' | 'builtin';
76

@@ -36,6 +35,14 @@ export interface InjectionOrigin {
3635
readonly variant: string;
3736
}
3837

38+
export interface ShellCommandOrigin {
39+
readonly kind: 'shell_command';
40+
readonly phase: 'input' | 'output';
41+
/** Only present on `phase: 'output'` — whether the command failed, so replay
42+
* can colour stderr red only for actual failures (not warnings). */
43+
readonly isError?: boolean;
44+
}
45+
3946
export interface CompactionSummaryOrigin {
4047
readonly kind: 'compaction_summary';
4148
}
@@ -52,6 +59,20 @@ export interface TaskOrigin {
5259
readonly notificationId: string;
5360
}
5461

62+
export interface CronJobOrigin {
63+
readonly kind: 'cron_job';
64+
readonly jobId: string;
65+
readonly cron: string;
66+
readonly recurring: boolean;
67+
readonly coalescedCount: number;
68+
readonly stale: boolean;
69+
}
70+
71+
export interface CronMissedOrigin {
72+
readonly kind: 'cron_missed';
73+
readonly count: number;
74+
}
75+
5576
export interface HookResultOrigin {
5677
readonly kind: 'hook_result';
5778
readonly event: string;
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/**
2+
* The wire `Message` shape — the legacy REST/streaming message format served
3+
* on the `messages`, `snapshot`, and `sessions` (`:undo`) edge surfaces.
4+
* Defined next to `messageProjection.ts`, which projects `ContextMessage`
5+
* into this shape; consumed by the `messageLegacy` edge adapter and the
6+
* transports.
7+
*/
8+
9+
import { z } from 'zod';
10+
11+
import { isoDateTimeSchema } from '#/_base/utils/isoDateTime';
12+
13+
export const messageRoleSchema = z.enum(['user', 'assistant', 'tool', 'system']);
14+
export type MessageRole = z.infer<typeof messageRoleSchema>;
15+
16+
export const textContentSchema = z.object({
17+
type: z.literal('text'),
18+
text: z.string(),
19+
});
20+
export type TextContent = z.infer<typeof textContentSchema>;
21+
22+
export const toolUseContentSchema = z.object({
23+
type: z.literal('tool_use'),
24+
tool_call_id: z.string().min(1),
25+
tool_name: z.string().min(1),
26+
input: z.unknown(),
27+
});
28+
export type ToolUseContent = z.infer<typeof toolUseContentSchema>;
29+
30+
export const toolResultContentSchema = z.object({
31+
type: z.literal('tool_result'),
32+
tool_call_id: z.string().min(1),
33+
output: z.unknown(),
34+
is_error: z.boolean().optional(),
35+
});
36+
export type ToolResultContent = z.infer<typeof toolResultContentSchema>;
37+
38+
export const imageSourceSchema = z.discriminatedUnion('kind', [
39+
z.object({ kind: z.literal('url'), url: z.string().min(1) }),
40+
z.object({
41+
kind: z.literal('base64'),
42+
media_type: z.string().min(1),
43+
data: z.string().min(1),
44+
}),
45+
z.object({ kind: z.literal('file'), file_id: z.string().min(1) }),
46+
]);
47+
export type ImageSource = z.infer<typeof imageSourceSchema>;
48+
49+
export const imageContentSchema = z.object({
50+
type: z.literal('image'),
51+
source: imageSourceSchema,
52+
});
53+
export type ImageContent = z.infer<typeof imageContentSchema>;
54+
55+
// Video uses the same source shape as image (url / base64 / uploaded file id).
56+
export const videoContentSchema = z.object({
57+
type: z.literal('video'),
58+
source: imageSourceSchema,
59+
});
60+
export type VideoContent = z.infer<typeof videoContentSchema>;
61+
62+
export const fileContentSchema = z.object({
63+
type: z.literal('file'),
64+
file_id: z.string().min(1),
65+
name: z.string(),
66+
media_type: z.string().min(1),
67+
size: z.number().int().nonnegative(),
68+
});
69+
export type FileContent = z.infer<typeof fileContentSchema>;
70+
71+
export const thinkingContentSchema = z.object({
72+
type: z.literal('thinking'),
73+
thinking: z.string(),
74+
signature: z.string().optional(),
75+
});
76+
export type ThinkingContent = z.infer<typeof thinkingContentSchema>;
77+
78+
export const messageContentSchema = z.discriminatedUnion('type', [
79+
textContentSchema,
80+
toolUseContentSchema,
81+
toolResultContentSchema,
82+
imageContentSchema,
83+
videoContentSchema,
84+
fileContentSchema,
85+
thinkingContentSchema,
86+
]);
87+
export type MessageContent = z.infer<typeof messageContentSchema>;
88+
89+
export const messageSchema = z.object({
90+
id: z.string().min(1),
91+
session_id: z.string().min(1),
92+
role: messageRoleSchema,
93+
content: z.array(messageContentSchema),
94+
created_at: isoDateTimeSchema,
95+
prompt_id: z.string().min(1).optional(),
96+
parent_message_id: z.string().min(1).optional(),
97+
metadata: z.record(z.string(), z.unknown()).optional(),
98+
});
99+
100+
export type Message = z.infer<typeof messageSchema>;

0 commit comments

Comments
 (0)