@zvada/agent-server 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +317 -0
- package/README.md +20 -4
- package/docs/consuming.md +269 -0
- package/docs/deploy.md +80 -0
- package/docs/harnesses.md +64 -0
- package/docs/rfds/0001-deterministic-echo-ids.md +44 -0
- package/package.json +23 -3
- package/src/client/client.ts +143 -50
- package/src/core/agents/acp/acp-agent.ts +9 -0
- package/src/core/agents/acp/mappings.ts +3 -3
- package/src/core/agents/base.ts +9 -1
- package/src/core/agents/claude-code/adapter.ts +116 -26
- package/src/core/agents/claude-code/claude-agent.ts +31 -3
- package/src/core/agents/claude-code/generator-session.ts +16 -5
- package/src/core/agents/claude-code/options.ts +13 -3
- package/src/core/agents/claude-code/session-manager.ts +9 -4
- package/src/core/agents/codex-app-server/codex-app-server-agent.ts +17 -3
- package/src/core/agents/codex-sdk/codex-sdk-agent.ts +3 -3
- package/src/core/agents/types.ts +1 -1
- package/src/core/diagnostics.ts +59 -0
- package/src/core/index.ts +3 -1
- package/src/core/presets.ts +15 -2
- package/src/core/provision/pins.ts +5 -1
- package/src/core/proxy/anthropic-proxy.ts +12 -0
- package/src/core/runtime/agent-runtime.ts +66 -18
- package/src/core/runtime/event-processor.ts +51 -26
- package/src/protocol/config.ts +8 -6
- package/src/{core/agents/error-classifier.ts → protocol/errors.ts} +33 -7
- package/src/protocol/factories.ts +106 -10
- package/src/protocol/guards.ts +53 -0
- package/src/protocol/index.ts +10 -0
- package/src/protocol/lifecycle.ts +289 -112
- package/src/protocol/meta.ts +14 -0
- package/src/protocol/part-input.ts +56 -7
- package/src/protocol/parts.ts +125 -10
- package/src/protocol/reduce.ts +749 -0
- package/src/protocol/selectors.ts +162 -0
- package/src/protocol/seq-cursor.ts +87 -0
- package/src/protocol/stop-reasons.ts +45 -0
- package/src/protocol/time.ts +23 -0
- package/src/protocol/tokens.ts +23 -0
- package/src/protocol/tool-state.ts +85 -25
- package/src/protocol/verify.ts +440 -0
- package/src/protocol/vocabulary.ts +18 -0
- package/src/protocol/wire.ts +81 -13
- package/src/server/acp/binding.ts +23 -2
- package/src/server/acp/translate.ts +51 -14
- package/src/server/agent-server.ts +85 -6
- package/AGENTS.md +0 -21
package/src/protocol/parts.ts
CHANGED
|
@@ -1,21 +1,34 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { isKnownPartType } from "./guards.ts";
|
|
3
|
+
import { MetaSchema } from "./meta.ts";
|
|
4
|
+
import { OpenTimeSpanSchema } from "./time.ts";
|
|
2
5
|
import { ToolKindSchema, ToolLocationSchema, ToolStateSchema } from "./tool-state.ts";
|
|
3
6
|
|
|
4
7
|
/**
|
|
5
|
-
* A Part is the atomic unit of
|
|
8
|
+
* A Part is the atomic unit of message content, normalized across harnesses.
|
|
6
9
|
* Every part belongs to a message within a session. Parts stream: a TextPart
|
|
7
10
|
* arrives `streaming` and is finalized `done`; a ToolPart's `state` advances
|
|
8
|
-
* through the tool lifecycle. `
|
|
9
|
-
* sub-agent / nested tool execution back to the tool call that spawned it.
|
|
11
|
+
* through the tool lifecycle. `parentToolCallId` links a part produced inside
|
|
12
|
+
* a sub-agent / nested tool execution back to the tool call that spawned it.
|
|
13
|
+
*
|
|
14
|
+
* Parts carry NO ordering fields — position is the event's knowledge
|
|
15
|
+
* (`partIndex`/`outputIndex` on message.* events); a part re-stated late (a
|
|
16
|
+
* tool completing after its message ended) must not be able to contradict its
|
|
17
|
+
* own position.
|
|
10
18
|
*
|
|
11
19
|
* Rule: this union contains only part types adapters actually emit — the
|
|
12
|
-
* protocol advertises nothing it doesn't deliver.
|
|
20
|
+
* protocol advertises nothing it doesn't deliver. There is deliberately no
|
|
21
|
+
* compaction part (compaction is the `session.compaction` entity) and no
|
|
22
|
+
* step/turn-mechanics part (mechanics live in events).
|
|
13
23
|
*/
|
|
14
24
|
const PartBase = z.object({
|
|
25
|
+
/** UUIDv7 — THE upsert key. */
|
|
15
26
|
id: z.string(),
|
|
16
27
|
sessionId: z.string(),
|
|
17
28
|
messageId: z.string(),
|
|
18
|
-
|
|
29
|
+
/** Set when this part was produced inside the subagent spawned by that tool call. */
|
|
30
|
+
parentToolCallId: z.string().optional(),
|
|
31
|
+
_meta: MetaSchema,
|
|
19
32
|
});
|
|
20
33
|
|
|
21
34
|
export const TextPartSchema = PartBase.extend({
|
|
@@ -29,32 +42,134 @@ export const ReasoningPartSchema = PartBase.extend({
|
|
|
29
42
|
type: z.literal("reasoning"),
|
|
30
43
|
text: z.string(),
|
|
31
44
|
state: z.enum(["streaming", "done"]).optional(),
|
|
45
|
+
/** Thought duration, when the adapter can stamp it (deus renders it). */
|
|
46
|
+
time: OpenTimeSpanSchema.optional(),
|
|
47
|
+
/** Harness extras — e.g. `{ redacted: true }` for Claude redacted_thinking. */
|
|
32
48
|
providerMetadata: z.record(z.string(), z.unknown()).optional(),
|
|
33
49
|
});
|
|
34
50
|
export type ReasoningPart = z.infer<typeof ReasoningPartSchema>;
|
|
35
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Metadata about the subagent a `task`-kind tool spawned, populated from the
|
|
54
|
+
* spawning tool's input (Claude `Task`: description/subagent_type/model).
|
|
55
|
+
* `agentId` is the harness-native id — the only reliable way to attribute
|
|
56
|
+
* PARALLEL subagents. Presence of `subagent` implies `kind: "task"`; tool-name
|
|
57
|
+
* allowlists are forbidden.
|
|
58
|
+
*/
|
|
59
|
+
export const SubagentMetadataSchema = z.object({
|
|
60
|
+
type: z.string().optional(),
|
|
61
|
+
description: z.string().optional(),
|
|
62
|
+
model: z.string().optional(),
|
|
63
|
+
agentId: z.string().optional(),
|
|
64
|
+
});
|
|
65
|
+
export type SubagentMetadata = z.infer<typeof SubagentMetadataSchema>;
|
|
66
|
+
|
|
36
67
|
export const ToolPartSchema = PartBase.extend({
|
|
37
68
|
type: z.literal("tool"),
|
|
38
69
|
toolCallId: z.string(),
|
|
39
|
-
/** Provider-native tool name (`Bash`, `shell`, `mcp.search`, …). */
|
|
70
|
+
/** Provider-native tool name (`Bash`, `shell`, `mcp.search`, …) — the renderer dispatch key. */
|
|
40
71
|
toolName: z.string(),
|
|
41
|
-
/** Normalized classification of what the call does (ACP ToolKind). */
|
|
72
|
+
/** Normalized classification of what the call does (ACP ToolKind + `task`). */
|
|
42
73
|
kind: ToolKindSchema.optional(),
|
|
43
74
|
/** Human-readable label when the harness provides one. */
|
|
44
75
|
title: z.string().optional(),
|
|
45
76
|
/** Files this call touches, for follow-along UIs. */
|
|
46
77
|
locations: z.array(ToolLocationSchema).optional(),
|
|
78
|
+
/** Present when this tool spawned a subagent (implies kind "task"). */
|
|
79
|
+
subagent: SubagentMetadataSchema.optional(),
|
|
47
80
|
state: ToolStateSchema,
|
|
48
81
|
});
|
|
49
82
|
export type ToolPart = z.infer<typeof ToolPartSchema>;
|
|
50
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Exactly one of `data` (base64) | `url` — ENFORCED, not merely documented: a
|
|
86
|
+
* payload-less blob part renders as a broken image, and one carrying both
|
|
87
|
+
* leaves every consumer to invent its own precedence rule.
|
|
88
|
+
*/
|
|
89
|
+
const exactlyOneSource = (part: { data?: string; url?: string }) =>
|
|
90
|
+
(part.data !== undefined) !== (part.url !== undefined);
|
|
91
|
+
|
|
92
|
+
/** Exactly one of `data` (base64) | `url`. Emitted by the user echo and image tool results. */
|
|
93
|
+
export const ImagePartSchema = PartBase.extend({
|
|
94
|
+
type: z.literal("image"),
|
|
95
|
+
data: z.string().min(1).optional(),
|
|
96
|
+
url: z.string().min(1).optional(),
|
|
97
|
+
mimeType: z.string(),
|
|
98
|
+
}).refine(exactlyOneSource, { message: "image part requires exactly one of data | url" });
|
|
99
|
+
export type ImagePart = z.infer<typeof ImagePartSchema>;
|
|
100
|
+
|
|
101
|
+
/** Exactly one of `data` (base64) | `url`. Emitted by the user echo. */
|
|
102
|
+
export const FilePartSchema = PartBase.extend({
|
|
103
|
+
type: z.literal("file"),
|
|
104
|
+
data: z.string().min(1).optional(),
|
|
105
|
+
url: z.string().min(1).optional(),
|
|
106
|
+
mimeType: z.string(),
|
|
107
|
+
filename: z.string().optional(),
|
|
108
|
+
}).refine(exactlyOneSource, { message: "file part requires exactly one of data | url" });
|
|
109
|
+
export type FilePart = z.infer<typeof FilePartSchema>;
|
|
110
|
+
|
|
51
111
|
export const PartSchema = z.discriminatedUnion("type", [
|
|
52
112
|
TextPartSchema,
|
|
53
113
|
ReasoningPartSchema,
|
|
54
114
|
ToolPartSchema,
|
|
115
|
+
ImagePartSchema,
|
|
116
|
+
FilePartSchema,
|
|
55
117
|
]);
|
|
56
118
|
export type Part = z.infer<typeof PartSchema>;
|
|
57
119
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
120
|
+
/**
|
|
121
|
+
* Law 6 — tolerant of the unknown, strict about the known: an unknown part
|
|
122
|
+
* type is preserved, in order, never dropped. A KNOWN type with a malformed
|
|
123
|
+
* body is a hard error (never wrapped as unknown).
|
|
124
|
+
*/
|
|
125
|
+
export interface UnknownPart {
|
|
126
|
+
type: string & {};
|
|
127
|
+
id: string;
|
|
128
|
+
sessionId: string;
|
|
129
|
+
messageId: string;
|
|
130
|
+
parentToolCallId?: string;
|
|
131
|
+
raw: Record<string, unknown>;
|
|
132
|
+
_meta?: Record<string, unknown>;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Decode a wire value into a Part, preserving unknown part types instead of
|
|
137
|
+
* failing the whole event that carries them (Law 6 is a STORAGE and FORWARDING
|
|
138
|
+
* requirement — a newer server's part type must survive a round trip through
|
|
139
|
+
* an older consumer). Throws on a known type with a malformed body, and on an
|
|
140
|
+
* unknown part that lacks the addressing fields every part owns: without
|
|
141
|
+
* `id`/`sessionId`/`messageId` there is nothing to upsert by, so it cannot be
|
|
142
|
+
* preserved in any useful sense.
|
|
143
|
+
*/
|
|
144
|
+
export function decodePart(value: unknown): Part | UnknownPart {
|
|
145
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
146
|
+
throw new Error("part must be an object");
|
|
147
|
+
}
|
|
148
|
+
const record = value as Record<string, unknown>;
|
|
149
|
+
if (isKnownPartType(record.type)) return PartSchema.parse(value);
|
|
150
|
+
const { type, id, sessionId, messageId, parentToolCallId } = record;
|
|
151
|
+
if (typeof type !== "string" || type.length === 0) {
|
|
152
|
+
throw new Error("part is missing its type");
|
|
153
|
+
}
|
|
154
|
+
if (typeof id !== "string" || typeof sessionId !== "string" || typeof messageId !== "string") {
|
|
155
|
+
throw new Error(`unknown part type "${type}" is missing its id/sessionId/messageId address`);
|
|
156
|
+
}
|
|
157
|
+
const meta = record._meta;
|
|
158
|
+
return {
|
|
159
|
+
type,
|
|
160
|
+
id,
|
|
161
|
+
sessionId,
|
|
162
|
+
messageId,
|
|
163
|
+
...(typeof parentToolCallId === "string" ? { parentToolCallId } : {}),
|
|
164
|
+
// Law 4 puts extensions in `_meta` on every shape — surface it on the
|
|
165
|
+
// preserved part too, so a consumer reading `part._meta` does not need
|
|
166
|
+
// to know to dig through `raw` for it.
|
|
167
|
+
...(meta !== null && typeof meta === "object" && !Array.isArray(meta)
|
|
168
|
+
? { _meta: meta as Record<string, unknown> }
|
|
169
|
+
: {}),
|
|
170
|
+
raw: record,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// `PART_TYPES`, `isKnownPartType` and `isUnknownPart` live in `./guards.ts` —
|
|
175
|
+
// same exports from the barrel, but reachable without loading zod.
|