diff --git a/packages/api/src/client/base.ts b/packages/api/src/client/base.ts index 135c6dd..7142f9f 100644 --- a/packages/api/src/client/base.ts +++ b/packages/api/src/client/base.ts @@ -97,6 +97,7 @@ import type { modifyWatchlistAssetsResponse, getTeamAllowanceResponse, getPermissionsResponse, + createChatCompletionOpenAIResponse, } from "../types"; import { LogLevel, type Logger, makeConsoleLogger, createFilteredLogger, noOpLogger } from "../logging"; import type { PaginatedResult, RequestOptions, ClientEventMap, ClientEventType, ClientEventHandler } from "./types"; @@ -106,12 +107,23 @@ import type { PaginatedResult, RequestOptions, ClientEventMap, ClientEventType, */ export interface AIInterface { /** - * Creates a chat completion using Messari's AI + * Creates a chat completion using OpenAI's API * @param params Parameters for the chat completion request * @param options Optional request configuration * @returns A promise resolving to the chat completion response */ - createChatCompletion(params: createChatCompletionParameters, options?: RequestOptions): Promise; + createChatCompletion(params: Omit, options?: RequestOptions): Promise; + + /** + * Creates a streaming chat completion using OpenAI's API + * @param params Parameters for the chat completion request + * @param options Optional request configuration + * @returns A promise resolving to a readable stream of chat completion chunks + */ + createChatCompletionStream( + params: Omit, + options?: RequestOptions, + ): Promise>; /** * Extracts entities from text content diff --git a/packages/api/src/client/client.ts b/packages/api/src/client/client.ts index 6d433da..bddb958 100644 --- a/packages/api/src/client/client.ts +++ b/packages/api/src/client/client.ts @@ -1,6 +1,7 @@ import { createChatCompletion, extractEntities, + createChatCompletionOpenAI, getNewsFeed, getNewsSources, getNewsFeedAssets, @@ -146,6 +147,8 @@ import type { getWatchlistResponse, updateWatchlistParameters, updateWatchlistResponse, + createChatCompletionOpenAIResponse, + createChatCompletionOpenAIParameters, } from "../types"; import type { Agent } from "node:http"; import { pick } from "../utils"; @@ -324,27 +327,197 @@ export class MessariClient extends MessariClientBase { // Check if the response is JSON or text based on Content-Type header const contentType = response.headers.get("Content-Type"); - let responseData: { data: T }; if (contentType?.toLowerCase().includes("application/json")) { - responseData = await response.json(); - } else { - responseData = { data: await response.text() } as { data: T }; + const jsonResponse = await response.json(); + // If response has data field and no error, unwrap it, otherwise use the whole response + const data = jsonResponse.data && !jsonResponse.error ? jsonResponse.data : jsonResponse; + return data as T; } - this.logger(LogLevel.DEBUG, "request success", { responseData }); + const text = await response.text(); + return text as T; + } catch (error) { + this.logger(LogLevel.ERROR, "request failed", { error }); - // Emit response event - this.emit("response", { - method, - path, - status: response.status, - data: responseData, + // Emit error event + this.emit("error", { + error: error as Error, + request: { + method, + path, + queryParams, + }, + }); + + throw error; + } + } + + private async requestStream({ method, path, body, queryParams = {}, options = {} }: RequestParameters): Promise> { + this.logger(LogLevel.DEBUG, "stream request start", { + method, + url: `${this.baseUrl}${path}`, + queryParams, + }); + + this.emit("request", { + method, + path, + queryParams, + }); + + const queryString = Object.entries(queryParams) + .filter(([_, value]) => value !== undefined) + .map(([key, value]) => { + if (Array.isArray(value)) { + return value.map((item) => `${encodeURIComponent(key)}=${encodeURIComponent(String(item))}`).join("&"); + } + return `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`; + }) + .join("&"); + + const url = `${this.baseUrl}${path}${queryString ? `?${queryString}` : ""}`; + + const headers = { + ...this.defaultHeaders, + ...options.headers, + "Accept": "text/event-stream", + "Cache-Control": "no-cache", + "Connection": "keep-alive", + }; + + const timeoutMs = options.timeoutMs || this.timeoutMs; + + try { + const response = await RequestTimeoutError.rejectAfterTimeout( + this.fetchFn(url, { + method, + headers, + body: body ? JSON.stringify(body) : undefined, + signal: options.signal, + cache: options.cache, + credentials: options.credentials, + integrity: options.integrity, + keepalive: options.keepalive, + mode: options.mode, + redirect: options.redirect, + referrer: options.referrer, + referrerPolicy: options.referrerPolicy, + // @ts-ignore - Next.js specific options + next: options.next, + // Node.js specific option + agent: this.agent, + }), + timeoutMs, + ); + + if (!response.ok) { + const errorData = await response.json(); + this.logger(LogLevel.ERROR, "request error", { + status: response.status, + statusText: response.statusText, + error: errorData, + }); + + const error = new Error(errorData.error || "An error occurred"); + + this.emit("error", { + error, + request: { + method, + path, + queryParams, + }, + }); + + throw error; + } + + // For streaming responses, return a transformed stream that parses the chunks + if (!response.body) { + throw new Error("No reader available for streaming response"); + } + + let buffer = ""; + const decoder = new TextDecoder(); + + // Create a TransformStream that will parse the raw bytes into the expected type T + const transformer = new TransformStream({ + transform: async (chunk, controller) => { + try { + // Decode the chunk and add to buffer + const text = decoder.decode(chunk, { stream: true }); + buffer += text; + + // Process any complete lines in the buffer + const lines = buffer.split("\n"); + // Keep the last potentially incomplete line in the buffer + buffer = lines.pop() || ""; + + for (const line of lines) { + if (line.startsWith("data: ")) { + const jsonData = line.slice(6).trim(); // Remove 'data: ' prefix + + // Skip [DONE] marker + if (jsonData === "[DONE]") { + continue; + } + + if (jsonData) { + try { + const parsed = JSON.parse(jsonData); + controller.enqueue(parsed as T); + } catch (e) { + this.logger(LogLevel.ERROR, "Error parsing JSON from stream", { + error: e, + data: jsonData, + }); + } + } + } else if (line.trim() && !line.startsWith(":")) { + // Try to parse non-empty lines that aren't comments + try { + const parsed = JSON.parse(line); + controller.enqueue(parsed as T); + } catch (e) { + // Not JSON, might be part of a multi-line chunk + if (line.trim()) { + this.logger(LogLevel.DEBUG, "Non-JSON line in stream", { line }); + } + } + } + } + } catch (error) { + this.logger(LogLevel.ERROR, "Error processing stream chunk", { error }); + controller.error(error); + } + }, + flush: (controller) => { + // Process any remaining data in the buffer + if (buffer.trim()) { + if (buffer.startsWith("data: ")) { + const jsonData = buffer.slice(6).trim(); + if (jsonData && jsonData !== "[DONE]") { + try { + const parsed = JSON.parse(jsonData); + controller.enqueue(parsed as T); + } catch (e) { + this.logger(LogLevel.ERROR, "Error parsing final JSON from stream", { + error: e, + data: jsonData, + }); + } + } + } + } + }, }); - return responseData.data; + // Pipe the response body through our transformer + return response.body.pipeThrough(transformer); } catch (error) { - this.logger(LogLevel.ERROR, "request failed", { error }); + this.logger(LogLevel.ERROR, "stream request failed", { error }); // Emit error event this.emit("error", { @@ -453,10 +626,16 @@ export class MessariClient extends MessariClientBase { data: responseData, }); - return { - data: responseData.data, - metadata: responseData.metadata, - }; + // If response has data field, return wrapped format, otherwise treat whole response as data + return responseData.data !== undefined + ? { + data: responseData.data, + metadata: responseData.metadata, + } + : { + data: responseData, + metadata: {} as M, + }; } catch (error) { this.logger(LogLevel.ERROR, "request with metadata failed", { error }); @@ -677,10 +856,17 @@ export class MessariClient extends MessariClientBase { public readonly ai: AIInterface = { createChatCompletion: (params: createChatCompletionParameters, options?: RequestOptions) => - this.request({ - method: createChatCompletion.method, - path: createChatCompletion.path(), - body: pick(params, createChatCompletion.bodyParams), + this.request({ + method: createChatCompletionOpenAI.method, + path: createChatCompletionOpenAI.path(), + body: pick(params, createChatCompletionOpenAI.bodyParams) as createChatCompletionOpenAIParameters & { stream: false }, + options, + }), + createChatCompletionStream: (params: createChatCompletionParameters, options?: RequestOptions) => + this.requestStream({ + method: createChatCompletionOpenAI.method, + path: createChatCompletionOpenAI.path(), + body: { ...pick(params, createChatCompletionOpenAI.bodyParams), stream: true }, options, }), extractEntities: (params: extractEntitiesParameters, options?: RequestOptions) => diff --git a/packages/api/src/types/index.ts b/packages/api/src/types/index.ts index 304a652..475f683 100644 --- a/packages/api/src/types/index.ts +++ b/packages/api/src/types/index.ts @@ -47,6 +47,21 @@ export const extractEntities = { } as const; +export type createChatCompletionOpenAIResponse = components['schemas']['ChatCompletionResponseOpenAI']; +export type createChatCompletionOpenAIError = components['schemas']['APIError']; + +export type createChatCompletionOpenAIParameters = components['schemas']['ChatCompletionRequest']; + + +export const createChatCompletionOpenAI = { + method: 'POST' as const, + pathParams: [] as const, + queryParams: [] as const, + bodyParams: ['messages', 'verbosity', 'response_format', 'inline_citations', 'stream'] as const, + path: () => '/ai/openai/chat/completions' +} as const; + + export type getAssetsV2Response = components['schemas']['V2AssetListItem'][]; export type getAssetsV2Error = components['schemas']['APIError']; diff --git a/packages/api/src/types/schema.ts b/packages/api/src/types/schema.ts index 6e42df0..e42a477 100644 --- a/packages/api/src/types/schema.ts +++ b/packages/api/src/types/schema.ts @@ -31,22 +31,40 @@ export type AssetV2Link = components['schemas']['AssetV2Link']; export type AssetV2MarketData = components['schemas']['AssetV2MarketData']; +export type Attribution = components['schemas']['Attribution']; + export type Author = components['schemas']['Author']; +export type ChartSource = components['schemas']['ChartSource']; + +export type ChartWidgetEntity = components['schemas']['ChartWidgetEntity']; + +export type ChartWidgetSpecification = components['schemas']['ChartWidgetSpecification']; + export type ChatCompletionMessage = components['schemas']['ChatCompletionMessage']; export type ChatCompletionRequest = components['schemas']['ChatCompletionRequest']; export type ChatCompletionResponse = components['schemas']['ChatCompletionResponse']; +export type ChatCompletionResponseChoiceOpenAI = components['schemas']['ChatCompletionResponseChoiceOpenAI']; + +export type ChatCompletionResponseMessageOpenAI = components['schemas']['ChatCompletionResponseMessageOpenAI']; + export type ChatCompletionResponseMetadata = components['schemas']['ChatCompletionResponseMetadata']; +export type ChatCompletionResponseMetadataV2 = components['schemas']['ChatCompletionResponseMetadataV2']; + +export type ChatCompletionResponseOpenAI = components['schemas']['ChatCompletionResponseOpenAI']; + export type CreateWatchlistRequest = components['schemas']['CreateWatchlistRequest']; export type Document = components['schemas']['Document']; export type DocumentList = components['schemas']['DocumentList']; +export type Domain = components['schemas']['Domain']; + export type Entity = components['schemas']['Entity']; export type EntityType = components['schemas']['EntityType']; @@ -123,6 +141,8 @@ export type PermissionsResponse = components['schemas']['PermissionsResponse']; export type Person = components['schemas']['Person']; +export type PointSchema = components['schemas']['PointSchema']; + export type Project = components['schemas']['Project']; export type ProjectRecapResponse = components['schemas']['ProjectRecapResponse']; @@ -143,12 +163,16 @@ export type Resource = components['schemas']['Resource']; export type SelectedEntity = components['schemas']['SelectedEntity']; +export type Series = components['schemas']['Series']; + export type Source = components['schemas']['Source']; export type SourceList = components['schemas']['SourceList']; export type SourceType = components['schemas']['SourceType']; +export type StandardSource = components['schemas']['StandardSource']; + export type SummaryResponse = components['schemas']['SummaryResponse']; export type Tag = components['schemas']['Tag']; @@ -165,6 +189,8 @@ export type TimeseriesMetadata = components['schemas']['TimeseriesMetadata']; export type TimeseriesPointSchema = components['schemas']['TimeseriesPointSchema']; +export type TimeseriesResult = components['schemas']['TimeseriesResult']; + export type TokenUnlockAllocation = components['schemas']['TokenUnlockAllocation']; export type TokenUnlockData = components['schemas']['TokenUnlockData']; diff --git a/packages/api/src/types/types.ts b/packages/api/src/types/types.ts index 43dd05a..154bd96 100644 --- a/packages/api/src/types/types.ts +++ b/packages/api/src/types/types.ts @@ -26,6 +26,16 @@ export type paths = { */ get: operations["getProjectRecap"]; }; + "/ai/openai/chat/completions": { + /** + * OpenAI-Compatible Chat Completion + * @description Creates a completion for the chat message in OpenAI-compatible format. + * Supports both streaming and non-streaming responses. + * The last message must be from the user role. + * Response is returned directly without the standard {data: } wrapper. + */ + post: operations["createChatCompletionOpenAI"]; + }; "/ai/v1/chat/completions": { /** * Chat Completion @@ -555,6 +565,8 @@ export type components = { */ volume24Hour?: number; }; + /** @description Attribution information (placeholder - add specific properties as needed) */ + Attribution: Record; Author: { /** @description Unique identifier for the author */ id: string; @@ -565,6 +577,41 @@ export type components = { /** @description Name of the author */ name: string; }; + ChartSource: { + /** @description Unique identifier for the citation */ + citationId?: number; + } & components["schemas"]["ChartWidgetSpecification"]; + ChartWidgetEntity: { + /** @description Identifier of the entity */ + entityId: string; + /** @description Type of the entity */ + entityType: string; + }; + ChartWidgetSpecification: { + /** @description Dataset identifier */ + dataset?: string; + /** + * Format: date-time + * @description End time for the chart data + */ + end?: string; + /** @description Array of entities for the chart */ + entities?: components["schemas"]["ChartWidgetEntity"][]; + /** @description Data granularity */ + granularity?: string; + /** @description The ID for the widget */ + id?: number; + /** @description Metric identifier */ + metric?: string; + metricTimeseries?: components["schemas"]["TimeseriesResult"]; + /** + * Format: date-time + * @description Start time for the chart data + */ + start?: string; + /** @description Tier information */ + tier?: string; + }; ChatCompletionMessage: { /** @description The message content */ content: string; @@ -602,10 +649,59 @@ export type components = { /** @description Array of response messages */ messages: components["schemas"]["ChatCompletionMessage"][]; }; + ChatCompletionResponseChoiceOpenAI: { + delta?: { + /** @description The content of the message */ + content?: string; + }; + /** @description Reason the completion finished */ + finish_reason: string; + /** @description Index of the choice in the array */ + index: number; + message: components["schemas"]["ChatCompletionResponseMessageOpenAI"]; + }; + ChatCompletionResponseMessageOpenAI: { + /** @description The message content */ + content: string; + /** + * @description The role of the message sender + * @enum {string} + */ + role: "system" | "user" | "assistant"; + }; ChatCompletionResponseMetadata: { /** @description Current status of the chat completion */ status: string; }; + ChatCompletionResponseMetadataV2: { + /** @description Array of charts referenced in the response */ + charts?: components["schemas"]["ChartSource"][]; + /** @description Array of sources cited in the response */ + cited_sources?: components["schemas"]["StandardSource"][]; + /** @description Current status of the chat completion */ + status: string; + /** + * Format: uuid + * @description Unique trace ID for the request + */ + trace_id: string; + }; + ChatCompletionResponseOpenAI: { + /** @description Array of completion choices */ + choices: components["schemas"]["ChatCompletionResponseChoiceOpenAI"][]; + /** + * Format: int64 + * @description Unix timestamp of when the completion was created + */ + created: number; + /** @description Unique identifier for the completion */ + id: string; + metadata?: components["schemas"]["ChatCompletionResponseMetadataV2"]; + /** @description The model used for completion */ + model: string; + /** @description Object type, always "chat.completion" */ + object: string; + }; CreateWatchlistRequest: { assetIds: string[]; title: string; @@ -637,6 +733,8 @@ export type components = { }; /** @description List of news documents */ DocumentList: components["schemas"]["Document"][]; + /** @description Domain information (placeholder - add specific properties as needed) */ + Domain: Record; Entity: { /** * Format: float @@ -1177,6 +1275,21 @@ export type components = { }; /** @description Person details (to be defined) */ Person: Record; + PointSchema: { + attribution?: components["schemas"]["Attribution"][]; + description?: string; + format?: string; + /** @description Aggregate operation performed for the group */ + group_aggregate_operation?: string; + /** @description Deprecated - Use slug instead */ + id?: string; + is_timestamp?: boolean; + name?: string; + slug?: string; + subcategory?: string; + /** @description Aggregate operation performed for the time bucket */ + time_bucket_aggregate_operation?: string; + }; Project: { /** @description Category of the project */ category?: string; @@ -1363,6 +1476,13 @@ export type components = { name?: string; relevanceScore?: string; }; + Series: { + entity?: { + [key: string]: unknown; + }; + key: string; + points: Record[][]; + }; Source: { /** * Format: uuid @@ -1381,6 +1501,15 @@ export type components = { * @enum {string} */ SourceType: "News" | "Forum" | "Blog"; + StandardSource: { + /** @description Unique identifier for the citation */ + citationId?: number; + domain?: components["schemas"]["Domain"]; + /** @description Title of the source */ + title?: string; + /** @description URL of the source */ + url?: string; + }; /** @description Summary information */ SummaryResponse: { summary?: string; @@ -1428,6 +1557,10 @@ export type components = { /** @description Slug of the metric */ slug: string; }; + TimeseriesResult: { + point_schema: components["schemas"]["PointSchema"][]; + series: components["schemas"]["Series"][]; + }; TokenUnlockAllocation: { allocationRecipientCount?: number; allocations?: { @@ -1972,6 +2105,45 @@ export type operations = { }; }; }; + /** + * OpenAI-Compatible Chat Completion + * @description Creates a completion for the chat message in OpenAI-compatible format. + * Supports both streaming and non-streaming responses. + * The last message must be from the user role. + * Response is returned directly without the standard {data: } wrapper. + */ + createChatCompletionOpenAI: { + parameters: { + header: { + "x-messari-api-key": components["parameters"]["apiKey"]; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ChatCompletionRequest"]; + }; + }; + responses: { + /** @description Client error response */ + "4XX": { + content: { + "application/json": components["schemas"]["APIError"]; + }; + }; + /** @description Successful response */ + 200: { + content: { + "application/json": components["schemas"]["ChatCompletionResponseOpenAI"]; + }; + }; + /** @description Server error response */ + 500: { + content: { + "application/json": components["schemas"]["APIError"]; + }; + }; + }; + }; /** * Chat Completion * @description Creates a completion for the chat message. Supports both streaming and non-streaming responses. diff --git a/packages/examples/src/ai.ts b/packages/examples/src/ai.ts index ad0c047..59b9c29 100644 --- a/packages/examples/src/ai.ts +++ b/packages/examples/src/ai.ts @@ -19,77 +19,61 @@ const client = new MessariClient({ apiKey: API_KEY, }); -async function main() { - try { - console.log("--------------------------------"); - console.log("AI Chat Completion"); - console.log("--------------------------------"); - console.log("Sending request..."); - console.log(`"What companies have both paradigm and a16z on their cap table?"`); - - // Call the createChatCompletion endpoint - const response = await client.ai.createChatCompletion({ - messages: [ - { - role: "user", - content: "What companies have both paradigm and a16z on their cap table?", - }, - ], - verbosity: "succinct", - response_format: "plaintext", - inline_citations: false, - stream: false, - }); +// Get command line arguments +const args = process.argv.slice(2); +const useStreaming = args.includes("stream"); - const assistantMessage = response.messages[0].content; - console.log(assistantMessage); - } catch (error) { - console.error("Error calling createChatCompletion:", error); - } - - // Call the createChatCompletion endpoint with streaming +async function main() { + // OpenAI Chat Completion try { console.log("\n--------------------------------"); - console.log("AI Chat Completion Streaming"); + console.log("OpenAI Chat Completion (Streaming)"); console.log("--------------------------------"); console.log("Sending request..."); - console.log(`"What is the all time high price of Bitcoin?"`); + console.log(`"What are the key differences between Bitcoin and Ethereum?"`); - // Call the createChatCompletion endpoint - const response = await client.ai.createChatCompletion({ - messages: [ - { - role: "user", - content: "What is the all time high price of Bitcoin?", - }, - ], - verbosity: "succinct", - response_format: "plaintext", - inline_citations: false, - stream: true, - }); - - // Treat the combined streamed Server-Sent Events (SSE) chunks as a single string - const rawResponse = response as unknown as string; - const chunks = rawResponse.split("\n\n").filter((line) => line.trim() !== ""); + if (useStreaming) { + // Call the createChatCompletionOpenAI endpoint + const response = await client.ai.createChatCompletionStream({ + messages: [ + { + role: "user", + content: "What are the key differences between Bitcoin and Ethereum?", + }, + ], + verbosity: "succinct", + response_format: "plaintext", + inline_citations: false, + }); - let content = ""; - for (const chunk of chunks) { - const dataMatch = chunk.match(/data: ({.*})/); - if (dataMatch) { - try { - const data = JSON.parse(dataMatch[1]); - if (data.data?.messages?.[0]?.delta?.content) { - content += data.data.messages[0].delta.content; - } - } catch (e) { - console.error("Error parsing SSE message:", e); + // Process the stream and progressively print out the text + process.stdout.write("Response: "); + for await (const chunk of response) { + if (chunk.choices.length > 0 && chunk.choices[0].delta?.content) { + const content = chunk.choices[0].delta.content; + process.stdout.write(content); } } + process.stdout.write("\n"); + + console.log("\n"); + } else { + const response = await client.ai.createChatCompletion({ + messages: [ + { + role: "user", + content: "What are the key differences between Bitcoin and Ethereum?", + }, + ], + verbosity: "succinct", + response_format: "plaintext", + inline_citations: false, + }); + console.log("Response received:"); + console.log(response); } - console.log(content); } catch (error) { - console.error("Error calling createChatCompletion:", error); + console.error("Error calling createChatCompletionOpenAI:", error); } // Entity Extraction diff --git a/typegen/openapi/index.yaml b/typegen/openapi/index.yaml index d133ea4..a0003ad 100644 --- a/typegen/openapi/index.yaml +++ b/typegen/openapi/index.yaml @@ -55,6 +55,8 @@ paths: $ref: "./services/ai/openapi.yaml#/paths/~1ai~1v1~1chat~1completions" /ai/v1/classification/extraction: $ref: "./services/ai/openapi.yaml#/paths/~1ai~1v1~1classification~1extraction" + /ai/openai/chat/completions: + $ref: "./services/ai/openapi.yaml#/paths/~1ai~1openai~1chat~1completions" # Asset Service Paths /metrics/v2/assets: diff --git a/typegen/openapi/services/ai/openapi.yaml b/typegen/openapi/services/ai/openapi.yaml index f36ed9b..f5cd9d2 100644 --- a/typegen/openapi/services/ai/openapi.yaml +++ b/typegen/openapi/services/ai/openapi.yaml @@ -123,6 +123,51 @@ paths: schema: $ref: '../../common/components.yaml#/components/schemas/APIError' + /ai/openai/chat/completions: + post: + operationId: createChatCompletionOpenAI + summary: OpenAI-Compatible Chat Completion + description: | + Creates a completion for the chat message in OpenAI-compatible format. + Supports both streaming and non-streaming responses. + The last message must be from the user role. + Response is returned directly without the standard {data: } wrapper. + tags: + - Chat + security: + - apiKey: [] + parameters: + - $ref: '../../common/parameters.yaml#/components/parameters/apiKey' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ChatCompletionRequest' + responses: + '200': + description: Successful response + content: + application/json: + schema: + $ref: '#/components/schemas/ChatCompletionResponseOpenAI' + text/event-stream: + schema: + type: string + description: Server-sent events stream for chat completion + '4XX': + description: Client error response + content: + application/json: + schema: + $ref: '../../common/components.yaml#/components/schemas/APIError' + '500': + description: Server error response + content: + application/json: + schema: + $ref: '../../common/components.yaml#/components/schemas/APIError' + components: schemas: ChatCompletionMessage: @@ -295,6 +340,239 @@ components: type: string description: Current status of the extraction request + ChatCompletionResponseOpenAI: + type: object + required: + - id + - object + - created + - model + - choices + properties: + id: + type: string + description: Unique identifier for the completion + object: + type: string + description: Object type, always "chat.completion" + created: + type: integer + format: int64 + description: Unix timestamp of when the completion was created + model: + type: string + description: The model used for completion + choices: + type: array + items: + $ref: '#/components/schemas/ChatCompletionResponseChoiceOpenAI' + description: Array of completion choices + metadata: + $ref: '#/components/schemas/ChatCompletionResponseMetadataV2' + + ChatCompletionResponseChoiceOpenAI: + type: object + required: + - index + - message + - finish_reason + properties: + index: + type: integer + description: Index of the choice in the array + message: + $ref: '#/components/schemas/ChatCompletionResponseMessageOpenAI' + delta: + type: object + properties: + content: + type: string + description: The content of the message + finish_reason: + type: string + description: Reason the completion finished + + ChatCompletionResponseMessageOpenAI: + type: object + required: + - role + - content + properties: + role: + type: string + enum: [system, user, assistant] + description: The role of the message sender + content: + type: string + description: The message content + + ChatCompletionResponseMetadataV2: + type: object + required: + - status + - trace_id + properties: + status: + type: string + description: Current status of the chat completion + trace_id: + type: string + format: uuid + description: Unique trace ID for the request + cited_sources: + type: array + items: + $ref: '#/components/schemas/StandardSource' + description: Array of sources cited in the response + charts: + type: array + items: + $ref: '#/components/schemas/ChartSource' + description: Array of charts referenced in the response + + StandardSource: + type: object + properties: + citationId: + type: integer + description: Unique identifier for the citation + domain: + $ref: '#/components/schemas/Domain' + title: + type: string + description: Title of the source + url: + type: string + description: URL of the source + + Domain: + type: object + description: Domain information (placeholder - add specific properties as needed) + + ChartSource: + type: object + allOf: + - type: object + properties: + citationId: + type: integer + description: Unique identifier for the citation + - $ref: '#/components/schemas/ChartWidgetSpecification' + + ChartWidgetSpecification: + type: object + properties: + id: + type: integer + description: The ID for the widget + entities: + type: array + items: + $ref: '#/components/schemas/ChartWidgetEntity' + description: Array of entities for the chart + dataset: + type: string + description: Dataset identifier + metric: + type: string + description: Metric identifier + start: + type: string + format: date-time + description: Start time for the chart data + end: + type: string + format: date-time + description: End time for the chart data + tier: + type: string + description: Tier information + metricTimeseries: + $ref: '#/components/schemas/TimeseriesResult' + granularity: + type: string + description: Data granularity + + ChartWidgetEntity: + type: object + required: + - entityType + - entityId + properties: + entityType: + type: string + description: Type of the entity + entityId: + type: string + description: Identifier of the entity + + TimeseriesResult: + type: object + required: + - point_schema + - series + properties: + point_schema: + type: array + items: + $ref: '#/components/schemas/PointSchema' + series: + type: array + items: + $ref: '#/components/schemas/Series' + + PointSchema: + type: object + properties: + id: + type: string + description: Deprecated - Use slug instead + name: + type: string + slug: + type: string + description: + type: string + is_timestamp: + type: boolean + format: + type: string + time_bucket_aggregate_operation: + type: string + description: Aggregate operation performed for the time bucket + group_aggregate_operation: + type: string + description: Aggregate operation performed for the group + subcategory: + type: string + attribution: + type: array + items: + $ref: '#/components/schemas/Attribution' + + Attribution: + type: object + description: Attribution information (placeholder - add specific properties as needed) + + Series: + type: object + required: + - key + - points + properties: + key: + type: string + entity: + type: object + additionalProperties: true + points: + type: array + items: + type: array + items: + type: object + description: Can be any type + securitySchemes: ApiKeyAuth: type: apiKey