@traceai/codex-sdk 0.1.0 → 0.1.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.
- package/README.md +14 -6
- package/dist/esm/codexWrapper.d.ts +5 -0
- package/dist/esm/codexWrapper.d.ts.map +1 -0
- package/dist/esm/codexWrapper.js +63 -0
- package/dist/esm/codexWrapper.js.map +1 -0
- package/dist/esm/instrumentation.d.ts +9 -5
- package/dist/esm/instrumentation.d.ts.map +1 -1
- package/dist/esm/instrumentation.js +40 -31
- package/dist/esm/instrumentation.js.map +1 -1
- package/dist/esm/spans.js +1 -1
- package/dist/esm/spans.js.map +1 -1
- package/dist/esm/threadWrapper.d.ts.map +1 -1
- package/dist/esm/threadWrapper.js +38 -16
- package/dist/esm/threadWrapper.js.map +1 -1
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/esm/version.js.map +1 -1
- package/dist/esnext/codexWrapper.d.ts +5 -0
- package/dist/esnext/codexWrapper.d.ts.map +1 -0
- package/dist/esnext/codexWrapper.js +65 -0
- package/dist/esnext/codexWrapper.js.map +1 -0
- package/dist/esnext/instrumentation.d.ts +9 -5
- package/dist/esnext/instrumentation.d.ts.map +1 -1
- package/dist/esnext/instrumentation.js +40 -31
- package/dist/esnext/instrumentation.js.map +1 -1
- package/dist/esnext/spans.js +1 -1
- package/dist/esnext/spans.js.map +1 -1
- package/dist/esnext/threadWrapper.d.ts.map +1 -1
- package/dist/esnext/threadWrapper.js +39 -16
- package/dist/esnext/threadWrapper.js.map +1 -1
- package/dist/esnext/version.d.ts +1 -1
- package/dist/esnext/version.js +1 -1
- package/dist/esnext/version.js.map +1 -1
- package/dist/src/codexWrapper.d.ts +5 -0
- package/dist/src/codexWrapper.d.ts.map +1 -0
- package/dist/src/codexWrapper.js +68 -0
- package/dist/src/codexWrapper.js.map +1 -0
- package/dist/src/instrumentation.d.ts +9 -5
- package/dist/src/instrumentation.d.ts.map +1 -1
- package/dist/src/instrumentation.js +40 -31
- package/dist/src/instrumentation.js.map +1 -1
- package/dist/src/spans.js +1 -1
- package/dist/src/spans.js.map +1 -1
- package/dist/src/threadWrapper.d.ts.map +1 -1
- package/dist/src/threadWrapper.js +39 -16
- package/dist/src/threadWrapper.js.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/dist/src/version.js.map +1 -1
- package/package.json +6 -5
- package/src/codexWrapper.ts +71 -0
- package/src/instrumentation.ts +39 -27
- package/src/spans.ts +1 -1
- package/src/threadWrapper.ts +38 -17
- package/src/version.ts +1 -1
package/src/instrumentation.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { TracerProvider, diag, trace } from "@opentelemetry/api";
|
|
7
7
|
import { FITracer, TraceConfigOptions } from "@traceai/fi-core";
|
|
8
|
+
import { patchCodex } from "./codexWrapper";
|
|
8
9
|
import { ContentPolicy } from "./spans";
|
|
9
10
|
import { TurnTracingContext, patchThread } from "./threadWrapper";
|
|
10
11
|
import { CodexModuleLike, ThreadLike } from "./types";
|
|
@@ -69,9 +70,20 @@ export function resolveContentPolicy(traceConfig?: TraceConfigOptions): ContentP
|
|
|
69
70
|
export class CodexSDKInstrumentation {
|
|
70
71
|
private tracerProvider?: TracerProvider;
|
|
71
72
|
private readonly traceConfig?: TraceConfigOptions;
|
|
72
|
-
/** Undo functions for
|
|
73
|
+
/** Undo functions for class patches. */
|
|
73
74
|
private readonly undo: Array<() => void> = [];
|
|
74
|
-
/**
|
|
75
|
+
/** Client undo functions hold only WeakRefs; collected clients release their bookkeeping. */
|
|
76
|
+
private readonly clientUndo = new Set<() => void>();
|
|
77
|
+
private readonly clientFinalizer = new FinalizationRegistry<() => void>((undo) => {
|
|
78
|
+
// Deactivate the owner too: saved factories must remain inert after collection.
|
|
79
|
+
undo();
|
|
80
|
+
this.clientUndo.delete(undo);
|
|
81
|
+
});
|
|
82
|
+
/** Thread undo functions hold only WeakRefs; collected threads release their bookkeeping. */
|
|
83
|
+
private readonly threadUndo = new Set<() => void>();
|
|
84
|
+
private readonly threadFinalizer = new FinalizationRegistry<() => void>((undo) => {
|
|
85
|
+
this.threadUndo.delete(undo);
|
|
86
|
+
});
|
|
75
87
|
private enabled = true;
|
|
76
88
|
|
|
77
89
|
constructor(config: CodexSDKInstrumentationConfig = {}) {
|
|
@@ -106,13 +118,16 @@ export class CodexSDKInstrumentation {
|
|
|
106
118
|
}
|
|
107
119
|
|
|
108
120
|
/**
|
|
109
|
-
* Patch one Thread instance. Returns the same instance.
|
|
110
|
-
*
|
|
111
|
-
* untraced instead of being removed.
|
|
121
|
+
* Patch one Thread instance. Returns the same instance. Only a weak reference
|
|
122
|
+
* is kept, so `unpatch()` can restore live threads without retaining dropped ones.
|
|
112
123
|
*/
|
|
113
124
|
wrapThread<T extends object>(thread: T): T {
|
|
114
125
|
this.enabled = true;
|
|
115
|
-
patchThread(thread as ThreadLike, () => this.tracingContext());
|
|
126
|
+
const undo = patchThread(thread as ThreadLike, () => this.tracingContext());
|
|
127
|
+
if (undo) {
|
|
128
|
+
this.threadUndo.add(undo);
|
|
129
|
+
this.threadFinalizer.register(thread, undo, undo);
|
|
130
|
+
}
|
|
116
131
|
return thread;
|
|
117
132
|
}
|
|
118
133
|
|
|
@@ -120,27 +135,10 @@ export class CodexSDKInstrumentation {
|
|
|
120
135
|
wrapCodex<T extends object>(codex: T): T {
|
|
121
136
|
if (!codex || typeof codex !== "object") return codex;
|
|
122
137
|
this.enabled = true;
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const original = target[method] as ((...args: unknown[]) => object) | undefined;
|
|
128
|
-
if (typeof original !== "function") continue;
|
|
129
|
-
const hadOwn = Object.prototype.hasOwnProperty.call(codex, method);
|
|
130
|
-
const self = this;
|
|
131
|
-
Object.defineProperty(codex, method, {
|
|
132
|
-
value: function tracedThreadFactory(this: unknown, ...args: unknown[]) {
|
|
133
|
-
return self.wrapThread(original.apply(this, args));
|
|
134
|
-
},
|
|
135
|
-
writable: true,
|
|
136
|
-
configurable: true,
|
|
137
|
-
});
|
|
138
|
-
this.undo.push(() => {
|
|
139
|
-
const live = ref.deref();
|
|
140
|
-
if (!live) return;
|
|
141
|
-
if (hadOwn) Object.defineProperty(live, method, { value: original, writable: true, configurable: true });
|
|
142
|
-
else delete live[method];
|
|
143
|
-
});
|
|
138
|
+
const undo = patchCodex(codex as Record<string | symbol, unknown>, (thread) => this.wrapThread(thread));
|
|
139
|
+
if (undo) {
|
|
140
|
+
this.clientUndo.add(undo);
|
|
141
|
+
this.clientFinalizer.register(codex, undo, undo);
|
|
144
142
|
}
|
|
145
143
|
return codex;
|
|
146
144
|
}
|
|
@@ -148,6 +146,20 @@ export class CodexSDKInstrumentation {
|
|
|
148
146
|
/** Remove every patch this instrumentation applied. */
|
|
149
147
|
unpatch(): void {
|
|
150
148
|
this.enabled = false;
|
|
149
|
+
for (const undo of this.threadUndo) {
|
|
150
|
+
this.threadFinalizer.unregister(undo);
|
|
151
|
+
undo();
|
|
152
|
+
}
|
|
153
|
+
this.threadUndo.clear();
|
|
154
|
+
for (const undo of this.clientUndo) {
|
|
155
|
+
this.clientFinalizer.unregister(undo);
|
|
156
|
+
try {
|
|
157
|
+
undo();
|
|
158
|
+
} catch (error) {
|
|
159
|
+
diag.debug(`${INSTRUMENTATION_NAME}: unpatch failed: ${error}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
this.clientUndo.clear();
|
|
151
163
|
while (this.undo.length) {
|
|
152
164
|
const fn = this.undo.pop();
|
|
153
165
|
try {
|
package/src/spans.ts
CHANGED
|
@@ -515,7 +515,7 @@ export class TurnTracer {
|
|
|
515
515
|
if (turnUsage) {
|
|
516
516
|
if (this.threadId) usageBaselines.set(this.threadId, cumulative);
|
|
517
517
|
} else {
|
|
518
|
-
// The total went down (a reset
|
|
518
|
+
// The total went down (a reset within the supported cumulative-usage SDKs): use it as
|
|
519
519
|
// reported, and keep the larger baseline so a later turn is not counted twice.
|
|
520
520
|
turnUsage = cumulative;
|
|
521
521
|
attributes[C.USAGE_NON_MONOTONIC] = true;
|
package/src/threadWrapper.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { ContentPolicy, TurnTracer, clockMs } from "./spans";
|
|
|
16
16
|
import { InputLike, ThreadEventLike, ThreadLike, TurnOptionsLike } from "./types";
|
|
17
17
|
|
|
18
18
|
const PATCHED = Symbol.for("@traceai/codex-sdk/patched");
|
|
19
|
+
const PATCH_RECORD = Symbol.for("@traceai/codex-sdk/patch-record");
|
|
19
20
|
const LOG = "@traceai/codex-sdk";
|
|
20
21
|
|
|
21
22
|
export interface TurnTracingContext {
|
|
@@ -26,16 +27,37 @@ export interface TurnTracingContext {
|
|
|
26
27
|
type GetContext = () => TurnTracingContext | undefined;
|
|
27
28
|
|
|
28
29
|
interface PatchRecord {
|
|
29
|
-
target: ThreadLike;
|
|
30
30
|
method: "runStreamedInternal" | "runStreamed";
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
originalDescriptor: PropertyDescriptor | undefined;
|
|
32
|
+
wrapper: unknown;
|
|
33
|
+
owner: symbol;
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
export function isPatched(target: unknown): boolean {
|
|
36
37
|
return !!target && (target as Record<symbol, unknown>)[PATCHED] === true;
|
|
37
38
|
}
|
|
38
39
|
|
|
40
|
+
/** The undo closure retains neither the target nor its possibly bound original method. */
|
|
41
|
+
function weakUndo(ref: WeakRef<ThreadLike>, owner: symbol): () => void {
|
|
42
|
+
return () => {
|
|
43
|
+
const target = ref.deref();
|
|
44
|
+
if (!target) return;
|
|
45
|
+
const record = Object.getOwnPropertyDescriptor(target, PATCH_RECORD)?.value as PatchRecord | undefined;
|
|
46
|
+
if (record?.owner !== owner) return;
|
|
47
|
+
try {
|
|
48
|
+
// Do not clobber a method that the application replaced after our patch.
|
|
49
|
+
if (Object.getOwnPropertyDescriptor(target, record.method)?.value === record.wrapper) {
|
|
50
|
+
if (record.originalDescriptor) Object.defineProperty(target, record.method, record.originalDescriptor);
|
|
51
|
+
else delete (target as Record<string, unknown>)[record.method];
|
|
52
|
+
}
|
|
53
|
+
delete (target as Record<symbol, unknown>)[PATCHED];
|
|
54
|
+
delete (target as Record<symbol, unknown>)[PATCH_RECORD];
|
|
55
|
+
} catch (error) {
|
|
56
|
+
diag.debug(`${LOG}: unpatch failed: ${error}`);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
39
61
|
/**
|
|
40
62
|
* Patch a Thread prototype or a single Thread instance. Returns an undo
|
|
41
63
|
* function, or `undefined` when the target was already patched or has no
|
|
@@ -44,10 +66,11 @@ export function isPatched(target: unknown): boolean {
|
|
|
44
66
|
export function patchThread(target: ThreadLike, getContext: GetContext): (() => void) | undefined {
|
|
45
67
|
if (!target || typeof target !== "object" || isPatched(target)) return undefined;
|
|
46
68
|
|
|
47
|
-
|
|
69
|
+
const owner = Symbol();
|
|
70
|
+
let record: PatchRecord;
|
|
48
71
|
if (typeof target.runStreamedInternal === "function") {
|
|
49
72
|
const original = target.runStreamedInternal;
|
|
50
|
-
|
|
73
|
+
const originalDescriptor = Object.getOwnPropertyDescriptor(target, "runStreamedInternal");
|
|
51
74
|
const traced = function tracedRunStreamedInternal(this: ThreadLike, input: InputLike, turnOptions?: TurnOptionsLike) {
|
|
52
75
|
const startTimeMs = clockMs();
|
|
53
76
|
const parentContext = otelContext.active();
|
|
@@ -55,10 +78,11 @@ export function patchThread(target: ThreadLike, getContext: GetContext): (() =>
|
|
|
55
78
|
const generator = original.apply(this, arguments as unknown as [InputLike, TurnOptionsLike?]);
|
|
56
79
|
return traceEvents(generator, this, input, turnOptions, parentContext, startTimeMs, getContext);
|
|
57
80
|
};
|
|
81
|
+
record = { method: "runStreamedInternal", originalDescriptor, wrapper: traced, owner };
|
|
58
82
|
defineMethod(target, "runStreamedInternal", traced);
|
|
59
83
|
} else if (typeof target.runStreamed === "function") {
|
|
60
84
|
const original = target.runStreamed;
|
|
61
|
-
|
|
85
|
+
const originalDescriptor = Object.getOwnPropertyDescriptor(target, "runStreamed");
|
|
62
86
|
diag.warn(`${LOG}: Thread.runStreamedInternal not found; tracing runStreamed() only, run() may be untraced`);
|
|
63
87
|
const traced = async function tracedRunStreamed(this: ThreadLike, input: InputLike, turnOptions?: TurnOptionsLike) {
|
|
64
88
|
const startTimeMs = clockMs();
|
|
@@ -71,22 +95,19 @@ export function patchThread(target: ThreadLike, getContext: GetContext): (() =>
|
|
|
71
95
|
events: traceEvents(result.events, this, input, turnOptions, parentContext, startTimeMs, getContext),
|
|
72
96
|
};
|
|
73
97
|
};
|
|
98
|
+
record = { method: "runStreamed", originalDescriptor, wrapper: traced, owner };
|
|
74
99
|
defineMethod(target, "runStreamed", traced);
|
|
75
100
|
} else {
|
|
76
101
|
return undefined;
|
|
77
102
|
}
|
|
78
103
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
} catch (error) {
|
|
87
|
-
diag.debug(`${LOG}: unpatch failed: ${error}`);
|
|
88
|
-
}
|
|
89
|
-
};
|
|
104
|
+
// Keep the public marker boolean so older installed copies also recognize an
|
|
105
|
+
// active patch. Store ownership separately, only on the weakly tracked target.
|
|
106
|
+
Object.defineProperties(target, {
|
|
107
|
+
[PATCHED]: { value: true, configurable: true },
|
|
108
|
+
[PATCH_RECORD]: { value: record, configurable: true },
|
|
109
|
+
});
|
|
110
|
+
return weakUndo(new WeakRef(target), owner);
|
|
90
111
|
}
|
|
91
112
|
|
|
92
113
|
function defineMethod(target: object, name: string, value: unknown): void {
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = "0.1.
|
|
1
|
+
export const VERSION = "0.1.1"; // x-release-please-version
|