@zvada/agent-server 0.2.2 → 0.3.1

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.
Files changed (51) hide show
  1. package/CHANGELOG.md +317 -0
  2. package/README.md +20 -4
  3. package/docs/consuming.md +269 -0
  4. package/docs/deploy.md +80 -0
  5. package/docs/harnesses.md +64 -0
  6. package/docs/rfds/0001-deterministic-echo-ids.md +44 -0
  7. package/package.json +23 -3
  8. package/src/client/client.ts +143 -50
  9. package/src/core/agents/acp/acp-agent.ts +9 -0
  10. package/src/core/agents/acp/mappings.ts +3 -3
  11. package/src/core/agents/base.ts +9 -1
  12. package/src/core/agents/claude-code/adapter.ts +116 -26
  13. package/src/core/agents/claude-code/claude-agent.ts +31 -3
  14. package/src/core/agents/claude-code/generator-session.ts +16 -5
  15. package/src/core/agents/claude-code/options.ts +13 -3
  16. package/src/core/agents/claude-code/session-manager.ts +9 -4
  17. package/src/core/agents/codex-app-server/codex-app-server-agent.ts +17 -3
  18. package/src/core/agents/codex-sdk/codex-sdk-agent.ts +3 -3
  19. package/src/core/agents/types.ts +1 -1
  20. package/src/core/diagnostics.ts +59 -0
  21. package/src/core/index.ts +3 -1
  22. package/src/core/presets.ts +15 -2
  23. package/src/core/provision/pins.ts +5 -1
  24. package/src/core/proxy/anthropic-proxy.ts +43 -2
  25. package/src/core/proxy/api-key-store.ts +37 -5
  26. package/src/core/proxy/index.ts +8 -1
  27. package/src/core/runtime/agent-runtime.ts +66 -18
  28. package/src/core/runtime/event-processor.ts +51 -26
  29. package/src/protocol/config.ts +8 -6
  30. package/src/{core/agents/error-classifier.ts → protocol/errors.ts} +33 -7
  31. package/src/protocol/factories.ts +106 -10
  32. package/src/protocol/guards.ts +53 -0
  33. package/src/protocol/index.ts +10 -0
  34. package/src/protocol/lifecycle.ts +289 -112
  35. package/src/protocol/meta.ts +14 -0
  36. package/src/protocol/part-input.ts +56 -7
  37. package/src/protocol/parts.ts +125 -10
  38. package/src/protocol/reduce.ts +749 -0
  39. package/src/protocol/selectors.ts +162 -0
  40. package/src/protocol/seq-cursor.ts +87 -0
  41. package/src/protocol/stop-reasons.ts +45 -0
  42. package/src/protocol/time.ts +23 -0
  43. package/src/protocol/tokens.ts +23 -0
  44. package/src/protocol/tool-state.ts +85 -25
  45. package/src/protocol/verify.ts +440 -0
  46. package/src/protocol/vocabulary.ts +18 -0
  47. package/src/protocol/wire.ts +81 -13
  48. package/src/server/acp/binding.ts +23 -2
  49. package/src/server/acp/translate.ts +51 -14
  50. package/src/server/agent-server.ts +85 -6
  51. package/AGENTS.md +0 -21
@@ -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 assistant output, normalized across harnesses.
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. `parentToolUseId` links a part produced inside a
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
- parentToolUseId: z.string().optional(),
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
- export const isTextPart = (p: Part): p is TextPart => p.type === "text";
59
- export const isReasoningPart = (p: Part): p is ReasoningPart => p.type === "reasoning";
60
- export const isToolPart = (p: Part): p is ToolPart => p.type === "tool";
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.