teckel-ai 0.5.1 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -5
- package/dist/index.d.mts +410 -105
- package/dist/index.d.ts +410 -105
- package/dist/index.js +5 -2
- package/dist/index.mjs +5 -2
- package/dist/otel/index.d.mts +212 -0
- package/dist/otel/index.d.ts +212 -0
- package/dist/otel/index.js +5 -0
- package/dist/otel/index.mjs +5 -0
- package/dist/types-c7iVpo4d.d.mts +177 -0
- package/dist/types-c7iVpo4d.d.ts +177 -0
- package/package.json +28 -4
package/dist/index.js
CHANGED
|
@@ -1,2 +1,5 @@
|
|
|
1
|
-
'use strict';var zod=require('zod');var c={METADATA_MAX_BYTES:1e4,TRACE_MAX_BYTES:3e6,MAX_DOCUMENTS:15},u=zod.z.object({id:zod.z.string().min(1,"id is required").max(500),name:zod.z.string().min(1,"name is required").max(500),text:zod.z.string().min(1,"text is required").max(5e4),lastUpdated:zod.z.string().datetime({offset:true}).optional(),url:zod.z.string().max(2e3).optional(),source:zod.z.string().max(100).optional(),fileFormat:zod.z.string().max(100).optional(),similarity:zod.z.number().min(0).max(1).optional(),rank:zod.z.number().int().nonnegative().optional(),ownerEmail:zod.z.string().email().max(254).optional()}),g=zod.z.object({prompt:zod.z.number().int().nonnegative(),completion:zod.z.number().int().nonnegative(),total:zod.z.number().int().nonnegative()}),m=zod.z.object({query:zod.z.string().min(1,"query is required").max(1e4,"query too long (max 10,000 chars)"),response:zod.z.string().min(1,"response is required").max(5e4,"response too long (max 50,000 chars)"),model:zod.z.string().max(100).optional(),latencyMs:zod.z.number().int().nonnegative().max(6e5).optional(),tokens:g.optional(),documents:zod.z.array(u).max(c.MAX_DOCUMENTS,`Too many documents (max ${c.MAX_DOCUMENTS})`).optional(),sessionId:zod.z.string().min(1).max(200,"sessionId must be 200 chars or less").optional(),userId:zod.z.string().min(1).max(255).optional(),traceId:zod.z.string().uuid("traceId must be a valid UUID").optional(),systemPrompt:zod.z.string().max(5e4,"systemPrompt too long (max 50,000 chars)").optional(),metadata:zod.z.record(zod.z.string(),zod.z.unknown()).optional().refine(r=>!r||JSON.stringify(r).length<=c.METADATA_MAX_BYTES,`metadata exceeds ${c.METADATA_MAX_BYTES/1e3}KB limit`)}).superRefine((r,i)=>{JSON.stringify(r).length>c.TRACE_MAX_BYTES&&i.addIssue({code:zod.z.ZodIssueCode.custom,message:`trace payload exceeds ${c.TRACE_MAX_BYTES/1e6}MB limit`,path:[]});}),d=zod.z.object({traceId:zod.z.string().uuid("traceId must be a valid UUID").optional(),sessionId:zod.z.string().min(1).max(200,"sessionId must be 200 chars or less").optional(),type:zod.z.enum(["thumbs_up","thumbs_down","flag","rating"]),value:zod.z.string().min(1).max(10).optional(),comment:zod.z.string().max(2e3).optional()}).refine(r=>r.traceId||r.sessionId,{message:"Either traceId or sessionId is required"}),l=zod.z.object({apiKey:zod.z.string().min(1,"apiKey is required"),endpoint:zod.z.string().url().optional(),debug:zod.z.boolean().optional(),timeoutMs:zod.z.number().int().positive().max(6e4).optional()});var p=class{constructor(i){this.sendQueue=Promise.resolve();let e=l.parse(i);if(!e.apiKey.startsWith("tk_live_")){let o=e.apiKey.startsWith("tk_")?"tk_***":"invalid prefix";console.error(`[Teckel] Invalid API key format. Expected "tk_live_***", got "${o}". Get your key at https://app.teckel.ai/settings/api-keys`);}this.apiKey=e.apiKey,this.endpoint=e.endpoint||"https://app.teckel.ai/api",this.debug=e.debug||false,this.timeoutMs=e.timeoutMs??5e3,this.debug&&console.log("[Teckel] Initialized",{endpoint:this.endpoint,timeoutMs:this.timeoutMs});}trace(i){try{let e=m.parse(i);this.debug&&console.log("[Teckel] Queueing trace:",{traceId:e.traceId,sessionId:e.sessionId,queryLength:e.query.length,documentCount:e.documents?.length||0}),this.enqueue(()=>this.send("/sdk/traces",e),"trace");}catch(e){this.logValidationError("Trace",e);}}feedback(i){try{let e=d.parse(i);this.debug&&console.log("[Teckel] Sending feedback:",{type:e.type,traceId:e.traceId}),this.enqueue(()=>this.send("/sdk/feedback",e),"feedback");}catch(e){this.logValidationError("Feedback",e);}}async flush(i){let e=i??this.timeoutMs,o=this.sendQueue.catch(()=>{}),n;try{await Promise.race([o,new Promise((a,s)=>{n=setTimeout(()=>s(new Error("Flush timeout")),e);})]);}catch(a){throw this.debug&&console.warn("[Teckel] Flush incomplete:",a.message),a}finally{n&&clearTimeout(n);}}async send(i,e){let o=`${this.endpoint}${i}`,n=await this.fetchWithRetry(o,{method:"POST",headers:{Authorization:`Bearer ${this.apiKey}`,"Content-Type":"application/json"},keepalive:true,signal:AbortSignal.timeout?.(this.timeoutMs),body:JSON.stringify(e)});if(!n.ok)throw this.logHttpError(i,n.status),new Error(`HTTP ${n.status}`);return n.json()}async fetchWithRetry(i,e){let n=()=>new Promise(a=>setTimeout(a,250+Math.random()*100));for(let a=1;a<=2;a++)try{let s=await fetch(i,e);if(s.ok||a===2)return s;if(s.status===429||s.status>=500){this.debug&&console.warn("[Teckel] Retrying",{status:s.status,attempt:a}),await n();continue}return s}catch(s){if(a===2)throw s;this.debug&&console.warn("[Teckel] Retry after error",{attempt:a}),await n();}throw new Error("Unreachable")}enqueue(i,e){this.sendQueue=this.sendQueue.then(()=>i()).catch(o=>{if(!(o instanceof Error&&o.message.startsWith("HTTP "))){let n=o instanceof Error?o.message:String(o);n.includes("timeout")||n.includes("abort")?console.warn(`[Teckel] ${e} timed out. Consider increasing timeoutMs.`):console.warn(`[Teckel] ${e} failed: ${n}`);}});}logValidationError(i,e){if(e instanceof zod.ZodError){let o=e.errors.map(n=>n.path.length?`${n.path.join(".")}: ${n.message}`:n.message).join("; ");console.error(`[Teckel] ${i} dropped - validation failed: ${o}`);}else this.debug&&console.error(`[Teckel] ${i} dropped:`,e);}logHttpError(i,e){let o=i.includes("trace")?"Trace":"Feedback",a={401:`${o} failed - invalid API key. Check https://app.teckel.ai/settings/api-keys`,400:`${o} failed - invalid request. Enable debug mode for details.`,404:`${o} failed - endpoint not found. Check your configuration.`,429:`${o} dropped - rate limit exceeded.`}[e]||(e>=500?`${o} dropped - server error (${e})`:`${o} failed - HTTP ${e}`);e>=500||e===429?console.warn(`[Teckel] ${a}`):console.error(`[Teckel] ${a}`);}};
|
|
2
|
-
|
|
1
|
+
'use strict';var zod=require('zod');var c={METADATA_MAX_BYTES:1e4,TRACE_MAX_BYTES:3e6,MAX_DOCUMENTS:15},f=zod.z.object({id:zod.z.string().min(1,"id is required").max(500),name:zod.z.string().min(1,"name is required").max(500),text:zod.z.string().min(1,"text is required").max(5e4),lastUpdated:zod.z.string().datetime({offset:true}).optional(),url:zod.z.string().max(2e3).optional(),source:zod.z.string().max(100).optional(),fileFormat:zod.z.string().max(100).optional(),similarity:zod.z.number().min(0).max(1).optional(),rank:zod.z.number().int().nonnegative().optional(),ownerEmail:zod.z.string().email().max(254).optional()}),b=zod.z.object({prompt:zod.z.number().int().nonnegative(),completion:zod.z.number().int().nonnegative(),total:zod.z.number().int().nonnegative()}),l={MAX_SPANS:100,JSONB_MAX_BYTES:512e3},T=zod.z.enum(["llm_call","tool_call","retrieval","agent","guardrail","custom"]),S=zod.z.enum(["running","completed","error"]),y=zod.z.object({spanId:zod.z.string().min(1).max(64).optional(),parentSpanId:zod.z.string().min(1).max(64).optional().nullable(),name:zod.z.string().min(1,"name is required").max(200),type:T.default("custom"),startedAt:zod.z.string().datetime({offset:true}),endedAt:zod.z.string().datetime({offset:true}).optional().nullable(),durationMs:zod.z.number().int().min(0).max(36e5).optional(),status:S.default("completed"),statusMessage:zod.z.string().max(2e3).optional().nullable(),toolName:zod.z.string().max(200).optional().nullable(),toolArguments:zod.z.record(zod.z.unknown()).optional().nullable().refine(s=>!s||JSON.stringify(s).length<=l.JSONB_MAX_BYTES,`toolArguments exceeds ${l.JSONB_MAX_BYTES/1e3}KB limit`),toolResult:zod.z.record(zod.z.unknown()).optional().nullable().refine(s=>!s||JSON.stringify(s).length<=l.JSONB_MAX_BYTES,`toolResult exceeds ${l.JSONB_MAX_BYTES/1e3}KB limit`),model:zod.z.string().max(100).optional().nullable(),promptTokens:zod.z.number().int().min(0).optional().nullable(),completionTokens:zod.z.number().int().min(0).optional().nullable(),costUsd:zod.z.number().min(0).optional().nullable(),input:zod.z.record(zod.z.unknown()).optional().nullable().refine(s=>!s||JSON.stringify(s).length<=l.JSONB_MAX_BYTES,`input exceeds ${l.JSONB_MAX_BYTES/1e3}KB limit`),output:zod.z.record(zod.z.unknown()).optional().nullable().refine(s=>!s||JSON.stringify(s).length<=l.JSONB_MAX_BYTES,`output exceeds ${l.JSONB_MAX_BYTES/1e3}KB limit`),metadata:zod.z.record(zod.z.unknown()).optional().nullable().refine(s=>!s||JSON.stringify(s).length<=l.JSONB_MAX_BYTES,`metadata exceeds ${l.JSONB_MAX_BYTES/1e3}KB limit`)}).refine(s=>s.type!=="tool_call"||s.toolName,{message:"toolName is required for tool_call spans",path:["toolName"]}),u=zod.z.object({query:zod.z.string().min(1,"query is required").max(1e4,"query too long (max 10,000 chars)"),response:zod.z.string().min(1,"response is required").max(5e4,"response too long (max 50,000 chars)"),agentName:zod.z.string().min(1,"agentName cannot be empty if provided").max(200,"agentName must be 200 chars or less").optional().nullable(),model:zod.z.string().max(100).optional(),latencyMs:zod.z.number().int().nonnegative().max(6e5).optional(),tokens:b.optional(),costUsd:zod.z.number().min(0).optional(),documents:zod.z.array(f).max(c.MAX_DOCUMENTS,`Too many documents (max ${c.MAX_DOCUMENTS})`).optional(),spans:zod.z.array(y).max(l.MAX_SPANS,`Too many spans (max ${l.MAX_SPANS})`).optional(),sessionId:zod.z.string().min(1).max(200,"sessionId must be 200 chars or less").optional(),userId:zod.z.string().min(1).max(255).optional(),traceId:zod.z.string().uuid("traceId must be a valid UUID").optional(),systemPrompt:zod.z.string().max(5e4,"systemPrompt too long (max 50,000 chars)").optional(),metadata:zod.z.record(zod.z.string(),zod.z.unknown()).optional().refine(s=>!s||JSON.stringify(s).length<=c.METADATA_MAX_BYTES,`metadata exceeds ${c.METADATA_MAX_BYTES/1e3}KB limit`)}).superRefine((s,n)=>{JSON.stringify(s).length>c.TRACE_MAX_BYTES&&n.addIssue({code:zod.z.ZodIssueCode.custom,message:`trace payload exceeds ${c.TRACE_MAX_BYTES/1e6}MB limit`,path:[]});}),m=zod.z.object({traceId:zod.z.string().uuid("traceId must be a valid UUID").optional(),sessionId:zod.z.string().min(1).max(200,"sessionId must be 200 chars or less").optional(),type:zod.z.enum(["thumbs_up","thumbs_down","flag","rating"]),value:zod.z.string().min(1).max(10).optional(),comment:zod.z.string().max(2e3).optional()}).refine(s=>s.traceId||s.sessionId,{message:"Either traceId or sessionId is required"}),k=zod.z.object({maxSize:zod.z.number().int().min(1).max(500).optional(),maxBytes:zod.z.number().int().min(1e3).max(1e7).optional(),flushIntervalMs:zod.z.number().int().min(10).max(1e4).optional()}),d=zod.z.object({apiKey:zod.z.string().min(1,"apiKey is required"),endpoint:zod.z.string().url().optional(),debug:zod.z.boolean().optional(),timeoutMs:zod.z.number().int().positive().max(6e4).optional(),batch:k.optional()});var v="0.7.1";function _(s){let n=0,t=0;for(let i of s)i.promptTokens!=null&&(n+=i.promptTokens),i.completionTokens!=null&&(t+=i.completionTokens);return {prompt:n,completion:t,total:n+t}}var h={maxSize:100,maxBytes:5e6,flushIntervalMs:100},p=class{constructor(n){this.sendQueue=Promise.resolve();this.batchBuffer=[];this.batchByteCount=0;this.batchFlushTimer=null;this.isDestroyed=false;let t=d.parse(n);if(!t.apiKey.startsWith("tk_live_")){let i=t.apiKey.startsWith("tk_")?"tk_***":"invalid prefix";console.error(`[Teckel] Invalid API key format. Expected "tk_live_***", got "${i}". Get your key at https://app.teckel.ai/settings/api-keys`);}this.apiKey=t.apiKey,this.endpoint=t.endpoint||"https://app.teckel.ai/api",this.debug=t.debug||false,this.timeoutMs=t.timeoutMs??5e3,this.batchConfig={maxSize:t.batch?.maxSize??h.maxSize,maxBytes:t.batch?.maxBytes??h.maxBytes,flushIntervalMs:t.batch?.flushIntervalMs??h.flushIntervalMs},this.startFlushTimer(),this.debug&&console.log("[Teckel] Initialized",{endpoint:this.endpoint,timeoutMs:this.timeoutMs,batchMaxSize:this.batchConfig.maxSize,batchFlushIntervalMs:this.batchConfig.flushIntervalMs});}trace(n){if(this.isDestroyed){this.debug&&console.warn("[Teckel] Trace dropped - tracer destroyed");return}try{let t=n;if(n.spans&&n.spans.length>0&&!n.tokens){let a=_(n.spans);a.total>0&&(t={...n,tokens:a},this.debug&&console.log("[Teckel] Auto-aggregated tokens from spans:",a));}let i=u.parse(t);this.debug&&console.log("[Teckel] Queueing trace:",{traceId:i.traceId,sessionId:i.sessionId,queryLength:i.query.length,documentCount:i.documents?.length||0});let o=JSON.stringify(i).length;this.batchBuffer.push(i),this.batchByteCount+=o,(this.batchBuffer.length>=this.batchConfig.maxSize||this.batchByteCount>=this.batchConfig.maxBytes)&&this.flushBatch();}catch(t){this.logValidationError("Trace",t,n);}}feedback(n){if(this.isDestroyed){this.debug&&console.warn("[Teckel] Feedback dropped - tracer destroyed");return}try{let t=m.parse(n);this.debug&&console.log("[Teckel] Sending feedback:",{type:t.type,traceId:t.traceId}),this.enqueue(()=>this.send("/sdk/feedback",t),"feedback");}catch(t){this.logValidationError("Feedback",t);}}async flush(n){let t=n??this.timeoutMs;this.batchBuffer.length>0&&this.flushBatch();let i=this.sendQueue.catch(()=>{}),o;try{await Promise.race([i,new Promise((a,r)=>{o=setTimeout(()=>r(new Error("Flush timeout")),t);})]);}catch(a){throw this.debug&&console.warn("[Teckel] Flush incomplete:",a.message),a}finally{o&&clearTimeout(o);}}async destroy(){if(!this.isDestroyed){this.isDestroyed=true,this.stopFlushTimer();try{await this.flush(this.timeoutMs*2);}catch{this.debug&&console.warn("[Teckel] Destroy: some traces may not have been sent");}}}get pendingTraceCount(){return this.batchBuffer.length}startFlushTimer(){this.batchFlushTimer||(this.batchFlushTimer=setInterval(()=>{this.batchBuffer.length>0&&this.flushBatch();},this.batchConfig.flushIntervalMs),typeof this.batchFlushTimer=="object"&&"unref"in this.batchFlushTimer&&this.batchFlushTimer.unref());}stopFlushTimer(){this.batchFlushTimer&&(clearInterval(this.batchFlushTimer),this.batchFlushTimer=null);}flushBatch(){if(this.batchBuffer.length===0)return;let n=this.batchBuffer;this.batchBuffer=[],this.batchByteCount=0,this.debug&&console.log("[Teckel] Flushing batch:",{traceCount:n.length}),this.enqueue(()=>this.sendBatch(n),"batch");}async sendBatch(n){let t=`${this.endpoint}/sdk/traces`,i=await this.fetchWithRetry(t,{method:"POST",headers:{Authorization:`Bearer ${this.apiKey}`,"Content-Type":"application/json"},keepalive:true,signal:AbortSignal.timeout?.(this.timeoutMs),body:JSON.stringify({traces:n})});if(!i.ok)throw this.logHttpError("/sdk/traces",i.status),new Error(`HTTP ${i.status}`);let o=await i.json();return this.debug&&console.log("[Teckel] Batch sent:",{requested:n.length,succeeded:o.successCount??n.length}),o}async send(n,t){let i=`${this.endpoint}${n}`,o=await this.fetchWithRetry(i,{method:"POST",headers:{Authorization:`Bearer ${this.apiKey}`,"Content-Type":"application/json"},keepalive:true,signal:AbortSignal.timeout?.(this.timeoutMs),body:JSON.stringify(t)});if(!o.ok)throw this.logHttpError(n,o.status),new Error(`HTTP ${o.status}`);return o.json()}async fetchWithRetry(n,t){let o=()=>new Promise(a=>setTimeout(a,250+Math.random()*100));for(let a=1;a<=2;a++)try{let r=await fetch(n,t);if(r.ok||a===2)return r;if(r.status===429||r.status>=500){this.debug&&console.warn("[Teckel] Retrying",{status:r.status,attempt:a}),await o();continue}return r}catch(r){if(a===2)throw r;this.debug&&console.warn("[Teckel] Retry after error",{attempt:a}),await o();}throw new Error("Unreachable")}enqueue(n,t){this.sendQueue=this.sendQueue.then(()=>n()).catch(i=>{if(!(i instanceof Error&&i.message.startsWith("HTTP "))){let o=i instanceof Error?i.message:String(i);o.includes("timeout")||o.includes("abort")?console.warn(`[Teckel] ${t} timed out. Consider increasing timeoutMs.`):console.warn(`[Teckel] ${t} failed: ${o}`);}});}logValidationError(n,t,i){if(t instanceof zod.ZodError){let o=t.errors.map(a=>{let r=a.path.length?a.path.join("."):"(root)",g=this.getValidationHint(r,a.message);return g?` - ${r}: ${a.message}
|
|
2
|
+
Hint: ${g}`:` - ${r}: ${a.message}`});console.error(`[Teckel] ${n} dropped - validation failed:
|
|
3
|
+
${o.join(`
|
|
4
|
+
`)}`),this.reportDropped({errorType:"validation_failed",errorDetails:t.errors.map(a=>`${a.path.join(".")}: ${a.message}`).join("; ").slice(0,1e3),fieldPath:t.errors[0]?.path.join("."),agentName:i?.agentName??void 0,sdkVersion:v,rawErrors:t.errors.map(a=>({path:a.path,message:a.message,code:a.code}))});}else this.debug&&console.error(`[Teckel] ${n} dropped:`,t);}getValidationHint(n,t){let i=t.toLowerCase();return n.includes("toolResult")&&i.includes("object")?"Wrap array results in an object: { items: [...] }":n.includes("toolArguments")&&i.includes("object")?"Wrap array arguments in an object: { args: [...] }":i.includes("exceeds")||i.includes("limit")?"Reduce payload size or truncate large fields":i.includes("uuid")?"Use crypto.randomUUID() or uuid package to generate valid UUIDs":n.includes("startedAt")||n.includes("endedAt")?"Use ISO 8601 format: new Date().toISOString()":i.includes("required")?`Ensure ${n.split(".").pop()} is provided and not empty`:null}reportDropped(n){this.endpoint&&fetch(`${this.endpoint}/sdk/dropped`,{method:"POST",headers:{Authorization:`Bearer ${this.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify(n)}).catch(()=>{});}logHttpError(n,t){let i=n.includes("batch")?"Batch":n.includes("trace")?"Trace":"Feedback",a={401:`${i} failed - invalid API key. Check https://app.teckel.ai/settings/api-keys`,400:`${i} failed - invalid request. Enable debug mode for details.`,404:`${i} failed - endpoint not found. Check your configuration.`,429:`${i} dropped - rate limit exceeded.`}[t]||(t>=500?`${i} dropped - server error (${t})`:`${i} failed - HTTP ${t}`);t>=500||t===429?console.warn(`[Teckel] ${a}`):console.error(`[Teckel] ${a}`);}};
|
|
5
|
+
exports.BatchConfigSchema=k;exports.DocumentSchema=f;exports.FeedbackDataSchema=m;exports.SPAN_SIZE_LIMITS=l;exports.SpanDataSchema=y;exports.SpanStatusSchema=S;exports.SpanTypeSchema=T;exports.TRACE_SIZE_LIMITS=c;exports.TeckelConfigSchema=d;exports.TeckelTracer=p;exports.TokenUsageSchema=b;exports.TraceDataSchema=u;
|
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1,5 @@
|
|
|
1
|
-
import {z,ZodError}from'zod';var c={METADATA_MAX_BYTES:1e4,TRACE_MAX_BYTES:3e6,MAX_DOCUMENTS:15},
|
|
2
|
-
|
|
1
|
+
import {z,ZodError}from'zod';var c={METADATA_MAX_BYTES:1e4,TRACE_MAX_BYTES:3e6,MAX_DOCUMENTS:15},f=z.object({id:z.string().min(1,"id is required").max(500),name:z.string().min(1,"name is required").max(500),text:z.string().min(1,"text is required").max(5e4),lastUpdated:z.string().datetime({offset:true}).optional(),url:z.string().max(2e3).optional(),source:z.string().max(100).optional(),fileFormat:z.string().max(100).optional(),similarity:z.number().min(0).max(1).optional(),rank:z.number().int().nonnegative().optional(),ownerEmail:z.string().email().max(254).optional()}),b=z.object({prompt:z.number().int().nonnegative(),completion:z.number().int().nonnegative(),total:z.number().int().nonnegative()}),l={MAX_SPANS:100,JSONB_MAX_BYTES:512e3},T=z.enum(["llm_call","tool_call","retrieval","agent","guardrail","custom"]),S=z.enum(["running","completed","error"]),y=z.object({spanId:z.string().min(1).max(64).optional(),parentSpanId:z.string().min(1).max(64).optional().nullable(),name:z.string().min(1,"name is required").max(200),type:T.default("custom"),startedAt:z.string().datetime({offset:true}),endedAt:z.string().datetime({offset:true}).optional().nullable(),durationMs:z.number().int().min(0).max(36e5).optional(),status:S.default("completed"),statusMessage:z.string().max(2e3).optional().nullable(),toolName:z.string().max(200).optional().nullable(),toolArguments:z.record(z.unknown()).optional().nullable().refine(s=>!s||JSON.stringify(s).length<=l.JSONB_MAX_BYTES,`toolArguments exceeds ${l.JSONB_MAX_BYTES/1e3}KB limit`),toolResult:z.record(z.unknown()).optional().nullable().refine(s=>!s||JSON.stringify(s).length<=l.JSONB_MAX_BYTES,`toolResult exceeds ${l.JSONB_MAX_BYTES/1e3}KB limit`),model:z.string().max(100).optional().nullable(),promptTokens:z.number().int().min(0).optional().nullable(),completionTokens:z.number().int().min(0).optional().nullable(),costUsd:z.number().min(0).optional().nullable(),input:z.record(z.unknown()).optional().nullable().refine(s=>!s||JSON.stringify(s).length<=l.JSONB_MAX_BYTES,`input exceeds ${l.JSONB_MAX_BYTES/1e3}KB limit`),output:z.record(z.unknown()).optional().nullable().refine(s=>!s||JSON.stringify(s).length<=l.JSONB_MAX_BYTES,`output exceeds ${l.JSONB_MAX_BYTES/1e3}KB limit`),metadata:z.record(z.unknown()).optional().nullable().refine(s=>!s||JSON.stringify(s).length<=l.JSONB_MAX_BYTES,`metadata exceeds ${l.JSONB_MAX_BYTES/1e3}KB limit`)}).refine(s=>s.type!=="tool_call"||s.toolName,{message:"toolName is required for tool_call spans",path:["toolName"]}),u=z.object({query:z.string().min(1,"query is required").max(1e4,"query too long (max 10,000 chars)"),response:z.string().min(1,"response is required").max(5e4,"response too long (max 50,000 chars)"),agentName:z.string().min(1,"agentName cannot be empty if provided").max(200,"agentName must be 200 chars or less").optional().nullable(),model:z.string().max(100).optional(),latencyMs:z.number().int().nonnegative().max(6e5).optional(),tokens:b.optional(),costUsd:z.number().min(0).optional(),documents:z.array(f).max(c.MAX_DOCUMENTS,`Too many documents (max ${c.MAX_DOCUMENTS})`).optional(),spans:z.array(y).max(l.MAX_SPANS,`Too many spans (max ${l.MAX_SPANS})`).optional(),sessionId:z.string().min(1).max(200,"sessionId must be 200 chars or less").optional(),userId:z.string().min(1).max(255).optional(),traceId:z.string().uuid("traceId must be a valid UUID").optional(),systemPrompt:z.string().max(5e4,"systemPrompt too long (max 50,000 chars)").optional(),metadata:z.record(z.string(),z.unknown()).optional().refine(s=>!s||JSON.stringify(s).length<=c.METADATA_MAX_BYTES,`metadata exceeds ${c.METADATA_MAX_BYTES/1e3}KB limit`)}).superRefine((s,n)=>{JSON.stringify(s).length>c.TRACE_MAX_BYTES&&n.addIssue({code:z.ZodIssueCode.custom,message:`trace payload exceeds ${c.TRACE_MAX_BYTES/1e6}MB limit`,path:[]});}),m=z.object({traceId:z.string().uuid("traceId must be a valid UUID").optional(),sessionId:z.string().min(1).max(200,"sessionId must be 200 chars or less").optional(),type:z.enum(["thumbs_up","thumbs_down","flag","rating"]),value:z.string().min(1).max(10).optional(),comment:z.string().max(2e3).optional()}).refine(s=>s.traceId||s.sessionId,{message:"Either traceId or sessionId is required"}),k=z.object({maxSize:z.number().int().min(1).max(500).optional(),maxBytes:z.number().int().min(1e3).max(1e7).optional(),flushIntervalMs:z.number().int().min(10).max(1e4).optional()}),d=z.object({apiKey:z.string().min(1,"apiKey is required"),endpoint:z.string().url().optional(),debug:z.boolean().optional(),timeoutMs:z.number().int().positive().max(6e4).optional(),batch:k.optional()});var v="0.7.1";function _(s){let n=0,t=0;for(let i of s)i.promptTokens!=null&&(n+=i.promptTokens),i.completionTokens!=null&&(t+=i.completionTokens);return {prompt:n,completion:t,total:n+t}}var h={maxSize:100,maxBytes:5e6,flushIntervalMs:100},p=class{constructor(n){this.sendQueue=Promise.resolve();this.batchBuffer=[];this.batchByteCount=0;this.batchFlushTimer=null;this.isDestroyed=false;let t=d.parse(n);if(!t.apiKey.startsWith("tk_live_")){let i=t.apiKey.startsWith("tk_")?"tk_***":"invalid prefix";console.error(`[Teckel] Invalid API key format. Expected "tk_live_***", got "${i}". Get your key at https://app.teckel.ai/settings/api-keys`);}this.apiKey=t.apiKey,this.endpoint=t.endpoint||"https://app.teckel.ai/api",this.debug=t.debug||false,this.timeoutMs=t.timeoutMs??5e3,this.batchConfig={maxSize:t.batch?.maxSize??h.maxSize,maxBytes:t.batch?.maxBytes??h.maxBytes,flushIntervalMs:t.batch?.flushIntervalMs??h.flushIntervalMs},this.startFlushTimer(),this.debug&&console.log("[Teckel] Initialized",{endpoint:this.endpoint,timeoutMs:this.timeoutMs,batchMaxSize:this.batchConfig.maxSize,batchFlushIntervalMs:this.batchConfig.flushIntervalMs});}trace(n){if(this.isDestroyed){this.debug&&console.warn("[Teckel] Trace dropped - tracer destroyed");return}try{let t=n;if(n.spans&&n.spans.length>0&&!n.tokens){let a=_(n.spans);a.total>0&&(t={...n,tokens:a},this.debug&&console.log("[Teckel] Auto-aggregated tokens from spans:",a));}let i=u.parse(t);this.debug&&console.log("[Teckel] Queueing trace:",{traceId:i.traceId,sessionId:i.sessionId,queryLength:i.query.length,documentCount:i.documents?.length||0});let o=JSON.stringify(i).length;this.batchBuffer.push(i),this.batchByteCount+=o,(this.batchBuffer.length>=this.batchConfig.maxSize||this.batchByteCount>=this.batchConfig.maxBytes)&&this.flushBatch();}catch(t){this.logValidationError("Trace",t,n);}}feedback(n){if(this.isDestroyed){this.debug&&console.warn("[Teckel] Feedback dropped - tracer destroyed");return}try{let t=m.parse(n);this.debug&&console.log("[Teckel] Sending feedback:",{type:t.type,traceId:t.traceId}),this.enqueue(()=>this.send("/sdk/feedback",t),"feedback");}catch(t){this.logValidationError("Feedback",t);}}async flush(n){let t=n??this.timeoutMs;this.batchBuffer.length>0&&this.flushBatch();let i=this.sendQueue.catch(()=>{}),o;try{await Promise.race([i,new Promise((a,r)=>{o=setTimeout(()=>r(new Error("Flush timeout")),t);})]);}catch(a){throw this.debug&&console.warn("[Teckel] Flush incomplete:",a.message),a}finally{o&&clearTimeout(o);}}async destroy(){if(!this.isDestroyed){this.isDestroyed=true,this.stopFlushTimer();try{await this.flush(this.timeoutMs*2);}catch{this.debug&&console.warn("[Teckel] Destroy: some traces may not have been sent");}}}get pendingTraceCount(){return this.batchBuffer.length}startFlushTimer(){this.batchFlushTimer||(this.batchFlushTimer=setInterval(()=>{this.batchBuffer.length>0&&this.flushBatch();},this.batchConfig.flushIntervalMs),typeof this.batchFlushTimer=="object"&&"unref"in this.batchFlushTimer&&this.batchFlushTimer.unref());}stopFlushTimer(){this.batchFlushTimer&&(clearInterval(this.batchFlushTimer),this.batchFlushTimer=null);}flushBatch(){if(this.batchBuffer.length===0)return;let n=this.batchBuffer;this.batchBuffer=[],this.batchByteCount=0,this.debug&&console.log("[Teckel] Flushing batch:",{traceCount:n.length}),this.enqueue(()=>this.sendBatch(n),"batch");}async sendBatch(n){let t=`${this.endpoint}/sdk/traces`,i=await this.fetchWithRetry(t,{method:"POST",headers:{Authorization:`Bearer ${this.apiKey}`,"Content-Type":"application/json"},keepalive:true,signal:AbortSignal.timeout?.(this.timeoutMs),body:JSON.stringify({traces:n})});if(!i.ok)throw this.logHttpError("/sdk/traces",i.status),new Error(`HTTP ${i.status}`);let o=await i.json();return this.debug&&console.log("[Teckel] Batch sent:",{requested:n.length,succeeded:o.successCount??n.length}),o}async send(n,t){let i=`${this.endpoint}${n}`,o=await this.fetchWithRetry(i,{method:"POST",headers:{Authorization:`Bearer ${this.apiKey}`,"Content-Type":"application/json"},keepalive:true,signal:AbortSignal.timeout?.(this.timeoutMs),body:JSON.stringify(t)});if(!o.ok)throw this.logHttpError(n,o.status),new Error(`HTTP ${o.status}`);return o.json()}async fetchWithRetry(n,t){let o=()=>new Promise(a=>setTimeout(a,250+Math.random()*100));for(let a=1;a<=2;a++)try{let r=await fetch(n,t);if(r.ok||a===2)return r;if(r.status===429||r.status>=500){this.debug&&console.warn("[Teckel] Retrying",{status:r.status,attempt:a}),await o();continue}return r}catch(r){if(a===2)throw r;this.debug&&console.warn("[Teckel] Retry after error",{attempt:a}),await o();}throw new Error("Unreachable")}enqueue(n,t){this.sendQueue=this.sendQueue.then(()=>n()).catch(i=>{if(!(i instanceof Error&&i.message.startsWith("HTTP "))){let o=i instanceof Error?i.message:String(i);o.includes("timeout")||o.includes("abort")?console.warn(`[Teckel] ${t} timed out. Consider increasing timeoutMs.`):console.warn(`[Teckel] ${t} failed: ${o}`);}});}logValidationError(n,t,i){if(t instanceof ZodError){let o=t.errors.map(a=>{let r=a.path.length?a.path.join("."):"(root)",g=this.getValidationHint(r,a.message);return g?` - ${r}: ${a.message}
|
|
2
|
+
Hint: ${g}`:` - ${r}: ${a.message}`});console.error(`[Teckel] ${n} dropped - validation failed:
|
|
3
|
+
${o.join(`
|
|
4
|
+
`)}`),this.reportDropped({errorType:"validation_failed",errorDetails:t.errors.map(a=>`${a.path.join(".")}: ${a.message}`).join("; ").slice(0,1e3),fieldPath:t.errors[0]?.path.join("."),agentName:i?.agentName??void 0,sdkVersion:v,rawErrors:t.errors.map(a=>({path:a.path,message:a.message,code:a.code}))});}else this.debug&&console.error(`[Teckel] ${n} dropped:`,t);}getValidationHint(n,t){let i=t.toLowerCase();return n.includes("toolResult")&&i.includes("object")?"Wrap array results in an object: { items: [...] }":n.includes("toolArguments")&&i.includes("object")?"Wrap array arguments in an object: { args: [...] }":i.includes("exceeds")||i.includes("limit")?"Reduce payload size or truncate large fields":i.includes("uuid")?"Use crypto.randomUUID() or uuid package to generate valid UUIDs":n.includes("startedAt")||n.includes("endedAt")?"Use ISO 8601 format: new Date().toISOString()":i.includes("required")?`Ensure ${n.split(".").pop()} is provided and not empty`:null}reportDropped(n){this.endpoint&&fetch(`${this.endpoint}/sdk/dropped`,{method:"POST",headers:{Authorization:`Bearer ${this.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify(n)}).catch(()=>{});}logHttpError(n,t){let i=n.includes("batch")?"Batch":n.includes("trace")?"Trace":"Feedback",a={401:`${i} failed - invalid API key. Check https://app.teckel.ai/settings/api-keys`,400:`${i} failed - invalid request. Enable debug mode for details.`,404:`${i} failed - endpoint not found. Check your configuration.`,429:`${i} dropped - rate limit exceeded.`}[t]||(t>=500?`${i} dropped - server error (${t})`:`${i} failed - HTTP ${t}`);t>=500||t===429?console.warn(`[Teckel] ${a}`):console.error(`[Teckel] ${a}`);}};
|
|
5
|
+
export{k as BatchConfigSchema,f as DocumentSchema,m as FeedbackDataSchema,l as SPAN_SIZE_LIMITS,y as SpanDataSchema,S as SpanStatusSchema,T as SpanTypeSchema,c as TRACE_SIZE_LIMITS,d as TeckelConfigSchema,p as TeckelTracer,b as TokenUsageSchema,u as TraceDataSchema};
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { Span, Context, Tracer } from '@opentelemetry/api';
|
|
2
|
+
import { SpanProcessor, ReadableSpan } from '@opentelemetry/sdk-trace-base';
|
|
3
|
+
import { S as SpanData } from '../types-c7iVpo4d.mjs';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* TeckelSpanProcessor - Auto-sends traces to Teckel from OTel spans
|
|
7
|
+
*
|
|
8
|
+
* This processor automatically groups spans by trace and sends complete
|
|
9
|
+
* traces to Teckel when root spans complete.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* import { TeckelSpanProcessor } from 'teckel-ai/otel'
|
|
13
|
+
* import { NodeSDK } from '@opentelemetry/sdk-node'
|
|
14
|
+
*
|
|
15
|
+
* new NodeSDK({
|
|
16
|
+
* spanProcessors: [new TeckelSpanProcessor({
|
|
17
|
+
* apiKey: process.env.TECKEL_API_KEY,
|
|
18
|
+
* endpoint: 'https://app.teckel.ai/api'
|
|
19
|
+
* })]
|
|
20
|
+
* }).start()
|
|
21
|
+
*
|
|
22
|
+
* // Then in AI calls, just enable telemetry:
|
|
23
|
+
* experimental_telemetry: {
|
|
24
|
+
* isEnabled: true,
|
|
25
|
+
* functionId: 'my-chatbot', // Becomes agentName
|
|
26
|
+
* metadata: { userId: '123', sessionId: 'abc' }
|
|
27
|
+
* }
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
interface TeckelSpanProcessorConfig {
|
|
31
|
+
/** Teckel API key (tk_live_...) */
|
|
32
|
+
apiKey: string;
|
|
33
|
+
/** API endpoint (default: https://app.teckel.ai/api) */
|
|
34
|
+
endpoint?: string;
|
|
35
|
+
/** Enable debug logging */
|
|
36
|
+
debug?: boolean;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* TeckelSpanProcessor - OpenTelemetry SpanProcessor that auto-sends traces to Teckel
|
|
40
|
+
*
|
|
41
|
+
* Implements the OTel SpanProcessor interface to automatically capture spans
|
|
42
|
+
* from AI SDK calls and send them as complete traces to Teckel.
|
|
43
|
+
*/
|
|
44
|
+
declare class TeckelSpanProcessor implements SpanProcessor {
|
|
45
|
+
private tracer;
|
|
46
|
+
private pendingSpans;
|
|
47
|
+
private debug;
|
|
48
|
+
private cleanupInterval;
|
|
49
|
+
private readonly MAX_PENDING_TRACES;
|
|
50
|
+
private readonly PENDING_TTL_MS;
|
|
51
|
+
constructor(config: TeckelSpanProcessorConfig);
|
|
52
|
+
/**
|
|
53
|
+
* Clean up traces that have been pending longer than PENDING_TTL_MS.
|
|
54
|
+
* This prevents memory leaks when root spans never complete (streaming interruptions, crashes).
|
|
55
|
+
*/
|
|
56
|
+
private cleanupStaleTraces;
|
|
57
|
+
/**
|
|
58
|
+
* Called when a span starts - we process on end
|
|
59
|
+
*/
|
|
60
|
+
onStart(_span: Span, _parentContext: Context): void;
|
|
61
|
+
/**
|
|
62
|
+
* Called when a span ends - buffer and send if root span
|
|
63
|
+
*/
|
|
64
|
+
onEnd(span: ReadableSpan): void;
|
|
65
|
+
/**
|
|
66
|
+
* Check if this is a root span (top-level AI SDK call)
|
|
67
|
+
* Must be exact match - child spans like ai.streamText.doStream should not match
|
|
68
|
+
*/
|
|
69
|
+
private isRootSpan;
|
|
70
|
+
/**
|
|
71
|
+
* Send a complete trace to Teckel
|
|
72
|
+
*/
|
|
73
|
+
private sendTrace;
|
|
74
|
+
/**
|
|
75
|
+
* Force flush pending traces
|
|
76
|
+
*/
|
|
77
|
+
forceFlush(): Promise<void>;
|
|
78
|
+
/**
|
|
79
|
+
* Shutdown the processor
|
|
80
|
+
*/
|
|
81
|
+
shutdown(): Promise<void>;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* TeckelSpanCollector - Captures OpenTelemetry spans from AI SDK
|
|
86
|
+
*
|
|
87
|
+
* This collector captures spans created by AI SDK's experimental_telemetry
|
|
88
|
+
* and converts them to Teckel's span format for ingestion.
|
|
89
|
+
*
|
|
90
|
+
* Usage:
|
|
91
|
+
* const collector = new TeckelSpanCollector()
|
|
92
|
+
* const result = await generateText({
|
|
93
|
+
* ...,
|
|
94
|
+
* experimental_telemetry: {
|
|
95
|
+
* isEnabled: true,
|
|
96
|
+
* tracer: collector.getTracer()
|
|
97
|
+
* }
|
|
98
|
+
* })
|
|
99
|
+
* const spans = collector.getSpans()
|
|
100
|
+
* await collector.shutdown()
|
|
101
|
+
*/
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* TeckelSpanCollector - Collects AI SDK telemetry spans
|
|
105
|
+
*
|
|
106
|
+
* Creates an isolated TracerProvider that captures spans without
|
|
107
|
+
* interfering with any global OpenTelemetry setup.
|
|
108
|
+
*
|
|
109
|
+
* Requires @opentelemetry/api and @opentelemetry/sdk-trace-base as peer dependencies.
|
|
110
|
+
*/
|
|
111
|
+
interface TeckelSpanCollectorOptions {
|
|
112
|
+
/**
|
|
113
|
+
* Register this provider as the global tracer provider.
|
|
114
|
+
* This may be required if the AI SDK ignores the custom tracer parameter
|
|
115
|
+
* and uses the global provider instead.
|
|
116
|
+
* Default: false
|
|
117
|
+
*/
|
|
118
|
+
registerGlobal?: boolean;
|
|
119
|
+
/**
|
|
120
|
+
* Enable debug logging for troubleshooting span collection.
|
|
121
|
+
* Default: false
|
|
122
|
+
*/
|
|
123
|
+
debug?: boolean;
|
|
124
|
+
}
|
|
125
|
+
declare class TeckelSpanCollector {
|
|
126
|
+
private provider;
|
|
127
|
+
private exporter;
|
|
128
|
+
private tracer;
|
|
129
|
+
constructor(options?: TeckelSpanCollectorOptions);
|
|
130
|
+
/**
|
|
131
|
+
* Get the tracer to pass to AI SDK's experimental_telemetry
|
|
132
|
+
*/
|
|
133
|
+
getTracer(): Tracer;
|
|
134
|
+
/**
|
|
135
|
+
* Force flush any pending spans
|
|
136
|
+
*/
|
|
137
|
+
forceFlush(): Promise<void>;
|
|
138
|
+
/**
|
|
139
|
+
* Get collected spans in Teckel format
|
|
140
|
+
*/
|
|
141
|
+
getSpans(): SpanData[];
|
|
142
|
+
/**
|
|
143
|
+
* Get raw OpenTelemetry spans (for debugging)
|
|
144
|
+
*/
|
|
145
|
+
getRawSpans(): unknown[];
|
|
146
|
+
/**
|
|
147
|
+
* Clear collected spans
|
|
148
|
+
*/
|
|
149
|
+
clear(): void;
|
|
150
|
+
/**
|
|
151
|
+
* Shutdown the provider (cleanup)
|
|
152
|
+
*/
|
|
153
|
+
shutdown(): Promise<void>;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Shared utilities for OpenTelemetry span conversion
|
|
158
|
+
*
|
|
159
|
+
* Provides functions for converting OTel spans to Teckel format
|
|
160
|
+
* and calculating token usage from spans.
|
|
161
|
+
*/
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Truncate object to fit within byte limit.
|
|
165
|
+
* For arrays, keeps as many items as fit.
|
|
166
|
+
* For objects, returns a truncation notice.
|
|
167
|
+
*/
|
|
168
|
+
declare function truncateToSize(obj: unknown, maxBytes: number): unknown;
|
|
169
|
+
/**
|
|
170
|
+
* Map AI SDK span names to Teckel span types.
|
|
171
|
+
*/
|
|
172
|
+
declare function mapSpanType(spanName: string): SpanData['type'];
|
|
173
|
+
/**
|
|
174
|
+
* Convert OTel span ID (16-char hex) to UUID format.
|
|
175
|
+
* OTel: "1234567890abcdef" -> UUID: "12345678-90ab-cdef-0000-000000000000"
|
|
176
|
+
*/
|
|
177
|
+
declare function spanIdToUuid(spanId: string): string;
|
|
178
|
+
interface OTelSpanContext {
|
|
179
|
+
traceId: string;
|
|
180
|
+
spanId: string;
|
|
181
|
+
}
|
|
182
|
+
interface OTelSpanStatus {
|
|
183
|
+
code: number;
|
|
184
|
+
message?: string;
|
|
185
|
+
}
|
|
186
|
+
interface OTelReadableSpan {
|
|
187
|
+
name: string;
|
|
188
|
+
spanContext(): OTelSpanContext;
|
|
189
|
+
parentSpanContext?: OTelSpanContext;
|
|
190
|
+
startTime: [number, number];
|
|
191
|
+
endTime: [number, number];
|
|
192
|
+
status: OTelSpanStatus;
|
|
193
|
+
attributes: Record<string, unknown>;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Convert OpenTelemetry span to Teckel span format.
|
|
197
|
+
* Works with any object that matches the OTel ReadableSpan interface.
|
|
198
|
+
*/
|
|
199
|
+
declare function convertToTeckelSpan(span: OTelReadableSpan): SpanData;
|
|
200
|
+
/**
|
|
201
|
+
* Calculate total token usage from spans.
|
|
202
|
+
*
|
|
203
|
+
* AI SDK's usage object may only reflect the last step in agentic flows.
|
|
204
|
+
* This function aggregates tokens from all spans for more accurate totals.
|
|
205
|
+
*/
|
|
206
|
+
declare function calculateTokensFromSpans(spans: SpanData[]): {
|
|
207
|
+
prompt: number;
|
|
208
|
+
completion: number;
|
|
209
|
+
total: number;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
export { TeckelSpanCollector, type TeckelSpanCollectorOptions, TeckelSpanProcessor, type TeckelSpanProcessorConfig, calculateTokensFromSpans, convertToTeckelSpan, mapSpanType, spanIdToUuid, truncateToSize };
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { Span, Context, Tracer } from '@opentelemetry/api';
|
|
2
|
+
import { SpanProcessor, ReadableSpan } from '@opentelemetry/sdk-trace-base';
|
|
3
|
+
import { S as SpanData } from '../types-c7iVpo4d.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* TeckelSpanProcessor - Auto-sends traces to Teckel from OTel spans
|
|
7
|
+
*
|
|
8
|
+
* This processor automatically groups spans by trace and sends complete
|
|
9
|
+
* traces to Teckel when root spans complete.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* import { TeckelSpanProcessor } from 'teckel-ai/otel'
|
|
13
|
+
* import { NodeSDK } from '@opentelemetry/sdk-node'
|
|
14
|
+
*
|
|
15
|
+
* new NodeSDK({
|
|
16
|
+
* spanProcessors: [new TeckelSpanProcessor({
|
|
17
|
+
* apiKey: process.env.TECKEL_API_KEY,
|
|
18
|
+
* endpoint: 'https://app.teckel.ai/api'
|
|
19
|
+
* })]
|
|
20
|
+
* }).start()
|
|
21
|
+
*
|
|
22
|
+
* // Then in AI calls, just enable telemetry:
|
|
23
|
+
* experimental_telemetry: {
|
|
24
|
+
* isEnabled: true,
|
|
25
|
+
* functionId: 'my-chatbot', // Becomes agentName
|
|
26
|
+
* metadata: { userId: '123', sessionId: 'abc' }
|
|
27
|
+
* }
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
interface TeckelSpanProcessorConfig {
|
|
31
|
+
/** Teckel API key (tk_live_...) */
|
|
32
|
+
apiKey: string;
|
|
33
|
+
/** API endpoint (default: https://app.teckel.ai/api) */
|
|
34
|
+
endpoint?: string;
|
|
35
|
+
/** Enable debug logging */
|
|
36
|
+
debug?: boolean;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* TeckelSpanProcessor - OpenTelemetry SpanProcessor that auto-sends traces to Teckel
|
|
40
|
+
*
|
|
41
|
+
* Implements the OTel SpanProcessor interface to automatically capture spans
|
|
42
|
+
* from AI SDK calls and send them as complete traces to Teckel.
|
|
43
|
+
*/
|
|
44
|
+
declare class TeckelSpanProcessor implements SpanProcessor {
|
|
45
|
+
private tracer;
|
|
46
|
+
private pendingSpans;
|
|
47
|
+
private debug;
|
|
48
|
+
private cleanupInterval;
|
|
49
|
+
private readonly MAX_PENDING_TRACES;
|
|
50
|
+
private readonly PENDING_TTL_MS;
|
|
51
|
+
constructor(config: TeckelSpanProcessorConfig);
|
|
52
|
+
/**
|
|
53
|
+
* Clean up traces that have been pending longer than PENDING_TTL_MS.
|
|
54
|
+
* This prevents memory leaks when root spans never complete (streaming interruptions, crashes).
|
|
55
|
+
*/
|
|
56
|
+
private cleanupStaleTraces;
|
|
57
|
+
/**
|
|
58
|
+
* Called when a span starts - we process on end
|
|
59
|
+
*/
|
|
60
|
+
onStart(_span: Span, _parentContext: Context): void;
|
|
61
|
+
/**
|
|
62
|
+
* Called when a span ends - buffer and send if root span
|
|
63
|
+
*/
|
|
64
|
+
onEnd(span: ReadableSpan): void;
|
|
65
|
+
/**
|
|
66
|
+
* Check if this is a root span (top-level AI SDK call)
|
|
67
|
+
* Must be exact match - child spans like ai.streamText.doStream should not match
|
|
68
|
+
*/
|
|
69
|
+
private isRootSpan;
|
|
70
|
+
/**
|
|
71
|
+
* Send a complete trace to Teckel
|
|
72
|
+
*/
|
|
73
|
+
private sendTrace;
|
|
74
|
+
/**
|
|
75
|
+
* Force flush pending traces
|
|
76
|
+
*/
|
|
77
|
+
forceFlush(): Promise<void>;
|
|
78
|
+
/**
|
|
79
|
+
* Shutdown the processor
|
|
80
|
+
*/
|
|
81
|
+
shutdown(): Promise<void>;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* TeckelSpanCollector - Captures OpenTelemetry spans from AI SDK
|
|
86
|
+
*
|
|
87
|
+
* This collector captures spans created by AI SDK's experimental_telemetry
|
|
88
|
+
* and converts them to Teckel's span format for ingestion.
|
|
89
|
+
*
|
|
90
|
+
* Usage:
|
|
91
|
+
* const collector = new TeckelSpanCollector()
|
|
92
|
+
* const result = await generateText({
|
|
93
|
+
* ...,
|
|
94
|
+
* experimental_telemetry: {
|
|
95
|
+
* isEnabled: true,
|
|
96
|
+
* tracer: collector.getTracer()
|
|
97
|
+
* }
|
|
98
|
+
* })
|
|
99
|
+
* const spans = collector.getSpans()
|
|
100
|
+
* await collector.shutdown()
|
|
101
|
+
*/
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* TeckelSpanCollector - Collects AI SDK telemetry spans
|
|
105
|
+
*
|
|
106
|
+
* Creates an isolated TracerProvider that captures spans without
|
|
107
|
+
* interfering with any global OpenTelemetry setup.
|
|
108
|
+
*
|
|
109
|
+
* Requires @opentelemetry/api and @opentelemetry/sdk-trace-base as peer dependencies.
|
|
110
|
+
*/
|
|
111
|
+
interface TeckelSpanCollectorOptions {
|
|
112
|
+
/**
|
|
113
|
+
* Register this provider as the global tracer provider.
|
|
114
|
+
* This may be required if the AI SDK ignores the custom tracer parameter
|
|
115
|
+
* and uses the global provider instead.
|
|
116
|
+
* Default: false
|
|
117
|
+
*/
|
|
118
|
+
registerGlobal?: boolean;
|
|
119
|
+
/**
|
|
120
|
+
* Enable debug logging for troubleshooting span collection.
|
|
121
|
+
* Default: false
|
|
122
|
+
*/
|
|
123
|
+
debug?: boolean;
|
|
124
|
+
}
|
|
125
|
+
declare class TeckelSpanCollector {
|
|
126
|
+
private provider;
|
|
127
|
+
private exporter;
|
|
128
|
+
private tracer;
|
|
129
|
+
constructor(options?: TeckelSpanCollectorOptions);
|
|
130
|
+
/**
|
|
131
|
+
* Get the tracer to pass to AI SDK's experimental_telemetry
|
|
132
|
+
*/
|
|
133
|
+
getTracer(): Tracer;
|
|
134
|
+
/**
|
|
135
|
+
* Force flush any pending spans
|
|
136
|
+
*/
|
|
137
|
+
forceFlush(): Promise<void>;
|
|
138
|
+
/**
|
|
139
|
+
* Get collected spans in Teckel format
|
|
140
|
+
*/
|
|
141
|
+
getSpans(): SpanData[];
|
|
142
|
+
/**
|
|
143
|
+
* Get raw OpenTelemetry spans (for debugging)
|
|
144
|
+
*/
|
|
145
|
+
getRawSpans(): unknown[];
|
|
146
|
+
/**
|
|
147
|
+
* Clear collected spans
|
|
148
|
+
*/
|
|
149
|
+
clear(): void;
|
|
150
|
+
/**
|
|
151
|
+
* Shutdown the provider (cleanup)
|
|
152
|
+
*/
|
|
153
|
+
shutdown(): Promise<void>;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Shared utilities for OpenTelemetry span conversion
|
|
158
|
+
*
|
|
159
|
+
* Provides functions for converting OTel spans to Teckel format
|
|
160
|
+
* and calculating token usage from spans.
|
|
161
|
+
*/
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Truncate object to fit within byte limit.
|
|
165
|
+
* For arrays, keeps as many items as fit.
|
|
166
|
+
* For objects, returns a truncation notice.
|
|
167
|
+
*/
|
|
168
|
+
declare function truncateToSize(obj: unknown, maxBytes: number): unknown;
|
|
169
|
+
/**
|
|
170
|
+
* Map AI SDK span names to Teckel span types.
|
|
171
|
+
*/
|
|
172
|
+
declare function mapSpanType(spanName: string): SpanData['type'];
|
|
173
|
+
/**
|
|
174
|
+
* Convert OTel span ID (16-char hex) to UUID format.
|
|
175
|
+
* OTel: "1234567890abcdef" -> UUID: "12345678-90ab-cdef-0000-000000000000"
|
|
176
|
+
*/
|
|
177
|
+
declare function spanIdToUuid(spanId: string): string;
|
|
178
|
+
interface OTelSpanContext {
|
|
179
|
+
traceId: string;
|
|
180
|
+
spanId: string;
|
|
181
|
+
}
|
|
182
|
+
interface OTelSpanStatus {
|
|
183
|
+
code: number;
|
|
184
|
+
message?: string;
|
|
185
|
+
}
|
|
186
|
+
interface OTelReadableSpan {
|
|
187
|
+
name: string;
|
|
188
|
+
spanContext(): OTelSpanContext;
|
|
189
|
+
parentSpanContext?: OTelSpanContext;
|
|
190
|
+
startTime: [number, number];
|
|
191
|
+
endTime: [number, number];
|
|
192
|
+
status: OTelSpanStatus;
|
|
193
|
+
attributes: Record<string, unknown>;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Convert OpenTelemetry span to Teckel span format.
|
|
197
|
+
* Works with any object that matches the OTel ReadableSpan interface.
|
|
198
|
+
*/
|
|
199
|
+
declare function convertToTeckelSpan(span: OTelReadableSpan): SpanData;
|
|
200
|
+
/**
|
|
201
|
+
* Calculate total token usage from spans.
|
|
202
|
+
*
|
|
203
|
+
* AI SDK's usage object may only reflect the last step in agentic flows.
|
|
204
|
+
* This function aggregates tokens from all spans for more accurate totals.
|
|
205
|
+
*/
|
|
206
|
+
declare function calculateTokensFromSpans(spans: SpanData[]): {
|
|
207
|
+
prompt: number;
|
|
208
|
+
completion: number;
|
|
209
|
+
total: number;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
export { TeckelSpanCollector, type TeckelSpanCollectorOptions, TeckelSpanProcessor, type TeckelSpanProcessorConfig, calculateTokensFromSpans, convertToTeckelSpan, mapSpanType, spanIdToUuid, truncateToSize };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
'use strict';var zod=require('zod'),api=require('@opentelemetry/api');var F=(s=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(s,{get:(e,t)=>(typeof require<"u"?require:e)[t]}):s)(function(s){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+s+'" is not supported')});var T={METADATA_MAX_BYTES:1e4,TRACE_MAX_BYTES:3e6,MAX_DOCUMENTS:15},X=zod.z.object({id:zod.z.string().min(1,"id is required").max(500),name:zod.z.string().min(1,"name is required").max(500),text:zod.z.string().min(1,"text is required").max(5e4),lastUpdated:zod.z.string().datetime({offset:true}).optional(),url:zod.z.string().max(2e3).optional(),source:zod.z.string().max(100).optional(),fileFormat:zod.z.string().max(100).optional(),similarity:zod.z.number().min(0).max(1).optional(),rank:zod.z.number().int().nonnegative().optional(),ownerEmail:zod.z.string().email().max(254).optional()}),q=zod.z.object({prompt:zod.z.number().int().nonnegative(),completion:zod.z.number().int().nonnegative(),total:zod.z.number().int().nonnegative()}),p={MAX_SPANS:100,JSONB_MAX_BYTES:512e3},J=zod.z.enum(["llm_call","tool_call","retrieval","agent","guardrail","custom"]),U=zod.z.enum(["running","completed","error"]),z=zod.z.object({spanId:zod.z.string().min(1).max(64).optional(),parentSpanId:zod.z.string().min(1).max(64).optional().nullable(),name:zod.z.string().min(1,"name is required").max(200),type:J.default("custom"),startedAt:zod.z.string().datetime({offset:true}),endedAt:zod.z.string().datetime({offset:true}).optional().nullable(),durationMs:zod.z.number().int().min(0).max(36e5).optional(),status:U.default("completed"),statusMessage:zod.z.string().max(2e3).optional().nullable(),toolName:zod.z.string().max(200).optional().nullable(),toolArguments:zod.z.record(zod.z.unknown()).optional().nullable().refine(s=>!s||JSON.stringify(s).length<=p.JSONB_MAX_BYTES,`toolArguments exceeds ${p.JSONB_MAX_BYTES/1e3}KB limit`),toolResult:zod.z.record(zod.z.unknown()).optional().nullable().refine(s=>!s||JSON.stringify(s).length<=p.JSONB_MAX_BYTES,`toolResult exceeds ${p.JSONB_MAX_BYTES/1e3}KB limit`),model:zod.z.string().max(100).optional().nullable(),promptTokens:zod.z.number().int().min(0).optional().nullable(),completionTokens:zod.z.number().int().min(0).optional().nullable(),costUsd:zod.z.number().min(0).optional().nullable(),input:zod.z.record(zod.z.unknown()).optional().nullable().refine(s=>!s||JSON.stringify(s).length<=p.JSONB_MAX_BYTES,`input exceeds ${p.JSONB_MAX_BYTES/1e3}KB limit`),output:zod.z.record(zod.z.unknown()).optional().nullable().refine(s=>!s||JSON.stringify(s).length<=p.JSONB_MAX_BYTES,`output exceeds ${p.JSONB_MAX_BYTES/1e3}KB limit`),metadata:zod.z.record(zod.z.unknown()).optional().nullable().refine(s=>!s||JSON.stringify(s).length<=p.JSONB_MAX_BYTES,`metadata exceeds ${p.JSONB_MAX_BYTES/1e3}KB limit`)}).refine(s=>s.type!=="tool_call"||s.toolName,{message:"toolName is required for tool_call spans",path:["toolName"]}),M=zod.z.object({query:zod.z.string().min(1,"query is required").max(1e4,"query too long (max 10,000 chars)"),response:zod.z.string().min(1,"response is required").max(5e4,"response too long (max 50,000 chars)"),agentName:zod.z.string().min(1,"agentName cannot be empty if provided").max(200,"agentName must be 200 chars or less").optional().nullable(),model:zod.z.string().max(100).optional(),latencyMs:zod.z.number().int().nonnegative().max(6e5).optional(),tokens:q.optional(),costUsd:zod.z.number().min(0).optional(),documents:zod.z.array(X).max(T.MAX_DOCUMENTS,`Too many documents (max ${T.MAX_DOCUMENTS})`).optional(),spans:zod.z.array(z).max(p.MAX_SPANS,`Too many spans (max ${p.MAX_SPANS})`).optional(),sessionId:zod.z.string().min(1).max(200,"sessionId must be 200 chars or less").optional(),userId:zod.z.string().min(1).max(255).optional(),traceId:zod.z.string().uuid("traceId must be a valid UUID").optional(),systemPrompt:zod.z.string().max(5e4,"systemPrompt too long (max 50,000 chars)").optional(),metadata:zod.z.record(zod.z.string(),zod.z.unknown()).optional().refine(s=>!s||JSON.stringify(s).length<=T.METADATA_MAX_BYTES,`metadata exceeds ${T.METADATA_MAX_BYTES/1e3}KB limit`)}).superRefine((s,e)=>{JSON.stringify(s).length>T.TRACE_MAX_BYTES&&e.addIssue({code:zod.z.ZodIssueCode.custom,message:`trace payload exceeds ${T.TRACE_MAX_BYTES/1e6}MB limit`,path:[]});}),E=zod.z.object({traceId:zod.z.string().uuid("traceId must be a valid UUID").optional(),sessionId:zod.z.string().min(1).max(200,"sessionId must be 200 chars or less").optional(),type:zod.z.enum(["thumbs_up","thumbs_down","flag","rating"]),value:zod.z.string().min(1).max(10).optional(),comment:zod.z.string().max(2e3).optional()}).refine(s=>s.traceId||s.sessionId,{message:"Either traceId or sessionId is required"}),Y=zod.z.object({maxSize:zod.z.number().int().min(1).max(500).optional(),maxBytes:zod.z.number().int().min(1e3).max(1e7).optional(),flushIntervalMs:zod.z.number().int().min(10).max(1e4).optional()}),C=zod.z.object({apiKey:zod.z.string().min(1,"apiKey is required"),endpoint:zod.z.string().url().optional(),debug:zod.z.boolean().optional(),timeoutMs:zod.z.number().int().positive().max(6e4).optional(),batch:Y.optional()});var j="0.7.1";function L(s){let e=0,t=0;for(let n of s)n.promptTokens!=null&&(e+=n.promptTokens),n.completionTokens!=null&&(t+=n.completionTokens);return {prompt:e,completion:t,total:e+t}}var x={maxSize:100,maxBytes:5e6,flushIntervalMs:100},y=class{constructor(e){this.sendQueue=Promise.resolve();this.batchBuffer=[];this.batchByteCount=0;this.batchFlushTimer=null;this.isDestroyed=false;let t=C.parse(e);if(!t.apiKey.startsWith("tk_live_")){let n=t.apiKey.startsWith("tk_")?"tk_***":"invalid prefix";console.error(`[Teckel] Invalid API key format. Expected "tk_live_***", got "${n}". Get your key at https://app.teckel.ai/settings/api-keys`);}this.apiKey=t.apiKey,this.endpoint=t.endpoint||"https://app.teckel.ai/api",this.debug=t.debug||false,this.timeoutMs=t.timeoutMs??5e3,this.batchConfig={maxSize:t.batch?.maxSize??x.maxSize,maxBytes:t.batch?.maxBytes??x.maxBytes,flushIntervalMs:t.batch?.flushIntervalMs??x.flushIntervalMs},this.startFlushTimer(),this.debug&&console.log("[Teckel] Initialized",{endpoint:this.endpoint,timeoutMs:this.timeoutMs,batchMaxSize:this.batchConfig.maxSize,batchFlushIntervalMs:this.batchConfig.flushIntervalMs});}trace(e){if(this.isDestroyed){this.debug&&console.warn("[Teckel] Trace dropped - tracer destroyed");return}try{let t=e;if(e.spans&&e.spans.length>0&&!e.tokens){let r=L(e.spans);r.total>0&&(t={...e,tokens:r},this.debug&&console.log("[Teckel] Auto-aggregated tokens from spans:",r));}let n=M.parse(t);this.debug&&console.log("[Teckel] Queueing trace:",{traceId:n.traceId,sessionId:n.sessionId,queryLength:n.query.length,documentCount:n.documents?.length||0});let o=JSON.stringify(n).length;this.batchBuffer.push(n),this.batchByteCount+=o,(this.batchBuffer.length>=this.batchConfig.maxSize||this.batchByteCount>=this.batchConfig.maxBytes)&&this.flushBatch();}catch(t){this.logValidationError("Trace",t,e);}}feedback(e){if(this.isDestroyed){this.debug&&console.warn("[Teckel] Feedback dropped - tracer destroyed");return}try{let t=E.parse(e);this.debug&&console.log("[Teckel] Sending feedback:",{type:t.type,traceId:t.traceId}),this.enqueue(()=>this.send("/sdk/feedback",t),"feedback");}catch(t){this.logValidationError("Feedback",t);}}async flush(e){let t=e??this.timeoutMs;this.batchBuffer.length>0&&this.flushBatch();let n=this.sendQueue.catch(()=>{}),o;try{await Promise.race([n,new Promise((r,i)=>{o=setTimeout(()=>i(new Error("Flush timeout")),t);})]);}catch(r){throw this.debug&&console.warn("[Teckel] Flush incomplete:",r.message),r}finally{o&&clearTimeout(o);}}async destroy(){if(!this.isDestroyed){this.isDestroyed=true,this.stopFlushTimer();try{await this.flush(this.timeoutMs*2);}catch{this.debug&&console.warn("[Teckel] Destroy: some traces may not have been sent");}}}get pendingTraceCount(){return this.batchBuffer.length}startFlushTimer(){this.batchFlushTimer||(this.batchFlushTimer=setInterval(()=>{this.batchBuffer.length>0&&this.flushBatch();},this.batchConfig.flushIntervalMs),typeof this.batchFlushTimer=="object"&&"unref"in this.batchFlushTimer&&this.batchFlushTimer.unref());}stopFlushTimer(){this.batchFlushTimer&&(clearInterval(this.batchFlushTimer),this.batchFlushTimer=null);}flushBatch(){if(this.batchBuffer.length===0)return;let e=this.batchBuffer;this.batchBuffer=[],this.batchByteCount=0,this.debug&&console.log("[Teckel] Flushing batch:",{traceCount:e.length}),this.enqueue(()=>this.sendBatch(e),"batch");}async sendBatch(e){let t=`${this.endpoint}/sdk/traces`,n=await this.fetchWithRetry(t,{method:"POST",headers:{Authorization:`Bearer ${this.apiKey}`,"Content-Type":"application/json"},keepalive:true,signal:AbortSignal.timeout?.(this.timeoutMs),body:JSON.stringify({traces:e})});if(!n.ok)throw this.logHttpError("/sdk/traces",n.status),new Error(`HTTP ${n.status}`);let o=await n.json();return this.debug&&console.log("[Teckel] Batch sent:",{requested:e.length,succeeded:o.successCount??e.length}),o}async send(e,t){let n=`${this.endpoint}${e}`,o=await this.fetchWithRetry(n,{method:"POST",headers:{Authorization:`Bearer ${this.apiKey}`,"Content-Type":"application/json"},keepalive:true,signal:AbortSignal.timeout?.(this.timeoutMs),body:JSON.stringify(t)});if(!o.ok)throw this.logHttpError(e,o.status),new Error(`HTTP ${o.status}`);return o.json()}async fetchWithRetry(e,t){let o=()=>new Promise(r=>setTimeout(r,250+Math.random()*100));for(let r=1;r<=2;r++)try{let i=await fetch(e,t);if(i.ok||r===2)return i;if(i.status===429||i.status>=500){this.debug&&console.warn("[Teckel] Retrying",{status:i.status,attempt:r}),await o();continue}return i}catch(i){if(r===2)throw i;this.debug&&console.warn("[Teckel] Retry after error",{attempt:r}),await o();}throw new Error("Unreachable")}enqueue(e,t){this.sendQueue=this.sendQueue.then(()=>e()).catch(n=>{if(!(n instanceof Error&&n.message.startsWith("HTTP "))){let o=n instanceof Error?n.message:String(n);o.includes("timeout")||o.includes("abort")?console.warn(`[Teckel] ${t} timed out. Consider increasing timeoutMs.`):console.warn(`[Teckel] ${t} failed: ${o}`);}});}logValidationError(e,t,n){if(t instanceof zod.ZodError){let o=t.errors.map(r=>{let i=r.path.length?r.path.join("."):"(root)",m=this.getValidationHint(i,r.message);return m?` - ${i}: ${r.message}
|
|
2
|
+
Hint: ${m}`:` - ${i}: ${r.message}`});console.error(`[Teckel] ${e} dropped - validation failed:
|
|
3
|
+
${o.join(`
|
|
4
|
+
`)}`),this.reportDropped({errorType:"validation_failed",errorDetails:t.errors.map(r=>`${r.path.join(".")}: ${r.message}`).join("; ").slice(0,1e3),fieldPath:t.errors[0]?.path.join("."),agentName:n?.agentName??void 0,sdkVersion:j,rawErrors:t.errors.map(r=>({path:r.path,message:r.message,code:r.code}))});}else this.debug&&console.error(`[Teckel] ${e} dropped:`,t);}getValidationHint(e,t){let n=t.toLowerCase();return e.includes("toolResult")&&n.includes("object")?"Wrap array results in an object: { items: [...] }":e.includes("toolArguments")&&n.includes("object")?"Wrap array arguments in an object: { args: [...] }":n.includes("exceeds")||n.includes("limit")?"Reduce payload size or truncate large fields":n.includes("uuid")?"Use crypto.randomUUID() or uuid package to generate valid UUIDs":e.includes("startedAt")||e.includes("endedAt")?"Use ISO 8601 format: new Date().toISOString()":n.includes("required")?`Ensure ${e.split(".").pop()} is provided and not empty`:null}reportDropped(e){this.endpoint&&fetch(`${this.endpoint}/sdk/dropped`,{method:"POST",headers:{Authorization:`Bearer ${this.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify(e)}).catch(()=>{});}logHttpError(e,t){let n=e.includes("batch")?"Batch":e.includes("trace")?"Trace":"Feedback",r={401:`${n} failed - invalid API key. Check https://app.teckel.ai/settings/api-keys`,400:`${n} failed - invalid request. Enable debug mode for details.`,404:`${n} failed - endpoint not found. Check your configuration.`,429:`${n} dropped - rate limit exceeded.`}[t]||(t>=500?`${n} dropped - server error (${t})`:`${n} failed - HTTP ${t}`);t>=500||t===429?console.warn(`[Teckel] ${r}`):console.error(`[Teckel] ${r}`);}};function _(s,e){let t=JSON.stringify(s);if(t.length<=e)return s;if(Array.isArray(s)){let n=[],o=2;for(let r of s){let i=JSON.stringify(r);if(o+i.length+1>e-50)break;n.push(r),o+=i.length+1;}return {_truncated:true,_originalCount:s.length,_keptCount:n.length,items:n}}return {_truncated:true,_originalSizeBytes:t.length,_maxSizeBytes:e,_preview:t.slice(0,500)}}function P(s){return s==="ai.toolCall"?"tool_call":s.includes("doGenerate")||s.includes("doStream")?"llm_call":s.startsWith("ai.generateText")||s.startsWith("ai.streamText")||s.startsWith("ai.generateObject")?"agent":"custom"}function O(s){let e=s.padEnd(32,"0");return `${e.slice(0,8)}-${e.slice(8,12)}-${e.slice(12,16)}-${e.slice(16,20)}-${e.slice(20,32)}`}var G=2;function b(s){let e=s.attributes,t=s.name,n=P(t),o=s.startTime[0]*1e3+s.startTime[1]/1e6,r=s.endTime[0]*1e3+s.endTime[1]/1e6,i=Math.round(r-o),m=s.parentSpanContext?.spanId,c={spanId:O(s.spanContext().spanId),parentSpanId:m?O(m):void 0,name:t,type:n,startedAt:new Date(o).toISOString(),endedAt:new Date(r).toISOString(),durationMs:i,status:s.status.code===G?"error":"completed",statusMessage:s.status.message||void 0};if(n==="tool_call"){c.toolName=e["ai.toolCall.name"]||void 0;let l=e["ai.toolCall.args"];if(l)try{let d=JSON.parse(l);c.toolArguments=_(d,512e3);}catch{c.toolArguments={raw:l.slice(0,512e3)};}let u=e["ai.toolCall.result"];if(u)try{let d=JSON.parse(u),f;Array.isArray(d)?f={items:d}:typeof d=="object"&&d!==null?f=d:f={value:d},c.toolResult=_(f,512e3);}catch{c.toolResult={raw:u.slice(0,512e3)};}}if(n==="llm_call"||n==="agent"){c.model=e["gen_ai.request.model"]||e["ai.model.id"]||void 0;let l=e["gen_ai.usage.input_tokens"],u=e["gen_ai.usage.output_tokens"];l!==void 0&&(c.promptTokens=l),u!==void 0&&(c.completionTokens=u);}if(n==="agent"){let l=e["ai.response.text"];l&&(c.output={text:l});}let S={};for(let[l,u]of Object.entries(e))l.startsWith("ai.toolCall.")||l.startsWith("gen_ai.usage.")||l==="ai.response.text"||l==="ai.model.id"||l==="gen_ai.request.model"||(S[l]=u);return Object.keys(S).length>0&&(c.metadata=S),c}function w(s){let e=0,t=0;for(let n of s)n.promptTokens!=null&&(e+=n.promptTokens),n.completionTokens!=null&&(t+=n.completionTokens);return {prompt:e,completion:t,total:e+t}}var I=class{constructor(e){this.pendingSpans=new Map;this.cleanupInterval=null;this.MAX_PENDING_TRACES=1e4;this.PENDING_TTL_MS=300*1e3;this.tracer=new y({apiKey:e.apiKey,endpoint:e.endpoint,debug:e.debug,batch:{maxSize:1,flushIntervalMs:10}}),this.debug=e.debug??false,this.cleanupInterval=setInterval(()=>this.cleanupStaleTraces(),this.PENDING_TTL_MS/2);}cleanupStaleTraces(){let e=Date.now(),t=0;for(let[n,o]of this.pendingSpans)e-o.firstSpanTime>this.PENDING_TTL_MS&&(this.pendingSpans.delete(n),t++);t>0&&this.debug&&console.log(`[TeckelSpanProcessor] Evicted ${t} stale traces due to TTL`);}onStart(e,t){}onEnd(e){let t=e.spanContext().traceId,n=b(e),o=Date.now();if(this.pendingSpans.size>=this.MAX_PENDING_TRACES&&!this.pendingSpans.has(t)){let i=this.pendingSpans.keys().next().value;i&&(this.pendingSpans.delete(i),this.debug&&console.log("[TeckelSpanProcessor] Evicted oldest pending trace due to capacity:",i));}let r=this.pendingSpans.get(t);if(r?r.spans.push(n):this.pendingSpans.set(t,{spans:[n],firstSpanTime:o}),this.isRootSpan(e)){let i=this.pendingSpans.get(t);i&&(this.sendTrace(t,e,i.spans),this.pendingSpans.delete(t));}}isRootSpan(e){let t=e.name;return t==="ai.generateText"||t==="ai.streamText"||t==="ai.generateObject"}sendTrace(e,t,n){let o=t.attributes,r="",i,m=o["ai.prompt.messages"],c=o["ai.prompt"];if(m)try{let h=JSON.parse(m);for(let g of h)if(g.role==="system"){i=g.content;break}for(let g=h.length-1;g>=0;g--)if(h[g].role==="user"){r=h[g].content;break}}catch{r=m;}else c&&(r=c);let S=o["ai.response.text"]||"",l=o["ai.telemetry.functionId"]||"unknown",u=o["gen_ai.request.model"]||o["ai.model.id"]||void 0,d=t.startTime[0]*1e3+t.startTime[1]/1e6,f=t.endTime[0]*1e3+t.endTime[1]/1e6,D=Math.round(f-d),k=w(n),R=o["ai.telemetry.metadata.sessionId"],N=o["ai.telemetry.metadata.userId"],v={};for(let[h,g]of Object.entries(o))if(h.startsWith("ai.telemetry.metadata.")&&h!=="ai.telemetry.metadata.sessionId"&&h!=="ai.telemetry.metadata.userId"){let $=h.replace("ai.telemetry.metadata.","");v[$]=g;}this.debug&&console.log("[TeckelSpanProcessor] Sending trace:",{traceId:e,agentName:l,spanCount:n.length,tokens:k}),this.tracer.trace({query:r,response:S,agentName:l,model:u,latencyMs:D,tokens:k.total>0?k:void 0,spans:n,sessionId:R,userId:N,systemPrompt:i,metadata:Object.keys(v).length>0?v:void 0});}forceFlush(){return this.tracer.flush()}shutdown(){return this.cleanupInterval&&(clearInterval(this.cleanupInterval),this.cleanupInterval=null),this.tracer.flush()}};var A=class{constructor(){this.spans=[];this.debug=false;}setDebug(e){this.debug=e;}export(e,t){this.debug&&console.log("[TeckelSpanCollector] Exporter.export called",{spanCount:e.length,spanNames:e.map(n=>n.name)}),this.spans.push(...e),t({code:0});}shutdown(){return Promise.resolve()}getSpans(){return this.spans}clear(){this.spans=[];}},B=class{constructor(e){let{BasicTracerProvider:t,SimpleSpanProcessor:n,AlwaysOnSampler:o}=F("@opentelemetry/sdk-trace-base");this.exporter=new A,e?.debug&&(this.exporter.setDebug(true),console.log("[TeckelSpanCollector] Debug mode enabled")),this.provider=new t({sampler:new o,spanProcessors:[new n(this.exporter)]}),e?.registerGlobal&&(api.trace.setGlobalTracerProvider(this.provider),e?.debug&&console.log("[TeckelSpanCollector] Registered as global tracer provider")),this.tracer=this.provider.getTracer("teckel-ai-sdk");}getTracer(){return this.tracer}async forceFlush(){await this.provider.forceFlush();}getSpans(){return this.exporter.getSpans().map(e=>b(e))}getRawSpans(){return this.exporter.getSpans()}clear(){this.exporter.clear();}async shutdown(){await this.provider.shutdown();}};
|
|
5
|
+
exports.TeckelSpanCollector=B;exports.TeckelSpanProcessor=I;exports.calculateTokensFromSpans=w;exports.convertToTeckelSpan=b;exports.mapSpanType=P;exports.spanIdToUuid=O;exports.truncateToSize=_;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import {z as z$1,ZodError}from'zod';import {trace}from'@opentelemetry/api';var F=(s=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(s,{get:(e,t)=>(typeof require<"u"?require:e)[t]}):s)(function(s){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+s+'" is not supported')});var T={METADATA_MAX_BYTES:1e4,TRACE_MAX_BYTES:3e6,MAX_DOCUMENTS:15},X=z$1.object({id:z$1.string().min(1,"id is required").max(500),name:z$1.string().min(1,"name is required").max(500),text:z$1.string().min(1,"text is required").max(5e4),lastUpdated:z$1.string().datetime({offset:true}).optional(),url:z$1.string().max(2e3).optional(),source:z$1.string().max(100).optional(),fileFormat:z$1.string().max(100).optional(),similarity:z$1.number().min(0).max(1).optional(),rank:z$1.number().int().nonnegative().optional(),ownerEmail:z$1.string().email().max(254).optional()}),q=z$1.object({prompt:z$1.number().int().nonnegative(),completion:z$1.number().int().nonnegative(),total:z$1.number().int().nonnegative()}),p={MAX_SPANS:100,JSONB_MAX_BYTES:512e3},J=z$1.enum(["llm_call","tool_call","retrieval","agent","guardrail","custom"]),U=z$1.enum(["running","completed","error"]),z=z$1.object({spanId:z$1.string().min(1).max(64).optional(),parentSpanId:z$1.string().min(1).max(64).optional().nullable(),name:z$1.string().min(1,"name is required").max(200),type:J.default("custom"),startedAt:z$1.string().datetime({offset:true}),endedAt:z$1.string().datetime({offset:true}).optional().nullable(),durationMs:z$1.number().int().min(0).max(36e5).optional(),status:U.default("completed"),statusMessage:z$1.string().max(2e3).optional().nullable(),toolName:z$1.string().max(200).optional().nullable(),toolArguments:z$1.record(z$1.unknown()).optional().nullable().refine(s=>!s||JSON.stringify(s).length<=p.JSONB_MAX_BYTES,`toolArguments exceeds ${p.JSONB_MAX_BYTES/1e3}KB limit`),toolResult:z$1.record(z$1.unknown()).optional().nullable().refine(s=>!s||JSON.stringify(s).length<=p.JSONB_MAX_BYTES,`toolResult exceeds ${p.JSONB_MAX_BYTES/1e3}KB limit`),model:z$1.string().max(100).optional().nullable(),promptTokens:z$1.number().int().min(0).optional().nullable(),completionTokens:z$1.number().int().min(0).optional().nullable(),costUsd:z$1.number().min(0).optional().nullable(),input:z$1.record(z$1.unknown()).optional().nullable().refine(s=>!s||JSON.stringify(s).length<=p.JSONB_MAX_BYTES,`input exceeds ${p.JSONB_MAX_BYTES/1e3}KB limit`),output:z$1.record(z$1.unknown()).optional().nullable().refine(s=>!s||JSON.stringify(s).length<=p.JSONB_MAX_BYTES,`output exceeds ${p.JSONB_MAX_BYTES/1e3}KB limit`),metadata:z$1.record(z$1.unknown()).optional().nullable().refine(s=>!s||JSON.stringify(s).length<=p.JSONB_MAX_BYTES,`metadata exceeds ${p.JSONB_MAX_BYTES/1e3}KB limit`)}).refine(s=>s.type!=="tool_call"||s.toolName,{message:"toolName is required for tool_call spans",path:["toolName"]}),M=z$1.object({query:z$1.string().min(1,"query is required").max(1e4,"query too long (max 10,000 chars)"),response:z$1.string().min(1,"response is required").max(5e4,"response too long (max 50,000 chars)"),agentName:z$1.string().min(1,"agentName cannot be empty if provided").max(200,"agentName must be 200 chars or less").optional().nullable(),model:z$1.string().max(100).optional(),latencyMs:z$1.number().int().nonnegative().max(6e5).optional(),tokens:q.optional(),costUsd:z$1.number().min(0).optional(),documents:z$1.array(X).max(T.MAX_DOCUMENTS,`Too many documents (max ${T.MAX_DOCUMENTS})`).optional(),spans:z$1.array(z).max(p.MAX_SPANS,`Too many spans (max ${p.MAX_SPANS})`).optional(),sessionId:z$1.string().min(1).max(200,"sessionId must be 200 chars or less").optional(),userId:z$1.string().min(1).max(255).optional(),traceId:z$1.string().uuid("traceId must be a valid UUID").optional(),systemPrompt:z$1.string().max(5e4,"systemPrompt too long (max 50,000 chars)").optional(),metadata:z$1.record(z$1.string(),z$1.unknown()).optional().refine(s=>!s||JSON.stringify(s).length<=T.METADATA_MAX_BYTES,`metadata exceeds ${T.METADATA_MAX_BYTES/1e3}KB limit`)}).superRefine((s,e)=>{JSON.stringify(s).length>T.TRACE_MAX_BYTES&&e.addIssue({code:z$1.ZodIssueCode.custom,message:`trace payload exceeds ${T.TRACE_MAX_BYTES/1e6}MB limit`,path:[]});}),E=z$1.object({traceId:z$1.string().uuid("traceId must be a valid UUID").optional(),sessionId:z$1.string().min(1).max(200,"sessionId must be 200 chars or less").optional(),type:z$1.enum(["thumbs_up","thumbs_down","flag","rating"]),value:z$1.string().min(1).max(10).optional(),comment:z$1.string().max(2e3).optional()}).refine(s=>s.traceId||s.sessionId,{message:"Either traceId or sessionId is required"}),Y=z$1.object({maxSize:z$1.number().int().min(1).max(500).optional(),maxBytes:z$1.number().int().min(1e3).max(1e7).optional(),flushIntervalMs:z$1.number().int().min(10).max(1e4).optional()}),C=z$1.object({apiKey:z$1.string().min(1,"apiKey is required"),endpoint:z$1.string().url().optional(),debug:z$1.boolean().optional(),timeoutMs:z$1.number().int().positive().max(6e4).optional(),batch:Y.optional()});var j="0.7.1";function L(s){let e=0,t=0;for(let n of s)n.promptTokens!=null&&(e+=n.promptTokens),n.completionTokens!=null&&(t+=n.completionTokens);return {prompt:e,completion:t,total:e+t}}var x={maxSize:100,maxBytes:5e6,flushIntervalMs:100},y=class{constructor(e){this.sendQueue=Promise.resolve();this.batchBuffer=[];this.batchByteCount=0;this.batchFlushTimer=null;this.isDestroyed=false;let t=C.parse(e);if(!t.apiKey.startsWith("tk_live_")){let n=t.apiKey.startsWith("tk_")?"tk_***":"invalid prefix";console.error(`[Teckel] Invalid API key format. Expected "tk_live_***", got "${n}". Get your key at https://app.teckel.ai/settings/api-keys`);}this.apiKey=t.apiKey,this.endpoint=t.endpoint||"https://app.teckel.ai/api",this.debug=t.debug||false,this.timeoutMs=t.timeoutMs??5e3,this.batchConfig={maxSize:t.batch?.maxSize??x.maxSize,maxBytes:t.batch?.maxBytes??x.maxBytes,flushIntervalMs:t.batch?.flushIntervalMs??x.flushIntervalMs},this.startFlushTimer(),this.debug&&console.log("[Teckel] Initialized",{endpoint:this.endpoint,timeoutMs:this.timeoutMs,batchMaxSize:this.batchConfig.maxSize,batchFlushIntervalMs:this.batchConfig.flushIntervalMs});}trace(e){if(this.isDestroyed){this.debug&&console.warn("[Teckel] Trace dropped - tracer destroyed");return}try{let t=e;if(e.spans&&e.spans.length>0&&!e.tokens){let r=L(e.spans);r.total>0&&(t={...e,tokens:r},this.debug&&console.log("[Teckel] Auto-aggregated tokens from spans:",r));}let n=M.parse(t);this.debug&&console.log("[Teckel] Queueing trace:",{traceId:n.traceId,sessionId:n.sessionId,queryLength:n.query.length,documentCount:n.documents?.length||0});let o=JSON.stringify(n).length;this.batchBuffer.push(n),this.batchByteCount+=o,(this.batchBuffer.length>=this.batchConfig.maxSize||this.batchByteCount>=this.batchConfig.maxBytes)&&this.flushBatch();}catch(t){this.logValidationError("Trace",t,e);}}feedback(e){if(this.isDestroyed){this.debug&&console.warn("[Teckel] Feedback dropped - tracer destroyed");return}try{let t=E.parse(e);this.debug&&console.log("[Teckel] Sending feedback:",{type:t.type,traceId:t.traceId}),this.enqueue(()=>this.send("/sdk/feedback",t),"feedback");}catch(t){this.logValidationError("Feedback",t);}}async flush(e){let t=e??this.timeoutMs;this.batchBuffer.length>0&&this.flushBatch();let n=this.sendQueue.catch(()=>{}),o;try{await Promise.race([n,new Promise((r,i)=>{o=setTimeout(()=>i(new Error("Flush timeout")),t);})]);}catch(r){throw this.debug&&console.warn("[Teckel] Flush incomplete:",r.message),r}finally{o&&clearTimeout(o);}}async destroy(){if(!this.isDestroyed){this.isDestroyed=true,this.stopFlushTimer();try{await this.flush(this.timeoutMs*2);}catch{this.debug&&console.warn("[Teckel] Destroy: some traces may not have been sent");}}}get pendingTraceCount(){return this.batchBuffer.length}startFlushTimer(){this.batchFlushTimer||(this.batchFlushTimer=setInterval(()=>{this.batchBuffer.length>0&&this.flushBatch();},this.batchConfig.flushIntervalMs),typeof this.batchFlushTimer=="object"&&"unref"in this.batchFlushTimer&&this.batchFlushTimer.unref());}stopFlushTimer(){this.batchFlushTimer&&(clearInterval(this.batchFlushTimer),this.batchFlushTimer=null);}flushBatch(){if(this.batchBuffer.length===0)return;let e=this.batchBuffer;this.batchBuffer=[],this.batchByteCount=0,this.debug&&console.log("[Teckel] Flushing batch:",{traceCount:e.length}),this.enqueue(()=>this.sendBatch(e),"batch");}async sendBatch(e){let t=`${this.endpoint}/sdk/traces`,n=await this.fetchWithRetry(t,{method:"POST",headers:{Authorization:`Bearer ${this.apiKey}`,"Content-Type":"application/json"},keepalive:true,signal:AbortSignal.timeout?.(this.timeoutMs),body:JSON.stringify({traces:e})});if(!n.ok)throw this.logHttpError("/sdk/traces",n.status),new Error(`HTTP ${n.status}`);let o=await n.json();return this.debug&&console.log("[Teckel] Batch sent:",{requested:e.length,succeeded:o.successCount??e.length}),o}async send(e,t){let n=`${this.endpoint}${e}`,o=await this.fetchWithRetry(n,{method:"POST",headers:{Authorization:`Bearer ${this.apiKey}`,"Content-Type":"application/json"},keepalive:true,signal:AbortSignal.timeout?.(this.timeoutMs),body:JSON.stringify(t)});if(!o.ok)throw this.logHttpError(e,o.status),new Error(`HTTP ${o.status}`);return o.json()}async fetchWithRetry(e,t){let o=()=>new Promise(r=>setTimeout(r,250+Math.random()*100));for(let r=1;r<=2;r++)try{let i=await fetch(e,t);if(i.ok||r===2)return i;if(i.status===429||i.status>=500){this.debug&&console.warn("[Teckel] Retrying",{status:i.status,attempt:r}),await o();continue}return i}catch(i){if(r===2)throw i;this.debug&&console.warn("[Teckel] Retry after error",{attempt:r}),await o();}throw new Error("Unreachable")}enqueue(e,t){this.sendQueue=this.sendQueue.then(()=>e()).catch(n=>{if(!(n instanceof Error&&n.message.startsWith("HTTP "))){let o=n instanceof Error?n.message:String(n);o.includes("timeout")||o.includes("abort")?console.warn(`[Teckel] ${t} timed out. Consider increasing timeoutMs.`):console.warn(`[Teckel] ${t} failed: ${o}`);}});}logValidationError(e,t,n){if(t instanceof ZodError){let o=t.errors.map(r=>{let i=r.path.length?r.path.join("."):"(root)",m=this.getValidationHint(i,r.message);return m?` - ${i}: ${r.message}
|
|
2
|
+
Hint: ${m}`:` - ${i}: ${r.message}`});console.error(`[Teckel] ${e} dropped - validation failed:
|
|
3
|
+
${o.join(`
|
|
4
|
+
`)}`),this.reportDropped({errorType:"validation_failed",errorDetails:t.errors.map(r=>`${r.path.join(".")}: ${r.message}`).join("; ").slice(0,1e3),fieldPath:t.errors[0]?.path.join("."),agentName:n?.agentName??void 0,sdkVersion:j,rawErrors:t.errors.map(r=>({path:r.path,message:r.message,code:r.code}))});}else this.debug&&console.error(`[Teckel] ${e} dropped:`,t);}getValidationHint(e,t){let n=t.toLowerCase();return e.includes("toolResult")&&n.includes("object")?"Wrap array results in an object: { items: [...] }":e.includes("toolArguments")&&n.includes("object")?"Wrap array arguments in an object: { args: [...] }":n.includes("exceeds")||n.includes("limit")?"Reduce payload size or truncate large fields":n.includes("uuid")?"Use crypto.randomUUID() or uuid package to generate valid UUIDs":e.includes("startedAt")||e.includes("endedAt")?"Use ISO 8601 format: new Date().toISOString()":n.includes("required")?`Ensure ${e.split(".").pop()} is provided and not empty`:null}reportDropped(e){this.endpoint&&fetch(`${this.endpoint}/sdk/dropped`,{method:"POST",headers:{Authorization:`Bearer ${this.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify(e)}).catch(()=>{});}logHttpError(e,t){let n=e.includes("batch")?"Batch":e.includes("trace")?"Trace":"Feedback",r={401:`${n} failed - invalid API key. Check https://app.teckel.ai/settings/api-keys`,400:`${n} failed - invalid request. Enable debug mode for details.`,404:`${n} failed - endpoint not found. Check your configuration.`,429:`${n} dropped - rate limit exceeded.`}[t]||(t>=500?`${n} dropped - server error (${t})`:`${n} failed - HTTP ${t}`);t>=500||t===429?console.warn(`[Teckel] ${r}`):console.error(`[Teckel] ${r}`);}};function _(s,e){let t=JSON.stringify(s);if(t.length<=e)return s;if(Array.isArray(s)){let n=[],o=2;for(let r of s){let i=JSON.stringify(r);if(o+i.length+1>e-50)break;n.push(r),o+=i.length+1;}return {_truncated:true,_originalCount:s.length,_keptCount:n.length,items:n}}return {_truncated:true,_originalSizeBytes:t.length,_maxSizeBytes:e,_preview:t.slice(0,500)}}function P(s){return s==="ai.toolCall"?"tool_call":s.includes("doGenerate")||s.includes("doStream")?"llm_call":s.startsWith("ai.generateText")||s.startsWith("ai.streamText")||s.startsWith("ai.generateObject")?"agent":"custom"}function O(s){let e=s.padEnd(32,"0");return `${e.slice(0,8)}-${e.slice(8,12)}-${e.slice(12,16)}-${e.slice(16,20)}-${e.slice(20,32)}`}var G=2;function b(s){let e=s.attributes,t=s.name,n=P(t),o=s.startTime[0]*1e3+s.startTime[1]/1e6,r=s.endTime[0]*1e3+s.endTime[1]/1e6,i=Math.round(r-o),m=s.parentSpanContext?.spanId,c={spanId:O(s.spanContext().spanId),parentSpanId:m?O(m):void 0,name:t,type:n,startedAt:new Date(o).toISOString(),endedAt:new Date(r).toISOString(),durationMs:i,status:s.status.code===G?"error":"completed",statusMessage:s.status.message||void 0};if(n==="tool_call"){c.toolName=e["ai.toolCall.name"]||void 0;let l=e["ai.toolCall.args"];if(l)try{let d=JSON.parse(l);c.toolArguments=_(d,512e3);}catch{c.toolArguments={raw:l.slice(0,512e3)};}let u=e["ai.toolCall.result"];if(u)try{let d=JSON.parse(u),f;Array.isArray(d)?f={items:d}:typeof d=="object"&&d!==null?f=d:f={value:d},c.toolResult=_(f,512e3);}catch{c.toolResult={raw:u.slice(0,512e3)};}}if(n==="llm_call"||n==="agent"){c.model=e["gen_ai.request.model"]||e["ai.model.id"]||void 0;let l=e["gen_ai.usage.input_tokens"],u=e["gen_ai.usage.output_tokens"];l!==void 0&&(c.promptTokens=l),u!==void 0&&(c.completionTokens=u);}if(n==="agent"){let l=e["ai.response.text"];l&&(c.output={text:l});}let S={};for(let[l,u]of Object.entries(e))l.startsWith("ai.toolCall.")||l.startsWith("gen_ai.usage.")||l==="ai.response.text"||l==="ai.model.id"||l==="gen_ai.request.model"||(S[l]=u);return Object.keys(S).length>0&&(c.metadata=S),c}function w(s){let e=0,t=0;for(let n of s)n.promptTokens!=null&&(e+=n.promptTokens),n.completionTokens!=null&&(t+=n.completionTokens);return {prompt:e,completion:t,total:e+t}}var I=class{constructor(e){this.pendingSpans=new Map;this.cleanupInterval=null;this.MAX_PENDING_TRACES=1e4;this.PENDING_TTL_MS=300*1e3;this.tracer=new y({apiKey:e.apiKey,endpoint:e.endpoint,debug:e.debug,batch:{maxSize:1,flushIntervalMs:10}}),this.debug=e.debug??false,this.cleanupInterval=setInterval(()=>this.cleanupStaleTraces(),this.PENDING_TTL_MS/2);}cleanupStaleTraces(){let e=Date.now(),t=0;for(let[n,o]of this.pendingSpans)e-o.firstSpanTime>this.PENDING_TTL_MS&&(this.pendingSpans.delete(n),t++);t>0&&this.debug&&console.log(`[TeckelSpanProcessor] Evicted ${t} stale traces due to TTL`);}onStart(e,t){}onEnd(e){let t=e.spanContext().traceId,n=b(e),o=Date.now();if(this.pendingSpans.size>=this.MAX_PENDING_TRACES&&!this.pendingSpans.has(t)){let i=this.pendingSpans.keys().next().value;i&&(this.pendingSpans.delete(i),this.debug&&console.log("[TeckelSpanProcessor] Evicted oldest pending trace due to capacity:",i));}let r=this.pendingSpans.get(t);if(r?r.spans.push(n):this.pendingSpans.set(t,{spans:[n],firstSpanTime:o}),this.isRootSpan(e)){let i=this.pendingSpans.get(t);i&&(this.sendTrace(t,e,i.spans),this.pendingSpans.delete(t));}}isRootSpan(e){let t=e.name;return t==="ai.generateText"||t==="ai.streamText"||t==="ai.generateObject"}sendTrace(e,t,n){let o=t.attributes,r="",i,m=o["ai.prompt.messages"],c=o["ai.prompt"];if(m)try{let h=JSON.parse(m);for(let g of h)if(g.role==="system"){i=g.content;break}for(let g=h.length-1;g>=0;g--)if(h[g].role==="user"){r=h[g].content;break}}catch{r=m;}else c&&(r=c);let S=o["ai.response.text"]||"",l=o["ai.telemetry.functionId"]||"unknown",u=o["gen_ai.request.model"]||o["ai.model.id"]||void 0,d=t.startTime[0]*1e3+t.startTime[1]/1e6,f=t.endTime[0]*1e3+t.endTime[1]/1e6,D=Math.round(f-d),k=w(n),R=o["ai.telemetry.metadata.sessionId"],N=o["ai.telemetry.metadata.userId"],v={};for(let[h,g]of Object.entries(o))if(h.startsWith("ai.telemetry.metadata.")&&h!=="ai.telemetry.metadata.sessionId"&&h!=="ai.telemetry.metadata.userId"){let $=h.replace("ai.telemetry.metadata.","");v[$]=g;}this.debug&&console.log("[TeckelSpanProcessor] Sending trace:",{traceId:e,agentName:l,spanCount:n.length,tokens:k}),this.tracer.trace({query:r,response:S,agentName:l,model:u,latencyMs:D,tokens:k.total>0?k:void 0,spans:n,sessionId:R,userId:N,systemPrompt:i,metadata:Object.keys(v).length>0?v:void 0});}forceFlush(){return this.tracer.flush()}shutdown(){return this.cleanupInterval&&(clearInterval(this.cleanupInterval),this.cleanupInterval=null),this.tracer.flush()}};var A=class{constructor(){this.spans=[];this.debug=false;}setDebug(e){this.debug=e;}export(e,t){this.debug&&console.log("[TeckelSpanCollector] Exporter.export called",{spanCount:e.length,spanNames:e.map(n=>n.name)}),this.spans.push(...e),t({code:0});}shutdown(){return Promise.resolve()}getSpans(){return this.spans}clear(){this.spans=[];}},B=class{constructor(e){let{BasicTracerProvider:t,SimpleSpanProcessor:n,AlwaysOnSampler:o}=F("@opentelemetry/sdk-trace-base");this.exporter=new A,e?.debug&&(this.exporter.setDebug(true),console.log("[TeckelSpanCollector] Debug mode enabled")),this.provider=new t({sampler:new o,spanProcessors:[new n(this.exporter)]}),e?.registerGlobal&&(trace.setGlobalTracerProvider(this.provider),e?.debug&&console.log("[TeckelSpanCollector] Registered as global tracer provider")),this.tracer=this.provider.getTracer("teckel-ai-sdk");}getTracer(){return this.tracer}async forceFlush(){await this.provider.forceFlush();}getSpans(){return this.exporter.getSpans().map(e=>b(e))}getRawSpans(){return this.exporter.getSpans()}clear(){this.exporter.clear();}async shutdown(){await this.provider.shutdown();}};
|
|
5
|
+
export{B as TeckelSpanCollector,I as TeckelSpanProcessor,w as calculateTokensFromSpans,b as convertToTeckelSpan,P as mapSpanType,O as spanIdToUuid,_ as truncateToSize};
|