Skip to content

Commit 0646284

Browse files
authored
Merge pull request #1168 from narengogi/feature/v1-messages-route
Feature: Beta support for /v1/messages route
2 parents b71cfa1 + 65308ad commit 0646284

30 files changed

+2467
-154
lines changed

src/handlers/messagesHandler.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { RouterError } from '../errors/RouterError';
2+
import {
3+
constructConfigFromRequestHeaders,
4+
tryTargetsRecursively,
5+
} from './handlerUtils';
6+
import { Context } from 'hono';
7+
8+
/**
9+
* Handles the '/messages' API request by selecting the appropriate provider(s) and making the request to them.
10+
*
11+
* @param {Context} c - The Cloudflare Worker context.
12+
* @returns {Promise<Response>} - The response from the provider.
13+
* @throws Will throw an error if no provider options can be determined or if the request to the provider(s) fails.
14+
* @throws Will throw an 500 error if the handler fails due to some reasons
15+
*/
16+
export async function messagesHandler(c: Context): Promise<Response> {
17+
try {
18+
let request = await c.req.json();
19+
let requestHeaders = Object.fromEntries(c.req.raw.headers);
20+
const camelCaseConfig = constructConfigFromRequestHeaders(requestHeaders);
21+
const tryTargetsResponse = await tryTargetsRecursively(
22+
c,
23+
camelCaseConfig ?? {},
24+
request,
25+
requestHeaders,
26+
'messages',
27+
'POST',
28+
'config'
29+
);
30+
31+
return tryTargetsResponse;
32+
} catch (err: any) {
33+
console.log('messages error', err.message);
34+
let statusCode = 500;
35+
let errorMessage = 'Something went wrong';
36+
37+
if (err instanceof RouterError) {
38+
statusCode = 400;
39+
errorMessage = err.message;
40+
}
41+
42+
return new Response(
43+
JSON.stringify({
44+
status: 'failure',
45+
message: errorMessage,
46+
}),
47+
{
48+
status: statusCode,
49+
headers: {
50+
'content-type': 'application/json',
51+
},
52+
}
53+
);
54+
}
55+
}

src/handlers/responseHandlers.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
import { HookSpan } from '../middlewares/hooks';
1818
import { env } from 'hono/adapter';
1919
import { OpenAIModelResponseJSONToStreamGenerator } from '../providers/open-ai-base/createModelResponse';
20+
import { anthropicMessagesJsonToStreamGenerator } from '../providers/anthropic-base/utils/streamGenerator';
2021

2122
/**
2223
* Handles various types of responses based on the specified parameters
@@ -81,6 +82,9 @@ export async function responseHandler(
8182
responseTransformerFunction =
8283
OpenAIChatCompleteJSONToStreamResponseTransform;
8384
break;
85+
case 'messages':
86+
responseTransformerFunction = anthropicMessagesJsonToStreamGenerator;
87+
break;
8488
case 'createModelResponse':
8589
responseTransformerFunction = OpenAIModelResponseJSONToStreamGenerator;
8690
break;

src/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import { realTimeHandler } from './handlers/realtimeHandler';
3131
import filesHandler from './handlers/filesHandler';
3232
import batchesHandler from './handlers/batchesHandler';
3333
import finetuneHandler from './handlers/finetuneHandler';
34+
import { messagesHandler } from './handlers/messagesHandler';
3435

3536
// Config
3637
import conf from '../conf.json';
@@ -117,6 +118,11 @@ app.onError((err, c) => {
117118
return c.json({ status: 'failure', message: err.message });
118119
});
119120

121+
/**
122+
* POST route for '/v1/messages' in anthropic format
123+
*/
124+
app.post('/v1/messages', requestValidator, messagesHandler);
125+
120126
/**
121127
* POST route for '/v1/chat/completions'.
122128
* Handles requests by passing them to the chatCompletionsHandler.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
export const ANTHROPIC_MESSAGE_START_EVENT = JSON.stringify({
2+
type: 'message_start',
3+
message: {
4+
id: '',
5+
type: 'message',
6+
role: 'assistant',
7+
model: '',
8+
content: [],
9+
stop_reason: null,
10+
stop_sequence: null,
11+
usage: {
12+
input_tokens: 0,
13+
cache_creation_input_tokens: 0,
14+
cache_read_input_tokens: 0,
15+
output_tokens: 0,
16+
},
17+
},
18+
});
19+
20+
export const ANTHROPIC_MESSAGE_DELTA_EVENT = JSON.stringify({
21+
type: 'message_delta',
22+
delta: {
23+
stop_reason: '',
24+
stop_sequence: null,
25+
},
26+
usage: {
27+
input_tokens: 0,
28+
output_tokens: 0,
29+
cache_read_input_tokens: 0,
30+
cache_creation_input_tokens: 0,
31+
},
32+
});
33+
34+
export const ANTHROPIC_MESSAGE_STOP_EVENT = {
35+
type: 'message_stop',
36+
};
37+
38+
export const ANTHROPIC_CONTENT_BLOCK_STOP_EVENT = JSON.stringify({
39+
type: 'content_block_stop',
40+
index: 0,
41+
});
42+
43+
export const ANTHROPIC_CONTENT_BLOCK_START_EVENT = JSON.stringify({
44+
type: 'content_block_start',
45+
index: 1,
46+
// handle other content block types here
47+
content_block: {
48+
type: 'text',
49+
text: '',
50+
},
51+
});
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { ProviderConfig } from '../types';
2+
3+
export const messagesBaseConfig: ProviderConfig = {
4+
model: {
5+
param: 'model',
6+
required: true,
7+
},
8+
messages: {
9+
param: 'messages',
10+
required: true,
11+
},
12+
max_tokens: {
13+
param: 'max_tokens',
14+
required: true,
15+
},
16+
container: {
17+
param: 'container',
18+
required: false,
19+
},
20+
mcp_servers: {
21+
param: 'mcp_servers',
22+
required: false,
23+
},
24+
metadata: {
25+
param: 'metadata',
26+
required: false,
27+
},
28+
service_tier: {
29+
param: 'service_tier',
30+
required: false,
31+
},
32+
stop_sequences: {
33+
param: 'stop_sequences',
34+
required: false,
35+
},
36+
stream: {
37+
param: 'stream',
38+
required: false,
39+
},
40+
system: {
41+
param: 'system',
42+
},
43+
temperature: {
44+
param: 'temperature',
45+
required: false,
46+
},
47+
thinking: {
48+
param: 'thinking',
49+
required: false,
50+
},
51+
tool_choice: {
52+
param: 'tool_choice',
53+
required: false,
54+
},
55+
tools: {
56+
param: 'tools',
57+
required: false,
58+
},
59+
top_k: {
60+
param: 'top_k',
61+
required: false,
62+
},
63+
top_p: {
64+
param: 'top_p',
65+
required: false,
66+
},
67+
};
68+
69+
export const getMessagesConfig = ({
70+
exclude = [],
71+
defaultValues = {},
72+
extra = {},
73+
}: {
74+
exclude?: string[];
75+
defaultValues?: Record<
76+
keyof typeof messagesBaseConfig,
77+
string | number | boolean
78+
>;
79+
extra?: ProviderConfig;
80+
}): ProviderConfig => {
81+
const baseParams = { ...messagesBaseConfig };
82+
if (defaultValues) {
83+
Object.keys(defaultValues).forEach((key) => {
84+
if (!Array.isArray(baseParams[key])) {
85+
baseParams[key].default = defaultValues[key];
86+
}
87+
});
88+
}
89+
exclude.forEach((key) => {
90+
// not checking if the key exists as if it doesnt, a build failure is expected
91+
delete baseParams[key];
92+
});
93+
94+
return { ...baseParams, ...extra };
95+
};
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
export interface AnthropicMessageStartEvent {
2+
type: 'message_start';
3+
message: {
4+
id: string;
5+
type: 'message';
6+
role: 'assistant';
7+
model: string;
8+
content: any[];
9+
stop_reason: string | null;
10+
stop_sequence: string | null;
11+
usage?: {
12+
input_tokens: number;
13+
cache_creation_input_tokens: number;
14+
cache_read_input_tokens: number;
15+
output_tokens: number;
16+
};
17+
};
18+
}
19+
20+
export interface AnthropicMessageDeltaEvent {
21+
type: 'message_delta';
22+
delta: {
23+
stop_reason: string;
24+
stop_sequence: string | null;
25+
};
26+
usage: {
27+
input_tokens?: number;
28+
output_tokens: number;
29+
cache_read_input_tokens?: number;
30+
cache_creation_input_tokens?: number;
31+
};
32+
}

0 commit comments

Comments
 (0)