twinny 0.0.0-dev.260525150705

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 (191) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +264 -0
  3. package/README.zh-CN.md +252 -0
  4. package/configs/banner.png +0 -0
  5. package/configs/logo.png +0 -0
  6. package/dist/app/caffeinate.d.ts +28 -0
  7. package/dist/app/caffeinate.js +96 -0
  8. package/dist/app/caffeinate.js.map +1 -0
  9. package/dist/app/daemon.d.ts +1 -0
  10. package/dist/app/daemon.js +44 -0
  11. package/dist/app/daemon.js.map +1 -0
  12. package/dist/app/lark-assets.d.ts +25 -0
  13. package/dist/app/lark-assets.js +108 -0
  14. package/dist/app/lark-assets.js.map +1 -0
  15. package/dist/app/startup-probe.d.ts +17 -0
  16. package/dist/app/startup-probe.js +90 -0
  17. package/dist/app/startup-probe.js.map +1 -0
  18. package/dist/app/wiring.d.ts +122 -0
  19. package/dist/app/wiring.js +694 -0
  20. package/dist/app/wiring.js.map +1 -0
  21. package/dist/cli/commands.d.ts +1 -0
  22. package/dist/cli/commands.js +47 -0
  23. package/dist/cli/commands.js.map +1 -0
  24. package/dist/cli/install-wizard.d.ts +41 -0
  25. package/dist/cli/install-wizard.js +629 -0
  26. package/dist/cli/install-wizard.js.map +1 -0
  27. package/dist/codex/appserver.d.ts +109 -0
  28. package/dist/codex/appserver.js +308 -0
  29. package/dist/codex/appserver.js.map +1 -0
  30. package/dist/codex/goal.d.ts +64 -0
  31. package/dist/codex/goal.js +433 -0
  32. package/dist/codex/goal.js.map +1 -0
  33. package/dist/codex/index.d.ts +6 -0
  34. package/dist/codex/index.js +7 -0
  35. package/dist/codex/index.js.map +1 -0
  36. package/dist/codex/protocol.d.ts +95 -0
  37. package/dist/codex/protocol.js +205 -0
  38. package/dist/codex/protocol.js.map +1 -0
  39. package/dist/codex/thread-name.d.ts +3 -0
  40. package/dist/codex/thread-name.js +27 -0
  41. package/dist/codex/thread-name.js.map +1 -0
  42. package/dist/codex/thread.d.ts +76 -0
  43. package/dist/codex/thread.js +80 -0
  44. package/dist/codex/thread.js.map +1 -0
  45. package/dist/codex/turn.d.ts +166 -0
  46. package/dist/codex/turn.js +746 -0
  47. package/dist/codex/turn.js.map +1 -0
  48. package/dist/config/bootstrap.d.ts +14 -0
  49. package/dist/config/bootstrap.js +56 -0
  50. package/dist/config/bootstrap.js.map +1 -0
  51. package/dist/config/index.d.ts +4 -0
  52. package/dist/config/index.js +5 -0
  53. package/dist/config/index.js.map +1 -0
  54. package/dist/config/loader.d.ts +49 -0
  55. package/dist/config/loader.js +467 -0
  56. package/dist/config/loader.js.map +1 -0
  57. package/dist/config/paths.d.ts +11 -0
  58. package/dist/config/paths.js +43 -0
  59. package/dist/config/paths.js.map +1 -0
  60. package/dist/config/secrets.d.ts +33 -0
  61. package/dist/config/secrets.js +85 -0
  62. package/dist/config/secrets.js.map +1 -0
  63. package/dist/conversation/manager.d.ts +701 -0
  64. package/dist/conversation/manager.js +7673 -0
  65. package/dist/conversation/manager.js.map +1 -0
  66. package/dist/conversation/queue.d.ts +8 -0
  67. package/dist/conversation/queue.js +28 -0
  68. package/dist/conversation/queue.js.map +1 -0
  69. package/dist/conversation/routing.d.ts +11 -0
  70. package/dist/conversation/routing.js +55 -0
  71. package/dist/conversation/routing.js.map +1 -0
  72. package/dist/errors.d.ts +6 -0
  73. package/dist/errors.js +17 -0
  74. package/dist/errors.js.map +1 -0
  75. package/dist/index.d.ts +3 -0
  76. package/dist/index.js +4 -0
  77. package/dist/index.js.map +1 -0
  78. package/dist/lark/auth.d.ts +41 -0
  79. package/dist/lark/auth.js +132 -0
  80. package/dist/lark/auth.js.map +1 -0
  81. package/dist/lark/browser-auth.d.ts +68 -0
  82. package/dist/lark/browser-auth.js +258 -0
  83. package/dist/lark/browser-auth.js.map +1 -0
  84. package/dist/lark/cards.d.ts +140 -0
  85. package/dist/lark/cards.js +1150 -0
  86. package/dist/lark/cards.js.map +1 -0
  87. package/dist/lark/contact.d.ts +41 -0
  88. package/dist/lark/contact.js +122 -0
  89. package/dist/lark/contact.js.map +1 -0
  90. package/dist/lark/events.d.ts +65 -0
  91. package/dist/lark/events.js +218 -0
  92. package/dist/lark/events.js.map +1 -0
  93. package/dist/lark/files.d.ts +36 -0
  94. package/dist/lark/files.js +191 -0
  95. package/dist/lark/files.js.map +1 -0
  96. package/dist/lark/filters.d.ts +73 -0
  97. package/dist/lark/filters.js +678 -0
  98. package/dist/lark/filters.js.map +1 -0
  99. package/dist/lark/index.d.ts +10 -0
  100. package/dist/lark/index.js +11 -0
  101. package/dist/lark/index.js.map +1 -0
  102. package/dist/lark/messages.d.ts +87 -0
  103. package/dist/lark/messages.js +428 -0
  104. package/dist/lark/messages.js.map +1 -0
  105. package/dist/lark/openapi.d.ts +58 -0
  106. package/dist/lark/openapi.js +206 -0
  107. package/dist/lark/openapi.js.map +1 -0
  108. package/dist/lark/redactor.d.ts +5 -0
  109. package/dist/lark/redactor.js +68 -0
  110. package/dist/lark/redactor.js.map +1 -0
  111. package/dist/lark/types.d.ts +49 -0
  112. package/dist/lark/types.js +18 -0
  113. package/dist/lark/types.js.map +1 -0
  114. package/dist/launchd/install.d.ts +22 -0
  115. package/dist/launchd/install.js +114 -0
  116. package/dist/launchd/install.js.map +1 -0
  117. package/dist/launchd/plist.d.ts +10 -0
  118. package/dist/launchd/plist.js +61 -0
  119. package/dist/launchd/plist.js.map +1 -0
  120. package/dist/lock/index.d.ts +20 -0
  121. package/dist/lock/index.js +74 -0
  122. package/dist/lock/index.js.map +1 -0
  123. package/dist/main.d.ts +2 -0
  124. package/dist/main.js +11 -0
  125. package/dist/main.js.map +1 -0
  126. package/dist/markdown.d.ts +12 -0
  127. package/dist/markdown.js +149 -0
  128. package/dist/markdown.js.map +1 -0
  129. package/dist/observability/health.d.ts +25 -0
  130. package/dist/observability/health.js +187 -0
  131. package/dist/observability/health.js.map +1 -0
  132. package/dist/observability/logs.d.ts +10 -0
  133. package/dist/observability/logs.js +34 -0
  134. package/dist/observability/logs.js.map +1 -0
  135. package/dist/observability/system-notifications.d.ts +25 -0
  136. package/dist/observability/system-notifications.js +33 -0
  137. package/dist/observability/system-notifications.js.map +1 -0
  138. package/dist/profiles/guest.d.ts +19 -0
  139. package/dist/profiles/guest.js +241 -0
  140. package/dist/profiles/guest.js.map +1 -0
  141. package/dist/profiles/index.d.ts +5 -0
  142. package/dist/profiles/index.js +14 -0
  143. package/dist/profiles/index.js.map +1 -0
  144. package/dist/profiles/owner.d.ts +1 -0
  145. package/dist/profiles/owner.js +6 -0
  146. package/dist/profiles/owner.js.map +1 -0
  147. package/dist/store/db.d.ts +10 -0
  148. package/dist/store/db.js +29 -0
  149. package/dist/store/db.js.map +1 -0
  150. package/dist/store/index.d.ts +3 -0
  151. package/dist/store/index.js +4 -0
  152. package/dist/store/index.js.map +1 -0
  153. package/dist/store/migrations.d.ts +13 -0
  154. package/dist/store/migrations.js +79 -0
  155. package/dist/store/migrations.js.map +1 -0
  156. package/dist/store/repositories.d.ts +227 -0
  157. package/dist/store/repositories.js +1384 -0
  158. package/dist/store/repositories.js.map +1 -0
  159. package/dist/telemetry/client.d.ts +60 -0
  160. package/dist/telemetry/client.js +204 -0
  161. package/dist/telemetry/client.js.map +1 -0
  162. package/dist/telemetry/hash.d.ts +1 -0
  163. package/dist/telemetry/hash.js +8 -0
  164. package/dist/telemetry/hash.js.map +1 -0
  165. package/dist/telemetry/index.d.ts +4 -0
  166. package/dist/telemetry/index.js +5 -0
  167. package/dist/telemetry/index.js.map +1 -0
  168. package/dist/telemetry/posthog.d.ts +27 -0
  169. package/dist/telemetry/posthog.js +45 -0
  170. package/dist/telemetry/posthog.js.map +1 -0
  171. package/dist/telemetry/reporter.d.ts +13 -0
  172. package/dist/telemetry/reporter.js +29 -0
  173. package/dist/telemetry/reporter.js.map +1 -0
  174. package/dist/types.d.ts +330 -0
  175. package/dist/types.js +9 -0
  176. package/dist/types.js.map +1 -0
  177. package/dist/version.d.ts +1 -0
  178. package/dist/version.js +1 -0
  179. package/dist/version.js.map +1 -0
  180. package/dist/version.json +3 -0
  181. package/dist/workspace/index.d.ts +2 -0
  182. package/dist/workspace/index.js +3 -0
  183. package/dist/workspace/index.js.map +1 -0
  184. package/dist/workspace/manager.d.ts +14 -0
  185. package/dist/workspace/manager.js +69 -0
  186. package/dist/workspace/manager.js.map +1 -0
  187. package/dist/workspace/slug.d.ts +8 -0
  188. package/dist/workspace/slug.js +59 -0
  189. package/dist/workspace/slug.js.map +1 -0
  190. package/migrations/0001_initial.sql +102 -0
  191. package/package.json +85 -0
@@ -0,0 +1,746 @@
1
+ import { TwinnyError, toErrorMessage } from "../errors.js";
2
+ import { isThreadGoal } from "./goal.js";
3
+ export function buildTextTurnInput(text) {
4
+ return {
5
+ type: "text",
6
+ text,
7
+ text_elements: []
8
+ };
9
+ }
10
+ export function buildLocalImageTurnInput(path) {
11
+ return {
12
+ type: "localImage",
13
+ path,
14
+ detail: null
15
+ };
16
+ }
17
+ export function normalizeCodexTurnInput(input, fallbackText = "") {
18
+ if (Array.isArray(input)) {
19
+ return input;
20
+ }
21
+ return [buildTextTurnInput(input ?? fallbackText)];
22
+ }
23
+ export function prefixCurrentThreadNameInput(input, threadName) {
24
+ const prefix = `<current_thread_name>${escapeXmlText(threadName)}</current_thread_name>\n`;
25
+ const firstTextIndex = input.findIndex((item) => item.type === "text");
26
+ if (firstTextIndex < 0) {
27
+ return [buildTextTurnInput(prefix), ...input];
28
+ }
29
+ return input.map((item, index) => {
30
+ if (index !== firstTextIndex || item.type !== "text") {
31
+ return item;
32
+ }
33
+ return {
34
+ ...item,
35
+ text: `${prefix}${item.text}`
36
+ };
37
+ });
38
+ }
39
+ export function buildTurnStartParams(options) {
40
+ const input = normalizeCodexTurnInput(options.input, options.text ?? "");
41
+ const params = {
42
+ threadId: options.threadId,
43
+ input: options.currentThreadName === undefined ? input : prefixCurrentThreadNameInput(input, options.currentThreadName),
44
+ cwd: options.cwd,
45
+ approvalPolicy: "never"
46
+ };
47
+ if (options.mode) {
48
+ params.collaborationMode = {
49
+ mode: options.mode,
50
+ settings: {
51
+ model: options.model ?? "gpt-5.5",
52
+ reasoning_effort: options.effort ?? "medium",
53
+ developer_instructions: null
54
+ }
55
+ };
56
+ }
57
+ return params;
58
+ }
59
+ export function buildTurnSteerParams(options) {
60
+ return {
61
+ threadId: options.threadId,
62
+ input: normalizeCodexTurnInput(options.input, options.text ?? ""),
63
+ expectedTurnId: options.turnId
64
+ };
65
+ }
66
+ export function buildTurnInterruptParams(options) {
67
+ return {
68
+ threadId: options.threadId,
69
+ turnId: options.turnId
70
+ };
71
+ }
72
+ export async function startCodexTurn(protocol, options, requestOptions = {}) {
73
+ const accumulator = new TurnOutputAccumulator(options.threadId, undefined, {
74
+ onTurnStarted: options.onTurnStarted,
75
+ onAgentMessage: options.onAgentMessage,
76
+ onImageGeneration: options.onImageGeneration,
77
+ onTokenUsage: options.onTokenUsage,
78
+ onGoalUpdated: options.onGoalUpdated,
79
+ onGoalCleared: options.onGoalCleared,
80
+ onPlanUpdated: options.onPlanUpdated
81
+ });
82
+ const onNotification = (notification) => {
83
+ accumulator.record(notification);
84
+ };
85
+ const onServerRequest = (request) => {
86
+ handleTurnServerRequest(protocol, options, request);
87
+ };
88
+ protocol.on("notification", onNotification);
89
+ protocol.on("serverRequest", onServerRequest);
90
+ try {
91
+ const response = await protocol.request("turn/start", buildTurnStartParams(options), { timeoutMs: requestOptions.requestTimeoutMs });
92
+ if (response.turn?.id) {
93
+ accumulator.setTurnId(response.turn.id, "response");
94
+ }
95
+ return await accumulator.wait(requestOptions.completionTimeoutMs);
96
+ }
97
+ catch (error) {
98
+ throw error instanceof Error
99
+ ? error
100
+ : new TwinnyError(toErrorMessage(error), "CODEX_TURN_FAILED", error);
101
+ }
102
+ finally {
103
+ protocol.off("notification", onNotification);
104
+ protocol.off("serverRequest", onServerRequest);
105
+ }
106
+ }
107
+ export async function compactCodexThread(protocol, options, requestOptions = {}) {
108
+ const accumulator = new TurnOutputAccumulator(options.threadId, undefined, {
109
+ onTurnStarted: options.onTurnStarted,
110
+ onTokenUsage: options.onTokenUsage
111
+ });
112
+ const onNotification = (notification) => {
113
+ accumulator.record(notification);
114
+ };
115
+ const onServerRequest = (request) => {
116
+ if (!requestMatchesThread(request, options.threadId)) {
117
+ return;
118
+ }
119
+ protocol.respondError(request.id, {
120
+ code: "TWINNY_UNSUPPORTED_SERVER_REQUEST",
121
+ message: `Twinny does not implement Codex server request ${request.method} during compact`
122
+ });
123
+ };
124
+ protocol.on("notification", onNotification);
125
+ protocol.on("serverRequest", onServerRequest);
126
+ try {
127
+ await protocol.request("thread/compact/start", { threadId: options.threadId }, { timeoutMs: requestOptions.requestTimeoutMs });
128
+ return await accumulator.wait(requestOptions.completionTimeoutMs);
129
+ }
130
+ catch (error) {
131
+ throw error instanceof Error
132
+ ? error
133
+ : new TwinnyError(toErrorMessage(error), "CODEX_TURN_FAILED", error);
134
+ }
135
+ finally {
136
+ protocol.off("notification", onNotification);
137
+ protocol.off("serverRequest", onServerRequest);
138
+ }
139
+ }
140
+ export async function steerCodexTurn(protocol, options) {
141
+ await protocol.request("turn/steer", buildTurnSteerParams(options));
142
+ }
143
+ export async function interruptCodexTurn(protocol, options) {
144
+ await protocol.request("turn/interrupt", buildTurnInterruptParams(options));
145
+ }
146
+ export class TurnOutputAccumulator {
147
+ threadId;
148
+ callbacks;
149
+ assistantMessages = new Map();
150
+ generatedImages = new Map();
151
+ pendingAgentMessageCallbacks = [];
152
+ agentMessageCallbackChain = Promise.resolve();
153
+ startedAt = Date.now();
154
+ finalAnswerText;
155
+ turnId;
156
+ turnIdSource;
157
+ emittedTurnStartedId;
158
+ completed;
159
+ completionError;
160
+ resolveWait;
161
+ rejectWait;
162
+ constructor(threadId, turnId, callbacks = {}) {
163
+ this.threadId = threadId;
164
+ this.callbacks = callbacks;
165
+ this.turnId = turnId;
166
+ this.turnIdSource = turnId ? "response" : undefined;
167
+ }
168
+ setTurnId(turnId, source = "notification") {
169
+ const canReplaceResponseTurnId = source === "notification" &&
170
+ this.turnIdSource === "response" &&
171
+ this.turnId !== undefined &&
172
+ this.turnId !== turnId;
173
+ if (!this.turnId || canReplaceResponseTurnId) {
174
+ this.turnId = turnId;
175
+ this.turnIdSource = source;
176
+ }
177
+ else if (!this.turnIdSource) {
178
+ this.turnIdSource = source;
179
+ }
180
+ if (this.emittedTurnStartedId !== this.turnId) {
181
+ this.emittedTurnStartedId = this.turnId;
182
+ void Promise.resolve(this.callbacks.onTurnStarted?.(this.turnId)).catch((error) => {
183
+ const parsedError = error instanceof Error ? error : new TwinnyError(toErrorMessage(error), "CODEX_TURN_STARTED_CALLBACK_FAILED");
184
+ this.completionError = parsedError;
185
+ this.rejectWait?.(parsedError);
186
+ });
187
+ }
188
+ }
189
+ record(notification) {
190
+ try {
191
+ if (notification.method === "turn/started") {
192
+ this.recordTurnStarted(notification.params);
193
+ return;
194
+ }
195
+ if (notification.method === "item/completed") {
196
+ this.recordItemCompleted(notification.params);
197
+ return;
198
+ }
199
+ if (notification.method === "turn/completed") {
200
+ this.recordTurnCompleted(notification.params);
201
+ return;
202
+ }
203
+ if (notification.method === "thread/tokenUsage/updated") {
204
+ this.recordTokenUsage(notification.params);
205
+ return;
206
+ }
207
+ if (notification.method === "thread/goal/updated") {
208
+ this.recordGoalUpdated(notification.params);
209
+ return;
210
+ }
211
+ if (notification.method === "thread/goal/cleared") {
212
+ this.recordGoalCleared(notification.params);
213
+ return;
214
+ }
215
+ if (notification.method === "error") {
216
+ this.recordError(notification.params);
217
+ }
218
+ }
219
+ catch (error) {
220
+ this.completionError =
221
+ error instanceof Error ? error : new TwinnyError(toErrorMessage(error), "CODEX_TURN_AGGREGATION_ERROR");
222
+ this.rejectWait?.(this.completionError);
223
+ }
224
+ }
225
+ wait(timeoutMs = 0) {
226
+ if (this.completionError) {
227
+ return Promise.reject(this.completionError);
228
+ }
229
+ if (this.completed) {
230
+ return this.waitForPendingAgentMessageCallbacks().then(() => this.toResult());
231
+ }
232
+ return new Promise((resolve, reject) => {
233
+ let timeout;
234
+ this.resolveWait = (result) => {
235
+ if (timeout) {
236
+ clearTimeout(timeout);
237
+ }
238
+ resolve(result);
239
+ };
240
+ this.rejectWait = (error) => {
241
+ if (timeout) {
242
+ clearTimeout(timeout);
243
+ }
244
+ reject(error);
245
+ };
246
+ if (timeoutMs > 0) {
247
+ timeout = setTimeout(() => {
248
+ reject(new TwinnyError("Timed out waiting for Codex turn completion", "CODEX_TURN_TIMEOUT"));
249
+ }, timeoutMs);
250
+ }
251
+ });
252
+ }
253
+ get text() {
254
+ return Array.from(this.assistantMessages.values())
255
+ .map((text) => text.trim())
256
+ .filter((text) => text.length > 0)
257
+ .join("\n\n");
258
+ }
259
+ recordTurnStarted(params) {
260
+ if (!isRecord(params) || params.threadId !== this.threadId || !isRecord(params.turn)) {
261
+ return;
262
+ }
263
+ const turnId = stringValue(params.turn.id);
264
+ if (turnId) {
265
+ this.setTurnId(turnId, "notification");
266
+ }
267
+ }
268
+ recordItemCompleted(params) {
269
+ if (!isItemCompletedParams(params)) {
270
+ return;
271
+ }
272
+ if (params.threadId !== this.threadId) {
273
+ return;
274
+ }
275
+ if (params.turnId) {
276
+ this.setTurnId(params.turnId, "notification");
277
+ }
278
+ if (this.turnId && params.turnId && params.turnId !== this.turnId) {
279
+ return;
280
+ }
281
+ const plan = extractPlanUpdate(params);
282
+ if (plan) {
283
+ this.emitPlanUpdate(plan);
284
+ return;
285
+ }
286
+ const item = extractAgentMessage(params.item);
287
+ if (item) {
288
+ this.assistantMessages.set(item.id, item.text);
289
+ if (item.phase === "final_answer") {
290
+ this.finalAnswerText = item.text;
291
+ }
292
+ this.emitAgentMessage(item);
293
+ return;
294
+ }
295
+ const image = extractImageGeneration(params.item);
296
+ if (image) {
297
+ this.recordImageGeneration(image);
298
+ this.emitImageGeneration(image);
299
+ }
300
+ }
301
+ recordTurnCompleted(params) {
302
+ if (!isTurnCompletedParams(params)) {
303
+ return;
304
+ }
305
+ if (params.threadId !== this.threadId) {
306
+ return;
307
+ }
308
+ const completedTurnId = stringValue(params.turn.id);
309
+ if (completedTurnId) {
310
+ this.setTurnId(completedTurnId, "notification");
311
+ }
312
+ if (this.turnId && completedTurnId && completedTurnId !== this.turnId) {
313
+ return;
314
+ }
315
+ for (const item of params.turn.items ?? []) {
316
+ const message = extractAgentMessage(item);
317
+ if (message) {
318
+ this.assistantMessages.set(message.id, message.text);
319
+ if (message.phase === "final_answer") {
320
+ this.finalAnswerText = message.text;
321
+ }
322
+ continue;
323
+ }
324
+ const image = extractImageGeneration(item);
325
+ if (image) {
326
+ this.recordImageGeneration(image);
327
+ }
328
+ }
329
+ this.completed = params;
330
+ void this.resolveCompleted();
331
+ }
332
+ recordError(params) {
333
+ if (isRetryableTurnError(params)) {
334
+ return;
335
+ }
336
+ const message = isRecord(params) && typeof params.message === "string"
337
+ ? params.message
338
+ : "Codex app-server reported an error";
339
+ this.completionError = new TwinnyError(message, "CODEX_TURN_FAILED", params);
340
+ this.rejectWait?.(this.completionError);
341
+ }
342
+ recordTokenUsage(params) {
343
+ if (!isRecord(params) || params.threadId !== this.threadId) {
344
+ return;
345
+ }
346
+ const totalTokens = extractTotalTokens(params);
347
+ if (totalTokens === undefined) {
348
+ return;
349
+ }
350
+ const usage = {
351
+ threadId: this.threadId,
352
+ turnId: stringValue(params.turnId),
353
+ totalTokens,
354
+ raw: params
355
+ };
356
+ void Promise.resolve(this.callbacks.onTokenUsage?.(usage)).catch((error) => {
357
+ const parsedError = error instanceof Error ? error : new TwinnyError(toErrorMessage(error), "CODEX_TOKEN_USAGE_CALLBACK_FAILED");
358
+ this.completionError = parsedError;
359
+ this.rejectWait?.(parsedError);
360
+ });
361
+ }
362
+ recordGoalUpdated(params) {
363
+ if (!isRecord(params) || params.threadId !== this.threadId || !isThreadGoal(params.goal)) {
364
+ return;
365
+ }
366
+ void Promise.resolve(this.callbacks.onGoalUpdated?.(params.goal, stringValue(params.turnId) ?? null)).catch((error) => {
367
+ const parsedError = error instanceof Error ? error : new TwinnyError(toErrorMessage(error), "CODEX_THREAD_GOAL_CALLBACK_FAILED");
368
+ this.completionError = parsedError;
369
+ this.rejectWait?.(parsedError);
370
+ });
371
+ }
372
+ recordGoalCleared(params) {
373
+ if (!isRecord(params) || params.threadId !== this.threadId) {
374
+ return;
375
+ }
376
+ void Promise.resolve(this.callbacks.onGoalCleared?.()).catch((error) => {
377
+ const parsedError = error instanceof Error ? error : new TwinnyError(toErrorMessage(error), "CODEX_THREAD_GOAL_CALLBACK_FAILED");
378
+ this.completionError = parsedError;
379
+ this.rejectWait?.(parsedError);
380
+ });
381
+ }
382
+ emitPlanUpdate(plan) {
383
+ this.setTurnId(plan.turnId);
384
+ void Promise.resolve(this.callbacks.onPlanUpdated?.(plan)).catch((error) => {
385
+ const parsedError = error instanceof Error ? error : new TwinnyError(toErrorMessage(error), "CODEX_PLAN_CALLBACK_FAILED");
386
+ this.completionError = parsedError;
387
+ this.rejectWait?.(parsedError);
388
+ });
389
+ }
390
+ toResult() {
391
+ const turn = this.completed?.turn;
392
+ const status = turn?.status === "failed" ? "failed" : turn?.status === "interrupted" ? "interrupted" : "completed";
393
+ const generatedImages = Array.from(this.generatedImages.values());
394
+ return {
395
+ threadId: this.threadId,
396
+ turnId: this.turnId,
397
+ text: this.finalAnswerText ?? this.text,
398
+ status,
399
+ error: status === "failed" ? extractErrorMessage(turn?.error) : undefined,
400
+ durationMs: typeof turn?.durationMs === "number" ? turn.durationMs : Date.now() - this.startedAt,
401
+ ...(generatedImages.length > 0 ? { generatedImages } : {})
402
+ };
403
+ }
404
+ recordImageGeneration(image) {
405
+ this.generatedImages.set(image.id, image);
406
+ }
407
+ emitAgentMessage(message) {
408
+ if (!this.callbacks.onAgentMessage) {
409
+ return;
410
+ }
411
+ const pending = this.agentMessageCallbackChain
412
+ .then(() => this.callbacks.onAgentMessage?.(message))
413
+ .catch((error) => {
414
+ const parsedError = error instanceof Error ? error : new TwinnyError(toErrorMessage(error), "CODEX_AGENT_MESSAGE_CALLBACK_FAILED");
415
+ this.completionError = parsedError;
416
+ this.rejectWait?.(parsedError);
417
+ });
418
+ this.agentMessageCallbackChain = pending.then(() => undefined);
419
+ this.pendingAgentMessageCallbacks.push(pending);
420
+ }
421
+ emitImageGeneration(image) {
422
+ if (!this.callbacks.onImageGeneration) {
423
+ return;
424
+ }
425
+ const pending = this.agentMessageCallbackChain
426
+ .then(() => this.callbacks.onImageGeneration?.(image))
427
+ .catch((error) => {
428
+ const parsedError = error instanceof Error ? error : new TwinnyError(toErrorMessage(error), "CODEX_IMAGE_GENERATION_CALLBACK_FAILED");
429
+ this.completionError = parsedError;
430
+ this.rejectWait?.(parsedError);
431
+ });
432
+ this.agentMessageCallbackChain = pending.then(() => undefined);
433
+ this.pendingAgentMessageCallbacks.push(pending);
434
+ }
435
+ async resolveCompleted() {
436
+ await this.waitForPendingAgentMessageCallbacks();
437
+ if (this.completionError) {
438
+ this.rejectWait?.(this.completionError);
439
+ return;
440
+ }
441
+ this.resolveWait?.(this.toResult());
442
+ }
443
+ async waitForPendingAgentMessageCallbacks() {
444
+ await Promise.all(this.pendingAgentMessageCallbacks);
445
+ }
446
+ }
447
+ function extractAgentMessage(item) {
448
+ if (!isRecord(item) || item.type !== "agentMessage") {
449
+ return undefined;
450
+ }
451
+ const id = stringValue(item.id);
452
+ const text = stringValue(item.text);
453
+ if (!id || text === undefined) {
454
+ return undefined;
455
+ }
456
+ const phase = agentMessagePhaseValue(item.phase);
457
+ return phase === undefined ? { id, text } : { id, text, phase };
458
+ }
459
+ function extractImageGeneration(item) {
460
+ if (!isRecord(item) || item.type !== "imageGeneration") {
461
+ return undefined;
462
+ }
463
+ const id = stringValue(item.id);
464
+ if (!id) {
465
+ return undefined;
466
+ }
467
+ const status = stringValue(item.status);
468
+ const savedPath = stringValue(item.savedPath) ?? stringValue(item.saved_path);
469
+ const revisedPrompt = stringValue(item.revisedPrompt) ?? stringValue(item.revised_prompt);
470
+ const result = stringValue(item.result);
471
+ return {
472
+ id,
473
+ ...(status ? { status } : {}),
474
+ ...(savedPath ? { savedPath } : {}),
475
+ ...(revisedPrompt ? { revisedPrompt } : {}),
476
+ ...(result ? { result } : {})
477
+ };
478
+ }
479
+ function extractPlanUpdate(params) {
480
+ const turnId = stringValue(params.turnId);
481
+ if (!turnId || !isRecord(params.item) || params.item.type !== "plan") {
482
+ return undefined;
483
+ }
484
+ const text = stringValue(params.item.text);
485
+ if (text === undefined) {
486
+ return undefined;
487
+ }
488
+ return {
489
+ threadId: params.threadId,
490
+ turnId,
491
+ explanation: text,
492
+ plan: []
493
+ };
494
+ }
495
+ export function handleTurnServerRequest(protocol, options, request) {
496
+ if (request.method === "item/tool/call") {
497
+ handleDynamicToolCallRequest(protocol, options, request);
498
+ return;
499
+ }
500
+ if (request.method !== "item/tool/requestUserInput") {
501
+ if (requestMatchesThread(request, options.threadId)) {
502
+ protocol.respondError(request.id, {
503
+ code: "TWINNY_UNSUPPORTED_SERVER_REQUEST",
504
+ message: `Twinny does not implement Codex server request ${request.method}`
505
+ });
506
+ }
507
+ return;
508
+ }
509
+ const params = parseRequestUserInputParams(request.params);
510
+ if (!params || params.threadId !== options.threadId) {
511
+ return;
512
+ }
513
+ if (!options.onRequestUserInput) {
514
+ protocol.respondError(request.id, {
515
+ code: "TWINNY_UNSUPPORTED_SERVER_REQUEST",
516
+ message: "Twinny does not implement item/tool/requestUserInput for this turn"
517
+ });
518
+ return;
519
+ }
520
+ let settled = false;
521
+ const responder = {
522
+ respond: (response) => {
523
+ if (settled) {
524
+ return;
525
+ }
526
+ settled = true;
527
+ protocol.respond(request.id, response);
528
+ },
529
+ reject: (error) => {
530
+ if (settled) {
531
+ return;
532
+ }
533
+ settled = true;
534
+ protocol.respondError(request.id, {
535
+ code: "TWINNY_REQUEST_USER_INPUT_FAILED",
536
+ message: typeof error === "string" ? error : error.message
537
+ });
538
+ }
539
+ };
540
+ void Promise.resolve(options.onRequestUserInput({
541
+ requestId: request.id,
542
+ params
543
+ }, responder)).catch((error) => {
544
+ responder.reject(error instanceof Error ? error : toErrorMessage(error));
545
+ });
546
+ }
547
+ function handleDynamicToolCallRequest(protocol, options, request) {
548
+ const params = parseDynamicToolCallParams(request.params);
549
+ if (!params) {
550
+ if (requestMatchesThread(request, options.threadId)) {
551
+ protocol.respondError(request.id, {
552
+ code: "TWINNY_INVALID_DYNAMIC_TOOL_CALL",
553
+ message: "Invalid Codex dynamic tool call request"
554
+ });
555
+ }
556
+ return;
557
+ }
558
+ if (params.threadId !== options.threadId) {
559
+ return;
560
+ }
561
+ if (params.namespace !== "twinny" || params.tool !== "set_thread_name") {
562
+ protocol.respondError(request.id, {
563
+ code: "TWINNY_UNSUPPORTED_SERVER_REQUEST",
564
+ message: `Twinny does not implement dynamic tool ${params.namespace ? `${params.namespace}.` : ""}${params.tool}`
565
+ });
566
+ return;
567
+ }
568
+ if (!options.onSetThreadName) {
569
+ protocol.respondError(request.id, {
570
+ code: "TWINNY_UNSUPPORTED_SERVER_REQUEST",
571
+ message: "Twinny does not implement twinny.set_thread_name for this turn"
572
+ });
573
+ return;
574
+ }
575
+ const name = parseSetThreadNameArguments(params.arguments);
576
+ if (!name) {
577
+ protocol.respond(request.id, dynamicToolTextResponse(false, "Invalid thread name: expected a non-empty name string."));
578
+ return;
579
+ }
580
+ void Promise.resolve(options.onSetThreadName({
581
+ requestId: request.id,
582
+ threadId: params.threadId,
583
+ turnId: params.turnId,
584
+ callId: params.callId,
585
+ name,
586
+ rawArguments: params.arguments
587
+ })).then((response) => protocol.respond(request.id, response), (error) => {
588
+ protocol.respond(request.id, dynamicToolTextResponse(false, `Failed to update thread name: ${toErrorMessage(error)}`));
589
+ });
590
+ }
591
+ function requestMatchesThread(request, threadId) {
592
+ return isRecord(request.params) && request.params.threadId === threadId;
593
+ }
594
+ function parseDynamicToolCallParams(value) {
595
+ if (!isRecord(value) ||
596
+ typeof value.threadId !== "string" ||
597
+ typeof value.turnId !== "string" ||
598
+ typeof value.callId !== "string" ||
599
+ typeof value.tool !== "string") {
600
+ return undefined;
601
+ }
602
+ if (value.namespace !== null && typeof value.namespace !== "string") {
603
+ return undefined;
604
+ }
605
+ return {
606
+ threadId: value.threadId,
607
+ turnId: value.turnId,
608
+ callId: value.callId,
609
+ namespace: value.namespace,
610
+ tool: value.tool,
611
+ arguments: value.arguments
612
+ };
613
+ }
614
+ function parseSetThreadNameArguments(value) {
615
+ if (!isRecord(value) || typeof value.name !== "string") {
616
+ return undefined;
617
+ }
618
+ const name = value.name.replace(/\s+/g, " ").trim();
619
+ return name || undefined;
620
+ }
621
+ export function dynamicToolTextResponse(success, text) {
622
+ return {
623
+ success,
624
+ contentItems: [{ type: "inputText", text }]
625
+ };
626
+ }
627
+ function escapeXmlText(value) {
628
+ return value
629
+ .replace(/&/g, "&amp;")
630
+ .replace(/</g, "&lt;")
631
+ .replace(/>/g, "&gt;");
632
+ }
633
+ function parseRequestUserInputParams(value) {
634
+ if (!isRecord(value) || typeof value.threadId !== "string" || typeof value.turnId !== "string") {
635
+ return undefined;
636
+ }
637
+ if (typeof value.itemId !== "string" || !Array.isArray(value.questions)) {
638
+ return undefined;
639
+ }
640
+ const questions = value.questions.map(parseRequestUserInputQuestion);
641
+ if (questions.some((question) => question === undefined)) {
642
+ return undefined;
643
+ }
644
+ return {
645
+ threadId: value.threadId,
646
+ turnId: value.turnId,
647
+ itemId: value.itemId,
648
+ questions: questions
649
+ };
650
+ }
651
+ function parseRequestUserInputQuestion(value) {
652
+ if (!isRecord(value)) {
653
+ return undefined;
654
+ }
655
+ const { id, header, question, isOther, isSecret, options } = value;
656
+ if (typeof id !== "string" ||
657
+ typeof header !== "string" ||
658
+ typeof question !== "string" ||
659
+ typeof isOther !== "boolean" ||
660
+ typeof isSecret !== "boolean") {
661
+ return undefined;
662
+ }
663
+ if (options !== null && options !== undefined && !Array.isArray(options)) {
664
+ return undefined;
665
+ }
666
+ const parsedOptions = (options ?? null) === null
667
+ ? null
668
+ : options.map((option) => {
669
+ if (!isRecord(option) || typeof option.label !== "string" || typeof option.description !== "string") {
670
+ return undefined;
671
+ }
672
+ return { label: option.label, description: option.description };
673
+ });
674
+ if (Array.isArray(parsedOptions) && parsedOptions.some((option) => option === undefined)) {
675
+ return undefined;
676
+ }
677
+ return {
678
+ id,
679
+ header,
680
+ question,
681
+ isOther,
682
+ isSecret,
683
+ options: parsedOptions
684
+ };
685
+ }
686
+ function agentMessagePhaseValue(value) {
687
+ if (value === null) {
688
+ return null;
689
+ }
690
+ return value === "commentary" || value === "final_answer" ? value : undefined;
691
+ }
692
+ function extractErrorMessage(error) {
693
+ if (!error) {
694
+ return undefined;
695
+ }
696
+ if (error instanceof Error) {
697
+ return error.message;
698
+ }
699
+ if (isRecord(error) && typeof error.message === "string") {
700
+ return error.message;
701
+ }
702
+ return String(error);
703
+ }
704
+ export function extractTotalTokens(params) {
705
+ return firstFiniteNumber(params.totalTokens, params.total_tokens, nestedValue(params, ["usage", "totalTokens"]), nestedValue(params, ["usage", "total_tokens"]), nestedValue(params, ["usage", "total", "totalTokens"]), nestedValue(params, ["usage", "total", "total_tokens"]), nestedValue(params, ["total", "totalTokens"]), nestedValue(params, ["total", "total_tokens"]), nestedValue(params, ["tokenUsage", "totalTokens"]), nestedValue(params, ["tokenUsage", "total_tokens"]), nestedValue(params, ["tokenUsage", "total", "totalTokens"]), nestedValue(params, ["tokenUsage", "total", "total_tokens"]));
706
+ }
707
+ function nestedValue(record, path) {
708
+ let current = record;
709
+ for (const key of path) {
710
+ if (!isRecord(current)) {
711
+ return undefined;
712
+ }
713
+ current = current[key];
714
+ }
715
+ return current;
716
+ }
717
+ function firstFiniteNumber(...values) {
718
+ for (const value of values) {
719
+ if (typeof value === "number" && Number.isFinite(value)) {
720
+ return value;
721
+ }
722
+ if (typeof value === "string" && value.trim() !== "") {
723
+ const parsed = Number(value);
724
+ if (Number.isFinite(parsed)) {
725
+ return parsed;
726
+ }
727
+ }
728
+ }
729
+ return undefined;
730
+ }
731
+ function isTurnCompletedParams(value) {
732
+ return isRecord(value) && typeof value.threadId === "string" && isRecord(value.turn);
733
+ }
734
+ function isItemCompletedParams(value) {
735
+ return isRecord(value) && typeof value.threadId === "string";
736
+ }
737
+ function isRetryableTurnError(value) {
738
+ return isRecord(value) && value.willRetry === true;
739
+ }
740
+ function stringValue(value) {
741
+ return typeof value === "string" ? value : undefined;
742
+ }
743
+ function isRecord(value) {
744
+ return typeof value === "object" && value !== null && !Array.isArray(value);
745
+ }
746
+ //# sourceMappingURL=turn.js.map