coaiajs 0.3.0 → 0.3.2

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 (179) hide show
  1. package/KINSHIP.md +1 -1
  2. package/README.md +191 -75
  3. package/dist/mcp/config.d.ts.map +1 -0
  4. package/dist/mcp/config.js +0 -2
  5. package/dist/mcp/config.js.map +1 -0
  6. package/dist/mcp/prompts.d.ts.map +1 -0
  7. package/dist/mcp/prompts.js.map +1 -0
  8. package/dist/mcp/resources.d.ts.map +1 -0
  9. package/dist/mcp/resources.js.map +1 -0
  10. package/dist/mcp/server.d.ts.map +1 -0
  11. package/dist/mcp/server.js +1 -1
  12. package/dist/mcp/server.js.map +1 -0
  13. package/dist/mcp/tools/coaiapy-tools.d.ts.map +1 -0
  14. package/dist/mcp/tools/coaiapy-tools.js.map +1 -0
  15. package/dist/mcp/tools/index.d.ts.map +1 -0
  16. package/dist/mcp/tools/index.js.map +1 -0
  17. package/dist/src/audio.d.ts.map +1 -0
  18. package/dist/src/audio.js.map +1 -0
  19. package/dist/src/cli-helpers.d.ts.map +1 -0
  20. package/dist/src/cli-helpers.js.map +1 -0
  21. package/dist/src/cli.d.ts.map +1 -0
  22. package/dist/src/cli.js +1 -0
  23. package/dist/src/cli.js.map +1 -0
  24. package/dist/src/config.d.ts.map +1 -0
  25. package/dist/src/config.js.map +1 -0
  26. package/dist/src/environment.d.ts.map +1 -0
  27. package/dist/src/environment.js.map +1 -0
  28. package/dist/src/github.d.ts.map +1 -0
  29. package/dist/src/github.js.map +1 -0
  30. package/dist/src/index.d.ts +1 -0
  31. package/dist/src/index.d.ts.map +1 -0
  32. package/dist/src/index.js +1 -0
  33. package/dist/src/index.js.map +1 -0
  34. package/dist/src/langfuse/client.d.ts.map +1 -0
  35. package/dist/src/langfuse/client.js.map +1 -0
  36. package/dist/src/langfuse/comments.d.ts.map +1 -0
  37. package/dist/src/langfuse/comments.js.map +1 -0
  38. package/dist/src/langfuse/datasets.d.ts.map +1 -0
  39. package/dist/src/langfuse/datasets.js.map +1 -0
  40. package/dist/src/langfuse/index.d.ts +1 -0
  41. package/dist/src/langfuse/index.d.ts.map +1 -0
  42. package/dist/src/langfuse/index.js.map +1 -0
  43. package/dist/src/langfuse/media.d.ts.map +1 -0
  44. package/dist/src/langfuse/media.js.map +1 -0
  45. package/dist/src/langfuse/observations.d.ts.map +1 -0
  46. package/dist/src/langfuse/observations.js.map +1 -0
  47. package/dist/src/langfuse/prompts.d.ts +5 -1
  48. package/dist/src/langfuse/prompts.d.ts.map +1 -0
  49. package/dist/src/langfuse/prompts.js +16 -3
  50. package/dist/src/langfuse/prompts.js.map +1 -0
  51. package/dist/src/langfuse/scores.d.ts.map +1 -0
  52. package/dist/src/langfuse/scores.js.map +1 -0
  53. package/dist/src/langfuse/traces.d.ts.map +1 -0
  54. package/dist/src/langfuse/traces.js.map +1 -0
  55. package/dist/src/llm.d.ts.map +1 -0
  56. package/dist/src/llm.js.map +1 -0
  57. package/dist/src/narrative/graph-manager.d.ts.map +1 -0
  58. package/dist/src/narrative/graph-manager.js.map +1 -0
  59. package/dist/src/narrative/index.d.ts.map +1 -0
  60. package/dist/src/narrative/index.js.map +1 -0
  61. package/dist/src/narrative/markdown-export.d.ts.map +1 -0
  62. package/dist/src/narrative/markdown-export.js.map +1 -0
  63. package/dist/src/narrative/tool-definitions.d.ts.map +1 -0
  64. package/dist/src/narrative/tool-definitions.js.map +1 -0
  65. package/dist/src/narrative/tool-handlers.d.ts.map +1 -0
  66. package/dist/src/narrative/tool-handlers.js.map +1 -0
  67. package/dist/src/narrative/types.d.ts.map +1 -0
  68. package/dist/src/narrative/types.js.map +1 -0
  69. package/dist/src/narrative/validation.d.ts.map +1 -0
  70. package/dist/src/narrative/validation.js.map +1 -0
  71. package/dist/src/pde/index.d.ts.map +1 -0
  72. package/dist/src/pde/index.js.map +1 -0
  73. package/dist/src/pde/mcp-handlers.d.ts.map +1 -0
  74. package/dist/src/pde/mcp-handlers.js.map +1 -0
  75. package/dist/src/pde/mcp-tools.d.ts.map +1 -0
  76. package/dist/src/pde/mcp-tools.js.map +1 -0
  77. package/dist/src/pde/session-manager.d.ts.map +1 -0
  78. package/dist/src/pde/session-manager.js.map +1 -0
  79. package/dist/src/pde/stc-mapper.d.ts.map +1 -0
  80. package/dist/src/pde/stc-mapper.js.map +1 -0
  81. package/dist/src/pipeline/index.d.ts.map +1 -0
  82. package/dist/src/pipeline/index.js.map +1 -0
  83. package/dist/src/pipeline/template-engine.d.ts.map +1 -0
  84. package/dist/src/pipeline/template-engine.js.map +1 -0
  85. package/dist/src/planning/index.d.ts.map +1 -0
  86. package/dist/src/planning/index.js.map +1 -0
  87. package/dist/src/planning/mcp-handlers.d.ts.map +1 -0
  88. package/dist/src/planning/mcp-handlers.js.map +1 -0
  89. package/dist/src/planning/mcp-tools.d.ts.map +1 -0
  90. package/dist/src/planning/mcp-tools.js.map +1 -0
  91. package/dist/src/planning/plan-parser.d.ts.map +1 -0
  92. package/dist/src/planning/plan-parser.js.map +1 -0
  93. package/dist/src/redis.d.ts.map +1 -0
  94. package/dist/src/redis.js.map +1 -0
  95. package/dist/src/types.d.ts.map +1 -0
  96. package/dist/src/types.js.map +1 -0
  97. package/dist/src/version.d.ts.map +1 -0
  98. package/dist/src/version.js.map +1 -0
  99. package/llms-full.txt +361 -0
  100. package/llms.txt +43 -0
  101. package/package.json +79 -12
  102. package/.claude/settings.local.json +0 -17
  103. package/.claude-plugin/marketplace.json +0 -26
  104. package/articles/academic/NOTES.md +0 -1
  105. package/articles/academic/README.md +0 -1
  106. package/articles/academic/creative-orientation-vs-problem-solving.md +0 -177
  107. package/articles/academic/jsonl-knowledge-graphs-agent-memory.md +0 -142
  108. package/articles/academic/langfuse-observability-llm-pipelines.md +0 -144
  109. package/articles/academic/medicine-wheel-software-architecture.md +0 -163
  110. package/articles/academic/mmot-autonomous-agents.md +0 -156
  111. package/articles/academic/model-context-protocol-interagent.md +0 -161
  112. package/articles/academic/pde-prompt-decomposition.md +0 -186
  113. package/articles/academic/structural-tension-in-ai-agents.md +0 -134
  114. package/articles/reviews/mcp-protocol-design-review.md +0 -170
  115. package/articles/reviews/observability-ai-systems-review.md +0 -176
  116. package/articles/reviews/prompt-engineering-decomposition-review.md +0 -184
  117. package/articles/surveys/agent-orchestration-survey.md +0 -186
  118. package/articles/surveys/knowledge-graph-storage-survey.md +0 -204
  119. package/articles/surveys/structural-tension-methodology-survey.md +0 -154
  120. package/articles/technical/aws-sdk-v3-polly.md +0 -270
  121. package/articles/technical/commander-cli-framework.md +0 -262
  122. package/articles/technical/dotenv-config-patterns.md +0 -360
  123. package/articles/technical/ioredis-vs-redis.md +0 -142
  124. package/articles/technical/langfuse-js-sdk-vs-rest.md +0 -191
  125. package/articles/technical/mcp-sdk-typescript.md +0 -291
  126. package/articles/technical/octokit-github-api.md +0 -293
  127. package/articles/technical/openai-sdk-modern.md +0 -231
  128. package/articles/technical/yaml-parsing-node.md +0 -266
  129. package/articles/technical/zod-runtime-validation.md +0 -212
  130. package/coaiajs-langfuse-claude-plugin/.claude-plugin/marketplace.json +0 -16
  131. package/coaiajs-langfuse-claude-plugin/.claude-plugin/plugin.json +0 -10
  132. package/coaiajs-langfuse-claude-plugin/.mcp.json +0 -8
  133. package/coaiajs-langfuse-claude-plugin/README.md +0 -60
  134. package/mcp/config.ts +0 -225
  135. package/mcp/prompts.ts +0 -131
  136. package/mcp/resources.ts +0 -84
  137. package/mcp/server.ts +0 -519
  138. package/mcp/tools/coaiapy-tools.ts +0 -366
  139. package/mcp/tools/index.ts +0 -4
  140. package/src/audio.ts +0 -76
  141. package/src/cli-helpers.ts +0 -86
  142. package/src/cli.ts +0 -1261
  143. package/src/config.ts +0 -207
  144. package/src/environment.ts +0 -171
  145. package/src/github.ts +0 -143
  146. package/src/index.ts +0 -82
  147. package/src/langfuse/client.ts +0 -105
  148. package/src/langfuse/comments.ts +0 -52
  149. package/src/langfuse/datasets.ts +0 -178
  150. package/src/langfuse/index.ts +0 -37
  151. package/src/langfuse/media.ts +0 -193
  152. package/src/langfuse/observations.ts +0 -155
  153. package/src/langfuse/prompts.ts +0 -157
  154. package/src/langfuse/scores.ts +0 -456
  155. package/src/langfuse/traces.ts +0 -353
  156. package/src/llm.ts +0 -106
  157. package/src/narrative/graph-manager.ts +0 -1358
  158. package/src/narrative/index.ts +0 -188
  159. package/src/narrative/markdown-export.ts +0 -535
  160. package/src/narrative/tool-definitions.ts +0 -635
  161. package/src/narrative/tool-handlers.ts +0 -528
  162. package/src/narrative/types.ts +0 -9
  163. package/src/narrative/validation.ts +0 -179
  164. package/src/pde/index.ts +0 -34
  165. package/src/pde/mcp-handlers.ts +0 -359
  166. package/src/pde/mcp-tools.ts +0 -201
  167. package/src/pde/session-manager.ts +0 -248
  168. package/src/pde/stc-mapper.ts +0 -298
  169. package/src/pipeline/index.ts +0 -7
  170. package/src/pipeline/template-engine.ts +0 -398
  171. package/src/planning/index.ts +0 -32
  172. package/src/planning/mcp-handlers.ts +0 -369
  173. package/src/planning/mcp-tools.ts +0 -155
  174. package/src/planning/plan-parser.ts +0 -587
  175. package/src/redis.ts +0 -111
  176. package/src/types.ts +0 -281
  177. package/src/version.ts +0 -39
  178. package/test/config.test.mjs +0 -93
  179. package/tsconfig.json +0 -26
@@ -1,52 +0,0 @@
1
- // coaiajs/src/langfuse/comments.ts — Comment operations
2
- // Port of cofuse.py comment functions
3
-
4
- import { getClient } from './client.js';
5
-
6
- export interface CommentFilters {
7
- objectType?: string;
8
- objectId?: string;
9
- authorUserId?: string;
10
- page?: number;
11
- limit?: number;
12
- }
13
-
14
- export async function listComments(filters: CommentFilters): Promise<string> {
15
- const client = getClient();
16
- const params = new URLSearchParams();
17
-
18
- if (filters.objectType) params.set('objectType', filters.objectType);
19
- if (filters.objectId) params.set('objectId', filters.objectId);
20
- if (filters.authorUserId) params.set('authorUserId', filters.authorUserId);
21
- params.set('page', String(filters.page ?? 1));
22
- params.set('limit', String(filters.limit ?? 50));
23
-
24
- const qs = params.toString();
25
- const result = await client.request<unknown>('GET', `/api/public/comments?${qs}`);
26
- return JSON.stringify(result, null, 2);
27
- }
28
-
29
- export async function getComment(commentId: string): Promise<string> {
30
- const client = getClient();
31
- const result = await client.request<unknown>('GET', `/api/public/comments/${commentId}`);
32
- return JSON.stringify(result, null, 2);
33
- }
34
-
35
- export async function createComment(params: {
36
- text: string;
37
- objectType: string;
38
- objectId: string;
39
- authorUserId?: string;
40
- }): Promise<string> {
41
- const client = getClient();
42
- const data: Record<string, unknown> = {
43
- content: params.text,
44
- objectType: params.objectType,
45
- objectId: params.objectId,
46
- };
47
-
48
- if (params.authorUserId) data.authorUserId = params.authorUserId;
49
-
50
- const result = await client.request<unknown>('POST', '/api/public/comments', data);
51
- return JSON.stringify(result, null, 2);
52
- }
@@ -1,178 +0,0 @@
1
- // coaiajs/src/langfuse/datasets.ts — Dataset operations
2
- // Port of cofuse.py dataset functions
3
-
4
- import { getClient } from './client.js';
5
-
6
- export async function listDatasets(): Promise<string> {
7
- const client = getClient();
8
- const result = await client.request<unknown>('GET', '/api/public/v2/datasets');
9
- return JSON.stringify(result, null, 2);
10
- }
11
-
12
- export async function getDataset(name: string): Promise<string> {
13
- const client = getClient();
14
- const result = await client.request<unknown>(
15
- 'GET',
16
- `/api/public/v2/datasets/${encodeURIComponent(name)}`,
17
- );
18
- return JSON.stringify(result, null, 2);
19
- }
20
-
21
- export async function createDataset(params: {
22
- name: string;
23
- description?: string;
24
- metadata?: unknown;
25
- }): Promise<string> {
26
- const client = getClient();
27
- const data: Record<string, unknown> = { name: params.name };
28
-
29
- if (params.description) data.description = params.description;
30
- if (params.metadata) {
31
- data.metadata = typeof params.metadata === 'string'
32
- ? safeParseJSON(params.metadata)
33
- : params.metadata;
34
- }
35
-
36
- const result = await client.request<unknown>('POST', '/api/public/v2/datasets', data);
37
- return JSON.stringify(result, null, 2);
38
- }
39
-
40
- export async function listDatasetItems(name: string): Promise<string> {
41
- const client = getClient();
42
- const allItems: unknown[] = [];
43
- let page = 1;
44
-
45
- while (true) {
46
- const data = await client.request<Record<string, unknown>>(
47
- 'GET',
48
- `/api/public/dataset-items?name=${encodeURIComponent(name)}&page=${page}`,
49
- );
50
-
51
- const items = (data as Record<string, unknown>).data;
52
- if (!items || !Array.isArray(items) || items.length === 0) break;
53
- allItems.push(...items);
54
-
55
- const meta = (data as Record<string, unknown>).meta as Record<string, unknown> | undefined;
56
- if (!meta || (meta.page as number ?? page) >= (meta.totalPages as number ?? 1)) break;
57
- page++;
58
- }
59
-
60
- return JSON.stringify(allItems, null, 2);
61
- }
62
-
63
- export async function createDatasetItem(params: {
64
- datasetName: string;
65
- input: unknown;
66
- expectedOutput?: unknown;
67
- metadata?: unknown;
68
- sourceTraceId?: string;
69
- sourceObservationId?: string;
70
- }): Promise<string> {
71
- const client = getClient();
72
- const data: Record<string, unknown> = {
73
- datasetName: params.datasetName,
74
- input: params.input,
75
- };
76
-
77
- if (params.expectedOutput !== undefined) data.expectedOutput = params.expectedOutput;
78
- if (params.metadata) data.metadata = params.metadata;
79
- if (params.sourceTraceId) data.sourceTraceId = params.sourceTraceId;
80
- if (params.sourceObservationId) data.sourceObservationId = params.sourceObservationId;
81
-
82
- const result = await client.request<unknown>('POST', '/api/public/dataset-items', data);
83
- return JSON.stringify(result, null, 2);
84
- }
85
-
86
- // ─── Formatters ─────────────────────────────────────────────────────
87
-
88
- export function formatDatasetsTable(json: unknown): string {
89
- try {
90
- const raw = typeof json === 'string' ? JSON.parse(json) : json;
91
- const datasets: Record<string, unknown>[] = Array.isArray(raw)
92
- ? raw
93
- : (raw as Record<string, unknown>).data as Record<string, unknown>[] ?? [];
94
-
95
- if (!datasets.length) return 'No datasets found.';
96
-
97
- const headers = ['Name', 'Description', 'Items', 'Created'];
98
- const rows = datasets.map((d) => [
99
- trunc(String(d.name ?? 'N/A'), 25),
100
- trunc(String(d.description ?? 'N/A'), 30),
101
- String(d.items?.toString() ?? 'N/A'),
102
- String(d.createdAt ?? 'N/A').slice(0, 10),
103
- ]);
104
-
105
- return formatTable(headers, rows) + `\nTotal datasets: ${datasets.length}`;
106
- } catch (e) {
107
- return `Error formatting datasets: ${e}`;
108
- }
109
- }
110
-
111
- export function formatDatasetForFinetuning(
112
- items: unknown,
113
- format: 'openai' | 'gemini',
114
- systemInstruction?: string,
115
- ): string {
116
- try {
117
- const parsed = (typeof items === 'string' ? JSON.parse(items) : items) as Record<string, unknown>[];
118
- const sysInstr = systemInstruction ?? 'You are a helpful assistant.';
119
- const lines: string[] = [];
120
-
121
- for (const item of parsed) {
122
- const input = item.input;
123
- const output = item.expectedOutput;
124
- if (!input || !output) continue;
125
-
126
- if (format === 'openai') {
127
- lines.push(JSON.stringify({
128
- messages: [
129
- { role: 'system', content: sysInstr },
130
- { role: 'user', content: input },
131
- { role: 'assistant', content: output },
132
- ],
133
- }));
134
- } else {
135
- lines.push(JSON.stringify({
136
- systemInstruction: {
137
- role: 'system',
138
- parts: [{ text: sysInstr }],
139
- },
140
- contents: [
141
- { role: 'user', parts: [{ text: input }] },
142
- { role: 'model', parts: [{ text: output }] },
143
- ],
144
- }));
145
- }
146
- }
147
-
148
- return lines.join('\n');
149
- } catch (e) {
150
- return `Error formatting for fine-tuning: ${e}`;
151
- }
152
- }
153
-
154
- // ─── Helpers ────────────────────────────────────────────────────────
155
-
156
- function trunc(s: string, len: number): string {
157
- return s.length > len ? s.slice(0, len - 3) + '...' : s;
158
- }
159
-
160
- function safeParseJSON(s: string): unknown {
161
- try {
162
- return JSON.parse(s);
163
- } catch {
164
- return { note: s };
165
- }
166
- }
167
-
168
- function formatTable(headers: string[], rows: string[][]): string {
169
- const widths = headers.map((h, i) =>
170
- Math.max(h.length, ...rows.map((r) => (r[i] ?? '').length)),
171
- );
172
- const sep = '+' + widths.map((w) => '-'.repeat(w + 2)).join('+') + '+';
173
- const hdr = '| ' + headers.map((h, i) => h.padEnd(widths[i])).join(' | ') + ' |';
174
- const body = rows.map(
175
- (r) => '| ' + r.map((c, i) => (c ?? '').padEnd(widths[i])).join(' | ') + ' |',
176
- );
177
- return [sep, hdr, sep, ...body, sep].join('\n');
178
- }
@@ -1,37 +0,0 @@
1
- // coaiajs/src/langfuse/index.ts — Barrel export
2
-
3
- export { LangfuseClient, LangfuseApiError, getClient, resetClient, nowISO } from './client.js';
4
- export type { IngestionEvent, LangfuseClientConfig } from './client.js';
5
-
6
- export {
7
- addTrace, createTrace, patchTraceOutput,
8
- listTraces, getTrace, traceView, sessionView,
9
- formatTracesTable, formatTracesMarkdown, formatTraceTree,
10
- } from './traces.js';
11
- export type { TraceFilters } from './traces.js';
12
-
13
- export { addObservation, addObservations, getObservation, formatObservationDisplay } from './observations.js';
14
-
15
- export { listPrompts, getPrompt, createPrompt, formatPromptsTable, formatPromptDisplay } from './prompts.js';
16
-
17
- export {
18
- listDatasets, getDataset, createDataset, listDatasetItems, createDatasetItem,
19
- formatDatasetsTable, formatDatasetForFinetuning,
20
- } from './datasets.js';
21
-
22
- export {
23
- createScore, applyScoreToTrace, createScoreForTarget, listScores,
24
- listScoreConfigs, getScoreConfig, createScoreConfig,
25
- exportScoreConfigs, importScoreConfigs,
26
- applyScoreConfig,
27
- getBuiltInPresets, installPreset,
28
- formatScoresTable, formatScoreConfigsTable,
29
- } from './scores.js';
30
- export type { ScoreFilters } from './scores.js';
31
-
32
- export { listComments, getComment, createComment } from './comments.js';
33
- export type { CommentFilters } from './comments.js';
34
-
35
- export {
36
- uploadAndAttachMedia, getMedia, detectContentType, formatMediaDisplay,
37
- } from './media.js';
@@ -1,193 +0,0 @@
1
- // coaiajs/src/langfuse/media.ts — Media upload operations
2
- // Port of cofuse.py media functions
3
-
4
- import { readFileSync, statSync } from 'node:fs';
5
- import { createHash } from 'node:crypto';
6
- import { basename, extname } from 'node:path';
7
- import { getClient } from './client.js';
8
-
9
- const SUPPORTED_CONTENT_TYPES = new Set([
10
- 'image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/svg+xml',
11
- 'image/tiff', 'image/bmp', 'image/avif',
12
- 'audio/mpeg', 'audio/mp4', 'audio/wav', 'audio/ogg', 'audio/webm', 'audio/flac',
13
- 'video/mp4', 'video/webm', 'video/ogg', 'video/quicktime',
14
- 'application/pdf',
15
- 'text/plain', 'text/html', 'text/css', 'text/csv',
16
- 'application/json', 'application/xml',
17
- ]);
18
-
19
- const EXTENSION_MAP: Record<string, string> = {
20
- '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.png': 'image/png',
21
- '.gif': 'image/gif', '.webp': 'image/webp', '.svg': 'image/svg+xml',
22
- '.tiff': 'image/tiff', '.tif': 'image/tiff', '.bmp': 'image/bmp',
23
- '.avif': 'image/avif',
24
- '.mp3': 'audio/mpeg', '.m4a': 'audio/mp4', '.wav': 'audio/wav',
25
- '.ogg': 'audio/ogg', '.flac': 'audio/flac', '.weba': 'audio/webm',
26
- '.mp4': 'video/mp4', '.webm': 'video/webm', '.ogv': 'video/ogg',
27
- '.mov': 'video/quicktime',
28
- '.pdf': 'application/pdf',
29
- '.txt': 'text/plain', '.html': 'text/html', '.htm': 'text/html',
30
- '.css': 'text/css', '.csv': 'text/csv',
31
- '.json': 'application/json', '.xml': 'application/xml',
32
- };
33
-
34
- const TRUSTED_DOMAINS = [
35
- 'amazonaws.com',
36
- 's3.amazonaws.com',
37
- 'storage.googleapis.com',
38
- 'blob.core.windows.net',
39
- 'r2.cloudflarestorage.com',
40
- ];
41
-
42
- export async function uploadAndAttachMedia(params: {
43
- filePath: string;
44
- traceId: string;
45
- field?: string;
46
- observationId?: string;
47
- contentType?: string;
48
- }): Promise<string> {
49
- const { filePath, traceId, observationId } = params;
50
- const field = params.field ?? 'input';
51
-
52
- // Detect content type
53
- const contentType = params.contentType ?? detectContentType(filePath);
54
- if (!SUPPORTED_CONTENT_TYPES.has(contentType)) {
55
- return JSON.stringify({ error: `Unsupported content type: ${contentType}` });
56
- }
57
-
58
- // Calculate file info
59
- const stat = statSync(filePath);
60
- const fileData = readFileSync(filePath);
61
- const sha256 = createHash('sha256').update(fileData).digest('base64');
62
-
63
- const client = getClient();
64
-
65
- // Step 1: Get presigned upload URL
66
- const uploadReqData: Record<string, unknown> = {
67
- traceId,
68
- contentType,
69
- contentLength: stat.size,
70
- sha256Hash: sha256,
71
- field,
72
- };
73
- if (observationId) uploadReqData.observationId = observationId;
74
-
75
- const uploadInfo = await client.request<Record<string, unknown>>(
76
- 'POST',
77
- '/api/public/media',
78
- uploadReqData,
79
- );
80
-
81
- const uploadUrl = uploadInfo.uploadUrl as string;
82
- const mediaId = uploadInfo.mediaId as string;
83
-
84
- if (!uploadUrl || !mediaId) {
85
- return JSON.stringify({ error: 'Failed to get upload URL', detail: uploadInfo });
86
- }
87
-
88
- // Step 2: Validate upload URL domain
89
- const url = new URL(uploadUrl);
90
- if (!isTrustedDomain(url.hostname)) {
91
- return JSON.stringify({
92
- error: `Security: Upload URL domain '${url.hostname}' is not trusted`,
93
- });
94
- }
95
-
96
- // Step 3: Upload to presigned URL
97
- const startTime = Date.now();
98
- const uploadResponse = await fetch(uploadUrl, {
99
- method: 'PUT',
100
- headers: {
101
- 'Content-Type': contentType,
102
- 'x-amz-checksum-sha256': sha256,
103
- },
104
- body: fileData,
105
- });
106
- const uploadTimeMs = Date.now() - startTime;
107
-
108
- // Step 4: Patch status
109
- await client.request('PATCH', `/api/public/media/${mediaId}`, {
110
- uploadHttpStatus: uploadResponse.status,
111
- uploadTimeMs,
112
- uploadedAt: new Date().toISOString(),
113
- });
114
-
115
- if (!uploadResponse.ok) {
116
- return JSON.stringify({
117
- error: `Upload failed: ${uploadResponse.status}`,
118
- mediaId,
119
- });
120
- }
121
-
122
- // Build media token
123
- const token = `@@@langfuseMedia:type=${contentType}|id=${mediaId}|source=file@@@`;
124
-
125
- return JSON.stringify({
126
- success: true,
127
- mediaId,
128
- fileName: basename(filePath),
129
- contentType,
130
- contentLength: stat.size,
131
- traceId,
132
- field,
133
- observationId: observationId ?? null,
134
- uploadTimeMs,
135
- mediaToken: token,
136
- }, null, 2);
137
- }
138
-
139
- export async function getMedia(mediaId: string): Promise<string> {
140
- const client = getClient();
141
- const result = await client.request<unknown>('GET', `/api/public/media/${mediaId}`);
142
- return JSON.stringify(result, null, 2);
143
- }
144
-
145
- export function detectContentType(filePath: string): string {
146
- const ext = extname(filePath).toLowerCase();
147
- return EXTENSION_MAP[ext] ?? 'application/octet-stream';
148
- }
149
-
150
- export function formatMediaDisplay(json: unknown): string {
151
- try {
152
- const media = (typeof json === 'string' ? JSON.parse(json) : json) as Record<string, unknown>;
153
- if ('error' in media) return `Error: ${media.error}`;
154
-
155
- const contentType = String(media.contentType ?? 'unknown');
156
- let glyph = '📎';
157
- if (contentType.startsWith('image/')) glyph = '🖼️';
158
- else if (contentType.startsWith('video/')) glyph = '🎥';
159
- else if (contentType.startsWith('audio/')) glyph = '🎵';
160
- else if (contentType === 'application/pdf') glyph = '📄';
161
-
162
- const lines: string[] = [];
163
- lines.push(`${glyph} Media: ${media.fileName ?? 'Unnamed'}`);
164
- lines.push(`├── 🆔 ID: ${media.id ?? media.mediaId ?? 'N/A'}`);
165
- lines.push(`├── 📝 Content Type: ${contentType}`);
166
- lines.push(`├── 📏 Size: ${media.contentLength ?? 0} bytes`);
167
- lines.push(`├── 🔗 Trace ID: ${media.traceId ?? 'N/A'}`);
168
-
169
- if (media.observationId) lines.push(`├── 👁️ Observation ID: ${media.observationId}`);
170
- if (media.field) lines.push(`├── 🏷️ Field: ${media.field}`);
171
- if (media.uploadedAt) lines.push(`├── ⏰ Uploaded: ${String(media.uploadedAt).slice(0, 19)}`);
172
-
173
- // Fix last line to └──
174
- if (lines.length > 1) {
175
- const last = lines[lines.length - 1];
176
- lines[lines.length - 1] = last.replace('├── ', '└── ');
177
- }
178
-
179
- return lines.join('\n');
180
- } catch (e) {
181
- return `Error formatting media: ${e}`;
182
- }
183
- }
184
-
185
- // ─── Helpers ────────────────────────────────────────────────────────
186
-
187
- function isTrustedDomain(hostname: string): boolean {
188
- const lower = hostname.toLowerCase();
189
- for (const domain of TRUSTED_DOMAINS) {
190
- if (lower === domain || lower.endsWith(`.${domain}`)) return true;
191
- }
192
- return false;
193
- }
@@ -1,155 +0,0 @@
1
- // coaiajs/src/langfuse/observations.ts — Observation operations
2
- // Port of cofuse.py observation functions
3
-
4
- import { v4 as uuidv4 } from 'uuid';
5
- import { getClient, nowISO } from './client.js';
6
- import type { IngestionEvent } from './client.js';
7
-
8
- export async function addObservation(params: {
9
- observationId?: string;
10
- traceId: string;
11
- type?: string;
12
- name: string;
13
- parentId?: string;
14
- parentObservationId?: string;
15
- input?: string;
16
- inputData?: unknown;
17
- output?: string;
18
- outputData?: unknown;
19
- metadata?: string | unknown;
20
- startTime?: string;
21
- endTime?: string;
22
- level?: string;
23
- model?: string;
24
- usage?: unknown;
25
- }): Promise<string> {
26
- const client = getClient();
27
- const now = nowISO();
28
- const obsId = params.observationId ?? uuidv4();
29
-
30
- const body: Record<string, unknown> = {
31
- id: obsId,
32
- traceId: params.traceId,
33
- type: params.type ?? 'EVENT',
34
- startTime: params.startTime ?? now,
35
- level: params.level ?? 'DEFAULT',
36
- };
37
-
38
- if (params.name) body.name = params.name;
39
- if (params.inputData ?? params.input) body.input = parseJsonOption(params.inputData ?? params.input);
40
- if (params.outputData ?? params.output) body.output = parseJsonOption(params.outputData ?? params.output);
41
- if (params.metadata) body.metadata = parseJsonOption(params.metadata);
42
- if (params.parentId ?? params.parentObservationId) body.parentObservationId = params.parentId ?? params.parentObservationId;
43
- if (params.endTime) body.endTime = params.endTime;
44
- if (params.model) body.model = params.model;
45
- if (params.usage) body.usage = params.usage;
46
-
47
- const event: IngestionEvent = {
48
- id: `${obsId}-event`,
49
- timestamp: now,
50
- type: 'observation-create',
51
- body,
52
- };
53
-
54
- await client.ingest([event]);
55
-
56
- return JSON.stringify({
57
- success: true,
58
- observationId: obsId,
59
- traceId: params.traceId,
60
- name: params.name,
61
- type: params.type ?? 'EVENT',
62
- }, null, 2);
63
- }
64
-
65
- export async function getObservation(observationId: string): Promise<string> {
66
- const client = getClient();
67
- const result = await client.request<unknown>('GET', `/api/public/observations/${observationId}`);
68
- return JSON.stringify(result, null, 2);
69
- }
70
-
71
- function parseJsonOption(value: unknown): unknown {
72
- if (typeof value !== 'string') return value;
73
- try {
74
- return JSON.parse(value) as unknown;
75
- } catch {
76
- return value;
77
- }
78
- }
79
-
80
- export async function addObservations(params: { traceId?: string; observations?: Array<Record<string, unknown>> }): Promise<string> {
81
- const observations = params.observations ?? [];
82
- const results: unknown[] = [];
83
- for (const observation of observations) {
84
- const traceId = String(observation.traceId ?? params.traceId ?? '');
85
- const name = String(observation.name ?? 'Observation');
86
- if (!traceId) throw new Error('traceId is required for every observation');
87
- results.push(JSON.parse(await addObservation({ ...observation, traceId, name })) as unknown);
88
- }
89
- return JSON.stringify({ success: true, count: results.length, observations: results }, null, 2);
90
- }
91
-
92
- // ─── Formatters ─────────────────────────────────────────────────────
93
-
94
- const TYPE_GLYPHS: Record<string, string> = {
95
- SPAN: '🔗',
96
- GENERATION: '🤖',
97
- EVENT: '⚡',
98
- DEFAULT: '📦',
99
- };
100
-
101
- export function formatObservationDisplay(json: unknown): string {
102
- try {
103
- const obs = (typeof json === 'string' ? JSON.parse(json) : json) as Record<string, unknown>;
104
- if ('error' in obs) return `Error: ${obs.error}`;
105
-
106
- const obsType = String(obs.type ?? 'UNKNOWN').toUpperCase();
107
- const glyph = TYPE_GLYPHS[obsType] ?? TYPE_GLYPHS.DEFAULT;
108
-
109
- const lines: string[] = [];
110
- lines.push(`${glyph} Observation: ${obs.name ?? 'Unnamed'}`);
111
- lines.push(`├── 🆔 ID: ${obs.id ?? 'N/A'}`);
112
- lines.push(`├── 📋 Type: ${obsType}`);
113
- lines.push(`├── 🔗 Trace: ${obs.traceId ?? 'N/A'}`);
114
- lines.push(`├── ⏰ Start: ${String(obs.startTime ?? 'N/A').slice(0, 19)}`);
115
-
116
- if (obs.endTime) lines.push(`├── ⏰ End: ${String(obs.endTime).slice(0, 19)}`);
117
- if (obs.level && obs.level !== 'DEFAULT') lines.push(`├── 📊 Level: ${obs.level}`);
118
- if (obs.model) lines.push(`├── 🤖 Model: ${obs.model}`);
119
-
120
- if (obs.parentObservationId) {
121
- lines.push(`├── 👆 Parent: ${obs.parentObservationId}`);
122
- }
123
-
124
- if (obs.input) {
125
- const txt = String(obs.input).replace(/\n/g, ' ');
126
- lines.push(`├── 📥 Input: ${txt.length > 100 ? txt.slice(0, 100) + '...' : txt}`);
127
- }
128
- if (obs.output) {
129
- const txt = String(obs.output).replace(/\n/g, ' ');
130
- lines.push(`├── 📤 Output: ${txt.length > 100 ? txt.slice(0, 100) + '...' : txt}`);
131
- }
132
-
133
- const metadata = obs.metadata as Record<string, unknown> | undefined;
134
- if (metadata && typeof metadata === 'object' && Object.keys(metadata).length) {
135
- lines.push(`└── 📋 Metadata:`);
136
- const entries = Object.entries(metadata);
137
- entries.forEach(([k, v], i) => {
138
- const pre = i === entries.length - 1 ? '└── ' : '├── ';
139
- lines.push(` ${pre}${k}: ${JSON.stringify(v)}`);
140
- });
141
- } else {
142
- // Fix last ├── to └──
143
- if (lines.length > 0) {
144
- const last = lines[lines.length - 1];
145
- if (last.startsWith('├── ')) {
146
- lines[lines.length - 1] = last.replace('├── ', '└── ');
147
- }
148
- }
149
- }
150
-
151
- return lines.join('\n');
152
- } catch (e) {
153
- return `Error formatting observation: ${e}`;
154
- }
155
- }