@narumitw/pi-langfuse 0.50.5 → 0.51.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/README.md +125 -62
- package/dist/chunks/chunk-IASLJKKB.js +316 -0
- package/dist/chunks/chunk-IASLJKKB.js.map +7 -0
- package/dist/chunks/runtime-6MIKLWNJ.js +352 -0
- package/dist/chunks/runtime-6MIKLWNJ.js.map +7 -0
- package/dist/config.d.ts +33 -0
- package/dist/git.d.ts +5 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1551 -0
- package/dist/index.js.map +7 -0
- package/dist/index.ts +594 -521
- package/dist/index.ts.map +4 -4
- package/dist/langfuse.d.ts +13 -0
- package/dist/pi-session.d.ts +43 -0
- package/dist/public-api.d.ts +4 -0
- package/dist/runtime-core.d.ts +26 -0
- package/dist/runtime.d.ts +22 -0
- package/dist/sanitizer.d.ts +2 -0
- package/dist/tracing.d.ts +202 -0
- package/package.json +63 -59
- package/src/config.ts +211 -232
- package/src/git.ts +60 -0
- package/src/index.ts +9 -0
- package/src/langfuse.ts +307 -563
- package/src/pi-session.ts +570 -0
- package/src/public-api.ts +17 -0
- package/src/runtime-core.ts +113 -0
- package/src/runtime.ts +393 -227
- package/src/sanitizer.ts +115 -117
- package/src/tracing.ts +828 -843
- package/dist/chunks/runtime-247EMSG3.js +0 -218
- package/dist/chunks/runtime-247EMSG3.js.map +0 -7
package/src/config.ts
CHANGED
|
@@ -7,287 +7,266 @@ const CONFIG_FILE_NAME = "pi-langfuse.json";
|
|
|
7
7
|
export const DEFAULT_BASE_URL = "https://us.cloud.langfuse.com";
|
|
8
8
|
|
|
9
9
|
export interface LangfuseConfig {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
publicKey: string;
|
|
11
|
+
secretKey: string;
|
|
12
|
+
baseUrl: string;
|
|
13
|
+
environment?: string;
|
|
14
|
+
release?: string;
|
|
15
|
+
userId?: string;
|
|
16
|
+
captureContent: boolean;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export type LangfuseConfigPatch = Partial<LangfuseConfig>;
|
|
20
20
|
|
|
21
21
|
export type LangfuseConfigResult =
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
| { ok: true; config: LangfuseConfig; path: string; warnings: string[] }
|
|
23
|
+
| { ok: false; path: string; warnings: string[]; reason: string };
|
|
24
24
|
|
|
25
25
|
export function langfuseConfigPath(): string {
|
|
26
|
-
|
|
26
|
+
return join(getAgentDir(), CONFIG_FILE_NAME);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
const configSaveQueues = new Map<string, Promise<void>>();
|
|
30
30
|
|
|
31
31
|
export async function waitForLangfuseConfigWrites(path = langfuseConfigPath()): Promise<void> {
|
|
32
|
-
|
|
32
|
+
await configSaveQueues.get(path);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
export function writeLangfuseConfig(
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (configSaveQueues.get(path) === tail) configSaveQueues.delete(path);
|
|
48
|
-
});
|
|
49
|
-
return operation;
|
|
35
|
+
export function writeLangfuseConfig(patch: LangfuseConfigPatch, path = langfuseConfigPath()): Promise<LangfuseConfig> {
|
|
36
|
+
const previous = configSaveQueues.get(path) ?? Promise.resolve();
|
|
37
|
+
const operation = previous.then(() => writeLangfuseConfigNow(patch, path));
|
|
38
|
+
const tail = operation.then(
|
|
39
|
+
() => undefined,
|
|
40
|
+
() => undefined,
|
|
41
|
+
);
|
|
42
|
+
configSaveQueues.set(path, tail);
|
|
43
|
+
void tail.then(() => {
|
|
44
|
+
if (configSaveQueues.get(path) === tail) configSaveQueues.delete(path);
|
|
45
|
+
});
|
|
46
|
+
return operation;
|
|
50
47
|
}
|
|
51
48
|
|
|
52
|
-
async function writeLangfuseConfigNow(
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
...unknownFields
|
|
68
|
-
} = currentDocument;
|
|
69
|
-
const nextDocument = { ...unknownFields, ...normalized.config };
|
|
49
|
+
async function writeLangfuseConfigNow(patch: LangfuseConfigPatch, path: string): Promise<LangfuseConfig> {
|
|
50
|
+
const currentDocument = await readLangfuseDocumentForUpdate(path);
|
|
51
|
+
const normalized = normalizeLangfuseConfig({ ...currentDocument, ...patch });
|
|
52
|
+
if (!normalized.ok) throw new Error(normalized.reason);
|
|
53
|
+
const {
|
|
54
|
+
publicKey: _publicKey,
|
|
55
|
+
secretKey: _secretKey,
|
|
56
|
+
baseUrl: _baseUrl,
|
|
57
|
+
environment: _environment,
|
|
58
|
+
release: _release,
|
|
59
|
+
userId: _userId,
|
|
60
|
+
captureContent: _captureContent,
|
|
61
|
+
...unknownFields
|
|
62
|
+
} = currentDocument;
|
|
63
|
+
const nextDocument = { ...unknownFields, ...normalized.config };
|
|
70
64
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
65
|
+
await mkdir(dirname(path), { recursive: true, mode: 0o700 });
|
|
66
|
+
const tempPath = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
67
|
+
try {
|
|
68
|
+
await writeFile(tempPath, `${JSON.stringify(nextDocument, null, 2)}\n`, {
|
|
69
|
+
encoding: "utf8",
|
|
70
|
+
mode: 0o600,
|
|
71
|
+
});
|
|
72
|
+
await chmod(tempPath, 0o600);
|
|
73
|
+
await rename(tempPath, path);
|
|
74
|
+
return normalized.config;
|
|
75
|
+
} catch (error) {
|
|
76
|
+
await rm(tempPath, { force: true }).catch(() => undefined);
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
85
79
|
}
|
|
86
80
|
|
|
87
81
|
async function readLangfuseDocumentForUpdate(path: string): Promise<Record<string, unknown>> {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
82
|
+
let parsed: unknown;
|
|
83
|
+
try {
|
|
84
|
+
parsed = JSON.parse(await readFile(path, "utf8")) as unknown;
|
|
85
|
+
} catch (error) {
|
|
86
|
+
if (isNodeError(error) && error.code === "ENOENT") return {};
|
|
87
|
+
throw new Error(
|
|
88
|
+
`Cannot update ${CONFIG_FILE_NAME} because the existing file is malformed or unreadable; repair it first.`,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
const normalized = normalizeLangfuseConfig(parsed);
|
|
92
|
+
if (!normalized.ok) {
|
|
93
|
+
throw new Error(
|
|
94
|
+
`Cannot update ${CONFIG_FILE_NAME} because the existing file has invalid recognized fields; repair it first.`,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
return { ...(parsed as Record<string, unknown>) };
|
|
104
98
|
}
|
|
105
99
|
|
|
106
|
-
export async function loadLangfuseConfig(
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
};
|
|
119
|
-
}
|
|
100
|
+
export async function loadLangfuseConfig(path = langfuseConfigPath()): Promise<LangfuseConfigResult> {
|
|
101
|
+
await configSaveQueues.get(path);
|
|
102
|
+
const warnings: string[] = [];
|
|
103
|
+
const permissionFailure = await ensurePrivatePermissions(path, warnings);
|
|
104
|
+
if (permissionFailure) {
|
|
105
|
+
return {
|
|
106
|
+
ok: false,
|
|
107
|
+
path,
|
|
108
|
+
warnings,
|
|
109
|
+
reason: `Refusing to load credentials: ${permissionFailure}`,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
120
112
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
113
|
+
let text: string;
|
|
114
|
+
try {
|
|
115
|
+
text = await readFile(path, "utf8");
|
|
116
|
+
} catch (error) {
|
|
117
|
+
if (isNodeError(error) && error.code === "ENOENT") {
|
|
118
|
+
return { ok: false, path, warnings, reason: `Configuration file not found: ${path}` };
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
ok: false,
|
|
122
|
+
path,
|
|
123
|
+
warnings,
|
|
124
|
+
reason: `Failed to read ${path}: ${formatError(error)}`,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
135
127
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
128
|
+
let parsed: unknown;
|
|
129
|
+
try {
|
|
130
|
+
parsed = JSON.parse(text) as unknown;
|
|
131
|
+
} catch {
|
|
132
|
+
return {
|
|
133
|
+
ok: false,
|
|
134
|
+
path,
|
|
135
|
+
warnings,
|
|
136
|
+
reason: `Failed to parse ${path} as JSON; repair the file before retrying.`,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
147
139
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
140
|
+
const normalized = normalizeLangfuseConfig(parsed);
|
|
141
|
+
if (!normalized.ok) return { ok: false, path, warnings, reason: normalized.reason };
|
|
142
|
+
return { ok: true, config: normalized.config, path, warnings };
|
|
151
143
|
}
|
|
152
144
|
|
|
153
145
|
export function normalizeLangfuseConfig(
|
|
154
|
-
|
|
146
|
+
value: unknown,
|
|
155
147
|
): { ok: true; config: LangfuseConfig } | { ok: false; reason: string } {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
};
|
|
181
|
-
}
|
|
148
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
149
|
+
return { ok: false, reason: "pi-langfuse.json must contain a JSON object." };
|
|
150
|
+
}
|
|
151
|
+
const input = value as Record<string, unknown>;
|
|
152
|
+
const publicKey = normalizeString(input.publicKey);
|
|
153
|
+
const secretKey = normalizeString(input.secretKey);
|
|
154
|
+
if (!publicKey) {
|
|
155
|
+
return { ok: false, reason: "pi-langfuse.json requires a literal publicKey string." };
|
|
156
|
+
}
|
|
157
|
+
if (!secretKey) {
|
|
158
|
+
return { ok: false, reason: "pi-langfuse.json requires a literal secretKey string." };
|
|
159
|
+
}
|
|
160
|
+
if (isInterpolation(publicKey)) {
|
|
161
|
+
return {
|
|
162
|
+
ok: false,
|
|
163
|
+
reason: "pi-langfuse.json publicKey must be literal; environment and command interpolation are not supported.",
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
if (isInterpolation(secretKey)) {
|
|
167
|
+
return {
|
|
168
|
+
ok: false,
|
|
169
|
+
reason: "pi-langfuse.json secretKey must be literal; environment and command interpolation are not supported.",
|
|
170
|
+
};
|
|
171
|
+
}
|
|
182
172
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
};
|
|
195
|
-
}
|
|
173
|
+
const rawBaseUrl = input.baseUrl === undefined ? DEFAULT_BASE_URL : normalizeString(input.baseUrl);
|
|
174
|
+
if (!rawBaseUrl) {
|
|
175
|
+
return { ok: false, reason: "pi-langfuse.json baseUrl must be a non-empty string." };
|
|
176
|
+
}
|
|
177
|
+
const baseUrl = normalizeBaseUrl(rawBaseUrl);
|
|
178
|
+
if (!baseUrl) {
|
|
179
|
+
return {
|
|
180
|
+
ok: false,
|
|
181
|
+
reason: "pi-langfuse.json baseUrl must use HTTP or HTTPS without credentials, a query, or a fragment.",
|
|
182
|
+
};
|
|
183
|
+
}
|
|
196
184
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
if (userId.value && userId.value.length > 200) {
|
|
217
|
-
return { ok: false, reason: "pi-langfuse.json userId must be at most 200 characters." };
|
|
218
|
-
}
|
|
185
|
+
if (input.captureContent !== undefined && typeof input.captureContent !== "boolean") {
|
|
186
|
+
return { ok: false, reason: "pi-langfuse.json captureContent must be a boolean." };
|
|
187
|
+
}
|
|
188
|
+
const environment = optionalString(input.environment, "environment");
|
|
189
|
+
if (!environment.ok) return environment;
|
|
190
|
+
if (environment.value && (environment.value.length > 40 || !/^(?!langfuse)[a-z0-9_-]+$/u.test(environment.value))) {
|
|
191
|
+
return {
|
|
192
|
+
ok: false,
|
|
193
|
+
reason:
|
|
194
|
+
"pi-langfuse.json environment must be at most 40 lowercase letters, numbers, hyphens, or underscores and must not start with langfuse.",
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
const release = optionalString(input.release, "release");
|
|
198
|
+
if (!release.ok) return release;
|
|
199
|
+
const userId = optionalString(input.userId, "userId");
|
|
200
|
+
if (!userId.ok) return userId;
|
|
201
|
+
if (userId.value && userId.value.length > 200) {
|
|
202
|
+
return { ok: false, reason: "pi-langfuse.json userId must be at most 200 characters." };
|
|
203
|
+
}
|
|
219
204
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
205
|
+
return {
|
|
206
|
+
ok: true,
|
|
207
|
+
config: {
|
|
208
|
+
publicKey,
|
|
209
|
+
secretKey,
|
|
210
|
+
baseUrl,
|
|
211
|
+
...(environment.value ? { environment: environment.value } : {}),
|
|
212
|
+
...(release.value ? { release: release.value } : {}),
|
|
213
|
+
...(userId.value ? { userId: userId.value } : {}),
|
|
214
|
+
captureContent: input.captureContent ?? true,
|
|
215
|
+
},
|
|
216
|
+
};
|
|
232
217
|
}
|
|
233
218
|
|
|
234
|
-
async function ensurePrivatePermissions(
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
)
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
return failure;
|
|
253
|
-
}
|
|
254
|
-
return undefined;
|
|
219
|
+
async function ensurePrivatePermissions(path: string, warnings: string[]): Promise<string | undefined> {
|
|
220
|
+
try {
|
|
221
|
+
const file = await stat(path);
|
|
222
|
+
if ((file.mode & 0o777) !== 0o600) {
|
|
223
|
+
await chmod(path, 0o600);
|
|
224
|
+
const repaired = await stat(path);
|
|
225
|
+
if ((repaired.mode & 0o777) !== 0o600) {
|
|
226
|
+
throw new Error(`permissions remained ${(repaired.mode & 0o777).toString(8)}`);
|
|
227
|
+
}
|
|
228
|
+
warnings.push(`Restricted ${path} permissions to 0600.`);
|
|
229
|
+
}
|
|
230
|
+
} catch (error) {
|
|
231
|
+
if (isNodeError(error) && error.code === "ENOENT") return undefined;
|
|
232
|
+
const failure = `Failed to enforce 0600 permissions for ${path}: ${formatError(error)}`;
|
|
233
|
+
warnings.push(failure);
|
|
234
|
+
return failure;
|
|
235
|
+
}
|
|
236
|
+
return undefined;
|
|
255
237
|
}
|
|
256
238
|
|
|
257
239
|
function normalizeBaseUrl(value: string): string | undefined {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
240
|
+
try {
|
|
241
|
+
const url = new URL(value);
|
|
242
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return undefined;
|
|
243
|
+
if (url.username || url.password || url.search || url.hash) return undefined;
|
|
244
|
+
return url.toString().replace(/\/+$/, "");
|
|
245
|
+
} catch {
|
|
246
|
+
return undefined;
|
|
247
|
+
}
|
|
266
248
|
}
|
|
267
249
|
|
|
268
|
-
function optionalString(
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
return normalized
|
|
275
|
-
? { ok: true, value: normalized }
|
|
276
|
-
: { ok: false, reason: `pi-langfuse.json ${name} must be a non-empty string.` };
|
|
250
|
+
function optionalString(value: unknown, name: string): { ok: true; value?: string } | { ok: false; reason: string } {
|
|
251
|
+
if (value === undefined) return { ok: true };
|
|
252
|
+
const normalized = normalizeString(value);
|
|
253
|
+
return normalized
|
|
254
|
+
? { ok: true, value: normalized }
|
|
255
|
+
: { ok: false, reason: `pi-langfuse.json ${name} must be a non-empty string.` };
|
|
277
256
|
}
|
|
278
257
|
|
|
279
258
|
function normalizeString(value: unknown): string | undefined {
|
|
280
|
-
|
|
259
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
281
260
|
}
|
|
282
261
|
|
|
283
262
|
function isInterpolation(value: string): boolean {
|
|
284
|
-
|
|
263
|
+
return value.startsWith("$") || value.startsWith("!");
|
|
285
264
|
}
|
|
286
265
|
|
|
287
266
|
function isNodeError(error: unknown): error is NodeJS.ErrnoException {
|
|
288
|
-
|
|
267
|
+
return error instanceof Error && "code" in error;
|
|
289
268
|
}
|
|
290
269
|
|
|
291
270
|
function formatError(error: unknown): string {
|
|
292
|
-
|
|
271
|
+
return error instanceof Error ? error.message : String(error);
|
|
293
272
|
}
|
package/src/git.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { GitMetadata } from "./tracing.js";
|
|
3
|
+
|
|
4
|
+
const GIT_LOOKUP_TIMEOUT_MS = 1_000;
|
|
5
|
+
const MAX_GIT_BRANCH_LENGTH = 256;
|
|
6
|
+
|
|
7
|
+
type GitExecutor = ExtensionAPI["exec"];
|
|
8
|
+
|
|
9
|
+
export async function resolveGitMetadata(exec: GitExecutor, cwd: string): Promise<GitMetadata | undefined> {
|
|
10
|
+
const [branchResult, commit] = await Promise.all([resolveGitBranch(exec, cwd), resolveGitCommit(exec, cwd)]);
|
|
11
|
+
if (!branchResult.resolved) return undefined;
|
|
12
|
+
if (branchResult.branch) {
|
|
13
|
+
return {
|
|
14
|
+
branch: branchResult.branch,
|
|
15
|
+
...(commit ? { commit } : {}),
|
|
16
|
+
detached: false,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
return commit ? { commit, detached: true } : undefined;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function resolveGitBranch(exec: GitExecutor, cwd: string): Promise<{ resolved: boolean; branch?: string }> {
|
|
23
|
+
try {
|
|
24
|
+
const result = await exec("git", ["symbolic-ref", "--quiet", "--short", "HEAD"], {
|
|
25
|
+
cwd,
|
|
26
|
+
timeout: GIT_LOOKUP_TIMEOUT_MS,
|
|
27
|
+
});
|
|
28
|
+
if (result.killed) return { resolved: false };
|
|
29
|
+
if (result.code === 1) return { resolved: true };
|
|
30
|
+
if (result.code !== 0) return { resolved: false };
|
|
31
|
+
const branch = normalizeGitBranch(result.stdout);
|
|
32
|
+
return branch ? { resolved: true, branch } : { resolved: false };
|
|
33
|
+
} catch {
|
|
34
|
+
return { resolved: false };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function resolveGitCommit(exec: GitExecutor, cwd: string): Promise<string | undefined> {
|
|
39
|
+
try {
|
|
40
|
+
const result = await exec("git", ["rev-parse", "--verify", "--short=12", "HEAD"], {
|
|
41
|
+
cwd,
|
|
42
|
+
timeout: GIT_LOOKUP_TIMEOUT_MS,
|
|
43
|
+
});
|
|
44
|
+
if (result.code !== 0 || result.killed) return undefined;
|
|
45
|
+
const commit = result.stdout.trim();
|
|
46
|
+
return /^[0-9a-f]{4,64}$/iu.test(commit) ? commit.toLowerCase() : undefined;
|
|
47
|
+
} catch {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function normalizeGitBranch(value: string): string | undefined {
|
|
53
|
+
const branch = value.trim();
|
|
54
|
+
if (!branch || branch.length > MAX_GIT_BRANCH_LENGTH) return undefined;
|
|
55
|
+
for (const character of branch) {
|
|
56
|
+
const code = character.codePointAt(0) ?? 0;
|
|
57
|
+
if (code <= 31 || code === 127) return undefined;
|
|
58
|
+
}
|
|
59
|
+
return branch;
|
|
60
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1 +1,10 @@
|
|
|
1
1
|
export { default } from "./langfuse.js";
|
|
2
|
+
export {
|
|
3
|
+
type CreateLangfuseRuntimeOptions,
|
|
4
|
+
createLangfuseRuntime,
|
|
5
|
+
createPiLangfuseSession,
|
|
6
|
+
type LangfuseRuntime,
|
|
7
|
+
type LangfuseRuntimeConfig,
|
|
8
|
+
type PiLangfuseSession,
|
|
9
|
+
type PiLangfuseSessionOptions,
|
|
10
|
+
} from "./public-api.js";
|