chore: improve upon mcp prompts (#494)

* chore: improve upon mcp prompts

* Update api/mcp_server/instructions.py

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
This commit is contained in:
Abhishek 2026-07-03 18:14:03 +05:30 committed by GitHub
parent 88f4477edb
commit 79a4a3c9f1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
39 changed files with 3890 additions and 4744 deletions

View file

@ -49,12 +49,16 @@ export default withSentryConfig(nextConfig, {
// side errors will fail.
tunnelRoute: "/monitoring",
// Automatically tree-shake Sentry logger statements to reduce bundle size
disableLogger: true,
webpack: {
// Automatically tree-shake Sentry logger statements to reduce bundle size
treeshake: {
removeDebugLogging: true,
},
// Enables automatic instrumentation of Vercel Cron Monitors. (Does not yet work with App Router route handlers.)
// See the following for more information:
// https://docs.sentry.io/product/crons/
// https://vercel.com/docs/cron-jobs
automaticVercelMonitors: true,
// Enables automatic instrumentation of Vercel Cron Monitors. (Does not yet work with App Router route handlers.)
// See the following for more information:
// https://docs.sentry.io/product/crons/
// https://vercel.com/docs/cron-jobs
automaticVercelMonitors: true,
},
});

View file

@ -1,10 +1,22 @@
import { defineConfig } from '@hey-api/openapi-ts';
import { loadEnvConfig } from '@next/env';
// Load .env.local / .env the same way Next.js does, so client generation targets
// the backend THIS worktree actually runs on (per-worktree BACKEND_URL set by
// scripts/worktree-assign-port.sh). Falls back to the default dev port if unset.
loadEnvConfig(process.cwd());
const backendUrl = (
process.env.BACKEND_URL ||
process.env.NEXT_PUBLIC_BACKEND_URL ||
'http://127.0.0.1:8000'
).replace(/\/+$/, '');
export default defineConfig({
input: 'http://127.0.0.1:8000/api/v1/openapi.json',
input: `${backendUrl}/api/v1/openapi.json`,
output: 'src/client',
plugins: [{
name: '@hey-api/client-fetch',
runtimeConfigPath: '../lib/apiClient',
runtimeConfigPath: './src/lib/apiClient',
}],
});

6878
ui/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -30,7 +30,7 @@
"@radix-ui/react-switch": "^1.1.4",
"@radix-ui/react-tabs": "^1.1.13",
"@radix-ui/react-tooltip": "^1.2.8",
"@sentry/nextjs": "^9.28.1",
"@sentry/nextjs": "^10.63.0",
"@stackframe/stack": "^2.8.80",
"@xyflow/react": "^12.10.2",
"class-variance-authority": "^0.7.1",
@ -60,7 +60,7 @@
},
"devDependencies": {
"@eslint/eslintrc": "^3",
"@hey-api/openapi-ts": "^0.95.0",
"@hey-api/openapi-ts": "^0.99.0",
"@tailwindcss/postcss": "^4",
"@types/node": "^20",
"@types/react": "^19",

View file

@ -679,11 +679,11 @@ export const useWebSocketRTC = ({ workflowId, workflowRunId, accessToken, initia
if (turnResponse.data) {
turnCredentialsRef.current = turnResponse.data;
logger.info(`TURN credentials obtained, TTL: ${turnResponse.data.ttl}s`);
} else if (turnResponse.response.status === 503) {
} else if (turnResponse.response?.status === 503) {
// TURN not configured on server - this is OK, we'll use STUN only
logger.info('TURN server not configured, using STUN only');
} else {
logger.warn(`Failed to fetch TURN credentials: ${turnResponse.response.status}`);
logger.warn(`Failed to fetch TURN credentials: ${turnResponse.response?.status}`);
}
} catch (e) {
logger.warn('Failed to fetch TURN credentials, continuing without TURN:', e);

View file

@ -2,7 +2,7 @@
import { createClientConfig } from '../lib/apiClient';
import { type ClientOptions, type Config, createClient, createConfig } from './client';
import { type Client, type ClientOptions, type Config, createClient, createConfig } from './client';
import type { ClientOptions as ClientOptions2 } from './types.gen';
/**
@ -15,4 +15,4 @@ import type { ClientOptions as ClientOptions2 } from './types.gen';
*/
export type CreateClientConfig<T extends ClientOptions = ClientOptions2> = (override?: Config<ClientOptions & T>) => Config<Required<ClientOptions> & T>;
export const client = createClient(createClientConfig(createConfig<ClientOptions2>({ baseUrl: 'https://app.dograh.com' })));
export const client: Client = createClient(createClientConfig(createConfig<ClientOptions2>({ baseUrl: 'https://app.dograh.com' })));

View file

@ -48,10 +48,7 @@ export const createClient = (config: Config = {}): Client => {
};
if (opts.security) {
await setAuthParams({
...opts,
security: opts.security,
});
await setAuthParams(opts);
}
if (opts.requestValidator) {
@ -75,171 +72,154 @@ export const createClient = (config: Config = {}): Client => {
};
const request: Client['request'] = async (options) => {
const { opts, url } = await beforeRequest(options);
const requestInit: ReqInit = {
redirect: 'follow',
...opts,
body: getValidRequestBody(opts),
};
const throwOnError = options.throwOnError ?? _config.throwOnError;
const responseStyle = options.responseStyle ?? _config.responseStyle;
let request = new Request(url, requestInit);
for (const fn of interceptors.request.fns) {
if (fn) {
request = await fn(request, opts);
}
}
// fetch must be assigned here, otherwise it would throw the error:
// TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation
const _fetch = opts.fetch!;
let response: Response;
let request: Request | undefined;
let response: Response | undefined;
try {
response = await _fetch(request);
} catch (error) {
// Handle fetch exceptions (AbortError, network errors, etc.)
let finalError = error;
const { opts, url } = await beforeRequest(options);
const requestInit: ReqInit = {
redirect: 'follow',
...opts,
body: getValidRequestBody(opts),
};
for (const fn of interceptors.error.fns) {
request = new Request(url, requestInit);
for (const fn of interceptors.request.fns) {
if (fn) {
finalError = (await fn(error, undefined as any, request, opts)) as unknown;
request = await fn(request, opts);
}
}
finalError = finalError || ({} as unknown);
// fetch must be assigned here, otherwise it would throw the error:
// TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation
const _fetch = opts.fetch!;
if (opts.throwOnError) {
throw finalError;
response = await _fetch(request);
for (const fn of interceptors.response.fns) {
if (fn) {
response = await fn(response, request, opts);
}
}
// Return error response
return opts.responseStyle === 'data'
? undefined
: {
error: finalError,
request,
response: undefined as any,
};
}
const result = {
request,
response,
};
for (const fn of interceptors.response.fns) {
if (fn) {
response = await fn(response, request, opts);
}
}
if (response.ok) {
const parseAs =
(opts.parseAs === 'auto'
? getParseAs(response.headers.get('Content-Type'))
: opts.parseAs) ?? 'json';
const result = {
request,
response,
};
if (response.status === 204 || response.headers.get('Content-Length') === '0') {
let emptyData: any;
switch (parseAs) {
case 'arrayBuffer':
case 'blob':
case 'text':
emptyData = await response[parseAs]();
break;
case 'formData':
emptyData = new FormData();
break;
case 'stream':
emptyData = response.body;
break;
case 'json':
default:
emptyData = {};
break;
}
return opts.responseStyle === 'data'
? emptyData
: {
data: emptyData,
...result,
};
}
if (response.ok) {
const parseAs =
(opts.parseAs === 'auto'
? getParseAs(response.headers.get('Content-Type'))
: opts.parseAs) ?? 'json';
if (response.status === 204 || response.headers.get('Content-Length') === '0') {
let emptyData: any;
let data: any;
switch (parseAs) {
case 'arrayBuffer':
case 'blob':
case 'text':
emptyData = await response[parseAs]();
break;
case 'formData':
emptyData = new FormData();
case 'text':
data = await response[parseAs]();
break;
case 'json': {
// Some servers return 200 with no Content-Length and empty body.
// response.json() would throw; read as text and parse if non-empty.
const text = await response.text();
data = text ? JSON.parse(text) : {};
break;
}
case 'stream':
emptyData = response.body;
break;
case 'json':
default:
emptyData = {};
break;
return opts.responseStyle === 'data'
? response.body
: {
data: response.body,
...result,
};
}
if (parseAs === 'json') {
if (opts.responseValidator) {
await opts.responseValidator(data);
}
if (opts.responseTransformer) {
data = await opts.responseTransformer(data);
}
}
return opts.responseStyle === 'data'
? emptyData
? data
: {
data: emptyData,
data,
...result,
};
}
let data: any;
switch (parseAs) {
case 'arrayBuffer':
case 'blob':
case 'formData':
case 'text':
data = await response[parseAs]();
break;
case 'json': {
// Some servers return 200 with no Content-Length and empty body.
// response.json() would throw; read as text and parse if non-empty.
const text = await response.text();
data = text ? JSON.parse(text) : {};
break;
}
case 'stream':
return opts.responseStyle === 'data'
? response.body
: {
data: response.body,
...result,
};
const textError = await response.text();
let jsonError: unknown;
try {
jsonError = JSON.parse(textError);
} catch {
// noop
}
if (parseAs === 'json') {
if (opts.responseValidator) {
await opts.responseValidator(data);
}
throw jsonError ?? textError;
} catch (error) {
let finalError = error;
if (opts.responseTransformer) {
data = await opts.responseTransformer(data);
for (const fn of interceptors.error.fns) {
if (fn) {
finalError = await fn(finalError, response, request, options as ResolvedRequestOptions);
}
}
return opts.responseStyle === 'data'
? data
finalError = finalError || {};
if (throwOnError) {
throw finalError;
}
// TODO: we probably want to return error and improve types
return responseStyle === 'data'
? undefined
: {
data,
...result,
error: finalError,
request,
response,
};
}
const textError = await response.text();
let jsonError: unknown;
try {
jsonError = JSON.parse(textError);
} catch {
// noop
}
const error = jsonError ?? textError;
let finalError = error;
for (const fn of interceptors.error.fns) {
if (fn) {
finalError = (await fn(error, response, request, opts)) as string;
}
}
finalError = finalError || ({} as string);
if (opts.throwOnError) {
throw finalError;
}
// TODO: we probably want to return error and improve types
return opts.responseStyle === 'data'
? undefined
: {
error: finalError,
...result,
};
};
const makeMethodFn = (method: Uppercase<HttpMethod>) => (options: RequestOptions) =>
@ -250,7 +230,6 @@ export const createClient = (config: Config = {}): Client => {
return createSseClient({
...opts,
body: opts.body as BodyInit | null | undefined,
headers: opts.headers as unknown as Record<string, string>,
method,
onRequest: async (url, init) => {
let request = new Request(url, init);

View file

@ -9,6 +9,8 @@ export {
} from '../core/bodySerializer.gen';
export { buildClientParams } from '../core/params.gen';
export { serializeQueryKeyValue } from '../core/queryKeySerializer.gen';
export type { ServerSentEventsResult } from '../core/serverSentEvents.gen';
export type { ClientMeta } from '../core/types.gen';
export { createClient } from './client.gen';
export type {
Client,

View file

@ -93,6 +93,7 @@ export interface ResolvedRequestOptions<
ThrowOnError extends boolean = boolean,
Url extends string = string,
> extends RequestOptions<unknown, TResponseStyle, ThrowOnError, Url> {
headers: Headers;
serializedBody?: string;
}
@ -126,8 +127,10 @@ export type RequestResult<
error: TError extends Record<string, unknown> ? TError[keyof TError] : TError;
}
) & {
request: Request;
response: Response;
/** request may be undefined, because error may be from building the request object itself */
request?: Request;
/** response may be undefined, because error may be from building the request object itself or from a network error */
response?: Response;
}
>;
@ -148,12 +151,13 @@ type MethodFn = <
type SseFn = <
TData = unknown,
TError = unknown,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_TError = unknown,
ThrowOnError extends boolean = false,
TResponseStyle extends ResponseStyle = 'fields',
>(
options: Omit<RequestOptions<never, TResponseStyle, ThrowOnError>, 'method'>,
) => Promise<ServerSentEventsResult<TData, TError>>;
) => Promise<ServerSentEventsResult<TData>>;
type RequestFn = <
TData = unknown,

View file

@ -14,8 +14,8 @@ import type { Client, ClientOptions, Config, RequestOptions } from './types.gen'
export const createQuerySerializer = <T = unknown>({
parameters = {},
...args
}: QuerySerializerOptions = {}) => {
const querySerializer = (queryParams: T) => {
}: QuerySerializerOptions = {}): ((queryParams: T) => string) => {
const querySerializer = (queryParams: T): string => {
const search: string[] = [];
if (queryParams && typeof queryParams === 'object') {
for (const name in queryParams) {
@ -118,14 +118,12 @@ const checkForExistence = (
return false;
};
export const setAuthParams = async ({
security,
...options
}: Pick<Required<RequestOptions>, 'security'> &
Pick<RequestOptions, 'auth' | 'query'> & {
export async function setAuthParams(
options: Pick<RequestOptions, 'auth' | 'query' | 'security'> & {
headers: Headers;
}) => {
for (const auth of security) {
},
): Promise<void> {
for (const auth of options.security ?? []) {
if (checkForExistence(options, auth.name)) {
continue;
}
@ -154,7 +152,7 @@ export const setAuthParams = async ({
break;
}
}
};
}
export const buildUrl: Client['buildUrl'] = (options) =>
getUrl({
@ -218,8 +216,10 @@ export const mergeHeaders = (
type ErrInterceptor<Err, Res, Req, Options> = (
error: Err,
response: Res,
request: Req,
/** response may be undefined due to a network error where no response object is produced */
response: Res | undefined,
/** request may be undefined, because error may be from building the request object itself */
request: Req | undefined,
options: Options,
) => Err | Promise<Err>;

View file

@ -9,6 +9,13 @@ export interface Auth {
* @default 'header'
*/
in?: 'header' | 'query' | 'cookie';
/**
* A unique identifier for the security scheme.
*
* Defined only when there are multiple security schemes whose `Auth`
* shape would otherwise be identical.
*/
key?: string;
/**
* Header or query parameter name.
*

View file

@ -62,7 +62,7 @@ type KeyMap = Map<
}
>;
const buildKeyMap = (fields: FieldsConfig, map?: KeyMap): KeyMap => {
function buildKeyMap(fields: FieldsConfig, map?: KeyMap): KeyMap {
if (!map) {
map = new Map();
}
@ -85,33 +85,42 @@ const buildKeyMap = (fields: FieldsConfig, map?: KeyMap): KeyMap => {
}
return map;
};
}
interface Params {
body: unknown;
body?: unknown;
headers: Record<string, unknown>;
path: Record<string, unknown>;
query: Record<string, unknown>;
}
const stripEmptySlots = (params: Params) => {
function stripEmptySlots(params: Params): void {
for (const [slot, value] of Object.entries(params)) {
if (slot === 'body') continue;
if (value && typeof value === 'object' && !Array.isArray(value) && !Object.keys(value).length) {
delete params[slot as Slot];
}
}
};
}
export const buildClientParams = (args: ReadonlyArray<unknown>, fields: FieldsConfig) => {
export function buildClientParams(args: ReadonlyArray<unknown>, fields: FieldsConfig): Params {
const params: Params = {
body: {},
headers: {},
path: {},
query: {},
headers: Object.create(null),
path: Object.create(null),
query: Object.create(null),
};
const map = buildKeyMap(fields);
function writeSlot(slot: Slot, key: string, value: unknown): void {
let record = params[slot] as Record<string, unknown> | undefined;
if (record === undefined) {
record = Object.create(null) as Record<string, unknown>;
params[slot] = record;
}
record[key] = value;
}
let config: FieldsConfig[number] | undefined;
for (const [index, arg] of args.entries()) {
@ -128,7 +137,7 @@ export const buildClientParams = (args: ReadonlyArray<unknown>, fields: FieldsCo
const field = map.get(config.key)!;
const name = field.map || config.key;
if (field.in) {
(params[field.in] as Record<string, unknown>)[name] = arg;
writeSlot(field.in, name, arg);
}
} else {
params.body = arg;
@ -140,7 +149,7 @@ export const buildClientParams = (args: ReadonlyArray<unknown>, fields: FieldsCo
if (field) {
if (field.in) {
const name = field.map || key;
(params[field.in] as Record<string, unknown>)[name] = value;
writeSlot(field.in, name, value);
} else {
params[field.map] = value;
}
@ -149,11 +158,11 @@ export const buildClientParams = (args: ReadonlyArray<unknown>, fields: FieldsCo
if (extra) {
const [prefix, slot] = extra;
(params[slot] as Record<string, unknown>)[key.slice(prefix.length)] = value;
writeSlot(slot, key.slice(prefix.length), value);
} else if ('allowExtra' in config && config.allowExtra) {
for (const [slot, allowed] of Object.entries(config.allowExtra)) {
if (allowed) {
(params[slot as Slot] as Record<string, unknown>)[key] = value;
writeSlot(slot as Slot, key, value);
break;
}
}
@ -166,4 +175,4 @@ export const buildClientParams = (args: ReadonlyArray<unknown>, fields: FieldsCo
stripEmptySlots(params);
return params;
};
}

View file

@ -25,7 +25,7 @@ interface SerializePrimitiveParam extends SerializePrimitiveOptions {
value: string;
}
export const separatorArrayExplode = (style: ArraySeparatorStyle) => {
export const separatorArrayExplode = (style: ArraySeparatorStyle): '.' | ';' | ',' | '&' => {
switch (style) {
case 'label':
return '.';
@ -38,7 +38,7 @@ export const separatorArrayExplode = (style: ArraySeparatorStyle) => {
}
};
export const separatorArrayNoExplode = (style: ArraySeparatorStyle) => {
export const separatorArrayNoExplode = (style: ArraySeparatorStyle): ',' | '|' | '%20' => {
switch (style) {
case 'form':
return ',';
@ -51,7 +51,7 @@ export const separatorArrayNoExplode = (style: ArraySeparatorStyle) => {
}
};
export const separatorObjectExplode = (style: ObjectSeparatorStyle) => {
export const separatorObjectExplode = (style: ObjectSeparatorStyle): '.' | ';' | ',' | '&' => {
switch (style) {
case 'label':
return '.';
@ -72,7 +72,7 @@ export const serializeArrayParam = ({
value,
}: SerializeOptions<ArraySeparatorStyle> & {
value: unknown[];
}) => {
}): string => {
if (!explode) {
const joinedValues = (
allowReserved ? value : value.map((v) => encodeURIComponent(v as string))
@ -110,7 +110,7 @@ export const serializePrimitiveParam = ({
allowReserved,
name,
value,
}: SerializePrimitiveParam) => {
}: SerializePrimitiveParam): string => {
if (value === undefined || value === null) {
return '';
}
@ -134,7 +134,7 @@ export const serializeObjectParam = ({
}: SerializeOptions<ObjectSeparatorStyle> & {
value: Record<string, unknown> | Date;
valueOnly?: boolean;
}) => {
}): string => {
if (value instanceof Date) {
return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`;
}

View file

@ -14,7 +14,7 @@ export type JsonValue =
/**
* Replacer that converts non-JSON values (bigint, Date, etc.) to safe substitutes.
*/
export const queryKeyJsonReplacer = (_key: string, value: unknown) => {
export const queryKeyJsonReplacer = (_key: string, value: unknown): unknown | undefined => {
if (value === undefined || typeof value === 'function' || typeof value === 'symbol') {
return undefined;
}

View file

@ -79,7 +79,7 @@ export type ServerSentEventsResult<TData = unknown, TReturn = void, TNext = unkn
>;
};
export const createSseClient = <TData = unknown>({
export function createSseClient<TData = unknown>({
onRequest,
onSseError,
onSseEvent,
@ -91,7 +91,7 @@ export const createSseClient = <TData = unknown>({
sseSleepFn,
url,
...options
}: ServerSentEventsOptions): ServerSentEventsResult<TData> => {
}: ServerSentEventsOptions): ServerSentEventsResult<TData> {
let lastEventId: string | undefined;
const sleep = sseSleepFn ?? ((ms: number) => new Promise((resolve) => setTimeout(resolve, ms)));
@ -155,8 +155,7 @@ export const createSseClient = <TData = unknown>({
const { done, value } = await reader.read();
if (done) break;
buffer += value;
// Normalize line endings: CRLF -> LF, then CR -> LF
buffer = buffer.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
buffer = buffer.replace(/\r\n?/g, '\n'); // normalize line endings
const chunks = buffer.split('\n\n');
buffer = chunks.pop() ?? '';
@ -240,4 +239,4 @@ export const createSseClient = <TData = unknown>({
const stream = createStream();
return { stream };
};
}

View file

@ -91,6 +91,12 @@ export interface Config {
responseValidator?: (data: unknown) => Promise<unknown>;
}
/**
* Arbitrary metadata passed through the `meta` request option.
*/
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
export interface ClientMeta {}
type IsExactlyNeverOrNeverUndefined<T> = [T] extends [never]
? true
: [T] extends [never | undefined]

View file

@ -13,9 +13,9 @@ export interface PathSerializer {
url: string;
}
export const PATH_PARAM_RE = /\{[^{}]+\}/g;
export const PATH_PARAM_RE: RegExp = /\{[^{}]+\}/g;
export const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => {
export const defaultPathSerializer = ({ path, url: _url }: PathSerializer): string => {
let url = _url;
const matches = _url.match(PATH_PARAM_RE);
if (matches) {
@ -94,7 +94,7 @@ export const getUrl = ({
query?: Record<string, unknown>;
querySerializer: QuerySerializer;
url: string;
}) => {
}): string => {
const pathUrl = _url.startsWith('/') ? _url : `/${_url}`;
let url = (baseUrl ?? '') + pathUrl;
if (path) {
@ -114,7 +114,7 @@ export function getValidRequestBody(options: {
body?: unknown;
bodySerializer?: BodySerializer | null;
serializedBody?: unknown;
}) {
}): unknown {
const hasBody = options.body !== undefined;
const isSerializedBody = hasBody && options.bodySerializer;

File diff suppressed because one or more lines are too long

View file

@ -5804,7 +5804,7 @@ export type TransferCallConfig = {
/**
* Destination
*
* Phone number or SIP endpoint to transfer the call to, e.g. +1234567890 or PJSIP/1234.
* Phone number, SIP endpoint, or template to transfer the call to, e.g. +1234567890, PJSIP/1234, or {{initial_context.transfer_destination}}.
*/
destination: string;
/**