langsmith 0.6.0 → 0.6.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/dist/client.cjs +169 -9
- package/dist/client.d.ts +19 -8
- package/dist/client.js +168 -8
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/utils/env.cjs +1 -0
- package/dist/utils/env.js +1 -0
- package/dist/utils/profiles.cjs +292 -0
- package/dist/utils/profiles.d.ts +48 -0
- package/dist/utils/profiles.js +253 -0
- package/package.json +1 -14
- package/dist/experimental/opencode/index.cjs +0 -36
- package/dist/experimental/opencode/index.d.ts +0 -3
- package/dist/experimental/opencode/index.js +0 -32
- package/dist/experimental/opencode/tracer.cjs +0 -389
- package/dist/experimental/opencode/tracer.d.ts +0 -30
- package/dist/experimental/opencode/tracer.js +0 -385
- package/experimental/opencode.cjs +0 -1
- package/experimental/opencode.d.cts +0 -1
- package/experimental/opencode.d.ts +0 -1
- package/experimental/opencode.js +0 -1
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.ProfileAuth = exports.DEFAULT_API_URL = void 0;
|
|
37
|
+
exports.hasValue = hasValue;
|
|
38
|
+
exports.loadProfileClientConfig = loadProfileClientConfig;
|
|
39
|
+
const env_js_1 = require("./env.cjs");
|
|
40
|
+
const fsUtils = __importStar(require("./fs.cjs"));
|
|
41
|
+
exports.DEFAULT_API_URL = "https://api.smith.langchain.com";
|
|
42
|
+
const OAUTH_CLIENT_ID = "langsmith-cli";
|
|
43
|
+
const TOKEN_REFRESH_LEEWAY_MS = 60_000;
|
|
44
|
+
const TOKEN_REFRESH_TIMEOUT_MS = 10_000;
|
|
45
|
+
function isBrowserLikeRuntime() {
|
|
46
|
+
const env = (0, env_js_1.getEnv)();
|
|
47
|
+
return env === "browser" || env === "webworker";
|
|
48
|
+
}
|
|
49
|
+
function getProfileConfigPath() {
|
|
50
|
+
const explicitPath = (0, env_js_1.getEnvironmentVariable)("LANGSMITH_CONFIG_FILE");
|
|
51
|
+
if (explicitPath) {
|
|
52
|
+
return explicitPath;
|
|
53
|
+
}
|
|
54
|
+
const home = (0, env_js_1.getEnvironmentVariable)("HOME") ?? (0, env_js_1.getEnvironmentVariable)("USERPROFILE");
|
|
55
|
+
if (!home) {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
return fsUtils.path.join(home, ".langsmith", "config.json");
|
|
59
|
+
}
|
|
60
|
+
function resolveProfileName(config) {
|
|
61
|
+
const envProfile = (0, env_js_1.getEnvironmentVariable)("LANGSMITH_PROFILE");
|
|
62
|
+
if (envProfile) {
|
|
63
|
+
return envProfile;
|
|
64
|
+
}
|
|
65
|
+
if (config.current_profile) {
|
|
66
|
+
return config.current_profile;
|
|
67
|
+
}
|
|
68
|
+
if (config.profiles?.default) {
|
|
69
|
+
return "default";
|
|
70
|
+
}
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
function loadProfileState() {
|
|
74
|
+
if (isBrowserLikeRuntime()) {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
const configPath = getProfileConfigPath();
|
|
78
|
+
if (!configPath || !fsUtils.existsSync(configPath)) {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
const config = JSON.parse(fsUtils.readFileSync(configPath));
|
|
83
|
+
const profileName = resolveProfileName(config);
|
|
84
|
+
const profile = profileName ? config.profiles?.[profileName] : undefined;
|
|
85
|
+
if (!profileName || !profile) {
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
return { configPath, config, profileName, profile };
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function hasValue(value) {
|
|
95
|
+
return value !== undefined && value !== null && value.trim() !== "";
|
|
96
|
+
}
|
|
97
|
+
function trimConfigValue(value) {
|
|
98
|
+
return value?.trim().replace(/^["']|["']$/g, "");
|
|
99
|
+
}
|
|
100
|
+
function shouldRefreshProfileToken(profile) {
|
|
101
|
+
const oauth = profile.oauth;
|
|
102
|
+
if (!oauth?.refresh_token) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
if (!oauth.access_token) {
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
if (!oauth.expires_at) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
const expiresAt = Date.parse(oauth.expires_at);
|
|
112
|
+
if (Number.isNaN(expiresAt)) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
return expiresAt <= Date.now() + TOKEN_REFRESH_LEEWAY_MS;
|
|
116
|
+
}
|
|
117
|
+
function normalizeConfigUrl(apiUrl) {
|
|
118
|
+
let normalized = apiUrl;
|
|
119
|
+
while (normalized.endsWith("/")) {
|
|
120
|
+
normalized = normalized.slice(0, -1);
|
|
121
|
+
}
|
|
122
|
+
const apiV1Suffix = "/api/v1";
|
|
123
|
+
return normalized.endsWith(apiV1Suffix)
|
|
124
|
+
? normalized.slice(0, -apiV1Suffix.length)
|
|
125
|
+
: normalized;
|
|
126
|
+
}
|
|
127
|
+
function applyTokenResponse(profile, token) {
|
|
128
|
+
profile.oauth ??= {};
|
|
129
|
+
if (token.access_token) {
|
|
130
|
+
profile.oauth.access_token = token.access_token;
|
|
131
|
+
}
|
|
132
|
+
if (token.refresh_token) {
|
|
133
|
+
profile.oauth.refresh_token = token.refresh_token;
|
|
134
|
+
}
|
|
135
|
+
if (typeof token.expires_in === "number" && token.expires_in > 0) {
|
|
136
|
+
profile.oauth.expires_at = new Date(Date.now() + token.expires_in * 1000).toISOString();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function getAbortReason(signal) {
|
|
140
|
+
return (signal.reason ??
|
|
141
|
+
new Error("The operation was aborted."));
|
|
142
|
+
}
|
|
143
|
+
async function waitForAbortSignal(promise, signal) {
|
|
144
|
+
if (!signal) {
|
|
145
|
+
return promise;
|
|
146
|
+
}
|
|
147
|
+
if (signal.aborted) {
|
|
148
|
+
throw getAbortReason(signal);
|
|
149
|
+
}
|
|
150
|
+
let cleanup;
|
|
151
|
+
const abortPromise = new Promise((_, reject) => {
|
|
152
|
+
const onAbort = () => {
|
|
153
|
+
reject(getAbortReason(signal));
|
|
154
|
+
};
|
|
155
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
156
|
+
cleanup = () => {
|
|
157
|
+
signal.removeEventListener("abort", onAbort);
|
|
158
|
+
};
|
|
159
|
+
});
|
|
160
|
+
try {
|
|
161
|
+
return await Promise.race([promise, abortPromise]);
|
|
162
|
+
}
|
|
163
|
+
finally {
|
|
164
|
+
cleanup?.();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
function loadProfileClientConfig() {
|
|
168
|
+
const state = loadProfileState();
|
|
169
|
+
const profile = state?.profile;
|
|
170
|
+
if (!state || !profile) {
|
|
171
|
+
return {};
|
|
172
|
+
}
|
|
173
|
+
const apiKey = trimConfigValue(profile.api_key);
|
|
174
|
+
const oauthAccessToken = trimConfigValue(profile.oauth?.access_token);
|
|
175
|
+
const oauthRefreshToken = trimConfigValue(profile.oauth?.refresh_token);
|
|
176
|
+
return {
|
|
177
|
+
apiUrl: profile.api_url,
|
|
178
|
+
apiKey,
|
|
179
|
+
workspaceId: profile.workspace_id,
|
|
180
|
+
oauthAccessToken,
|
|
181
|
+
oauthRefreshToken,
|
|
182
|
+
profileAuth: apiKey || oauthAccessToken || oauthRefreshToken
|
|
183
|
+
? new ProfileAuth(state)
|
|
184
|
+
: undefined,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
class ProfileAuth {
|
|
188
|
+
constructor(state) {
|
|
189
|
+
Object.defineProperty(this, "state", {
|
|
190
|
+
enumerable: true,
|
|
191
|
+
configurable: true,
|
|
192
|
+
writable: true,
|
|
193
|
+
value: state
|
|
194
|
+
});
|
|
195
|
+
Object.defineProperty(this, "refreshPromise", {
|
|
196
|
+
enumerable: true,
|
|
197
|
+
configurable: true,
|
|
198
|
+
writable: true,
|
|
199
|
+
value: void 0
|
|
200
|
+
});
|
|
201
|
+
Object.defineProperty(this, "managedAuthorizationValue", {
|
|
202
|
+
enumerable: true,
|
|
203
|
+
configurable: true,
|
|
204
|
+
writable: true,
|
|
205
|
+
value: void 0
|
|
206
|
+
});
|
|
207
|
+
this.rememberProfileAuthHeader(this.currentAuthHeader());
|
|
208
|
+
}
|
|
209
|
+
currentAuthHeader() {
|
|
210
|
+
const header = currentAuthHeaderFromProfile(this.state.profile);
|
|
211
|
+
this.rememberProfileAuthHeader(header);
|
|
212
|
+
return header;
|
|
213
|
+
}
|
|
214
|
+
async getAuthHeader(fetchImplementation, signal) {
|
|
215
|
+
if (shouldRefreshProfileToken(this.state.profile)) {
|
|
216
|
+
if (!this.refreshPromise) {
|
|
217
|
+
this.refreshPromise = this.refreshOAuthToken(fetchImplementation).finally(() => {
|
|
218
|
+
this.refreshPromise = undefined;
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
await waitForAbortSignal(this.refreshPromise, signal);
|
|
222
|
+
}
|
|
223
|
+
const header = authHeaderFromProfile(this.state.profile);
|
|
224
|
+
this.rememberProfileAuthHeader(header);
|
|
225
|
+
return header;
|
|
226
|
+
}
|
|
227
|
+
isProfileAuthorizationHeader(value) {
|
|
228
|
+
return value === this.managedAuthorizationValue;
|
|
229
|
+
}
|
|
230
|
+
async refreshOAuthToken(fetchImplementation) {
|
|
231
|
+
const refreshToken = this.state.profile.oauth?.refresh_token;
|
|
232
|
+
if (!refreshToken) {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
const refreshApiUrl = trimConfigValue(this.state.profile.api_url) ?? exports.DEFAULT_API_URL;
|
|
236
|
+
try {
|
|
237
|
+
const body = new URLSearchParams({
|
|
238
|
+
grant_type: "refresh_token",
|
|
239
|
+
client_id: OAUTH_CLIENT_ID,
|
|
240
|
+
refresh_token: refreshToken,
|
|
241
|
+
});
|
|
242
|
+
const response = await fetchImplementation(`${normalizeConfigUrl(refreshApiUrl)}/oauth/token`, {
|
|
243
|
+
method: "POST",
|
|
244
|
+
headers: {
|
|
245
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
246
|
+
},
|
|
247
|
+
body: body.toString(),
|
|
248
|
+
signal: AbortSignal.timeout(TOKEN_REFRESH_TIMEOUT_MS),
|
|
249
|
+
});
|
|
250
|
+
if (!response.ok) {
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
const token = (await response.json());
|
|
254
|
+
if (!token.access_token) {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
applyTokenResponse(this.state.profile, token);
|
|
258
|
+
this.state.config.profiles ??= {};
|
|
259
|
+
this.state.config.profiles[this.state.profileName] = this.state.profile;
|
|
260
|
+
await fsUtils.writeFileAtomic(this.state.configPath, `${JSON.stringify(this.state.config, null, 2)}\n`);
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
rememberProfileAuthHeader(header) {
|
|
267
|
+
this.managedAuthorizationValue =
|
|
268
|
+
header?.name === "Authorization" ? header.value : undefined;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
exports.ProfileAuth = ProfileAuth;
|
|
272
|
+
function currentAuthHeaderFromProfile(profile) {
|
|
273
|
+
const oauthAccessToken = trimConfigValue(profile.oauth?.access_token);
|
|
274
|
+
if (oauthAccessToken) {
|
|
275
|
+
return { name: "Authorization", value: `Bearer ${oauthAccessToken}` };
|
|
276
|
+
}
|
|
277
|
+
if (trimConfigValue(profile.oauth?.refresh_token)) {
|
|
278
|
+
return undefined;
|
|
279
|
+
}
|
|
280
|
+
return authHeaderFromProfile(profile);
|
|
281
|
+
}
|
|
282
|
+
function authHeaderFromProfile(profile) {
|
|
283
|
+
const oauthAccessToken = trimConfigValue(profile.oauth?.access_token);
|
|
284
|
+
if (oauthAccessToken) {
|
|
285
|
+
return { name: "Authorization", value: `Bearer ${oauthAccessToken}` };
|
|
286
|
+
}
|
|
287
|
+
const apiKey = trimConfigValue(profile.api_key);
|
|
288
|
+
if (apiKey) {
|
|
289
|
+
return { name: "x-api-key", value: apiKey };
|
|
290
|
+
}
|
|
291
|
+
return undefined;
|
|
292
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export declare const DEFAULT_API_URL = "https://api.smith.langchain.com";
|
|
2
|
+
type LangSmithProfileOAuth = {
|
|
3
|
+
access_token?: string;
|
|
4
|
+
refresh_token?: string;
|
|
5
|
+
expires_at?: string;
|
|
6
|
+
};
|
|
7
|
+
type LangSmithProfile = {
|
|
8
|
+
api_key?: string;
|
|
9
|
+
api_url?: string;
|
|
10
|
+
workspace_id?: string;
|
|
11
|
+
oauth?: LangSmithProfileOAuth;
|
|
12
|
+
};
|
|
13
|
+
type LangSmithProfileConfigFile = {
|
|
14
|
+
current_profile?: string;
|
|
15
|
+
profiles?: Record<string, LangSmithProfile>;
|
|
16
|
+
};
|
|
17
|
+
type LangSmithProfileState = {
|
|
18
|
+
configPath: string;
|
|
19
|
+
config: LangSmithProfileConfigFile;
|
|
20
|
+
profileName: string;
|
|
21
|
+
profile: LangSmithProfile;
|
|
22
|
+
};
|
|
23
|
+
export type ProfileAuthHeader = {
|
|
24
|
+
name: "Authorization" | "x-api-key";
|
|
25
|
+
value: string;
|
|
26
|
+
};
|
|
27
|
+
export type ProfileClientConfig = {
|
|
28
|
+
apiUrl?: string;
|
|
29
|
+
apiKey?: string;
|
|
30
|
+
workspaceId?: string;
|
|
31
|
+
oauthAccessToken?: string;
|
|
32
|
+
oauthRefreshToken?: string;
|
|
33
|
+
profileAuth?: ProfileAuth;
|
|
34
|
+
};
|
|
35
|
+
export declare function hasValue(value?: string | null): boolean;
|
|
36
|
+
export declare function loadProfileClientConfig(): ProfileClientConfig;
|
|
37
|
+
export declare class ProfileAuth {
|
|
38
|
+
private state;
|
|
39
|
+
private refreshPromise?;
|
|
40
|
+
private managedAuthorizationValue?;
|
|
41
|
+
constructor(state: LangSmithProfileState);
|
|
42
|
+
currentAuthHeader(): ProfileAuthHeader | undefined;
|
|
43
|
+
getAuthHeader(fetchImplementation: typeof fetch, signal?: AbortSignal | null): Promise<ProfileAuthHeader | undefined>;
|
|
44
|
+
isProfileAuthorizationHeader(value: string): boolean;
|
|
45
|
+
private refreshOAuthToken;
|
|
46
|
+
private rememberProfileAuthHeader;
|
|
47
|
+
}
|
|
48
|
+
export {};
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { getEnv, getEnvironmentVariable } from "./env.js";
|
|
2
|
+
import * as fsUtils from "./fs.js";
|
|
3
|
+
export const DEFAULT_API_URL = "https://api.smith.langchain.com";
|
|
4
|
+
const OAUTH_CLIENT_ID = "langsmith-cli";
|
|
5
|
+
const TOKEN_REFRESH_LEEWAY_MS = 60_000;
|
|
6
|
+
const TOKEN_REFRESH_TIMEOUT_MS = 10_000;
|
|
7
|
+
function isBrowserLikeRuntime() {
|
|
8
|
+
const env = getEnv();
|
|
9
|
+
return env === "browser" || env === "webworker";
|
|
10
|
+
}
|
|
11
|
+
function getProfileConfigPath() {
|
|
12
|
+
const explicitPath = getEnvironmentVariable("LANGSMITH_CONFIG_FILE");
|
|
13
|
+
if (explicitPath) {
|
|
14
|
+
return explicitPath;
|
|
15
|
+
}
|
|
16
|
+
const home = getEnvironmentVariable("HOME") ?? getEnvironmentVariable("USERPROFILE");
|
|
17
|
+
if (!home) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
return fsUtils.path.join(home, ".langsmith", "config.json");
|
|
21
|
+
}
|
|
22
|
+
function resolveProfileName(config) {
|
|
23
|
+
const envProfile = getEnvironmentVariable("LANGSMITH_PROFILE");
|
|
24
|
+
if (envProfile) {
|
|
25
|
+
return envProfile;
|
|
26
|
+
}
|
|
27
|
+
if (config.current_profile) {
|
|
28
|
+
return config.current_profile;
|
|
29
|
+
}
|
|
30
|
+
if (config.profiles?.default) {
|
|
31
|
+
return "default";
|
|
32
|
+
}
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
function loadProfileState() {
|
|
36
|
+
if (isBrowserLikeRuntime()) {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
const configPath = getProfileConfigPath();
|
|
40
|
+
if (!configPath || !fsUtils.existsSync(configPath)) {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
const config = JSON.parse(fsUtils.readFileSync(configPath));
|
|
45
|
+
const profileName = resolveProfileName(config);
|
|
46
|
+
const profile = profileName ? config.profiles?.[profileName] : undefined;
|
|
47
|
+
if (!profileName || !profile) {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
return { configPath, config, profileName, profile };
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
export function hasValue(value) {
|
|
57
|
+
return value !== undefined && value !== null && value.trim() !== "";
|
|
58
|
+
}
|
|
59
|
+
function trimConfigValue(value) {
|
|
60
|
+
return value?.trim().replace(/^["']|["']$/g, "");
|
|
61
|
+
}
|
|
62
|
+
function shouldRefreshProfileToken(profile) {
|
|
63
|
+
const oauth = profile.oauth;
|
|
64
|
+
if (!oauth?.refresh_token) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
if (!oauth.access_token) {
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
if (!oauth.expires_at) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
const expiresAt = Date.parse(oauth.expires_at);
|
|
74
|
+
if (Number.isNaN(expiresAt)) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
return expiresAt <= Date.now() + TOKEN_REFRESH_LEEWAY_MS;
|
|
78
|
+
}
|
|
79
|
+
function normalizeConfigUrl(apiUrl) {
|
|
80
|
+
let normalized = apiUrl;
|
|
81
|
+
while (normalized.endsWith("/")) {
|
|
82
|
+
normalized = normalized.slice(0, -1);
|
|
83
|
+
}
|
|
84
|
+
const apiV1Suffix = "/api/v1";
|
|
85
|
+
return normalized.endsWith(apiV1Suffix)
|
|
86
|
+
? normalized.slice(0, -apiV1Suffix.length)
|
|
87
|
+
: normalized;
|
|
88
|
+
}
|
|
89
|
+
function applyTokenResponse(profile, token) {
|
|
90
|
+
profile.oauth ??= {};
|
|
91
|
+
if (token.access_token) {
|
|
92
|
+
profile.oauth.access_token = token.access_token;
|
|
93
|
+
}
|
|
94
|
+
if (token.refresh_token) {
|
|
95
|
+
profile.oauth.refresh_token = token.refresh_token;
|
|
96
|
+
}
|
|
97
|
+
if (typeof token.expires_in === "number" && token.expires_in > 0) {
|
|
98
|
+
profile.oauth.expires_at = new Date(Date.now() + token.expires_in * 1000).toISOString();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function getAbortReason(signal) {
|
|
102
|
+
return (signal.reason ??
|
|
103
|
+
new Error("The operation was aborted."));
|
|
104
|
+
}
|
|
105
|
+
async function waitForAbortSignal(promise, signal) {
|
|
106
|
+
if (!signal) {
|
|
107
|
+
return promise;
|
|
108
|
+
}
|
|
109
|
+
if (signal.aborted) {
|
|
110
|
+
throw getAbortReason(signal);
|
|
111
|
+
}
|
|
112
|
+
let cleanup;
|
|
113
|
+
const abortPromise = new Promise((_, reject) => {
|
|
114
|
+
const onAbort = () => {
|
|
115
|
+
reject(getAbortReason(signal));
|
|
116
|
+
};
|
|
117
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
118
|
+
cleanup = () => {
|
|
119
|
+
signal.removeEventListener("abort", onAbort);
|
|
120
|
+
};
|
|
121
|
+
});
|
|
122
|
+
try {
|
|
123
|
+
return await Promise.race([promise, abortPromise]);
|
|
124
|
+
}
|
|
125
|
+
finally {
|
|
126
|
+
cleanup?.();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
export function loadProfileClientConfig() {
|
|
130
|
+
const state = loadProfileState();
|
|
131
|
+
const profile = state?.profile;
|
|
132
|
+
if (!state || !profile) {
|
|
133
|
+
return {};
|
|
134
|
+
}
|
|
135
|
+
const apiKey = trimConfigValue(profile.api_key);
|
|
136
|
+
const oauthAccessToken = trimConfigValue(profile.oauth?.access_token);
|
|
137
|
+
const oauthRefreshToken = trimConfigValue(profile.oauth?.refresh_token);
|
|
138
|
+
return {
|
|
139
|
+
apiUrl: profile.api_url,
|
|
140
|
+
apiKey,
|
|
141
|
+
workspaceId: profile.workspace_id,
|
|
142
|
+
oauthAccessToken,
|
|
143
|
+
oauthRefreshToken,
|
|
144
|
+
profileAuth: apiKey || oauthAccessToken || oauthRefreshToken
|
|
145
|
+
? new ProfileAuth(state)
|
|
146
|
+
: undefined,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
export class ProfileAuth {
|
|
150
|
+
constructor(state) {
|
|
151
|
+
Object.defineProperty(this, "state", {
|
|
152
|
+
enumerable: true,
|
|
153
|
+
configurable: true,
|
|
154
|
+
writable: true,
|
|
155
|
+
value: state
|
|
156
|
+
});
|
|
157
|
+
Object.defineProperty(this, "refreshPromise", {
|
|
158
|
+
enumerable: true,
|
|
159
|
+
configurable: true,
|
|
160
|
+
writable: true,
|
|
161
|
+
value: void 0
|
|
162
|
+
});
|
|
163
|
+
Object.defineProperty(this, "managedAuthorizationValue", {
|
|
164
|
+
enumerable: true,
|
|
165
|
+
configurable: true,
|
|
166
|
+
writable: true,
|
|
167
|
+
value: void 0
|
|
168
|
+
});
|
|
169
|
+
this.rememberProfileAuthHeader(this.currentAuthHeader());
|
|
170
|
+
}
|
|
171
|
+
currentAuthHeader() {
|
|
172
|
+
const header = currentAuthHeaderFromProfile(this.state.profile);
|
|
173
|
+
this.rememberProfileAuthHeader(header);
|
|
174
|
+
return header;
|
|
175
|
+
}
|
|
176
|
+
async getAuthHeader(fetchImplementation, signal) {
|
|
177
|
+
if (shouldRefreshProfileToken(this.state.profile)) {
|
|
178
|
+
if (!this.refreshPromise) {
|
|
179
|
+
this.refreshPromise = this.refreshOAuthToken(fetchImplementation).finally(() => {
|
|
180
|
+
this.refreshPromise = undefined;
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
await waitForAbortSignal(this.refreshPromise, signal);
|
|
184
|
+
}
|
|
185
|
+
const header = authHeaderFromProfile(this.state.profile);
|
|
186
|
+
this.rememberProfileAuthHeader(header);
|
|
187
|
+
return header;
|
|
188
|
+
}
|
|
189
|
+
isProfileAuthorizationHeader(value) {
|
|
190
|
+
return value === this.managedAuthorizationValue;
|
|
191
|
+
}
|
|
192
|
+
async refreshOAuthToken(fetchImplementation) {
|
|
193
|
+
const refreshToken = this.state.profile.oauth?.refresh_token;
|
|
194
|
+
if (!refreshToken) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
const refreshApiUrl = trimConfigValue(this.state.profile.api_url) ?? DEFAULT_API_URL;
|
|
198
|
+
try {
|
|
199
|
+
const body = new URLSearchParams({
|
|
200
|
+
grant_type: "refresh_token",
|
|
201
|
+
client_id: OAUTH_CLIENT_ID,
|
|
202
|
+
refresh_token: refreshToken,
|
|
203
|
+
});
|
|
204
|
+
const response = await fetchImplementation(`${normalizeConfigUrl(refreshApiUrl)}/oauth/token`, {
|
|
205
|
+
method: "POST",
|
|
206
|
+
headers: {
|
|
207
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
208
|
+
},
|
|
209
|
+
body: body.toString(),
|
|
210
|
+
signal: AbortSignal.timeout(TOKEN_REFRESH_TIMEOUT_MS),
|
|
211
|
+
});
|
|
212
|
+
if (!response.ok) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
const token = (await response.json());
|
|
216
|
+
if (!token.access_token) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
applyTokenResponse(this.state.profile, token);
|
|
220
|
+
this.state.config.profiles ??= {};
|
|
221
|
+
this.state.config.profiles[this.state.profileName] = this.state.profile;
|
|
222
|
+
await fsUtils.writeFileAtomic(this.state.configPath, `${JSON.stringify(this.state.config, null, 2)}\n`);
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
rememberProfileAuthHeader(header) {
|
|
229
|
+
this.managedAuthorizationValue =
|
|
230
|
+
header?.name === "Authorization" ? header.value : undefined;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
function currentAuthHeaderFromProfile(profile) {
|
|
234
|
+
const oauthAccessToken = trimConfigValue(profile.oauth?.access_token);
|
|
235
|
+
if (oauthAccessToken) {
|
|
236
|
+
return { name: "Authorization", value: `Bearer ${oauthAccessToken}` };
|
|
237
|
+
}
|
|
238
|
+
if (trimConfigValue(profile.oauth?.refresh_token)) {
|
|
239
|
+
return undefined;
|
|
240
|
+
}
|
|
241
|
+
return authHeaderFromProfile(profile);
|
|
242
|
+
}
|
|
243
|
+
function authHeaderFromProfile(profile) {
|
|
244
|
+
const oauthAccessToken = trimConfigValue(profile.oauth?.access_token);
|
|
245
|
+
if (oauthAccessToken) {
|
|
246
|
+
return { name: "Authorization", value: `Bearer ${oauthAccessToken}` };
|
|
247
|
+
}
|
|
248
|
+
const apiKey = trimConfigValue(profile.api_key);
|
|
249
|
+
if (apiKey) {
|
|
250
|
+
return { name: "x-api-key", value: apiKey };
|
|
251
|
+
}
|
|
252
|
+
return undefined;
|
|
253
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "langsmith",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "Client library to connect to the LangSmith Observability and Evaluation Platform.",
|
|
5
5
|
"packageManager": "pnpm@10.33.0",
|
|
6
6
|
"files": [
|
|
@@ -101,10 +101,6 @@
|
|
|
101
101
|
"experimental/sandbox.js",
|
|
102
102
|
"experimental/sandbox.d.ts",
|
|
103
103
|
"experimental/sandbox.d.cts",
|
|
104
|
-
"experimental/opencode.cjs",
|
|
105
|
-
"experimental/opencode.js",
|
|
106
|
-
"experimental/opencode.d.ts",
|
|
107
|
-
"experimental/opencode.d.cts",
|
|
108
104
|
"index.cjs",
|
|
109
105
|
"index.js",
|
|
110
106
|
"index.d.ts",
|
|
@@ -459,15 +455,6 @@
|
|
|
459
455
|
"import": "./experimental/sandbox.js",
|
|
460
456
|
"require": "./experimental/sandbox.cjs"
|
|
461
457
|
},
|
|
462
|
-
"./experimental/opencode": {
|
|
463
|
-
"types": {
|
|
464
|
-
"import": "./experimental/opencode.d.ts",
|
|
465
|
-
"require": "./experimental/opencode.d.cts",
|
|
466
|
-
"default": "./experimental/opencode.d.ts"
|
|
467
|
-
},
|
|
468
|
-
"import": "./experimental/opencode.js",
|
|
469
|
-
"require": "./experimental/opencode.cjs"
|
|
470
|
-
},
|
|
471
458
|
"./package.json": "./package.json"
|
|
472
459
|
},
|
|
473
460
|
"pnpm": {
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.LangSmithPlugin = void 0;
|
|
4
|
-
// import type { Plugin } from "@opencode-ai/plugin";
|
|
5
|
-
const tracer_js_1 = require("./tracer.cjs");
|
|
6
|
-
const LangSmithPlugin = async (ctx) => {
|
|
7
|
-
const tracer = new tracer_js_1.OpenCodeSessionTracer();
|
|
8
|
-
async function getSessionHistory(sessionID) {
|
|
9
|
-
const past = await ctx.client.session.messages({
|
|
10
|
-
path: { id: sessionID },
|
|
11
|
-
});
|
|
12
|
-
if (past.error)
|
|
13
|
-
throw past.error;
|
|
14
|
-
return past.data;
|
|
15
|
-
}
|
|
16
|
-
return {
|
|
17
|
-
"experimental.chat.system.transform": async (input, output) => {
|
|
18
|
-
const sessionID = input.sessionID;
|
|
19
|
-
if (!sessionID)
|
|
20
|
-
return;
|
|
21
|
-
await tracer.handleSessionLoad(sessionID, getSessionHistory);
|
|
22
|
-
await tracer.handleSystem(input, output);
|
|
23
|
-
},
|
|
24
|
-
event: async (input) => {
|
|
25
|
-
const sessionID = "sessionID" in input.event.properties &&
|
|
26
|
-
typeof input.event.properties.sessionID === "string"
|
|
27
|
-
? input.event.properties.sessionID
|
|
28
|
-
: undefined;
|
|
29
|
-
if (!sessionID)
|
|
30
|
-
return;
|
|
31
|
-
await tracer.handleSessionLoad(sessionID, getSessionHistory);
|
|
32
|
-
await tracer.handleEvent(input);
|
|
33
|
-
},
|
|
34
|
-
};
|
|
35
|
-
};
|
|
36
|
-
exports.LangSmithPlugin = LangSmithPlugin;
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
// import type { Plugin } from "@opencode-ai/plugin";
|
|
2
|
-
import { OpenCodeSessionTracer } from "./tracer.js";
|
|
3
|
-
export const LangSmithPlugin = async (ctx) => {
|
|
4
|
-
const tracer = new OpenCodeSessionTracer();
|
|
5
|
-
async function getSessionHistory(sessionID) {
|
|
6
|
-
const past = await ctx.client.session.messages({
|
|
7
|
-
path: { id: sessionID },
|
|
8
|
-
});
|
|
9
|
-
if (past.error)
|
|
10
|
-
throw past.error;
|
|
11
|
-
return past.data;
|
|
12
|
-
}
|
|
13
|
-
return {
|
|
14
|
-
"experimental.chat.system.transform": async (input, output) => {
|
|
15
|
-
const sessionID = input.sessionID;
|
|
16
|
-
if (!sessionID)
|
|
17
|
-
return;
|
|
18
|
-
await tracer.handleSessionLoad(sessionID, getSessionHistory);
|
|
19
|
-
await tracer.handleSystem(input, output);
|
|
20
|
-
},
|
|
21
|
-
event: async (input) => {
|
|
22
|
-
const sessionID = "sessionID" in input.event.properties &&
|
|
23
|
-
typeof input.event.properties.sessionID === "string"
|
|
24
|
-
? input.event.properties.sessionID
|
|
25
|
-
: undefined;
|
|
26
|
-
if (!sessionID)
|
|
27
|
-
return;
|
|
28
|
-
await tracer.handleSessionLoad(sessionID, getSessionHistory);
|
|
29
|
-
await tracer.handleEvent(input);
|
|
30
|
-
},
|
|
31
|
-
};
|
|
32
|
-
};
|