coaiajs 0.4.2 → 0.4.3
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 +5 -2
- package/agents/custom_gpt/ceremony-media-proxy.instructions.md +86 -0
- package/agents/custom_gpt/ceremony-media-proxy.yml +158 -0
- package/agents/custom_gpt/ceremony-observations.instructions.md +38 -5
- package/agents/custom_gpt/ceremony-observations.yml +737 -287
- package/dist/src/cli.js +2 -2
- package/dist/src/config.d.ts +2 -0
- package/dist/src/config.d.ts.map +1 -1
- package/dist/src/config.js +10 -2
- package/dist/src/config.js.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/langfuse/index.d.ts +2 -1
- package/dist/src/langfuse/index.d.ts.map +1 -1
- package/dist/src/langfuse/index.js +1 -1
- package/dist/src/langfuse/index.js.map +1 -1
- package/dist/src/langfuse/media.d.ts +30 -0
- package/dist/src/langfuse/media.d.ts.map +1 -1
- package/dist/src/langfuse/media.js +128 -77
- package/dist/src/langfuse/media.js.map +1 -1
- package/dist/src/media-upload-proxy.d.ts +32 -0
- package/dist/src/media-upload-proxy.d.ts.map +1 -0
- package/dist/src/media-upload-proxy.js +210 -0
- package/dist/src/media-upload-proxy.js.map +1 -0
- package/dist/src/redis.d.ts +10 -5
- package/dist/src/redis.d.ts.map +1 -1
- package/dist/src/redis.js +94 -8
- package/dist/src/redis.js.map +1 -1
- package/llms-full.txt +9 -1
- package/llms.txt +1 -0
- package/package.json +7 -2
- package/rispecs/03-langfuse-module.spec.md +2 -1
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// coaiajs/src/media-upload-proxy.ts — Custom GPT file-to-Langfuse media bridge
|
|
3
|
+
import { timingSafeEqual } from 'node:crypto';
|
|
4
|
+
import { createServer } from 'node:http';
|
|
5
|
+
import { pathToFileURL } from 'node:url';
|
|
6
|
+
import { uploadMediaBytes } from './langfuse/media.js';
|
|
7
|
+
const DEFAULT_MAX_FILE_BYTES = 10 * 1024 * 1024;
|
|
8
|
+
const DEFAULT_BODY_BYTES = 1024 * 1024;
|
|
9
|
+
const DEFAULT_OPENAI_FILE_HOSTS = ['files.oaiusercontent.com', 'files.openai.com'];
|
|
10
|
+
/** Download one Custom GPT conversation file and complete the Langfuse media upload flow. */
|
|
11
|
+
export async function uploadOpenAIFileToLangfuse(input, options = {}) {
|
|
12
|
+
if (!Array.isArray(input.openaiFileIdRefs) || input.openaiFileIdRefs.length !== 1) {
|
|
13
|
+
throw new Error('openaiFileIdRefs must contain exactly one conversation file');
|
|
14
|
+
}
|
|
15
|
+
const file = parseOpenAIFileReference(input.openaiFileIdRefs[0]);
|
|
16
|
+
const allowedHosts = options.openAIFileHosts ?? configuredOpenAIFileHosts();
|
|
17
|
+
const bytes = await downloadFile(file.download_link, options.maxFileBytes ?? configuredMaxFileBytes(), allowedHosts);
|
|
18
|
+
const result = await uploadMediaBytes({
|
|
19
|
+
data: bytes,
|
|
20
|
+
fileName: file.name,
|
|
21
|
+
contentType: detectDownloadedContentType(bytes, file.mime_type),
|
|
22
|
+
traceId: input.traceId,
|
|
23
|
+
observationId: input.observationId,
|
|
24
|
+
datasetId: input.datasetId,
|
|
25
|
+
datasetItemId: input.datasetItemId,
|
|
26
|
+
field: input.field,
|
|
27
|
+
source: 'bytes',
|
|
28
|
+
});
|
|
29
|
+
return { ...result, openaiFileId: file.id };
|
|
30
|
+
}
|
|
31
|
+
/** Create the small HTTP service used by the companion Custom GPT action spec. */
|
|
32
|
+
export function createMediaUploadProxyServer(options = {}) {
|
|
33
|
+
const apiKey = options.apiKey ?? process.env['COAIA_MEDIA_PROXY_API_KEY'];
|
|
34
|
+
if (!apiKey)
|
|
35
|
+
throw new Error('COAIA_MEDIA_PROXY_API_KEY is required');
|
|
36
|
+
const maxBodyBytes = options.maxBodyBytes ?? DEFAULT_BODY_BYTES;
|
|
37
|
+
return createServer(async (request, response) => {
|
|
38
|
+
try {
|
|
39
|
+
if (request.method === 'GET' && request.url === '/health') {
|
|
40
|
+
sendJson(response, 200, { ok: true });
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (request.method !== 'POST' || request.url !== '/media/upload') {
|
|
44
|
+
sendJson(response, 404, { error: 'Not found' });
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (!secureEqual(request.headers['x-coaia-proxy-key'], apiKey)) {
|
|
48
|
+
sendJson(response, 401, { error: 'Unauthorized' });
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const body = await readJsonBody(request, maxBodyBytes);
|
|
52
|
+
const result = await uploadOpenAIFileToLangfuse(body, options);
|
|
53
|
+
sendJson(response, 200, result);
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
57
|
+
const status = /too large/i.test(message) ? 413 : 400;
|
|
58
|
+
sendJson(response, status, { error: message });
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
async function readJsonBody(request, maxBytes) {
|
|
63
|
+
const contentType = String(request.headers['content-type'] ?? '').toLowerCase();
|
|
64
|
+
if (!contentType.startsWith('application/json'))
|
|
65
|
+
throw new Error('Content-Type must be application/json');
|
|
66
|
+
const chunks = [];
|
|
67
|
+
let size = 0;
|
|
68
|
+
for await (const chunk of request) {
|
|
69
|
+
const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
70
|
+
size += bytes.byteLength;
|
|
71
|
+
if (size > maxBytes)
|
|
72
|
+
throw new Error(`Request body is too large (maximum ${maxBytes} bytes)`);
|
|
73
|
+
chunks.push(bytes);
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
return JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
throw new Error('Request body must contain valid JSON');
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function parseOpenAIFileReference(value) {
|
|
83
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
84
|
+
throw new Error('Custom GPT did not provide a runtime file reference object');
|
|
85
|
+
}
|
|
86
|
+
const record = value;
|
|
87
|
+
const file = {
|
|
88
|
+
name: record.name,
|
|
89
|
+
id: record.id,
|
|
90
|
+
mime_type: record.mime_type,
|
|
91
|
+
download_link: record.download_link,
|
|
92
|
+
};
|
|
93
|
+
for (const [key, item] of Object.entries(file)) {
|
|
94
|
+
if (typeof item !== 'string' || !item)
|
|
95
|
+
throw new Error(`Conversation file reference is missing ${key}`);
|
|
96
|
+
}
|
|
97
|
+
return file;
|
|
98
|
+
}
|
|
99
|
+
async function downloadFile(urlValue, maxBytes, allowedHosts) {
|
|
100
|
+
let url = validateDownloadUrl(urlValue, allowedHosts);
|
|
101
|
+
for (let redirect = 0; redirect <= 3; redirect += 1) {
|
|
102
|
+
const response = await fetch(url, { redirect: 'manual' });
|
|
103
|
+
if (response.status >= 300 && response.status < 400) {
|
|
104
|
+
const location = response.headers.get('location');
|
|
105
|
+
if (!location)
|
|
106
|
+
throw new Error('Conversation file redirect did not include a location');
|
|
107
|
+
url = validateDownloadUrl(new URL(location, url).toString(), allowedHosts);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (!response.ok || !response.body) {
|
|
111
|
+
throw new Error(`Unable to download conversation file (HTTP ${response.status})`);
|
|
112
|
+
}
|
|
113
|
+
const declaredLength = Number(response.headers.get('content-length') ?? 0);
|
|
114
|
+
if (declaredLength > maxBytes)
|
|
115
|
+
throw new Error(`Conversation file is too large (maximum ${maxBytes} bytes)`);
|
|
116
|
+
const chunks = [];
|
|
117
|
+
let size = 0;
|
|
118
|
+
const reader = response.body.getReader();
|
|
119
|
+
while (true) {
|
|
120
|
+
const { done, value } = await reader.read();
|
|
121
|
+
if (done)
|
|
122
|
+
break;
|
|
123
|
+
size += value.byteLength;
|
|
124
|
+
if (size > maxBytes) {
|
|
125
|
+
await reader.cancel();
|
|
126
|
+
throw new Error(`Conversation file is too large (maximum ${maxBytes} bytes)`);
|
|
127
|
+
}
|
|
128
|
+
chunks.push(Buffer.from(value));
|
|
129
|
+
}
|
|
130
|
+
return Buffer.concat(chunks);
|
|
131
|
+
}
|
|
132
|
+
throw new Error('Conversation file download exceeded the redirect limit');
|
|
133
|
+
}
|
|
134
|
+
function validateDownloadUrl(value, allowedHosts) {
|
|
135
|
+
const url = new URL(value);
|
|
136
|
+
const allowHttp = process.env['COAIA_MEDIA_ALLOW_HTTP'] === 'true';
|
|
137
|
+
if (url.protocol !== 'https:' && !(allowHttp && url.protocol === 'http:')) {
|
|
138
|
+
throw new Error(`Conversation file URL protocol '${url.protocol}' is not allowed`);
|
|
139
|
+
}
|
|
140
|
+
if (!isAllowedHost(url.hostname, allowedHosts)) {
|
|
141
|
+
throw new Error(`Conversation file URL domain '${url.hostname}' is not allowed`);
|
|
142
|
+
}
|
|
143
|
+
if (url.username || url.password)
|
|
144
|
+
throw new Error('Conversation file URL credentials are not allowed');
|
|
145
|
+
return url;
|
|
146
|
+
}
|
|
147
|
+
function detectDownloadedContentType(bytes, declaredType) {
|
|
148
|
+
if (bytes.subarray(0, 8).equals(Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]))) {
|
|
149
|
+
return 'image/png';
|
|
150
|
+
}
|
|
151
|
+
if (bytes.length >= 3 && bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff)
|
|
152
|
+
return 'image/jpeg';
|
|
153
|
+
const prefix = bytes.subarray(0, 6).toString('ascii');
|
|
154
|
+
if (prefix === 'GIF87a' || prefix === 'GIF89a')
|
|
155
|
+
return 'image/gif';
|
|
156
|
+
if (bytes.subarray(0, 4).toString('ascii') === 'RIFF' && bytes.subarray(8, 12).toString('ascii') === 'WEBP') {
|
|
157
|
+
return 'image/webp';
|
|
158
|
+
}
|
|
159
|
+
if (bytes.subarray(0, 5).toString('ascii') === '%PDF-')
|
|
160
|
+
return 'application/pdf';
|
|
161
|
+
return declaredType;
|
|
162
|
+
}
|
|
163
|
+
function configuredOpenAIFileHosts() {
|
|
164
|
+
const extra = (process.env['COAIA_OPENAI_FILE_HOSTS'] ?? '')
|
|
165
|
+
.split(',')
|
|
166
|
+
.map((host) => host.trim().toLowerCase())
|
|
167
|
+
.filter(Boolean);
|
|
168
|
+
return [...DEFAULT_OPENAI_FILE_HOSTS, ...extra];
|
|
169
|
+
}
|
|
170
|
+
function configuredMaxFileBytes() {
|
|
171
|
+
const configured = Number(process.env['COAIA_MEDIA_MAX_BYTES']);
|
|
172
|
+
return Number.isSafeInteger(configured) && configured > 0 ? configured : DEFAULT_MAX_FILE_BYTES;
|
|
173
|
+
}
|
|
174
|
+
function isAllowedHost(hostname, allowedHosts) {
|
|
175
|
+
const lower = hostname.toLowerCase();
|
|
176
|
+
return allowedHosts.some((host) => lower === host || lower.endsWith(`.${host}`));
|
|
177
|
+
}
|
|
178
|
+
function secureEqual(actual, expected) {
|
|
179
|
+
if (!actual || Array.isArray(actual))
|
|
180
|
+
return false;
|
|
181
|
+
const actualBytes = Buffer.from(actual);
|
|
182
|
+
const expectedBytes = Buffer.from(expected);
|
|
183
|
+
return actualBytes.byteLength === expectedBytes.byteLength && timingSafeEqual(actualBytes, expectedBytes);
|
|
184
|
+
}
|
|
185
|
+
function sendJson(response, status, body) {
|
|
186
|
+
response.writeHead(status, {
|
|
187
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
188
|
+
'Cache-Control': 'no-store',
|
|
189
|
+
});
|
|
190
|
+
response.end(JSON.stringify(body));
|
|
191
|
+
}
|
|
192
|
+
export async function startMediaUploadProxy() {
|
|
193
|
+
const port = Number(process.env['PORT'] ?? process.env['COAIA_MEDIA_PROXY_PORT'] ?? 8787);
|
|
194
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535)
|
|
195
|
+
throw new Error(`Invalid proxy port: ${port}`);
|
|
196
|
+
const server = createMediaUploadProxyServer();
|
|
197
|
+
await new Promise((resolve, reject) => {
|
|
198
|
+
server.once('error', reject);
|
|
199
|
+
server.listen(port, '0.0.0.0', () => resolve());
|
|
200
|
+
});
|
|
201
|
+
console.log(`coaiajs media upload proxy listening on port ${port}`);
|
|
202
|
+
return server;
|
|
203
|
+
}
|
|
204
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
205
|
+
startMediaUploadProxy().catch((error) => {
|
|
206
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
207
|
+
process.exitCode = 1;
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
//# sourceMappingURL=media-upload-proxy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-upload-proxy.js","sourceRoot":"","sources":["../../src/media-upload-proxy.ts"],"names":[],"mappings":"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"}
|
package/dist/src/redis.d.ts
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/** Minimal key-value surface shared by the TCP and REST backends. */
|
|
2
|
+
export interface KvClient {
|
|
3
|
+
set(key: string, value: string, ttlSeconds?: number): Promise<unknown>;
|
|
4
|
+
get(key: string): Promise<string | null>;
|
|
5
|
+
del(key: string): Promise<number>;
|
|
6
|
+
keys(pattern: string): Promise<string[]>;
|
|
7
|
+
exists(key: string): Promise<number>;
|
|
8
|
+
quit(): Promise<unknown>;
|
|
9
|
+
}
|
|
4
10
|
/** Get or create the lazy Redis client. */
|
|
5
|
-
export declare function getClient():
|
|
11
|
+
export declare function getClient(): KvClient;
|
|
6
12
|
/** Store a key-value pair with optional TTL in minutes, matching coaiapy. */
|
|
7
13
|
export declare function tash(key: string, value: string, ttl?: number): Promise<void>;
|
|
8
14
|
/** Fetch a value by key. Returns null if not found. */
|
|
@@ -17,5 +23,4 @@ export declare function exists(key: string): Promise<boolean>;
|
|
|
17
23
|
export declare function disconnect(): Promise<void>;
|
|
18
24
|
/** Reset client (testing). */
|
|
19
25
|
export declare function resetClient(): void;
|
|
20
|
-
export {};
|
|
21
26
|
//# sourceMappingURL=redis.d.ts.map
|
package/dist/src/redis.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redis.d.ts","sourceRoot":"","sources":["../../src/redis.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"redis.d.ts","sourceRoot":"","sources":["../../src/redis.ts"],"names":[],"mappings":"AASA,qEAAqE;AACrE,MAAM,WAAW,QAAQ;IACvB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACvE,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACzC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAClC,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACzC,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACrC,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CAC1B;AAkJD,2CAA2C;AAC3C,wBAAgB,SAAS,IAAI,QAAQ,CAKpC;AAED,6EAA6E;AAC7E,wBAAsB,IAAI,CACxB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,CAOf;AAED,uDAAuD;AACvD,wBAAsB,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAG/D;AAED,oDAAoD;AACpD,wBAAsB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAGtD;AAED,oCAAoC;AACpC,wBAAsB,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAG7D;AAED,6BAA6B;AAC7B,wBAAsB,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAG1D;AAED,6BAA6B;AAC7B,wBAAsB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAKhD;AAED,8BAA8B;AAC9B,wBAAgB,WAAW,IAAI,IAAI,CAElC"}
|
package/dist/src/redis.js
CHANGED
|
@@ -4,18 +4,104 @@ import IORedis from 'ioredis';
|
|
|
4
4
|
import { getConfig } from './config.js';
|
|
5
5
|
const Redis = IORedis.default ?? IORedis;
|
|
6
6
|
let _client = null;
|
|
7
|
+
/**
|
|
8
|
+
* Upstash-compatible REST backend. The endpoint accepts a JSON command array
|
|
9
|
+
* (`["SET","k","v"]`) POSTed to the base URL and answers `{"result": ...}`.
|
|
10
|
+
*/
|
|
11
|
+
class RestRedisClient {
|
|
12
|
+
baseUrl;
|
|
13
|
+
token;
|
|
14
|
+
constructor(url, token) {
|
|
15
|
+
this.baseUrl = url.replace(/\/+$/, '');
|
|
16
|
+
this.token = token;
|
|
17
|
+
}
|
|
18
|
+
async command(args) {
|
|
19
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
20
|
+
if (this.token)
|
|
21
|
+
headers['Authorization'] = `Bearer ${this.token}`;
|
|
22
|
+
const res = await globalThis.fetch(this.baseUrl, {
|
|
23
|
+
method: 'POST',
|
|
24
|
+
headers,
|
|
25
|
+
body: JSON.stringify(args.map(String)),
|
|
26
|
+
});
|
|
27
|
+
const text = await res.text();
|
|
28
|
+
if (!res.ok) {
|
|
29
|
+
throw new Error(`Redis REST ${res.status} ${res.statusText}: ${text.slice(0, 200)}`);
|
|
30
|
+
}
|
|
31
|
+
let payload;
|
|
32
|
+
try {
|
|
33
|
+
payload = JSON.parse(text);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
throw new Error(`Redis REST returned non-JSON response: ${text.slice(0, 200)}`);
|
|
37
|
+
}
|
|
38
|
+
if (payload.error)
|
|
39
|
+
throw new Error(`Redis REST error: ${payload.error}`);
|
|
40
|
+
return payload.result;
|
|
41
|
+
}
|
|
42
|
+
set(key, value, ttlSeconds) {
|
|
43
|
+
const args = ttlSeconds && ttlSeconds > 0 ? ['EX', String(ttlSeconds)] : [];
|
|
44
|
+
return this.command(['SET', key, value, ...args]);
|
|
45
|
+
}
|
|
46
|
+
get(key) {
|
|
47
|
+
return this.command(['GET', key]);
|
|
48
|
+
}
|
|
49
|
+
del(key) {
|
|
50
|
+
return this.command(['DEL', key]);
|
|
51
|
+
}
|
|
52
|
+
keys(pattern) {
|
|
53
|
+
return this.command(['KEYS', pattern]);
|
|
54
|
+
}
|
|
55
|
+
exists(key) {
|
|
56
|
+
return this.command(['EXISTS', key]);
|
|
57
|
+
}
|
|
58
|
+
async quit() {
|
|
59
|
+
return 'OK';
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/** ioredis-backed TCP/TLS backend, adapted to the shared KvClient surface. */
|
|
63
|
+
class TcpRedisClient {
|
|
64
|
+
redis;
|
|
65
|
+
constructor(redis) {
|
|
66
|
+
this.redis = redis;
|
|
67
|
+
}
|
|
68
|
+
set(key, value, ttlSeconds) {
|
|
69
|
+
return ttlSeconds && ttlSeconds > 0
|
|
70
|
+
? this.redis.set(key, value, 'EX', ttlSeconds)
|
|
71
|
+
: this.redis.set(key, value);
|
|
72
|
+
}
|
|
73
|
+
get(key) {
|
|
74
|
+
return this.redis.get(key);
|
|
75
|
+
}
|
|
76
|
+
del(key) {
|
|
77
|
+
return this.redis.del(key);
|
|
78
|
+
}
|
|
79
|
+
keys(pattern) {
|
|
80
|
+
return this.redis.keys(pattern);
|
|
81
|
+
}
|
|
82
|
+
exists(key) {
|
|
83
|
+
return this.redis.exists(key);
|
|
84
|
+
}
|
|
85
|
+
quit() {
|
|
86
|
+
return this.redis.quit();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
7
89
|
function upstashClient(url, token) {
|
|
8
90
|
const parsed = new URL(url);
|
|
9
|
-
|
|
91
|
+
// An http(s) URL is a REST endpoint, not a TCP socket.
|
|
92
|
+
if (parsed.protocol === 'http:' || parsed.protocol === 'https:') {
|
|
93
|
+
return new RestRedisClient(url, token ?? (decodeURIComponent(parsed.password) || undefined));
|
|
94
|
+
}
|
|
95
|
+
const tls = parsed.protocol === 'rediss:';
|
|
10
96
|
const password = token ?? decodeURIComponent(parsed.password);
|
|
11
|
-
return new Redis({
|
|
97
|
+
return new TcpRedisClient(new Redis({
|
|
12
98
|
host: parsed.hostname,
|
|
13
99
|
port: parsed.port ? parseInt(parsed.port, 10) : 6379,
|
|
14
100
|
username: parsed.username ? decodeURIComponent(parsed.username) : undefined,
|
|
15
101
|
password: password || undefined,
|
|
16
102
|
lazyConnect: true,
|
|
17
103
|
tls: tls ? {} : undefined,
|
|
18
|
-
});
|
|
104
|
+
}));
|
|
19
105
|
}
|
|
20
106
|
function buildClient(cfg) {
|
|
21
107
|
const r = cfg.redis;
|
|
@@ -25,19 +111,19 @@ function buildClient(cfg) {
|
|
|
25
111
|
}
|
|
26
112
|
// Explicit URL takes priority
|
|
27
113
|
if (r?.url) {
|
|
28
|
-
return new Redis(r.url, {
|
|
114
|
+
return new TcpRedisClient(new Redis(r.url, {
|
|
29
115
|
lazyConnect: true,
|
|
30
116
|
tls: r.url.startsWith('rediss://') ? {} : undefined,
|
|
31
|
-
});
|
|
117
|
+
}));
|
|
32
118
|
}
|
|
33
119
|
// Host / port fallback
|
|
34
|
-
return new Redis({
|
|
120
|
+
return new TcpRedisClient(new Redis({
|
|
35
121
|
host: r?.host ?? '127.0.0.1',
|
|
36
122
|
port: r?.port ?? 6379,
|
|
37
123
|
password: r?.password,
|
|
38
124
|
lazyConnect: true,
|
|
39
125
|
tls: r?.ssl ? {} : undefined,
|
|
40
|
-
});
|
|
126
|
+
}));
|
|
41
127
|
}
|
|
42
128
|
/** Get or create the lazy Redis client. */
|
|
43
129
|
export function getClient() {
|
|
@@ -50,7 +136,7 @@ export function getClient() {
|
|
|
50
136
|
export async function tash(key, value, ttl) {
|
|
51
137
|
const client = getClient();
|
|
52
138
|
if (ttl && ttl > 0) {
|
|
53
|
-
await client.set(key, value,
|
|
139
|
+
await client.set(key, value, ttl * 60);
|
|
54
140
|
}
|
|
55
141
|
else {
|
|
56
142
|
await client.set(key, value);
|
package/dist/src/redis.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redis.js","sourceRoot":"","sources":["../../src/redis.ts"],"names":[],"mappings":"AAAA,oDAAoD;AACpD,2CAA2C;AAE3C,OAAO,OAAO,MAAM,SAAS,CAAC;AAC9B,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAGxC,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"redis.js","sourceRoot":"","sources":["../../src/redis.ts"],"names":[],"mappings":"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"}
|
package/llms-full.txt
CHANGED
|
@@ -170,10 +170,18 @@ Langfuse Cloud v4 compatibility: writes use the scoped JS SDK v5 and OpenTelemet
|
|
|
170
170
|
- **Datasets:** `createDataset`, `getDataset`, `listDatasets`, `createDatasetItem`, `listDatasetItems`
|
|
171
171
|
- **Scores:** `createScore`, `listScores`, `createScoreForTarget`, `applyScoreToTrace`, `createScoreConfig`, `getScoreConfig`, `listScoreConfigs`, `applyScoreConfig`, `importScoreConfigs`, `exportScoreConfigs`, `getBuiltInPresets`, `installPreset`
|
|
172
172
|
- **Comments:** `createComment`, `getComment`, `listComments`
|
|
173
|
-
- **Media:** `uploadAndAttachMedia`, `getMedia`
|
|
173
|
+
- **Media:** `uploadMediaBytes`, `uploadAndAttachMedia`, `getMedia`
|
|
174
174
|
- **Projects:** `listProjects`
|
|
175
175
|
- **Formatters:** `formatTracesTable`, `formatTracesMarkdown`, `formatTraceTree`, `formatObservationDisplay`, `formatPromptMarkdown`, `formatPromptDisplay`, `formatPromptsTable`, `formatDatasetsTable`, `formatDatasetForFinetuning`, `formatScoresTable`, `formatScoreConfigsTable`, `formatMediaDisplay`
|
|
176
176
|
|
|
177
|
+
### `coaiajs/media-upload-proxy`
|
|
178
|
+
|
|
179
|
+
```
|
|
180
|
+
uploadOpenAIFileToLangfuse, createMediaUploadProxyServer, startMediaUploadProxy
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
The deployable bridge consumes Custom GPT `openaiFileIdRefs`, downloads the actual conversation file from an allowlisted host, completes the Langfuse presigned media upload, and returns a renderable media token.
|
|
184
|
+
|
|
177
185
|
### Exported types
|
|
178
186
|
|
|
179
187
|
```
|
package/llms.txt
CHANGED
|
@@ -29,6 +29,7 @@ Key facts an agent usually needs first:
|
|
|
29
29
|
- `coaiajs/environment`: `EnvironmentManager`, `createEnvironment`, `findEnvironment`
|
|
30
30
|
- `coaiajs/version`: `getPackageVersion`
|
|
31
31
|
- `coaiajs/langfuse`: Langfuse v4-compatible OpenTelemetry tracing, Observations API v2, Scores API v3, prompts, datasets, comments, media, projects
|
|
32
|
+
- `coaiajs/media-upload-proxy`: deployable `openaiFileIdRefs` to Langfuse media upload bridge (`coaiajs-media-proxy` binary)
|
|
32
33
|
- `coaiajs/narrative`: `KnowledgeGraphManager`, structural tension charts, markdown export
|
|
33
34
|
- `coaiajs/pde`: Prompt Decomposition Engine — sessions, STC mapping
|
|
34
35
|
- `coaiajs/planning`: `parsePlan`, `planToSTC`, `syncToChart`, `syncToPlan`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "coaiajs",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "CoAIA unified TypeScript monorepo — CLI, MCP server, and library consolidating coaia-narrative, coaia-pde, coaia-planning, and coaiapy patterns into one package.",
|
|
6
6
|
"main": "./dist/src/index.js",
|
|
@@ -42,6 +42,10 @@
|
|
|
42
42
|
"types": "./dist/src/langfuse/index.d.ts",
|
|
43
43
|
"import": "./dist/src/langfuse/index.js"
|
|
44
44
|
},
|
|
45
|
+
"./media-upload-proxy": {
|
|
46
|
+
"types": "./dist/src/media-upload-proxy.d.ts",
|
|
47
|
+
"import": "./dist/src/media-upload-proxy.js"
|
|
48
|
+
},
|
|
45
49
|
"./narrative": {
|
|
46
50
|
"types": "./dist/src/narrative/index.d.ts",
|
|
47
51
|
"import": "./dist/src/narrative/index.js"
|
|
@@ -72,7 +76,8 @@
|
|
|
72
76
|
],
|
|
73
77
|
"bin": {
|
|
74
78
|
"coaia": "dist/src/cli.js",
|
|
75
|
-
"coaiajs-mcp": "dist/mcp/server.js"
|
|
79
|
+
"coaiajs-mcp": "dist/mcp/server.js",
|
|
80
|
+
"coaiajs-media-proxy": "dist/src/media-upload-proxy.js"
|
|
76
81
|
},
|
|
77
82
|
"engines": {
|
|
78
83
|
"node": ">=20.0.0"
|
|
@@ -17,7 +17,8 @@ A comprehensive Langfuse client covering traces, observations, prompts, datasets
|
|
|
17
17
|
- Langfuse v4 migration is implemented: trace writes use the scoped JS SDK v5 and `POST /api/public/otel/v1/traces`; trace, observation, and session reads use Observations API v2; score reads use Scores API v3.
|
|
18
18
|
- V4 observations are immutable: root input/output must be complete before export, sessions are reconstructed by `sessionId`, and the legacy patch-output MCP/CLI operation is removed.
|
|
19
19
|
- The broad Custom GPT action spec at [`agents/custom_gpt/ceremony.yml`](../agents/custom_gpt/ceremony.yml) remains OpenAPI 3.1.0 and uses the supported v4 endpoints.
|
|
20
|
-
- The focused [`agents/custom_gpt/ceremony-observations.yml`](../agents/custom_gpt/ceremony-observations.yml) spec provides
|
|
20
|
+
- The focused [`agents/custom_gpt/ceremony-observations.yml`](../agents/custom_gpt/ceremony-observations.yml) spec provides 23 GPT Actions for root-trace creation, nested observation append through OTLP/HTTP JSON, observations-first reads, and the ceremony's core prompt, dataset, score, comment, media, and project operations. Its companion instructions document Basic authorization, v4 ingestion headers, immutable-span rules, parent context, and the exact-byte media workflow using padded Base64 SHA-256 digests and a separate presigned PUT.
|
|
21
|
+
- The deployable [`src/media-upload-proxy.ts`](../src/media-upload-proxy.ts) bridge and one-operation [`ceremony-media-proxy.yml`](../agents/custom_gpt/ceremony-media-proxy.yml) companion Action use OpenAI `openaiFileIdRefs` to transfer the actual conversation file, complete the Langfuse record/PUT/finalize flow, and return a renderable media token. Together the focused specs expose 24 non-consequential actions.
|
|
21
22
|
- Types defined in [`src/types.ts`](../src/types.ts): `ScoreCategory`, `ScoreConfig`.
|
|
22
23
|
- Remaining gap: not every Langfuse formatting and cache helper from `cofuse.py` has a TypeScript equivalent; the core trace/observation/prompt/dataset/score/comment/media path is present.
|
|
23
24
|
|