@zvada/agent-server 0.2.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/LICENSE +21 -0
- package/README.md +71 -0
- package/package.json +87 -0
- package/src/client/client.ts +589 -0
- package/src/client/index.ts +18 -0
- package/src/client/transports.ts +84 -0
- package/src/core/agents/acp/acp-agent.ts +322 -0
- package/src/core/agents/acp/adapter.ts +260 -0
- package/src/core/agents/acp/client.ts +212 -0
- package/src/core/agents/acp/known-agents.ts +29 -0
- package/src/core/agents/acp/mappings.ts +136 -0
- package/src/core/agents/base.ts +145 -0
- package/src/core/agents/claude-code/adapter.ts +451 -0
- package/src/core/agents/claude-code/claude-agent.ts +235 -0
- package/src/core/agents/claude-code/generator-session.ts +344 -0
- package/src/core/agents/claude-code/options.ts +161 -0
- package/src/core/agents/claude-code/session-manager.ts +159 -0
- package/src/core/agents/codex-app-server/adapter.ts +214 -0
- package/src/core/agents/codex-app-server/client.ts +221 -0
- package/src/core/agents/codex-app-server/codex-app-server-agent.ts +385 -0
- package/src/core/agents/codex-items.ts +122 -0
- package/src/core/agents/codex-sdk/adapter.ts +204 -0
- package/src/core/agents/codex-sdk/codex-sdk-agent.ts +236 -0
- package/src/core/agents/config-fingerprint.ts +19 -0
- package/src/core/agents/error-classifier.ts +68 -0
- package/src/core/agents/registry.ts +40 -0
- package/src/core/agents/session-store.ts +72 -0
- package/src/core/agents/tool-meta.ts +68 -0
- package/src/core/agents/types.ts +54 -0
- package/src/core/index.ts +114 -0
- package/src/core/presets.ts +78 -0
- package/src/core/provision/extract.ts +31 -0
- package/src/core/provision/index.ts +10 -0
- package/src/core/provision/npm.ts +114 -0
- package/src/core/provision/pins.ts +51 -0
- package/src/core/provision/platform.ts +73 -0
- package/src/core/provision/provisioner.ts +478 -0
- package/src/core/proxy/anthropic-proxy.ts +69 -0
- package/src/core/proxy/api-key-store.ts +34 -0
- package/src/core/proxy/index.ts +7 -0
- package/src/core/runtime/agent-runtime.ts +363 -0
- package/src/core/runtime/event-processor.ts +218 -0
- package/src/core/runtime/event-sink.ts +37 -0
- package/src/core/utils/errors.ts +41 -0
- package/src/index.ts +4 -0
- package/src/protocol/async-queue.ts +68 -0
- package/src/protocol/config.ts +100 -0
- package/src/protocol/factories.ts +125 -0
- package/src/protocol/harness.ts +50 -0
- package/src/protocol/ids.ts +53 -0
- package/src/protocol/index.ts +16 -0
- package/src/protocol/lifecycle.ts +309 -0
- package/src/protocol/models.ts +45 -0
- package/src/protocol/part-input.ts +58 -0
- package/src/protocol/parts.ts +60 -0
- package/src/protocol/thinking.ts +32 -0
- package/src/protocol/tokens.ts +39 -0
- package/src/protocol/tool-state.ts +89 -0
- package/src/protocol/wire.ts +313 -0
- package/src/server/acp/binding.ts +163 -0
- package/src/server/acp/translate.ts +160 -0
- package/src/server/agent-server.ts +357 -0
- package/src/server/bin.ts +174 -0
- package/src/server/index.ts +24 -0
- package/src/server/install.ts +51 -0
- package/src/server/session-log.ts +66 -0
- package/src/server/transports.ts +149 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single-consumer async channel. Producers `push()` values and then `end()`
|
|
3
|
+
* (graceful) or `fail(error)` (consumers reject once buffered values drain);
|
|
4
|
+
* a single consumer iterates with `for await`. `push` after end/fail is a
|
|
5
|
+
* no-op; `return()` lets a consumer `break` out and end the stream. Shared by
|
|
6
|
+
* core (prompt feeding, per-turn event buffering) and client (turn streams).
|
|
7
|
+
*/
|
|
8
|
+
export class AsyncQueue<T> implements AsyncIterableIterator<T> {
|
|
9
|
+
private queue: T[] = [];
|
|
10
|
+
private waiting: Array<{
|
|
11
|
+
resolve: (r: IteratorResult<T>) => void;
|
|
12
|
+
reject: (e: unknown) => void;
|
|
13
|
+
}> = [];
|
|
14
|
+
private done = false;
|
|
15
|
+
private failure: unknown;
|
|
16
|
+
|
|
17
|
+
push(value: T): void {
|
|
18
|
+
if (this.done) return;
|
|
19
|
+
const w = this.waiting.shift();
|
|
20
|
+
if (w) w.resolve({ value, done: false });
|
|
21
|
+
else this.queue.push(value);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
end(): void {
|
|
25
|
+
if (this.done) return;
|
|
26
|
+
this.done = true;
|
|
27
|
+
for (const w of this.waiting) w.resolve({ value: undefined as never, done: true });
|
|
28
|
+
this.waiting = [];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
fail(error: unknown): void {
|
|
32
|
+
if (this.done) return;
|
|
33
|
+
this.done = true;
|
|
34
|
+
this.failure = error;
|
|
35
|
+
for (const w of this.waiting) w.reject(error);
|
|
36
|
+
this.waiting = [];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
get size(): number {
|
|
40
|
+
return this.queue.length;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
next(): Promise<IteratorResult<T>> {
|
|
44
|
+
// Check length, not the shifted value: a queued `undefined`/falsy item is
|
|
45
|
+
// a real value and must not be mistaken for an empty queue.
|
|
46
|
+
if (this.queue.length > 0) {
|
|
47
|
+
return Promise.resolve({ value: this.queue.shift() as T, done: false });
|
|
48
|
+
}
|
|
49
|
+
if (this.done) {
|
|
50
|
+
return this.failure !== undefined
|
|
51
|
+
? Promise.reject(this.failure)
|
|
52
|
+
: Promise.resolve({ value: undefined as never, done: true });
|
|
53
|
+
}
|
|
54
|
+
return new Promise((resolve, reject) => this.waiting.push({ resolve, reject }));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return(): Promise<IteratorResult<T>> {
|
|
58
|
+
// IteratorClose is terminal: a consumer that breaks has discarded every
|
|
59
|
+
// buffered value, even when return() is called directly.
|
|
60
|
+
this.queue = [];
|
|
61
|
+
this.end();
|
|
62
|
+
return Promise.resolve({ value: undefined as never, done: true });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
[Symbol.asyncIterator](): AsyncIterableIterator<T> {
|
|
66
|
+
return this;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { AgentHarnessSchema } from "./harness.ts";
|
|
3
|
+
import { AgentInputSchema } from "./part-input.ts";
|
|
4
|
+
import { ThinkingLevelSchema } from "./thinking.ts";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Permission posture for a turn. `bypassPermissions`/`yolo` auto-approve every
|
|
8
|
+
* tool (the right default inside a sandbox); `plan` is read-only planning;
|
|
9
|
+
* `dontAsk` never prompts — unapproved tools are denied, sandboxes stay at
|
|
10
|
+
* their normal (non-dangerous) level. Harnesses map this onto their own modes.
|
|
11
|
+
*/
|
|
12
|
+
export const PermissionModeSchema = z.enum([
|
|
13
|
+
"default",
|
|
14
|
+
"plan",
|
|
15
|
+
"acceptEdits",
|
|
16
|
+
"dontAsk",
|
|
17
|
+
"bypassPermissions",
|
|
18
|
+
]);
|
|
19
|
+
export type PermissionMode = z.infer<typeof PermissionModeSchema>;
|
|
20
|
+
|
|
21
|
+
/** Minimal external MCP server descriptor (stdio or http/sse). */
|
|
22
|
+
export const McpServerConfigSchema = z.discriminatedUnion("type", [
|
|
23
|
+
z.object({
|
|
24
|
+
type: z.literal("stdio"),
|
|
25
|
+
command: z.string(),
|
|
26
|
+
args: z.array(z.string()).optional(),
|
|
27
|
+
env: z.record(z.string(), z.string()).optional(),
|
|
28
|
+
}),
|
|
29
|
+
z.object({
|
|
30
|
+
type: z.literal("http"),
|
|
31
|
+
url: z.string(),
|
|
32
|
+
headers: z.record(z.string(), z.string()).optional(),
|
|
33
|
+
}),
|
|
34
|
+
z.object({
|
|
35
|
+
type: z.literal("sse"),
|
|
36
|
+
url: z.string(),
|
|
37
|
+
headers: z.record(z.string(), z.string()).optional(),
|
|
38
|
+
}),
|
|
39
|
+
]);
|
|
40
|
+
export type McpServerConfig = z.infer<typeof McpServerConfigSchema>;
|
|
41
|
+
|
|
42
|
+
/** Everything needed to configure a turn against a harness. */
|
|
43
|
+
export const RunConfigSchema = z.object({
|
|
44
|
+
harness: AgentHarnessSchema,
|
|
45
|
+
/** Free-form model id/alias; omit to use the harness default. */
|
|
46
|
+
model: z.string().optional(),
|
|
47
|
+
thinkingLevel: ThinkingLevelSchema.optional(),
|
|
48
|
+
/** Working directory the agent operates in. */
|
|
49
|
+
cwd: z.string(),
|
|
50
|
+
/**
|
|
51
|
+
* Extra directories the agent may access beyond `cwd`. Mapped by the
|
|
52
|
+
* claude-code and codex-sdk harnesses; codex-app-server and acp have no
|
|
53
|
+
* native equivalent today and ignore it.
|
|
54
|
+
*/
|
|
55
|
+
additionalDirectories: z.array(z.string()).optional(),
|
|
56
|
+
systemPromptAppend: z.string().optional(),
|
|
57
|
+
maxTurns: z.number().int().positive().optional(),
|
|
58
|
+
permissionMode: PermissionModeSchema.optional(),
|
|
59
|
+
/**
|
|
60
|
+
* Native session/thread id to resume (from a prior `session.created`). When
|
|
61
|
+
* set, the harness continues that conversation instead of starting fresh.
|
|
62
|
+
*/
|
|
63
|
+
resumeSessionId: z.string().optional(),
|
|
64
|
+
/**
|
|
65
|
+
* Resume from a specific historical message within the resumed session
|
|
66
|
+
* (branch/retry flows). Claude-only today; other harnesses ignore it.
|
|
67
|
+
*/
|
|
68
|
+
resumeSessionAt: z.string().optional(),
|
|
69
|
+
mcpServers: z.record(z.string(), McpServerConfigSchema).optional(),
|
|
70
|
+
/** Extra env vars layered onto the agent subprocess. */
|
|
71
|
+
env: z.record(z.string(), z.string()).optional(),
|
|
72
|
+
/** Per-turn API key override (BYOK). Falls back to ambient/proxy auth. */
|
|
73
|
+
apiKey: z.string().optional(),
|
|
74
|
+
/**
|
|
75
|
+
* Disable all built-in tools for this turn (text-only). Honored by harnesses
|
|
76
|
+
* that support it (Claude); Codex achieves the same via a read-only sandbox.
|
|
77
|
+
*/
|
|
78
|
+
disableTools: z.boolean().optional(),
|
|
79
|
+
/**
|
|
80
|
+
* Interleave `raw` passthrough events (the harness's unparsed events) with
|
|
81
|
+
* the normalized stream. Migration/debug escape hatch — see `RawEvent`.
|
|
82
|
+
*/
|
|
83
|
+
includeRaw: z.boolean().optional(),
|
|
84
|
+
});
|
|
85
|
+
export type RunConfig = z.infer<typeof RunConfigSchema>;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* A single request to run one turn.
|
|
89
|
+
* - `sessionId` is OUR stable logical id (the session-manager key). It is the
|
|
90
|
+
* same across every turn of a conversation.
|
|
91
|
+
* - the harness-native id (for resume) is discovered at runtime and surfaced
|
|
92
|
+
* via the `session.created` lifecycle event.
|
|
93
|
+
*/
|
|
94
|
+
export const RunRequestSchema = z.object({
|
|
95
|
+
sessionId: z.string(),
|
|
96
|
+
turnId: z.string(),
|
|
97
|
+
input: AgentInputSchema,
|
|
98
|
+
config: RunConfigSchema,
|
|
99
|
+
});
|
|
100
|
+
export type RunRequest = z.infer<typeof RunRequestSchema>;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { generateUUIDv7 } from "./ids.ts";
|
|
2
|
+
import type { ReasoningPart, TextPart, ToolPart } from "./parts.ts";
|
|
3
|
+
import type { ToolKind, ToolLocation } from "./tool-state.ts";
|
|
4
|
+
|
|
5
|
+
/** Context an adapter threads through while building parts for one message. */
|
|
6
|
+
export interface StreamContext {
|
|
7
|
+
sessionId: string;
|
|
8
|
+
messageId: string;
|
|
9
|
+
parentToolUseId?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Optional normalized metadata for a tool part (see ToolPart). */
|
|
13
|
+
export interface ToolMeta {
|
|
14
|
+
kind?: ToolKind;
|
|
15
|
+
title?: string;
|
|
16
|
+
locations?: ToolLocation[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function createTextPart(ctx: StreamContext, text: string, streaming = true): TextPart {
|
|
20
|
+
return {
|
|
21
|
+
id: generateUUIDv7(),
|
|
22
|
+
sessionId: ctx.sessionId,
|
|
23
|
+
messageId: ctx.messageId,
|
|
24
|
+
...(ctx.parentToolUseId && { parentToolUseId: ctx.parentToolUseId }),
|
|
25
|
+
type: "text",
|
|
26
|
+
text,
|
|
27
|
+
state: streaming ? "streaming" : "done",
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function createReasoningPart(
|
|
32
|
+
ctx: StreamContext,
|
|
33
|
+
text: string,
|
|
34
|
+
streaming = true,
|
|
35
|
+
): ReasoningPart {
|
|
36
|
+
return {
|
|
37
|
+
id: generateUUIDv7(),
|
|
38
|
+
sessionId: ctx.sessionId,
|
|
39
|
+
messageId: ctx.messageId,
|
|
40
|
+
...(ctx.parentToolUseId && { parentToolUseId: ctx.parentToolUseId }),
|
|
41
|
+
type: "reasoning",
|
|
42
|
+
text,
|
|
43
|
+
state: streaming ? "streaming" : "done",
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function toolBase(ctx: StreamContext, toolCallId: string, toolName: string, meta?: ToolMeta) {
|
|
48
|
+
return {
|
|
49
|
+
id: generateUUIDv7(),
|
|
50
|
+
sessionId: ctx.sessionId,
|
|
51
|
+
messageId: ctx.messageId,
|
|
52
|
+
...(ctx.parentToolUseId && { parentToolUseId: ctx.parentToolUseId }),
|
|
53
|
+
type: "tool" as const,
|
|
54
|
+
toolCallId,
|
|
55
|
+
toolName,
|
|
56
|
+
...(meta?.kind && { kind: meta.kind }),
|
|
57
|
+
...(meta?.title && { title: meta.title }),
|
|
58
|
+
...(meta?.locations?.length && { locations: meta.locations }),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function createToolPart(
|
|
63
|
+
ctx: StreamContext,
|
|
64
|
+
toolCallId: string,
|
|
65
|
+
toolName: string,
|
|
66
|
+
input: Record<string, unknown>,
|
|
67
|
+
meta?: ToolMeta,
|
|
68
|
+
): ToolPart {
|
|
69
|
+
return {
|
|
70
|
+
...toolBase(ctx, toolCallId, toolName, meta),
|
|
71
|
+
state: { status: "in_progress", input, time: { start: Date.now() } },
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** A tool call whose input is still streaming in (partial JSON not yet parseable). */
|
|
76
|
+
export function createPendingToolPart(
|
|
77
|
+
ctx: StreamContext,
|
|
78
|
+
toolCallId: string,
|
|
79
|
+
toolName: string,
|
|
80
|
+
meta?: ToolMeta,
|
|
81
|
+
): ToolPart {
|
|
82
|
+
return {
|
|
83
|
+
...toolBase(ctx, toolCallId, toolName, meta),
|
|
84
|
+
state: { status: "pending", partialInput: "" },
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Mutates a pending tool part into `in_progress` once its input has fully parsed. */
|
|
89
|
+
export function startToolPart(part: ToolPart, input: Record<string, unknown>): void {
|
|
90
|
+
if (part.state.status === "pending") {
|
|
91
|
+
part.state = { status: "in_progress", input, time: { start: Date.now() } };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Appends a streamed JSON fragment to a still-pending tool part. */
|
|
96
|
+
export function appendToolInput(part: ToolPart, partialJson: string): void {
|
|
97
|
+
if (part.state.status === "pending") {
|
|
98
|
+
part.state.partialInput += partialJson;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Attach/refresh normalized metadata on an existing tool part. */
|
|
103
|
+
export function setToolMeta(part: ToolPart, meta: ToolMeta): void {
|
|
104
|
+
if (meta.kind) part.kind = meta.kind;
|
|
105
|
+
if (meta.title) part.title = meta.title;
|
|
106
|
+
if (meta.locations?.length) part.locations = meta.locations;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Mutates a tool part into its terminal `completed`/`failed` state. */
|
|
110
|
+
export function completeToolPart(
|
|
111
|
+
part: ToolPart,
|
|
112
|
+
result: { output: string; isError?: boolean; title?: string },
|
|
113
|
+
): void {
|
|
114
|
+
const start = part.state.status === "in_progress" ? part.state.time.start : Date.now();
|
|
115
|
+
const input = "input" in part.state ? part.state.input : {};
|
|
116
|
+
part.state = result.isError
|
|
117
|
+
? { status: "failed", input, error: result.output, time: { start, end: Date.now() } }
|
|
118
|
+
: {
|
|
119
|
+
status: "completed",
|
|
120
|
+
input,
|
|
121
|
+
output: result.output,
|
|
122
|
+
title: result.title ?? part.toolName,
|
|
123
|
+
time: { start, end: Date.now() },
|
|
124
|
+
};
|
|
125
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The agent harnesses this engine can drive:
|
|
5
|
+
* - `claude-code` — Claude Code via @anthropic-ai/claude-agent-sdk (CLI under the hood)
|
|
6
|
+
* - `codex-sdk` — OpenAI Codex via @openai/codex-sdk (wraps the `codex` CLI in exec mode)
|
|
7
|
+
* - `codex-app-server` — OpenAI Codex via `codex app-server` (long-lived JSON-RPC subprocess)
|
|
8
|
+
* - `acp` — any Agent Client Protocol v1 agent subprocess (`gemini --acp`,
|
|
9
|
+
* `claude-code-acp`, `codex-acp`, ACP registry agents); the launch
|
|
10
|
+
* command is operator-configured, never taken from the wire
|
|
11
|
+
*/
|
|
12
|
+
export const AGENT_HARNESSES = ["claude-code", "codex-sdk", "codex-app-server", "acp"] as const;
|
|
13
|
+
export type AgentHarness = (typeof AGENT_HARNESSES)[number];
|
|
14
|
+
export const AgentHarnessSchema = z.enum(AGENT_HARNESSES);
|
|
15
|
+
|
|
16
|
+
export function isAgentHarness(value: string): value is AgentHarness {
|
|
17
|
+
return (AGENT_HARNESSES as readonly string[]).includes(value);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* How a harness handles changing the model mid-conversation:
|
|
22
|
+
* - `in-session` — swap live without losing context (Claude `setModel`)
|
|
23
|
+
* - `restart-session` — must start a fresh underlying session (Codex)
|
|
24
|
+
* - `unsupported` — model is fixed for the life of the session
|
|
25
|
+
*/
|
|
26
|
+
export const ModelSwitchModeSchema = z.enum(["in-session", "restart-session", "unsupported"]);
|
|
27
|
+
export type ModelSwitchMode = z.infer<typeof ModelSwitchModeSchema>;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Declarative feature matrix per harness. Callers consult this to decide what
|
|
31
|
+
* is safe to ask for (e.g. whether to expect resume, or whether a model change
|
|
32
|
+
* forces a restart). Returned in the engine's session snapshot.
|
|
33
|
+
*/
|
|
34
|
+
export const AgentCapabilitiesSchema = z.object({
|
|
35
|
+
/** Can feed multiple turns into one long-lived underlying session. */
|
|
36
|
+
multiTurn: z.boolean(),
|
|
37
|
+
/** Can resume a prior session from its native id. */
|
|
38
|
+
sessionResume: z.boolean(),
|
|
39
|
+
/** How a model change is handled. */
|
|
40
|
+
modelSwitch: ModelSwitchModeSchema,
|
|
41
|
+
/** Honors a thinking/reasoning-effort level. */
|
|
42
|
+
thinkingLevels: z.boolean(),
|
|
43
|
+
/** Accepts image input parts. */
|
|
44
|
+
images: z.boolean(),
|
|
45
|
+
/** Accepts external MCP servers. */
|
|
46
|
+
mcpServers: z.boolean(),
|
|
47
|
+
/** Can raise interactive `permission.requested` round-trips mid-turn. */
|
|
48
|
+
permissionRequests: z.boolean(),
|
|
49
|
+
});
|
|
50
|
+
export type AgentCapabilities = z.infer<typeof AgentCapabilitiesSchema>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UUIDv7 — time-ordered identifiers. The leading 48 bits are a millisecond
|
|
3
|
+
* timestamp, so ids sort chronologically (handy for ordering parts/messages
|
|
4
|
+
* without a separate createdAt column). Uses Web Crypto, available on both
|
|
5
|
+
* Bun and Node >= 20 via `globalThis.crypto`.
|
|
6
|
+
*/
|
|
7
|
+
export function generateUUIDv7(): string {
|
|
8
|
+
const timestamp = Date.now();
|
|
9
|
+
|
|
10
|
+
const rand = new Uint8Array(10);
|
|
11
|
+
crypto.getRandomValues(rand);
|
|
12
|
+
|
|
13
|
+
const bytes = new Uint8Array(16);
|
|
14
|
+
|
|
15
|
+
// 48-bit big-endian timestamp.
|
|
16
|
+
bytes[0] = (timestamp / 2 ** 40) & 0xff;
|
|
17
|
+
bytes[1] = (timestamp / 2 ** 32) & 0xff;
|
|
18
|
+
bytes[2] = (timestamp / 2 ** 24) & 0xff;
|
|
19
|
+
bytes[3] = (timestamp / 2 ** 16) & 0xff;
|
|
20
|
+
bytes[4] = (timestamp / 2 ** 8) & 0xff;
|
|
21
|
+
bytes[5] = timestamp & 0xff;
|
|
22
|
+
|
|
23
|
+
// version (7) + 4 bits random
|
|
24
|
+
bytes[6] = 0x70 | (rand[0]! & 0x0f);
|
|
25
|
+
bytes[7] = rand[1]!;
|
|
26
|
+
// variant (10xx) + 6 bits random
|
|
27
|
+
bytes[8] = 0x80 | (rand[2]! & 0x3f);
|
|
28
|
+
bytes[9] = rand[3]!;
|
|
29
|
+
bytes[10] = rand[4]!;
|
|
30
|
+
bytes[11] = rand[5]!;
|
|
31
|
+
bytes[12] = rand[6]!;
|
|
32
|
+
bytes[13] = rand[7]!;
|
|
33
|
+
bytes[14] = rand[8]!;
|
|
34
|
+
bytes[15] = rand[9]!;
|
|
35
|
+
|
|
36
|
+
const hex: string[] = [];
|
|
37
|
+
for (let i = 0; i < 16; i++) hex.push(bytes[i]!.toString(16).padStart(2, "0"));
|
|
38
|
+
return `${hex.slice(0, 4).join("")}-${hex.slice(4, 6).join("")}-${hex
|
|
39
|
+
.slice(6, 8)
|
|
40
|
+
.join("")}-${hex.slice(8, 10).join("")}-${hex.slice(10, 16).join("")}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Extract the embedded millisecond timestamp from a UUIDv7. */
|
|
44
|
+
export function timestampFromUUIDv7(id: string): number {
|
|
45
|
+
const hex = id.replace(/-/g, "").slice(0, 12);
|
|
46
|
+
return Number.parseInt(hex, 16);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Short, non-sortable id for ad-hoc correlation (e.g. JSON-RPC request ids). */
|
|
50
|
+
export function generateId(prefix?: string): string {
|
|
51
|
+
const id = crypto.randomUUID().replace(/-/g, "").slice(0, 16);
|
|
52
|
+
return prefix ? `${prefix}_${id}` : id;
|
|
53
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// @zvada/agent-server/protocol — the wire contract.
|
|
2
|
+
// Normalized data shapes shared by the engine and every consumer.
|
|
3
|
+
|
|
4
|
+
export * from "./ids.ts";
|
|
5
|
+
export * from "./async-queue.ts";
|
|
6
|
+
export * from "./tokens.ts";
|
|
7
|
+
export * from "./tool-state.ts";
|
|
8
|
+
export * from "./parts.ts";
|
|
9
|
+
export * from "./part-input.ts";
|
|
10
|
+
export * from "./factories.ts";
|
|
11
|
+
export * from "./harness.ts";
|
|
12
|
+
export * from "./thinking.ts";
|
|
13
|
+
export * from "./models.ts";
|
|
14
|
+
export * from "./config.ts";
|
|
15
|
+
export * from "./lifecycle.ts";
|
|
16
|
+
export * from "./wire.ts";
|