@traceai/codex-sdk 0.0.0-stage → 0.1.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.
Files changed (94) hide show
  1. package/README.md +164 -2
  2. package/dist/esm/attributes.d.ts +101 -0
  3. package/dist/esm/attributes.d.ts.map +1 -0
  4. package/dist/esm/attributes.js +104 -0
  5. package/dist/esm/attributes.js.map +1 -0
  6. package/dist/esm/index.d.ts +8 -0
  7. package/dist/esm/index.d.ts.map +1 -0
  8. package/dist/esm/index.js +5 -0
  9. package/dist/esm/index.js.map +1 -0
  10. package/dist/esm/instrumentation.d.ts +81 -0
  11. package/dist/esm/instrumentation.d.ts.map +1 -0
  12. package/dist/esm/instrumentation.js +186 -0
  13. package/dist/esm/instrumentation.js.map +1 -0
  14. package/dist/esm/package.json +1 -0
  15. package/dist/esm/spans.d.ts +152 -0
  16. package/dist/esm/spans.d.ts.map +1 -0
  17. package/dist/esm/spans.js +678 -0
  18. package/dist/esm/spans.js.map +1 -0
  19. package/dist/esm/threadWrapper.d.ts +35 -0
  20. package/dist/esm/threadWrapper.d.ts.map +1 -0
  21. package/dist/esm/threadWrapper.js +186 -0
  22. package/dist/esm/threadWrapper.js.map +1 -0
  23. package/dist/esm/types.d.ts +123 -0
  24. package/dist/esm/types.d.ts.map +1 -0
  25. package/dist/esm/types.js +10 -0
  26. package/dist/esm/types.js.map +1 -0
  27. package/dist/esm/version.d.ts +2 -0
  28. package/dist/esm/version.d.ts.map +1 -0
  29. package/dist/esm/version.js +2 -0
  30. package/dist/esm/version.js.map +1 -0
  31. package/dist/esnext/attributes.d.ts +101 -0
  32. package/dist/esnext/attributes.d.ts.map +1 -0
  33. package/dist/esnext/attributes.js +104 -0
  34. package/dist/esnext/attributes.js.map +1 -0
  35. package/dist/esnext/index.d.ts +8 -0
  36. package/dist/esnext/index.d.ts.map +1 -0
  37. package/dist/esnext/index.js +5 -0
  38. package/dist/esnext/index.js.map +1 -0
  39. package/dist/esnext/instrumentation.d.ts +81 -0
  40. package/dist/esnext/instrumentation.d.ts.map +1 -0
  41. package/dist/esnext/instrumentation.js +191 -0
  42. package/dist/esnext/instrumentation.js.map +1 -0
  43. package/dist/esnext/spans.d.ts +152 -0
  44. package/dist/esnext/spans.d.ts.map +1 -0
  45. package/dist/esnext/spans.js +688 -0
  46. package/dist/esnext/spans.js.map +1 -0
  47. package/dist/esnext/threadWrapper.d.ts +35 -0
  48. package/dist/esnext/threadWrapper.d.ts.map +1 -0
  49. package/dist/esnext/threadWrapper.js +183 -0
  50. package/dist/esnext/threadWrapper.js.map +1 -0
  51. package/dist/esnext/types.d.ts +123 -0
  52. package/dist/esnext/types.d.ts.map +1 -0
  53. package/dist/esnext/types.js +10 -0
  54. package/dist/esnext/types.js.map +1 -0
  55. package/dist/esnext/version.d.ts +2 -0
  56. package/dist/esnext/version.d.ts.map +1 -0
  57. package/dist/esnext/version.js +2 -0
  58. package/dist/esnext/version.js.map +1 -0
  59. package/dist/src/attributes.d.ts +101 -0
  60. package/dist/src/attributes.d.ts.map +1 -0
  61. package/dist/src/attributes.js +107 -0
  62. package/dist/src/attributes.js.map +1 -0
  63. package/dist/src/index.d.ts +8 -0
  64. package/dist/src/index.d.ts.map +1 -0
  65. package/dist/src/index.js +21 -0
  66. package/dist/src/index.js.map +1 -0
  67. package/dist/src/instrumentation.d.ts +81 -0
  68. package/dist/src/instrumentation.d.ts.map +1 -0
  69. package/dist/src/instrumentation.js +213 -0
  70. package/dist/src/instrumentation.js.map +1 -0
  71. package/dist/src/spans.d.ts +152 -0
  72. package/dist/src/spans.d.ts.map +1 -0
  73. package/dist/src/spans.js +698 -0
  74. package/dist/src/spans.js.map +1 -0
  75. package/dist/src/threadWrapper.d.ts +35 -0
  76. package/dist/src/threadWrapper.d.ts.map +1 -0
  77. package/dist/src/threadWrapper.js +206 -0
  78. package/dist/src/threadWrapper.js.map +1 -0
  79. package/dist/src/types.d.ts +123 -0
  80. package/dist/src/types.d.ts.map +1 -0
  81. package/dist/src/types.js +11 -0
  82. package/dist/src/types.js.map +1 -0
  83. package/dist/src/version.d.ts +2 -0
  84. package/dist/src/version.d.ts.map +1 -0
  85. package/dist/src/version.js +5 -0
  86. package/dist/src/version.js.map +1 -0
  87. package/package.json +75 -3
  88. package/src/attributes.ts +113 -0
  89. package/src/index.ts +27 -0
  90. package/src/instrumentation.ts +215 -0
  91. package/src/spans.ts +729 -0
  92. package/src/threadWrapper.ts +200 -0
  93. package/src/types.ts +113 -0
  94. package/src/version.ts +1 -0
@@ -0,0 +1,200 @@
1
+ /**
2
+ * Patch `Thread` so every turn is traced.
3
+ *
4
+ * `Thread.run()` and `Thread.runStreamed()` both drive the private async
5
+ * generator `runStreamedInternal(input, turnOptions)` (every stable SDK
6
+ * release from 0.44.0 to 0.162.1), so wrapping that one method traces both
7
+ * entry points with real per-event timing. The wrapper yields the SDK's own
8
+ * event objects unchanged and forwards `return()` / `throw()`, so `run()`
9
+ * keeps its exact behaviour (including `break` on `turn.failed`).
10
+ *
11
+ * If a future SDK drops that method, the wrapper falls back to the public
12
+ * `runStreamed()` and logs once that `run()` may be untraced.
13
+ */
14
+ import { Tracer, context as otelContext, diag } from "@opentelemetry/api";
15
+ import { ContentPolicy, TurnTracer, clockMs } from "./spans";
16
+ import { InputLike, ThreadEventLike, ThreadLike, TurnOptionsLike } from "./types";
17
+
18
+ const PATCHED = Symbol.for("@traceai/codex-sdk/patched");
19
+ const LOG = "@traceai/codex-sdk";
20
+
21
+ export interface TurnTracingContext {
22
+ tracer: Tracer;
23
+ policy: ContentPolicy;
24
+ }
25
+
26
+ type GetContext = () => TurnTracingContext | undefined;
27
+
28
+ interface PatchRecord {
29
+ target: ThreadLike;
30
+ method: "runStreamedInternal" | "runStreamed";
31
+ original: unknown;
32
+ hadOwn: boolean;
33
+ }
34
+
35
+ export function isPatched(target: unknown): boolean {
36
+ return !!target && (target as Record<symbol, unknown>)[PATCHED] === true;
37
+ }
38
+
39
+ /**
40
+ * Patch a Thread prototype or a single Thread instance. Returns an undo
41
+ * function, or `undefined` when the target was already patched or has no
42
+ * method to wrap.
43
+ */
44
+ export function patchThread(target: ThreadLike, getContext: GetContext): (() => void) | undefined {
45
+ if (!target || typeof target !== "object" || isPatched(target)) return undefined;
46
+
47
+ let record: PatchRecord | undefined;
48
+ if (typeof target.runStreamedInternal === "function") {
49
+ const original = target.runStreamedInternal;
50
+ record = { target, method: "runStreamedInternal", original, hadOwn: Object.prototype.hasOwnProperty.call(target, "runStreamedInternal") };
51
+ const traced = function tracedRunStreamedInternal(this: ThreadLike, input: InputLike, turnOptions?: TurnOptionsLike) {
52
+ const startTimeMs = clockMs();
53
+ const parentContext = otelContext.active();
54
+ // eslint-disable-next-line prefer-rest-params
55
+ const generator = original.apply(this, arguments as unknown as [InputLike, TurnOptionsLike?]);
56
+ return traceEvents(generator, this, input, turnOptions, parentContext, startTimeMs, getContext);
57
+ };
58
+ defineMethod(target, "runStreamedInternal", traced);
59
+ } else if (typeof target.runStreamed === "function") {
60
+ const original = target.runStreamed;
61
+ record = { target, method: "runStreamed", original, hadOwn: Object.prototype.hasOwnProperty.call(target, "runStreamed") };
62
+ diag.warn(`${LOG}: Thread.runStreamedInternal not found; tracing runStreamed() only, run() may be untraced`);
63
+ const traced = async function tracedRunStreamed(this: ThreadLike, input: InputLike, turnOptions?: TurnOptionsLike) {
64
+ const startTimeMs = clockMs();
65
+ const parentContext = otelContext.active();
66
+ // eslint-disable-next-line prefer-rest-params
67
+ const result = await original.apply(this, arguments as unknown as [InputLike, TurnOptionsLike?]);
68
+ if (!result || typeof result !== "object" || !result.events) return result;
69
+ return {
70
+ ...result,
71
+ events: traceEvents(result.events, this, input, turnOptions, parentContext, startTimeMs, getContext),
72
+ };
73
+ };
74
+ defineMethod(target, "runStreamed", traced);
75
+ } else {
76
+ return undefined;
77
+ }
78
+
79
+ Object.defineProperty(target, PATCHED, { value: true, configurable: true });
80
+ const done = record;
81
+ return () => {
82
+ try {
83
+ if (done.hadOwn) defineMethod(done.target, done.method, done.original);
84
+ else delete (done.target as Record<string, unknown>)[done.method];
85
+ delete (done.target as Record<symbol, unknown>)[PATCHED];
86
+ } catch (error) {
87
+ diag.debug(`${LOG}: unpatch failed: ${error}`);
88
+ }
89
+ };
90
+ }
91
+
92
+ function defineMethod(target: object, name: string, value: unknown): void {
93
+ Object.defineProperty(target, name, { value, writable: true, configurable: true, enumerable: false });
94
+ }
95
+
96
+ /**
97
+ * Return a generator that yields exactly what `source` yields and drives a
98
+ * `TurnTracer`. A tracing failure disables tracing for that turn only.
99
+ */
100
+ export function traceEvents(
101
+ source: AsyncGenerator<ThreadEventLike>,
102
+ thread: ThreadLike,
103
+ input: InputLike,
104
+ turnOptions: TurnOptionsLike | undefined,
105
+ parentContext: ReturnType<typeof otelContext.active>,
106
+ startTimeMs: number,
107
+ getContext: GetContext,
108
+ ): AsyncGenerator<ThreadEventLike> {
109
+ let turnTracer: TurnTracer | undefined;
110
+ try {
111
+ const ctx = getContext();
112
+ if (ctx) {
113
+ turnTracer = new TurnTracer({
114
+ tracer: ctx.tracer,
115
+ policy: ctx.policy,
116
+ parentContext,
117
+ input,
118
+ turnOptions,
119
+ threadOptions: readThreadOptions(thread),
120
+ threadId: readThreadId(thread),
121
+ startTimeMs,
122
+ });
123
+ }
124
+ } catch (error) {
125
+ diag.debug(`${LOG}: tracing disabled for this turn: ${error}`);
126
+ }
127
+ if (!turnTracer || !source || typeof source.next !== "function") return source;
128
+ const tracer = turnTracer;
129
+
130
+ const safe = (fn: () => void) => {
131
+ try {
132
+ fn();
133
+ } catch (error) {
134
+ diag.debug(`${LOG}: ${error}`);
135
+ }
136
+ };
137
+
138
+ const proxy: AsyncGenerator<ThreadEventLike> = {
139
+ async next(...args: [] | [unknown]) {
140
+ safe(() => tracer.start());
141
+ let result: IteratorResult<ThreadEventLike>;
142
+ try {
143
+ result = await source.next(...(args as []));
144
+ } catch (error) {
145
+ safe(() => tracer.finish({ kind: "error", error }));
146
+ throw error;
147
+ }
148
+ if (result.done) safe(() => tracer.finish({ kind: "completed" }));
149
+ else safe(() => tracer.onEvent(result.value));
150
+ return result;
151
+ },
152
+ async return(value?: unknown) {
153
+ try {
154
+ return await source.return(value as never);
155
+ } finally {
156
+ safe(() => tracer.finish({ kind: "returned" }));
157
+ }
158
+ },
159
+ async throw(error?: unknown) {
160
+ let result: IteratorResult<ThreadEventLike>;
161
+ try {
162
+ result = await source.throw(error);
163
+ } catch (thrown) {
164
+ safe(() => tracer.finish({ kind: "error", error: thrown }));
165
+ throw thrown;
166
+ }
167
+ if (result.done) safe(() => tracer.finish({ kind: "returned" }));
168
+ else safe(() => tracer.onEvent(result.value));
169
+ return result;
170
+ },
171
+ [Symbol.asyncIterator]() {
172
+ return proxy;
173
+ },
174
+ } as AsyncGenerator<ThreadEventLike>;
175
+ const asyncDispose = (Symbol as unknown as { asyncDispose?: symbol }).asyncDispose;
176
+ if (asyncDispose) {
177
+ (proxy as unknown as Record<symbol, unknown>)[asyncDispose] = async () => {
178
+ await proxy.return(undefined);
179
+ };
180
+ }
181
+ return proxy;
182
+ }
183
+
184
+ function readThreadId(thread: ThreadLike): string | null {
185
+ try {
186
+ const id = thread?.id;
187
+ return typeof id === "string" && id ? id : null;
188
+ } catch {
189
+ return null;
190
+ }
191
+ }
192
+
193
+ function readThreadOptions(thread: ThreadLike): ThreadLike["_threadOptions"] {
194
+ try {
195
+ const options = thread?._threadOptions;
196
+ return options && typeof options === "object" ? options : undefined;
197
+ } catch {
198
+ return undefined;
199
+ }
200
+ }
package/src/types.ts ADDED
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Structural copies of the `@openai/codex-sdk` types this package reads
3
+ * (dist/index.d.ts of 0.162.1). The SDK is a peer dependency, so nothing here
4
+ * imports it: the package type-checks and runs whether or not it is installed.
5
+ *
6
+ * Every field is optional or `unknown` because the wrapper must never throw on
7
+ * an event shape a newer SDK adds.
8
+ */
9
+
10
+ /** `UserInput` / `Input` accepted by `Thread.run()` and `Thread.runStreamed()`. */
11
+ export type UserInputLike = { type?: string; text?: unknown; path?: unknown };
12
+ export type InputLike = string | UserInputLike[];
13
+
14
+ /** `TurnOptions`. Only `signal` is read (abort detection). */
15
+ export interface TurnOptionsLike {
16
+ signal?: AbortSignalLike;
17
+ outputSchema?: unknown;
18
+ }
19
+
20
+ export interface AbortSignalLike {
21
+ aborted?: boolean;
22
+ reason?: unknown;
23
+ }
24
+
25
+ /** `ThreadOptions`, kept on the private `_threadOptions` field of a Thread. */
26
+ export interface ThreadOptionsLike {
27
+ model?: string;
28
+ sandboxMode?: string;
29
+ approvalPolicy?: string;
30
+ modelReasoningEffort?: string;
31
+ webSearchMode?: string;
32
+ webSearchEnabled?: boolean;
33
+ networkAccessEnabled?: boolean;
34
+ skipGitRepoCheck?: boolean;
35
+ workingDirectory?: string;
36
+ }
37
+
38
+ /** `Usage` on `turn.completed`. */
39
+ export interface UsageLike {
40
+ input_tokens?: number;
41
+ cached_input_tokens?: number;
42
+ cache_write_input_tokens?: number;
43
+ output_tokens?: number;
44
+ reasoning_output_tokens?: number;
45
+ }
46
+
47
+ /** Union of `ThreadItem` members, flattened. */
48
+ export interface ThreadItemLike {
49
+ id?: string;
50
+ type?: string;
51
+ /** agent_message, reasoning, todo item */
52
+ text?: string;
53
+ /** command_execution */
54
+ command?: string;
55
+ aggregated_output?: string;
56
+ /** `null` while the command runs (the binary sends unset optionals as `null`). */
57
+ exit_code?: number | null;
58
+ /** command_execution, file_change, mcp_tool_call */
59
+ status?: string;
60
+ /** file_change */
61
+ changes?: Array<{ path?: string; kind?: string }>;
62
+ /** mcp_tool_call */
63
+ server?: string;
64
+ tool?: string;
65
+ arguments?: unknown;
66
+ /**
67
+ * `null` (not missing) on `item.started` and on a failed call: codex-rs
68
+ * serializes these `Option`s as `null`, unlike the SDK's `result?:` typing.
69
+ */
70
+ result?: { content?: unknown; structured_content?: unknown } | null;
71
+ error?: { message?: string } | null;
72
+ /** web_search */
73
+ query?: string;
74
+ /** todo_list */
75
+ items?: Array<{ text?: string; completed?: boolean }>;
76
+ /** error item */
77
+ message?: string;
78
+ }
79
+
80
+ /** `ThreadEvent`. */
81
+ export interface ThreadEventLike {
82
+ type?: string;
83
+ thread_id?: string;
84
+ usage?: UsageLike;
85
+ error?: { message?: string };
86
+ item?: ThreadItemLike;
87
+ message?: string;
88
+ }
89
+
90
+ /**
91
+ * The parts of a `Thread` instance the wrapper touches. `runStreamedInternal`
92
+ * is private in the SDK but is the one method both `run()` and `runStreamed()`
93
+ * call (every stable release from 0.44.0 to 0.162.1); `id` and
94
+ * `_threadOptions` are read for the session id and model.
95
+ */
96
+ export interface ThreadLike {
97
+ readonly id?: string | null;
98
+ _threadOptions?: ThreadOptionsLike;
99
+ runStreamedInternal?: (input: InputLike, turnOptions?: TurnOptionsLike) => AsyncGenerator<ThreadEventLike>;
100
+ runStreamed?: (input: InputLike, turnOptions?: TurnOptionsLike) => Promise<{ events: AsyncGenerator<ThreadEventLike> }>;
101
+ run?: (input: InputLike, turnOptions?: TurnOptionsLike) => Promise<unknown>;
102
+ }
103
+
104
+ /**
105
+ * The SDK module namespace (`import * as CodexSDK from "@openai/codex-sdk"`)
106
+ * or any object exposing the `Thread` class. Typed loosely on purpose: the
107
+ * SDK's `Thread` declares its fields `private`, so it is not assignable to an
108
+ * interface that names them.
109
+ */
110
+ export interface CodexModuleLike {
111
+ // eslint-disable-next-line @typescript-eslint/ban-types
112
+ Thread?: Function;
113
+ }
package/src/version.ts ADDED
@@ -0,0 +1 @@
1
+ export const VERSION = "0.1.0";