wtagent 0.1.0-alpha.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.
@@ -0,0 +1,202 @@
1
+ // Session exporters: canonical transcript -> Codex rollout JSONL and Claude
2
+ // Code project JSONL.
3
+ //
4
+ // The canonical transcript is already Codex-shaped, so the Codex exporter is a
5
+ // thin framing layer. The Claude Code exporter maps Responses-style items onto
6
+ // Anthropic Messages blocks (tool_use / tool_result) with the uuid/parentUuid
7
+ // linkage that Claude Code's project JSONL uses.
8
+ //
9
+ // Both exporters strip WTAgent-specific scaffolding: `developer` messages
10
+ // (protocol + tool catalog) and any legacy/current protocol or reminder blocks
11
+ // in user text. The WTAgent XML protocol and its tool set must never surface
12
+ // in a Codex or Claude Code session — those runtimes drive their own tools.
13
+
14
+ import { randomUUID } from "node:crypto";
15
+ import { stripSystemMarkers } from "../protocol/markers.js";
16
+ import { isDeveloper, messageText } from "./canonical-transcript.js";
17
+
18
+ function jsonl(records) {
19
+ return records.map((record) => JSON.stringify(record)).join("\n") + "\n";
20
+ }
21
+
22
+ function firstTimestamp(items, fallback) {
23
+ return items.find((item) => item.timestamp)?.timestamp ?? fallback;
24
+ }
25
+
26
+ // The uploaded bytes of an @file attachment live only in the ChatGPT
27
+ // conversation, not in the portable transcript. So exports append a short,
28
+ // self-describing note naming the attached files, keeping the exported session
29
+ // honest about what the user actually provided.
30
+ function withAttachmentNote(text, item) {
31
+ const attachments = Array.isArray(item?.attachments) ? item.attachments : [];
32
+ if (attachments.length === 0) {
33
+ return text;
34
+ }
35
+ const names = attachments.map((file) => file.name ?? file.path).filter(Boolean);
36
+ if (names.length === 0) {
37
+ return text;
38
+ }
39
+ const note = `[attached: ${names.join(", ")}]`;
40
+ return text ? `${text}\n\n${note}` : note;
41
+ }
42
+
43
+ // ---------------------------------------------------------------------------
44
+ // Codex rollout export
45
+ // ---------------------------------------------------------------------------
46
+ // Produces `{timestamp, type, payload}` records: a leading `session_meta`, then
47
+ // one `response_item` per transcript item. WTAgent's transport-only developer
48
+ // scaffold is omitted entirely; it must not become Codex instructions.
49
+ export function toCodexRollout(transcript, { now } = {}) {
50
+ const meta = transcript.meta ?? {};
51
+ const items = transcript.items ?? [];
52
+ const stamp = now ?? firstTimestamp(items, meta.createdAt) ?? null;
53
+ const sessionId = meta.sessionId ?? randomUUID();
54
+
55
+ const records = [];
56
+ records.push({
57
+ timestamp: stamp,
58
+ type: "session_meta",
59
+ payload: {
60
+ id: sessionId,
61
+ session_id: sessionId,
62
+ timestamp: meta.createdAt ?? stamp,
63
+ cwd: meta.cwd ?? null,
64
+ originator: "wtagent",
65
+ source: "wtagent",
66
+ base_instructions: meta.baseInstructions ?? null,
67
+ },
68
+ });
69
+
70
+ for (const entry of items) {
71
+ const item = entry.item;
72
+ if (isDeveloper(item)) {
73
+ // This is the XML/tool transport scaffold used only on ChatGPT Web.
74
+ continue;
75
+ }
76
+
77
+ let payload = item;
78
+ if (item.type === "message" && item.role === "user") {
79
+ const cleaned = withAttachmentNote(stripSystemMarkers(messageText(item)), item);
80
+ if (!cleaned) {
81
+ continue;
82
+ }
83
+ payload = {
84
+ type: "message",
85
+ role: "user",
86
+ content: [{ type: "input_text", text: cleaned }],
87
+ };
88
+ }
89
+
90
+ records.push({
91
+ timestamp: entry.timestamp ?? stamp,
92
+ type: "response_item",
93
+ payload,
94
+ });
95
+ }
96
+
97
+ return jsonl(records);
98
+ }
99
+
100
+ // ---------------------------------------------------------------------------
101
+ // Claude Code project export
102
+ // ---------------------------------------------------------------------------
103
+ // Produces uuid/parentUuid-linked records with Anthropic Messages content.
104
+ // function_call -> assistant message with a tool_use block.
105
+ // function_call_output -> user message with a tool_result block.
106
+ export function toClaudeCodeSession(transcript, { sessionId, now } = {}) {
107
+ const meta = transcript.meta ?? {};
108
+ const items = transcript.items ?? [];
109
+ const session = sessionId ?? meta.sessionId ?? randomUUID();
110
+ const cwd = meta.cwd ?? null;
111
+ const stamp = now ?? firstTimestamp(items, meta.createdAt) ?? null;
112
+
113
+ const records = [];
114
+ let parentUuid = null;
115
+
116
+ const push = (partial, timestamp) => {
117
+ const uuid = randomUUID();
118
+ records.push({
119
+ parentUuid,
120
+ isSidechain: false,
121
+ type: partial.type,
122
+ message: partial.message,
123
+ uuid,
124
+ timestamp: timestamp ?? stamp,
125
+ cwd,
126
+ sessionId: session,
127
+ version: "wtagent-export",
128
+ ...(partial.extra ?? {}),
129
+ });
130
+ parentUuid = uuid;
131
+ };
132
+
133
+ for (const entry of items) {
134
+ const item = entry.item;
135
+ const ts = entry.timestamp ?? stamp;
136
+
137
+ if (isDeveloper(item)) {
138
+ continue;
139
+ }
140
+
141
+ if (item.type === "message" && item.role === "user") {
142
+ const cleaned = withAttachmentNote(stripSystemMarkers(messageText(item)), item);
143
+ if (!cleaned) {
144
+ continue;
145
+ }
146
+ push({
147
+ type: "user",
148
+ message: { role: "user", content: cleaned },
149
+ }, ts);
150
+ } else if (item.type === "message" && item.role === "assistant") {
151
+ const text = messageText(item);
152
+ if (!text.trim()) {
153
+ continue;
154
+ }
155
+ push({
156
+ type: "assistant",
157
+ message: {
158
+ role: "assistant",
159
+ content: [{ type: "text", text }],
160
+ },
161
+ }, ts);
162
+ } else if (item.type === "function_call") {
163
+ let input = {};
164
+ try {
165
+ input = JSON.parse(item.arguments || "{}");
166
+ } catch {
167
+ input = { _raw: item.arguments };
168
+ }
169
+ push({
170
+ type: "assistant",
171
+ message: {
172
+ role: "assistant",
173
+ content: [{
174
+ type: "tool_use",
175
+ id: item.call_id,
176
+ name: item.name,
177
+ input,
178
+ }],
179
+ },
180
+ }, ts);
181
+ } else if (item.type === "function_call_output") {
182
+ push({
183
+ type: "user",
184
+ message: {
185
+ role: "user",
186
+ content: [{
187
+ type: "tool_result",
188
+ tool_use_id: item.call_id,
189
+ content: item.output,
190
+ }],
191
+ },
192
+ }, ts);
193
+ }
194
+ }
195
+
196
+ return jsonl(records);
197
+ }
198
+
199
+ export const EXPORTERS = {
200
+ codex: toCodexRollout,
201
+ "claude-code": toClaudeCodeSession,
202
+ };
@@ -0,0 +1,5 @@
1
+ // Backward-compatible module path for pre-session integrations.
2
+ export {
3
+ AgentSession,
4
+ AgentSession as TaskSession,
5
+ } from "./agent-session.js";
@@ -0,0 +1,52 @@
1
+ export class WTAgentError extends Error {
2
+ constructor(message, options = {}) {
3
+ super(message, options);
4
+ this.name = this.constructor.name;
5
+ this.code = options.code ?? "WTAGENT_ERROR";
6
+ this.recoverable = options.recoverable ?? false;
7
+ this.details = options.details;
8
+ }
9
+ }
10
+
11
+ export class ProtocolError extends WTAgentError {
12
+ constructor(message, options = {}) {
13
+ super(message, {
14
+ code: "PROTOCOL_ERROR",
15
+ recoverable: true,
16
+ ...options,
17
+ });
18
+ }
19
+ }
20
+
21
+ export class BrowserAdapterError extends WTAgentError {
22
+ constructor(message, options = {}) {
23
+ super(message, {
24
+ code: "BROWSER_ADAPTER_ERROR",
25
+ recoverable: true,
26
+ ...options,
27
+ });
28
+ }
29
+ }
30
+
31
+ export class ToolValidationError extends WTAgentError {
32
+ constructor(message, options = {}) {
33
+ super(message, {
34
+ code: "TOOL_VALIDATION_ERROR",
35
+ recoverable: true,
36
+ ...options,
37
+ });
38
+ }
39
+ }
40
+
41
+ export class PolicyDeniedError extends WTAgentError {
42
+ constructor(message, options = {}) {
43
+ super(message, {
44
+ code: "POLICY_DENIED",
45
+ recoverable: true,
46
+ ...options,
47
+ });
48
+ }
49
+ }
50
+
51
+ // Compatibility alias for integrations written before the product rename.
52
+ export { WTAgentError as WebAgentError };
@@ -0,0 +1,31 @@
1
+ export const DEFAULT_LIMITS = Object.freeze({
2
+ maxTurns: 36,
3
+ maxProtocolErrors: 3,
4
+ modelTurnTimeoutMs: 10 * 60_000,
5
+ modelStableWindowMs: 1_500,
6
+ loginTimeoutMs: 15 * 60_000,
7
+ toolTimeoutMs: 2 * 60_000,
8
+ maxToolOutputBytes: 64 * 1024,
9
+ maxFileReadBytes: 128 * 1024,
10
+ maxDirectoryEntries: 500,
11
+ maxSearchResults: 200,
12
+ });
13
+
14
+ export function resolveLimits({ modelTurnTimeoutMs } = {}) {
15
+ if (modelTurnTimeoutMs == null || modelTurnTimeoutMs === "") {
16
+ return DEFAULT_LIMITS;
17
+ }
18
+
19
+ const parsed = Number(modelTurnTimeoutMs);
20
+ if (!Number.isSafeInteger(parsed) || parsed <= 0) {
21
+ throw new TypeError(
22
+ "Model turn timeout must be a positive integer number of milliseconds.",
23
+ );
24
+ }
25
+
26
+ return Object.freeze({
27
+ ...DEFAULT_LIMITS,
28
+ modelTurnTimeoutMs: parsed,
29
+ });
30
+ }
31
+