mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
chore: add aibridge database resources & define RBAC policies (#19796)
Closes https://github.com/coder/internal/issues/986
This commit is contained in:
Generated
+2
@@ -16001,6 +16001,7 @@ const docTemplate = `{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"*",
|
||||
"aibridge_interception",
|
||||
"api_key",
|
||||
"assign_org_role",
|
||||
"assign_role",
|
||||
@@ -16043,6 +16044,7 @@ const docTemplate = `{
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"ResourceWildcard",
|
||||
"ResourceAibridgeInterception",
|
||||
"ResourceApiKey",
|
||||
"ResourceAssignOrgRole",
|
||||
"ResourceAssignRole",
|
||||
|
||||
Generated
+2
@@ -14536,6 +14536,7 @@
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"*",
|
||||
"aibridge_interception",
|
||||
"api_key",
|
||||
"assign_org_role",
|
||||
"assign_role",
|
||||
@@ -14578,6 +14579,7 @@
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"ResourceWildcard",
|
||||
"ResourceAibridgeInterception",
|
||||
"ResourceApiKey",
|
||||
"ResourceAssignOrgRole",
|
||||
"ResourceAssignRole",
|
||||
|
||||
@@ -175,6 +175,22 @@ func (q *querier) authorizePrebuiltWorkspace(ctx context.Context, action policy.
|
||||
return xerrors.Errorf("authorize context: %w", workspaceErr)
|
||||
}
|
||||
|
||||
// authorizeAIBridgeInterceptionUpdate validates that the context's actor matches the initiator of the AIBridgeInterception.
|
||||
// This is used by all of the sub-resources which fall under the [ResourceAibridgeInterception] umbrella.
|
||||
func (q *querier) authorizeAIBridgeInterceptionUpdate(ctx context.Context, interceptionID uuid.UUID) error {
|
||||
inter, err := q.db.GetAIBridgeInterceptionByID(ctx, interceptionID)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("fetch aibridge interception %q: %w", interceptionID, err)
|
||||
}
|
||||
|
||||
err = q.authorizeContext(ctx, policy.ActionUpdate, inter.RBACObject())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type authContextKey struct{}
|
||||
|
||||
// ActorFromContext returns the authorization subject from the context.
|
||||
@@ -542,6 +558,29 @@ var (
|
||||
}),
|
||||
Scope: rbac.ScopeAll,
|
||||
}.WithCachedASTValue()
|
||||
|
||||
// See aibridged package.
|
||||
subjectAibridged = rbac.Subject{
|
||||
Type: rbac.SubjectAibridged,
|
||||
FriendlyName: "AIBridge Daemon",
|
||||
ID: uuid.Nil.String(),
|
||||
Roles: rbac.Roles([]rbac.Role{
|
||||
{
|
||||
Identifier: rbac.RoleIdentifier{Name: "aibridged"},
|
||||
DisplayName: "AIBridge Daemon",
|
||||
Site: rbac.Permissions(map[string][]policy.Action{
|
||||
rbac.ResourceUser.Type: {
|
||||
policy.ActionReadPersonal, // Required to read users' external auth links. // TODO: this is too broad; reduce scope to just external_auth_links by creating separate resource.
|
||||
},
|
||||
rbac.ResourceApiKey.Type: {policy.ActionRead}, // Validate API keys.
|
||||
rbac.ResourceAibridgeInterception.Type: {policy.ActionCreate, policy.ActionRead, policy.ActionUpdate},
|
||||
}),
|
||||
Org: map[string][]rbac.Permission{},
|
||||
User: []rbac.Permission{},
|
||||
},
|
||||
}),
|
||||
Scope: rbac.ScopeAll,
|
||||
}.WithCachedASTValue()
|
||||
)
|
||||
|
||||
// AsProvisionerd returns a context with an actor that has permissions required
|
||||
@@ -624,6 +663,12 @@ func AsUsagePublisher(ctx context.Context) context.Context {
|
||||
return As(ctx, subjectUsagePublisher)
|
||||
}
|
||||
|
||||
// AsAIBridged returns a context with an actor that has permissions
|
||||
// required for creating, reading, and updating aibridge-related resources.
|
||||
func AsAIBridged(ctx context.Context) context.Context {
|
||||
return As(ctx, subjectAibridged)
|
||||
}
|
||||
|
||||
var AsRemoveActor = rbac.Subject{
|
||||
ID: "remove-actor",
|
||||
}
|
||||
@@ -1878,6 +1923,10 @@ func (q *querier) FindMatchingPresetID(ctx context.Context, arg database.FindMat
|
||||
return q.db.FindMatchingPresetID(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) GetAIBridgeInterceptionByID(ctx context.Context, id uuid.UUID) (database.AIBridgeInterception, error) {
|
||||
return fetch(q.log, q.auth, q.db.GetAIBridgeInterceptionByID)(ctx, id)
|
||||
}
|
||||
|
||||
func (q *querier) GetAPIKeyByID(ctx context.Context, id string) (database.APIKey, error) {
|
||||
return fetch(q.log, q.auth, q.db.GetAPIKeyByID)(ctx, id)
|
||||
}
|
||||
@@ -3757,6 +3806,34 @@ func (q *querier) GetWorkspacesEligibleForTransition(ctx context.Context, now ti
|
||||
return q.db.GetWorkspacesEligibleForTransition(ctx, now)
|
||||
}
|
||||
|
||||
func (q *querier) InsertAIBridgeInterception(ctx context.Context, arg database.InsertAIBridgeInterceptionParams) (database.AIBridgeInterception, error) {
|
||||
return insert(q.log, q.auth, rbac.ResourceAibridgeInterception.WithOwner(arg.InitiatorID.String()), q.db.InsertAIBridgeInterception)(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) InsertAIBridgeTokenUsage(ctx context.Context, arg database.InsertAIBridgeTokenUsageParams) error {
|
||||
// All aibridge_token_usages records belong to the initiator of their associated interception.
|
||||
if err := q.authorizeAIBridgeInterceptionUpdate(ctx, arg.InterceptionID); err != nil {
|
||||
return err
|
||||
}
|
||||
return q.db.InsertAIBridgeTokenUsage(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) InsertAIBridgeToolUsage(ctx context.Context, arg database.InsertAIBridgeToolUsageParams) error {
|
||||
// All aibridge_tool_usages records belong to the initiator of their associated interception.
|
||||
if err := q.authorizeAIBridgeInterceptionUpdate(ctx, arg.InterceptionID); err != nil {
|
||||
return err
|
||||
}
|
||||
return q.db.InsertAIBridgeToolUsage(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) InsertAIBridgeUserPrompt(ctx context.Context, arg database.InsertAIBridgeUserPromptParams) error {
|
||||
// All aibridge_user_prompts records belong to the initiator of their associated interception.
|
||||
if err := q.authorizeAIBridgeInterceptionUpdate(ctx, arg.InterceptionID); err != nil {
|
||||
return err
|
||||
}
|
||||
return q.db.InsertAIBridgeUserPrompt(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) InsertAPIKey(ctx context.Context, arg database.InsertAPIKeyParams) (database.APIKey, error) {
|
||||
// TODO(Cian): ideally this would be encoded in the policy, but system users are just members and we
|
||||
// don't currently have a capability to conditionally deny creating resources by owner ID in a role.
|
||||
|
||||
@@ -4332,3 +4332,55 @@ func TestInsertAPIKey_AsPrebuildsUser(t *testing.T) {
|
||||
_, err := dbz.InsertAPIKey(ctx, testutil.Fake(t, faker, database.InsertAPIKeyParams{}))
|
||||
require.True(t, dbauthz.IsNotAuthorizedError(err))
|
||||
}
|
||||
|
||||
func (s *MethodTestSuite) TestAIBridge() {
|
||||
s.Run("GetAIBridgeInterceptionByID", s.Mocked(func(db *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
sessID := uuid.UUID{2}
|
||||
sess := testutil.Fake(s.T(), faker, database.AIBridgeInterception{ID: sessID})
|
||||
db.EXPECT().GetAIBridgeInterceptionByID(gomock.Any(), sessID).Return(sess, nil).AnyTimes()
|
||||
check.Args(sessID).Asserts(sess, policy.ActionRead).Returns(sess)
|
||||
}))
|
||||
|
||||
s.Run("InsertAIBridgeInterception", s.Mocked(func(db *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
initID := uuid.UUID{3}
|
||||
user := testutil.Fake(s.T(), faker, database.User{ID: initID})
|
||||
// testutil.Fake cannot distinguish between a zero value and an explicitly requested value which is equivalent.
|
||||
user.IsSystem = false
|
||||
user.Deleted = false
|
||||
|
||||
sessID := uuid.UUID{2}
|
||||
sess := testutil.Fake(s.T(), faker, database.AIBridgeInterception{ID: sessID, InitiatorID: initID})
|
||||
|
||||
params := database.InsertAIBridgeInterceptionParams{ID: sess.ID, InitiatorID: sess.InitiatorID, Provider: sess.Provider, Model: sess.Model}
|
||||
db.EXPECT().GetUserByID(gomock.Any(), initID).Return(user, nil).AnyTimes() // Validation.
|
||||
db.EXPECT().InsertAIBridgeInterception(gomock.Any(), params).Return(sess, nil).AnyTimes()
|
||||
check.Args(params).Asserts(sess, policy.ActionCreate)
|
||||
}))
|
||||
|
||||
s.Run("InsertAIBridgeTokenUsage", s.Mocked(func(db *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
sessID := uuid.UUID{2}
|
||||
sess := testutil.Fake(s.T(), faker, database.AIBridgeInterception{ID: sessID})
|
||||
params := database.InsertAIBridgeTokenUsageParams{InterceptionID: sess.ID}
|
||||
db.EXPECT().GetAIBridgeInterceptionByID(gomock.Any(), sessID).Return(sess, nil).AnyTimes() // Validation.
|
||||
db.EXPECT().InsertAIBridgeTokenUsage(gomock.Any(), params).Return(nil).AnyTimes()
|
||||
check.Args(params).Asserts(sess, policy.ActionUpdate)
|
||||
}))
|
||||
|
||||
s.Run("InsertAIBridgeToolUsage", s.Mocked(func(db *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
sessID := uuid.UUID{2}
|
||||
sess := testutil.Fake(s.T(), faker, database.AIBridgeInterception{ID: sessID})
|
||||
params := database.InsertAIBridgeToolUsageParams{InterceptionID: sess.ID}
|
||||
db.EXPECT().GetAIBridgeInterceptionByID(gomock.Any(), sessID).Return(sess, nil).AnyTimes() // Validation.
|
||||
db.EXPECT().InsertAIBridgeToolUsage(gomock.Any(), params).Return(nil).AnyTimes()
|
||||
check.Args(params).Asserts(sess, policy.ActionUpdate)
|
||||
}))
|
||||
|
||||
s.Run("InsertAIBridgeUserPrompt", s.Mocked(func(db *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
sessID := uuid.UUID{2}
|
||||
sess := testutil.Fake(s.T(), faker, database.AIBridgeInterception{ID: sessID})
|
||||
params := database.InsertAIBridgeUserPromptParams{InterceptionID: sess.ID}
|
||||
db.EXPECT().GetAIBridgeInterceptionByID(gomock.Any(), sessID).Return(sess, nil).AnyTimes() // Validation.
|
||||
db.EXPECT().InsertAIBridgeUserPrompt(gomock.Any(), params).Return(nil).AnyTimes()
|
||||
check.Args(params).Asserts(sess, policy.ActionUpdate)
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -586,6 +586,13 @@ func (m queryMetricsStore) FindMatchingPresetID(ctx context.Context, arg databas
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) GetAIBridgeInterceptionByID(ctx context.Context, id uuid.UUID) (database.AIBridgeInterception, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.GetAIBridgeInterceptionByID(ctx, id)
|
||||
m.queryLatencies.WithLabelValues("GetAIBridgeInterceptionByID").Observe(time.Since(start).Seconds())
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) GetAPIKeyByID(ctx context.Context, id string) (database.APIKey, error) {
|
||||
start := time.Now()
|
||||
apiKey, err := m.s.GetAPIKeyByID(ctx, id)
|
||||
@@ -2168,6 +2175,34 @@ func (m queryMetricsStore) GetWorkspacesEligibleForTransition(ctx context.Contex
|
||||
return workspaces, err
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) InsertAIBridgeInterception(ctx context.Context, arg database.InsertAIBridgeInterceptionParams) (database.AIBridgeInterception, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.InsertAIBridgeInterception(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("InsertAIBridgeInterception").Observe(time.Since(start).Seconds())
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) InsertAIBridgeTokenUsage(ctx context.Context, arg database.InsertAIBridgeTokenUsageParams) error {
|
||||
start := time.Now()
|
||||
r0 := m.s.InsertAIBridgeTokenUsage(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("InsertAIBridgeTokenUsage").Observe(time.Since(start).Seconds())
|
||||
return r0
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) InsertAIBridgeToolUsage(ctx context.Context, arg database.InsertAIBridgeToolUsageParams) error {
|
||||
start := time.Now()
|
||||
r0 := m.s.InsertAIBridgeToolUsage(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("InsertAIBridgeToolUsage").Observe(time.Since(start).Seconds())
|
||||
return r0
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) InsertAIBridgeUserPrompt(ctx context.Context, arg database.InsertAIBridgeUserPromptParams) error {
|
||||
start := time.Now()
|
||||
r0 := m.s.InsertAIBridgeUserPrompt(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("InsertAIBridgeUserPrompt").Observe(time.Since(start).Seconds())
|
||||
return r0
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) InsertAPIKey(ctx context.Context, arg database.InsertAPIKeyParams) (database.APIKey, error) {
|
||||
start := time.Now()
|
||||
key, err := m.s.InsertAPIKey(ctx, arg)
|
||||
|
||||
@@ -1094,6 +1094,21 @@ func (mr *MockStoreMockRecorder) FindMatchingPresetID(ctx, arg any) *gomock.Call
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindMatchingPresetID", reflect.TypeOf((*MockStore)(nil).FindMatchingPresetID), ctx, arg)
|
||||
}
|
||||
|
||||
// GetAIBridgeInterceptionByID mocks base method.
|
||||
func (m *MockStore) GetAIBridgeInterceptionByID(ctx context.Context, id uuid.UUID) (database.AIBridgeInterception, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetAIBridgeInterceptionByID", ctx, id)
|
||||
ret0, _ := ret[0].(database.AIBridgeInterception)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetAIBridgeInterceptionByID indicates an expected call of GetAIBridgeInterceptionByID.
|
||||
func (mr *MockStoreMockRecorder) GetAIBridgeInterceptionByID(ctx, id any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAIBridgeInterceptionByID", reflect.TypeOf((*MockStore)(nil).GetAIBridgeInterceptionByID), ctx, id)
|
||||
}
|
||||
|
||||
// GetAPIKeyByID mocks base method.
|
||||
func (m *MockStore) GetAPIKeyByID(ctx context.Context, id string) (database.APIKey, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -4633,6 +4648,63 @@ func (mr *MockStoreMockRecorder) InTx(arg0, arg1 any) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InTx", reflect.TypeOf((*MockStore)(nil).InTx), arg0, arg1)
|
||||
}
|
||||
|
||||
// InsertAIBridgeInterception mocks base method.
|
||||
func (m *MockStore) InsertAIBridgeInterception(ctx context.Context, arg database.InsertAIBridgeInterceptionParams) (database.AIBridgeInterception, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "InsertAIBridgeInterception", ctx, arg)
|
||||
ret0, _ := ret[0].(database.AIBridgeInterception)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// InsertAIBridgeInterception indicates an expected call of InsertAIBridgeInterception.
|
||||
func (mr *MockStoreMockRecorder) InsertAIBridgeInterception(ctx, arg any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertAIBridgeInterception", reflect.TypeOf((*MockStore)(nil).InsertAIBridgeInterception), ctx, arg)
|
||||
}
|
||||
|
||||
// InsertAIBridgeTokenUsage mocks base method.
|
||||
func (m *MockStore) InsertAIBridgeTokenUsage(ctx context.Context, arg database.InsertAIBridgeTokenUsageParams) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "InsertAIBridgeTokenUsage", ctx, arg)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// InsertAIBridgeTokenUsage indicates an expected call of InsertAIBridgeTokenUsage.
|
||||
func (mr *MockStoreMockRecorder) InsertAIBridgeTokenUsage(ctx, arg any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertAIBridgeTokenUsage", reflect.TypeOf((*MockStore)(nil).InsertAIBridgeTokenUsage), ctx, arg)
|
||||
}
|
||||
|
||||
// InsertAIBridgeToolUsage mocks base method.
|
||||
func (m *MockStore) InsertAIBridgeToolUsage(ctx context.Context, arg database.InsertAIBridgeToolUsageParams) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "InsertAIBridgeToolUsage", ctx, arg)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// InsertAIBridgeToolUsage indicates an expected call of InsertAIBridgeToolUsage.
|
||||
func (mr *MockStoreMockRecorder) InsertAIBridgeToolUsage(ctx, arg any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertAIBridgeToolUsage", reflect.TypeOf((*MockStore)(nil).InsertAIBridgeToolUsage), ctx, arg)
|
||||
}
|
||||
|
||||
// InsertAIBridgeUserPrompt mocks base method.
|
||||
func (m *MockStore) InsertAIBridgeUserPrompt(ctx context.Context, arg database.InsertAIBridgeUserPromptParams) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "InsertAIBridgeUserPrompt", ctx, arg)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// InsertAIBridgeUserPrompt indicates an expected call of InsertAIBridgeUserPrompt.
|
||||
func (mr *MockStoreMockRecorder) InsertAIBridgeUserPrompt(ctx, arg any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertAIBridgeUserPrompt", reflect.TypeOf((*MockStore)(nil).InsertAIBridgeUserPrompt), ctx, arg)
|
||||
}
|
||||
|
||||
// InsertAPIKey mocks base method.
|
||||
func (m *MockStore) InsertAPIKey(ctx context.Context, arg database.InsertAPIKeyParams) (database.APIKey, error) {
|
||||
m.ctrl.T.Helper()
|
||||
|
||||
Generated
+88
@@ -847,6 +847,68 @@ BEGIN
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE TABLE aibridge_interceptions (
|
||||
id uuid NOT NULL,
|
||||
initiator_id uuid NOT NULL,
|
||||
provider text NOT NULL,
|
||||
model text NOT NULL,
|
||||
started_at timestamp with time zone NOT NULL
|
||||
);
|
||||
|
||||
COMMENT ON TABLE aibridge_interceptions IS 'Audit log of requests intercepted by AI Bridge';
|
||||
|
||||
COMMENT ON COLUMN aibridge_interceptions.initiator_id IS 'Relates to a users record, but FK is elided for performance.';
|
||||
|
||||
CREATE TABLE aibridge_token_usages (
|
||||
id uuid NOT NULL,
|
||||
interception_id uuid NOT NULL,
|
||||
provider_response_id text NOT NULL,
|
||||
input_tokens bigint NOT NULL,
|
||||
output_tokens bigint NOT NULL,
|
||||
metadata jsonb,
|
||||
created_at timestamp with time zone NOT NULL
|
||||
);
|
||||
|
||||
COMMENT ON TABLE aibridge_token_usages IS 'Audit log of tokens used by intercepted requests in AI Bridge';
|
||||
|
||||
COMMENT ON COLUMN aibridge_token_usages.provider_response_id IS 'The ID for the response in which the tokens were used, produced by the provider.';
|
||||
|
||||
CREATE TABLE aibridge_tool_usages (
|
||||
id uuid NOT NULL,
|
||||
interception_id uuid NOT NULL,
|
||||
provider_response_id text NOT NULL,
|
||||
server_url text,
|
||||
tool text NOT NULL,
|
||||
input text NOT NULL,
|
||||
injected boolean DEFAULT false NOT NULL,
|
||||
invocation_error text,
|
||||
metadata jsonb,
|
||||
created_at timestamp with time zone NOT NULL
|
||||
);
|
||||
|
||||
COMMENT ON TABLE aibridge_tool_usages IS 'Audit log of tool calls in intercepted requests in AI Bridge';
|
||||
|
||||
COMMENT ON COLUMN aibridge_tool_usages.provider_response_id IS 'The ID for the response in which the tools were used, produced by the provider.';
|
||||
|
||||
COMMENT ON COLUMN aibridge_tool_usages.server_url IS 'The name of the MCP server against which this tool was invoked. May be NULL, in which case the tool was defined by the client, not injected.';
|
||||
|
||||
COMMENT ON COLUMN aibridge_tool_usages.injected IS 'Whether this tool was injected; i.e. Bridge injected these tools into the request from an MCP server. If false it means a tool was defined by the client and already existed in the request (MCP or built-in).';
|
||||
|
||||
COMMENT ON COLUMN aibridge_tool_usages.invocation_error IS 'Only injected tools are invoked.';
|
||||
|
||||
CREATE TABLE aibridge_user_prompts (
|
||||
id uuid NOT NULL,
|
||||
interception_id uuid NOT NULL,
|
||||
provider_response_id text NOT NULL,
|
||||
prompt text NOT NULL,
|
||||
metadata jsonb,
|
||||
created_at timestamp with time zone NOT NULL
|
||||
);
|
||||
|
||||
COMMENT ON TABLE aibridge_user_prompts IS 'Audit log of prompts used by intercepted requests in AI Bridge';
|
||||
|
||||
COMMENT ON COLUMN aibridge_user_prompts.provider_response_id IS 'The ID for the response to the given prompt, produced by the provider.';
|
||||
|
||||
CREATE TABLE api_keys (
|
||||
id text NOT NULL,
|
||||
hashed_secret bytea NOT NULL,
|
||||
@@ -2597,6 +2659,18 @@ ALTER TABLE ONLY workspace_resource_metadata ALTER COLUMN id SET DEFAULT nextval
|
||||
ALTER TABLE ONLY workspace_agent_stats
|
||||
ADD CONSTRAINT agent_stats_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY aibridge_interceptions
|
||||
ADD CONSTRAINT aibridge_interceptions_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY aibridge_token_usages
|
||||
ADD CONSTRAINT aibridge_token_usages_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY aibridge_tool_usages
|
||||
ADD CONSTRAINT aibridge_tool_usages_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY aibridge_user_prompts
|
||||
ADD CONSTRAINT aibridge_user_prompts_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY api_keys
|
||||
ADD CONSTRAINT api_keys_pkey PRIMARY KEY (id);
|
||||
|
||||
@@ -2896,6 +2970,20 @@ CREATE INDEX idx_agent_stats_created_at ON workspace_agent_stats USING btree (cr
|
||||
|
||||
CREATE INDEX idx_agent_stats_user_id ON workspace_agent_stats USING btree (user_id);
|
||||
|
||||
CREATE INDEX idx_aibridge_interceptions_initiator_id ON aibridge_interceptions USING btree (initiator_id);
|
||||
|
||||
CREATE INDEX idx_aibridge_token_usages_interception_id ON aibridge_token_usages USING btree (interception_id);
|
||||
|
||||
CREATE INDEX idx_aibridge_token_usages_provider_response_id ON aibridge_token_usages USING btree (provider_response_id);
|
||||
|
||||
CREATE INDEX idx_aibridge_tool_usages_interception_id ON aibridge_tool_usages USING btree (interception_id);
|
||||
|
||||
CREATE INDEX idx_aibridge_tool_usagesprovider_response_id ON aibridge_tool_usages USING btree (provider_response_id);
|
||||
|
||||
CREATE INDEX idx_aibridge_user_prompts_interception_id ON aibridge_user_prompts USING btree (interception_id);
|
||||
|
||||
CREATE INDEX idx_aibridge_user_prompts_provider_response_id ON aibridge_user_prompts USING btree (provider_response_id);
|
||||
|
||||
CREATE UNIQUE INDEX idx_api_key_name ON api_keys USING btree (user_id, token_name) WHERE (login_type = 'token'::login_type);
|
||||
|
||||
CREATE INDEX idx_api_keys_user ON api_keys USING btree (user_id);
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
DROP TABLE IF EXISTS aibridge_tool_usages CASCADE;
|
||||
DROP TABLE IF EXISTS aibridge_user_prompts CASCADE;
|
||||
DROP TABLE IF EXISTS aibridge_token_usages CASCADE;
|
||||
DROP TABLE IF EXISTS aibridge_interceptions CASCADE;
|
||||
@@ -0,0 +1,68 @@
|
||||
CREATE TABLE IF NOT EXISTS aibridge_interceptions (
|
||||
id UUID PRIMARY KEY,
|
||||
initiator_id uuid NOT NULL,
|
||||
provider TEXT NOT NULL,
|
||||
model TEXT NOT NULL,
|
||||
started_at TIMESTAMP WITH TIME ZONE NOT NULL
|
||||
);
|
||||
|
||||
COMMENT ON TABLE aibridge_interceptions IS 'Audit log of requests intercepted by AI Bridge';
|
||||
COMMENT ON COLUMN aibridge_interceptions.initiator_id IS 'Relates to a users record, but FK is elided for performance.';
|
||||
|
||||
CREATE INDEX idx_aibridge_interceptions_initiator_id ON aibridge_interceptions (initiator_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS aibridge_token_usages (
|
||||
id UUID PRIMARY KEY,
|
||||
interception_id UUID NOT NULL,
|
||||
provider_response_id TEXT NOT NULL,
|
||||
input_tokens BIGINT NOT NULL,
|
||||
output_tokens BIGINT NOT NULL,
|
||||
metadata JSONB DEFAULT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL
|
||||
);
|
||||
|
||||
COMMENT ON TABLE aibridge_token_usages IS 'Audit log of tokens used by intercepted requests in AI Bridge';
|
||||
COMMENT ON COLUMN aibridge_token_usages.provider_response_id IS 'The ID for the response in which the tokens were used, produced by the provider.';
|
||||
|
||||
CREATE INDEX idx_aibridge_token_usages_interception_id ON aibridge_token_usages (interception_id);
|
||||
|
||||
CREATE INDEX idx_aibridge_token_usages_provider_response_id ON aibridge_token_usages (provider_response_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS aibridge_user_prompts (
|
||||
id UUID PRIMARY KEY,
|
||||
interception_id UUID NOT NULL,
|
||||
provider_response_id TEXT NOT NULL,
|
||||
prompt TEXT NOT NULL,
|
||||
metadata JSONB DEFAULT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL
|
||||
);
|
||||
|
||||
COMMENT ON TABLE aibridge_user_prompts IS 'Audit log of prompts used by intercepted requests in AI Bridge';
|
||||
COMMENT ON COLUMN aibridge_user_prompts.provider_response_id IS 'The ID for the response to the given prompt, produced by the provider.';
|
||||
|
||||
CREATE INDEX idx_aibridge_user_prompts_interception_id ON aibridge_user_prompts (interception_id);
|
||||
|
||||
CREATE INDEX idx_aibridge_user_prompts_provider_response_id ON aibridge_user_prompts (provider_response_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS aibridge_tool_usages (
|
||||
id UUID PRIMARY KEY,
|
||||
interception_id UUID NOT NULL,
|
||||
provider_response_id TEXT NOT NULL,
|
||||
server_url TEXT NULL,
|
||||
tool TEXT NOT NULL,
|
||||
input TEXT NOT NULL,
|
||||
injected BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
invocation_error TEXT NULL,
|
||||
metadata JSONB DEFAULT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL
|
||||
);
|
||||
|
||||
COMMENT ON TABLE aibridge_tool_usages IS 'Audit log of tool calls in intercepted requests in AI Bridge';
|
||||
COMMENT ON COLUMN aibridge_tool_usages.provider_response_id IS 'The ID for the response in which the tools were used, produced by the provider.';
|
||||
COMMENT ON COLUMN aibridge_tool_usages.server_url IS 'The name of the MCP server against which this tool was invoked. May be NULL, in which case the tool was defined by the client, not injected.';
|
||||
COMMENT ON COLUMN aibridge_tool_usages.injected IS 'Whether this tool was injected; i.e. Bridge injected these tools into the request from an MCP server. If false it means a tool was defined by the client and already existed in the request (MCP or built-in).';
|
||||
COMMENT ON COLUMN aibridge_tool_usages.invocation_error IS 'Only injected tools are invoked.';
|
||||
|
||||
CREATE INDEX idx_aibridge_tool_usages_interception_id ON aibridge_tool_usages (interception_id);
|
||||
|
||||
CREATE INDEX idx_aibridge_tool_usagesprovider_response_id ON aibridge_tool_usages (provider_response_id);
|
||||
@@ -0,0 +1,79 @@
|
||||
INSERT INTO
|
||||
aibridge_interceptions (
|
||||
id,
|
||||
initiator_id,
|
||||
provider,
|
||||
model,
|
||||
started_at
|
||||
)
|
||||
VALUES (
|
||||
'be003e1e-b38f-43bf-847d-928074dd0aa8',
|
||||
'30095c71-380b-457a-8995-97b8ee6e5307',
|
||||
'openai',
|
||||
'gpt-5',
|
||||
'2025-09-15 12:45:13.921148+00'
|
||||
);
|
||||
|
||||
INSERT INTO
|
||||
aibridge_token_usages (
|
||||
id,
|
||||
interception_id,
|
||||
provider_response_id,
|
||||
input_tokens,
|
||||
output_tokens,
|
||||
metadata,
|
||||
created_at
|
||||
)
|
||||
VALUES (
|
||||
'c56ca89d-af65-47b0-871f-0b9cd2af6575',
|
||||
'be003e1e-b38f-43bf-847d-928074dd0aa8',
|
||||
'chatcmpl-CG2s28QlpKIoooUtXuLTmGbdtyS1k',
|
||||
10950,
|
||||
118,
|
||||
'{"prompt_audio": 0, "prompt_cached": 5376, "completion_audio": 0, "completion_reasoning": 64, "completion_accepted_prediction": 0, "completion_rejected_prediction": 0}',
|
||||
'2025-09-15 12:45:21.674413+00'
|
||||
);
|
||||
|
||||
INSERT INTO
|
||||
aibridge_tool_usages (
|
||||
id,
|
||||
interception_id,
|
||||
provider_response_id,
|
||||
server_url,
|
||||
tool,
|
||||
input,
|
||||
injected,
|
||||
invocation_error,
|
||||
metadata,
|
||||
created_at
|
||||
)
|
||||
VALUES (
|
||||
'613b4cfa-a257-4e88-99e6-4d2e99ea25f0',
|
||||
'be003e1e-b38f-43bf-847d-928074dd0aa8',
|
||||
'chatcmpl-CG2ryDxMp6n53aMjgo7P6BHno3fTr',
|
||||
'http://localhost:3000/api/experimental/mcp/http',
|
||||
'coder_list_workspaces',
|
||||
'{}',
|
||||
true,
|
||||
NULL,
|
||||
'{}',
|
||||
'2025-09-15 12:45:17.65274+00'
|
||||
);
|
||||
|
||||
INSERT INTO
|
||||
aibridge_user_prompts (
|
||||
id,
|
||||
interception_id,
|
||||
provider_response_id,
|
||||
prompt,
|
||||
metadata,
|
||||
created_at
|
||||
)
|
||||
VALUES (
|
||||
'ac1ea8c3-5109-4105-9b62-489fca220ef7',
|
||||
'be003e1e-b38f-43bf-847d-928074dd0aa8',
|
||||
'chatcmpl-CG2s28QlpKIoooUtXuLTmGbdtyS1k',
|
||||
'how many workspaces do i have',
|
||||
'{}',
|
||||
'2025-09-15 12:45:21.674335+00'
|
||||
);
|
||||
@@ -636,3 +636,7 @@ func (m WorkspaceAgentVolumeResourceMonitor) Debounce(
|
||||
func (s UserSecret) RBACObject() rbac.Object {
|
||||
return rbac.ResourceUserSecret.WithID(s.ID).WithOwner(s.UserID.String())
|
||||
}
|
||||
|
||||
func (s AIBridgeInterception) RBACObject() rbac.Object {
|
||||
return rbac.ResourceAibridgeInterception.WithOwner(s.InitiatorID.String())
|
||||
}
|
||||
|
||||
@@ -2955,6 +2955,57 @@ func AllWorkspaceTransitionValues() []WorkspaceTransition {
|
||||
}
|
||||
}
|
||||
|
||||
// Audit log of requests intercepted by AI Bridge
|
||||
type AIBridgeInterception struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
// Relates to a users record, but FK is elided for performance.
|
||||
InitiatorID uuid.UUID `db:"initiator_id" json:"initiator_id"`
|
||||
Provider string `db:"provider" json:"provider"`
|
||||
Model string `db:"model" json:"model"`
|
||||
StartedAt time.Time `db:"started_at" json:"started_at"`
|
||||
}
|
||||
|
||||
// Audit log of tokens used by intercepted requests in AI Bridge
|
||||
type AIBridgeTokenUsage struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
InterceptionID uuid.UUID `db:"interception_id" json:"interception_id"`
|
||||
// The ID for the response in which the tokens were used, produced by the provider.
|
||||
ProviderResponseID string `db:"provider_response_id" json:"provider_response_id"`
|
||||
InputTokens int64 `db:"input_tokens" json:"input_tokens"`
|
||||
OutputTokens int64 `db:"output_tokens" json:"output_tokens"`
|
||||
Metadata pqtype.NullRawMessage `db:"metadata" json:"metadata"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
}
|
||||
|
||||
// Audit log of tool calls in intercepted requests in AI Bridge
|
||||
type AIBridgeToolUsage struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
InterceptionID uuid.UUID `db:"interception_id" json:"interception_id"`
|
||||
// The ID for the response in which the tools were used, produced by the provider.
|
||||
ProviderResponseID string `db:"provider_response_id" json:"provider_response_id"`
|
||||
// The name of the MCP server against which this tool was invoked. May be NULL, in which case the tool was defined by the client, not injected.
|
||||
ServerUrl sql.NullString `db:"server_url" json:"server_url"`
|
||||
Tool string `db:"tool" json:"tool"`
|
||||
Input string `db:"input" json:"input"`
|
||||
// Whether this tool was injected; i.e. Bridge injected these tools into the request from an MCP server. If false it means a tool was defined by the client and already existed in the request (MCP or built-in).
|
||||
Injected bool `db:"injected" json:"injected"`
|
||||
// Only injected tools are invoked.
|
||||
InvocationError sql.NullString `db:"invocation_error" json:"invocation_error"`
|
||||
Metadata pqtype.NullRawMessage `db:"metadata" json:"metadata"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
}
|
||||
|
||||
// Audit log of prompts used by intercepted requests in AI Bridge
|
||||
type AIBridgeUserPrompt struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
InterceptionID uuid.UUID `db:"interception_id" json:"interception_id"`
|
||||
// The ID for the response to the given prompt, produced by the provider.
|
||||
ProviderResponseID string `db:"provider_response_id" json:"provider_response_id"`
|
||||
Prompt string `db:"prompt" json:"prompt"`
|
||||
Metadata pqtype.NullRawMessage `db:"metadata" json:"metadata"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
}
|
||||
|
||||
type APIKey struct {
|
||||
ID string `db:"id" json:"id"`
|
||||
// hashed_secret contains a SHA256 hash of the key secret. This is considered a secret and MUST NOT be returned from the API as it is used for API key encryption in app proxying code.
|
||||
|
||||
@@ -148,6 +148,7 @@ type sqlcQuerier interface {
|
||||
// The query finds presets where all preset parameters are present in the provided parameters,
|
||||
// and returns the preset with the most parameters (largest subset).
|
||||
FindMatchingPresetID(ctx context.Context, arg FindMatchingPresetIDParams) (uuid.UUID, error)
|
||||
GetAIBridgeInterceptionByID(ctx context.Context, id uuid.UUID) (AIBridgeInterception, error)
|
||||
GetAPIKeyByID(ctx context.Context, id string) (APIKey, error)
|
||||
// there is no unique constraint on empty token names
|
||||
GetAPIKeyByName(ctx context.Context, arg GetAPIKeyByNameParams) (APIKey, error)
|
||||
@@ -502,6 +503,10 @@ type sqlcQuerier interface {
|
||||
GetWorkspacesAndAgentsByOwnerID(ctx context.Context, ownerID uuid.UUID) ([]GetWorkspacesAndAgentsByOwnerIDRow, error)
|
||||
GetWorkspacesByTemplateID(ctx context.Context, templateID uuid.UUID) ([]WorkspaceTable, error)
|
||||
GetWorkspacesEligibleForTransition(ctx context.Context, now time.Time) ([]GetWorkspacesEligibleForTransitionRow, error)
|
||||
InsertAIBridgeInterception(ctx context.Context, arg InsertAIBridgeInterceptionParams) (AIBridgeInterception, error)
|
||||
InsertAIBridgeTokenUsage(ctx context.Context, arg InsertAIBridgeTokenUsageParams) error
|
||||
InsertAIBridgeToolUsage(ctx context.Context, arg InsertAIBridgeToolUsageParams) error
|
||||
InsertAIBridgeUserPrompt(ctx context.Context, arg InsertAIBridgeUserPromptParams) error
|
||||
InsertAPIKey(ctx context.Context, arg InsertAPIKeyParams) (APIKey, error)
|
||||
// We use the organization_id as the id
|
||||
// for simplicity since all users is
|
||||
|
||||
@@ -111,6 +111,153 @@ func (q *sqlQuerier) ActivityBumpWorkspace(ctx context.Context, arg ActivityBump
|
||||
return err
|
||||
}
|
||||
|
||||
const getAIBridgeInterceptionByID = `-- name: GetAIBridgeInterceptionByID :one
|
||||
SELECT id, initiator_id, provider, model, started_at FROM aibridge_interceptions WHERE id = $1::uuid
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) GetAIBridgeInterceptionByID(ctx context.Context, id uuid.UUID) (AIBridgeInterception, error) {
|
||||
row := q.db.QueryRowContext(ctx, getAIBridgeInterceptionByID, id)
|
||||
var i AIBridgeInterception
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.InitiatorID,
|
||||
&i.Provider,
|
||||
&i.Model,
|
||||
&i.StartedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const insertAIBridgeInterception = `-- name: InsertAIBridgeInterception :one
|
||||
INSERT INTO aibridge_interceptions (id, initiator_id, provider, model, started_at)
|
||||
VALUES ($1::uuid, $2::uuid, $3, $4, $5)
|
||||
RETURNING id, initiator_id, provider, model, started_at
|
||||
`
|
||||
|
||||
type InsertAIBridgeInterceptionParams struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
InitiatorID uuid.UUID `db:"initiator_id" json:"initiator_id"`
|
||||
Provider string `db:"provider" json:"provider"`
|
||||
Model string `db:"model" json:"model"`
|
||||
StartedAt time.Time `db:"started_at" json:"started_at"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) InsertAIBridgeInterception(ctx context.Context, arg InsertAIBridgeInterceptionParams) (AIBridgeInterception, error) {
|
||||
row := q.db.QueryRowContext(ctx, insertAIBridgeInterception,
|
||||
arg.ID,
|
||||
arg.InitiatorID,
|
||||
arg.Provider,
|
||||
arg.Model,
|
||||
arg.StartedAt,
|
||||
)
|
||||
var i AIBridgeInterception
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.InitiatorID,
|
||||
&i.Provider,
|
||||
&i.Model,
|
||||
&i.StartedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const insertAIBridgeTokenUsage = `-- name: InsertAIBridgeTokenUsage :exec
|
||||
INSERT INTO aibridge_token_usages (
|
||||
id, interception_id, provider_response_id, input_tokens, output_tokens, metadata, created_at
|
||||
) VALUES (
|
||||
$1, $2, $3, $4, $5, COALESCE($6::jsonb, '{}'::jsonb), $7
|
||||
)
|
||||
`
|
||||
|
||||
type InsertAIBridgeTokenUsageParams struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
InterceptionID uuid.UUID `db:"interception_id" json:"interception_id"`
|
||||
ProviderResponseID string `db:"provider_response_id" json:"provider_response_id"`
|
||||
InputTokens int64 `db:"input_tokens" json:"input_tokens"`
|
||||
OutputTokens int64 `db:"output_tokens" json:"output_tokens"`
|
||||
Metadata json.RawMessage `db:"metadata" json:"metadata"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) InsertAIBridgeTokenUsage(ctx context.Context, arg InsertAIBridgeTokenUsageParams) error {
|
||||
_, err := q.db.ExecContext(ctx, insertAIBridgeTokenUsage,
|
||||
arg.ID,
|
||||
arg.InterceptionID,
|
||||
arg.ProviderResponseID,
|
||||
arg.InputTokens,
|
||||
arg.OutputTokens,
|
||||
arg.Metadata,
|
||||
arg.CreatedAt,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
const insertAIBridgeToolUsage = `-- name: InsertAIBridgeToolUsage :exec
|
||||
INSERT INTO aibridge_tool_usages (
|
||||
id, interception_id, provider_response_id, tool, server_url, input, injected, invocation_error, metadata, created_at
|
||||
) VALUES (
|
||||
$1, $2, $3, $4, $5, $6, $7, $8, COALESCE($9::jsonb, '{}'::jsonb), $10
|
||||
)
|
||||
`
|
||||
|
||||
type InsertAIBridgeToolUsageParams struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
InterceptionID uuid.UUID `db:"interception_id" json:"interception_id"`
|
||||
ProviderResponseID string `db:"provider_response_id" json:"provider_response_id"`
|
||||
Tool string `db:"tool" json:"tool"`
|
||||
ServerUrl sql.NullString `db:"server_url" json:"server_url"`
|
||||
Input string `db:"input" json:"input"`
|
||||
Injected bool `db:"injected" json:"injected"`
|
||||
InvocationError sql.NullString `db:"invocation_error" json:"invocation_error"`
|
||||
Metadata json.RawMessage `db:"metadata" json:"metadata"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) InsertAIBridgeToolUsage(ctx context.Context, arg InsertAIBridgeToolUsageParams) error {
|
||||
_, err := q.db.ExecContext(ctx, insertAIBridgeToolUsage,
|
||||
arg.ID,
|
||||
arg.InterceptionID,
|
||||
arg.ProviderResponseID,
|
||||
arg.Tool,
|
||||
arg.ServerUrl,
|
||||
arg.Input,
|
||||
arg.Injected,
|
||||
arg.InvocationError,
|
||||
arg.Metadata,
|
||||
arg.CreatedAt,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
const insertAIBridgeUserPrompt = `-- name: InsertAIBridgeUserPrompt :exec
|
||||
INSERT INTO aibridge_user_prompts (
|
||||
id, interception_id, provider_response_id, prompt, metadata, created_at
|
||||
) VALUES (
|
||||
$1, $2, $3, $4, COALESCE($5::jsonb, '{}'::jsonb), $6
|
||||
)
|
||||
`
|
||||
|
||||
type InsertAIBridgeUserPromptParams struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
InterceptionID uuid.UUID `db:"interception_id" json:"interception_id"`
|
||||
ProviderResponseID string `db:"provider_response_id" json:"provider_response_id"`
|
||||
Prompt string `db:"prompt" json:"prompt"`
|
||||
Metadata json.RawMessage `db:"metadata" json:"metadata"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) InsertAIBridgeUserPrompt(ctx context.Context, arg InsertAIBridgeUserPromptParams) error {
|
||||
_, err := q.db.ExecContext(ctx, insertAIBridgeUserPrompt,
|
||||
arg.ID,
|
||||
arg.InterceptionID,
|
||||
arg.ProviderResponseID,
|
||||
arg.Prompt,
|
||||
arg.Metadata,
|
||||
arg.CreatedAt,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
const deleteAPIKeyByID = `-- name: DeleteAPIKeyByID :exec
|
||||
DELETE FROM
|
||||
api_keys
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
-- name: InsertAIBridgeInterception :one
|
||||
INSERT INTO aibridge_interceptions (id, initiator_id, provider, model, started_at)
|
||||
VALUES (@id::uuid, @initiator_id::uuid, @provider, @model, @started_at)
|
||||
RETURNING *;
|
||||
|
||||
-- name: InsertAIBridgeTokenUsage :exec
|
||||
INSERT INTO aibridge_token_usages (
|
||||
id, interception_id, provider_response_id, input_tokens, output_tokens, metadata, created_at
|
||||
) VALUES (
|
||||
@id, @interception_id, @provider_response_id, @input_tokens, @output_tokens, COALESCE(@metadata::jsonb, '{}'::jsonb), @created_at
|
||||
);
|
||||
|
||||
-- name: InsertAIBridgeUserPrompt :exec
|
||||
INSERT INTO aibridge_user_prompts (
|
||||
id, interception_id, provider_response_id, prompt, metadata, created_at
|
||||
) VALUES (
|
||||
@id, @interception_id, @provider_response_id, @prompt, COALESCE(@metadata::jsonb, '{}'::jsonb), @created_at
|
||||
);
|
||||
|
||||
-- name: InsertAIBridgeToolUsage :exec
|
||||
INSERT INTO aibridge_tool_usages (
|
||||
id, interception_id, provider_response_id, tool, server_url, input, injected, invocation_error, metadata, created_at
|
||||
) VALUES (
|
||||
@id, @interception_id, @provider_response_id, @tool, @server_url, @input, @injected, @invocation_error, COALESCE(@metadata::jsonb, '{}'::jsonb), @created_at
|
||||
);
|
||||
|
||||
-- name: GetAIBridgeInterceptionByID :one
|
||||
SELECT * FROM aibridge_interceptions WHERE id = @id::uuid;
|
||||
@@ -163,6 +163,10 @@ sql:
|
||||
ai_task_sidebar_app_id: AITaskSidebarAppID
|
||||
latest_build_has_ai_task: LatestBuildHasAITask
|
||||
cors_behavior: CorsBehavior
|
||||
aibridge_interception: AIBridgeInterception
|
||||
aibridge_tool_usage: AIBridgeToolUsage
|
||||
aibridge_token_usage: AIBridgeTokenUsage
|
||||
aibridge_user_prompt: AIBridgeUserPrompt
|
||||
rules:
|
||||
- name: do-not-use-public-schema-in-queries
|
||||
message: "do not use public schema in queries"
|
||||
|
||||
@@ -7,6 +7,10 @@ type UniqueConstraint string
|
||||
// UniqueConstraint enums.
|
||||
const (
|
||||
UniqueAgentStatsPkey UniqueConstraint = "agent_stats_pkey" // ALTER TABLE ONLY workspace_agent_stats ADD CONSTRAINT agent_stats_pkey PRIMARY KEY (id);
|
||||
UniqueAibridgeInterceptionsPkey UniqueConstraint = "aibridge_interceptions_pkey" // ALTER TABLE ONLY aibridge_interceptions ADD CONSTRAINT aibridge_interceptions_pkey PRIMARY KEY (id);
|
||||
UniqueAibridgeTokenUsagesPkey UniqueConstraint = "aibridge_token_usages_pkey" // ALTER TABLE ONLY aibridge_token_usages ADD CONSTRAINT aibridge_token_usages_pkey PRIMARY KEY (id);
|
||||
UniqueAibridgeToolUsagesPkey UniqueConstraint = "aibridge_tool_usages_pkey" // ALTER TABLE ONLY aibridge_tool_usages ADD CONSTRAINT aibridge_tool_usages_pkey PRIMARY KEY (id);
|
||||
UniqueAibridgeUserPromptsPkey UniqueConstraint = "aibridge_user_prompts_pkey" // ALTER TABLE ONLY aibridge_user_prompts ADD CONSTRAINT aibridge_user_prompts_pkey PRIMARY KEY (id);
|
||||
UniqueAPIKeysPkey UniqueConstraint = "api_keys_pkey" // ALTER TABLE ONLY api_keys ADD CONSTRAINT api_keys_pkey PRIMARY KEY (id);
|
||||
UniqueAuditLogsPkey UniqueConstraint = "audit_logs_pkey" // ALTER TABLE ONLY audit_logs ADD CONSTRAINT audit_logs_pkey PRIMARY KEY (id);
|
||||
UniqueConnectionLogsPkey UniqueConstraint = "connection_logs_pkey" // ALTER TABLE ONLY connection_logs ADD CONSTRAINT connection_logs_pkey PRIMARY KEY (id);
|
||||
|
||||
@@ -77,6 +77,7 @@ const (
|
||||
SubjectTypeSubAgentAPI SubjectType = "sub_agent_api"
|
||||
SubjectTypeFileReader SubjectType = "file_reader"
|
||||
SubjectTypeUsagePublisher SubjectType = "usage_publisher"
|
||||
SubjectAibridged SubjectType = "aibridged"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -15,6 +15,15 @@ var (
|
||||
Type: "*",
|
||||
}
|
||||
|
||||
// ResourceAibridgeInterception
|
||||
// Valid Actions
|
||||
// - "ActionCreate" :: create aibridge interceptions & related records
|
||||
// - "ActionRead" :: read aibridge interceptions & related records
|
||||
// - "ActionUpdate" :: update aibridge interceptions & related records
|
||||
ResourceAibridgeInterception = Object{
|
||||
Type: "aibridge_interception",
|
||||
}
|
||||
|
||||
// ResourceApiKey
|
||||
// Valid Actions
|
||||
// - "ActionCreate" :: create an api key
|
||||
@@ -391,6 +400,7 @@ var (
|
||||
func AllResources() []Objecter {
|
||||
return []Objecter{
|
||||
ResourceWildcard,
|
||||
ResourceAibridgeInterception,
|
||||
ResourceApiKey,
|
||||
ResourceAssignOrgRole,
|
||||
ResourceAssignRole,
|
||||
|
||||
@@ -358,4 +358,11 @@ var RBACPermissions = map[string]PermissionDefinition{
|
||||
ActionUpdate: "update usage events",
|
||||
},
|
||||
},
|
||||
"aibridge_interception": {
|
||||
Actions: map[Action]ActionDefinition{
|
||||
ActionRead: "read aibridge interceptions & related records",
|
||||
ActionUpdate: "update aibridge interceptions & related records",
|
||||
ActionCreate: "create aibridge interceptions & related records",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -888,6 +888,21 @@ func TestRolePermissions(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "AIBridgeInterceptions",
|
||||
Actions: []policy.Action{policy.ActionCreate, policy.ActionRead, policy.ActionUpdate},
|
||||
Resource: rbac.ResourceAibridgeInterception.WithOwner(currentUser.String()),
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner, memberMe, orgMemberMe},
|
||||
false: {
|
||||
otherOrgMember,
|
||||
orgAdmin, otherOrgAdmin,
|
||||
orgAuditor, otherOrgAuditor,
|
||||
templateAdmin, orgTemplateAdmin, otherOrgTemplateAdmin,
|
||||
userAdmin, orgUserAdmin, otherOrgUserAdmin,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// We expect every permission to be tested above.
|
||||
|
||||
@@ -5,6 +5,7 @@ type RBACResource string
|
||||
|
||||
const (
|
||||
ResourceWildcard RBACResource = "*"
|
||||
ResourceAibridgeInterception RBACResource = "aibridge_interception"
|
||||
ResourceApiKey RBACResource = "api_key"
|
||||
ResourceAssignOrgRole RBACResource = "assign_org_role"
|
||||
ResourceAssignRole RBACResource = "assign_role"
|
||||
@@ -71,6 +72,7 @@ const (
|
||||
// said resource type.
|
||||
var RBACResourceActions = map[RBACResource][]RBACAction{
|
||||
ResourceWildcard: {},
|
||||
ResourceAibridgeInterception: {ActionCreate, ActionRead, ActionUpdate},
|
||||
ResourceApiKey: {ActionCreate, ActionDelete, ActionRead, ActionUpdate},
|
||||
ResourceAssignOrgRole: {ActionAssign, ActionCreate, ActionDelete, ActionRead, ActionUnassign, ActionUpdate},
|
||||
ResourceAssignRole: {ActionAssign, ActionRead, ActionUnassign},
|
||||
|
||||
Generated
+5
@@ -183,6 +183,7 @@ Status Code **200**
|
||||
| `action` | `start` |
|
||||
| `action` | `stop` |
|
||||
| `resource_type` | `*` |
|
||||
| `resource_type` | `aibridge_interception` |
|
||||
| `resource_type` | `api_key` |
|
||||
| `resource_type` | `assign_org_role` |
|
||||
| `resource_type` | `assign_role` |
|
||||
@@ -355,6 +356,7 @@ Status Code **200**
|
||||
| `action` | `start` |
|
||||
| `action` | `stop` |
|
||||
| `resource_type` | `*` |
|
||||
| `resource_type` | `aibridge_interception` |
|
||||
| `resource_type` | `api_key` |
|
||||
| `resource_type` | `assign_org_role` |
|
||||
| `resource_type` | `assign_role` |
|
||||
@@ -527,6 +529,7 @@ Status Code **200**
|
||||
| `action` | `start` |
|
||||
| `action` | `stop` |
|
||||
| `resource_type` | `*` |
|
||||
| `resource_type` | `aibridge_interception` |
|
||||
| `resource_type` | `api_key` |
|
||||
| `resource_type` | `assign_org_role` |
|
||||
| `resource_type` | `assign_role` |
|
||||
@@ -668,6 +671,7 @@ Status Code **200**
|
||||
| `action` | `start` |
|
||||
| `action` | `stop` |
|
||||
| `resource_type` | `*` |
|
||||
| `resource_type` | `aibridge_interception` |
|
||||
| `resource_type` | `api_key` |
|
||||
| `resource_type` | `assign_org_role` |
|
||||
| `resource_type` | `assign_role` |
|
||||
@@ -1031,6 +1035,7 @@ Status Code **200**
|
||||
| `action` | `start` |
|
||||
| `action` | `stop` |
|
||||
| `resource_type` | `*` |
|
||||
| `resource_type` | `aibridge_interception` |
|
||||
| `resource_type` | `api_key` |
|
||||
| `resource_type` | `assign_org_role` |
|
||||
| `resource_type` | `assign_role` |
|
||||
|
||||
Generated
+1
@@ -6519,6 +6519,7 @@ Only certain features set these fields: - FeatureManagedAgentLimit|
|
||||
| Value |
|
||||
|------------------------------------|
|
||||
| `*` |
|
||||
| `aibridge_interception` |
|
||||
| `api_key` |
|
||||
| `assign_org_role` |
|
||||
| `assign_role` |
|
||||
|
||||
@@ -8,6 +8,11 @@ import type { RBACAction, RBACResource } from "./typesGenerated";
|
||||
export const RBACResourceActions: Partial<
|
||||
Record<RBACResource, Partial<Record<RBACAction, string>>>
|
||||
> = {
|
||||
aibridge_interception: {
|
||||
create: "create aibridge interceptions & related records",
|
||||
read: "read aibridge interceptions & related records",
|
||||
update: "update aibridge interceptions & related records",
|
||||
},
|
||||
api_key: {
|
||||
create: "create an api key",
|
||||
delete: "delete an api key",
|
||||
|
||||
Generated
+2
@@ -2415,6 +2415,7 @@ export const RBACActions: RBACAction[] = [
|
||||
|
||||
// From codersdk/rbacresources_gen.go
|
||||
export type RBACResource =
|
||||
| "aibridge_interception"
|
||||
| "api_key"
|
||||
| "assign_org_role"
|
||||
| "assign_role"
|
||||
@@ -2457,6 +2458,7 @@ export type RBACResource =
|
||||
| "workspace_proxy";
|
||||
|
||||
export const RBACResources: RBACResource[] = [
|
||||
"aibridge_interception",
|
||||
"api_key",
|
||||
"assign_org_role",
|
||||
"assign_role",
|
||||
|
||||
Reference in New Issue
Block a user