feat(core): mark policies experimental
This commit is contained in:
parent
e24b589da1
commit
4cd69df7c8
9 changed files with 146 additions and 110 deletions
|
|
@ -82,7 +82,7 @@ export const layer = Layer.effect(
|
||||||
const configs = [...(supplementary[0] ?? []), ...direct, ...supplementary.slice(1).flat()]
|
const configs = [...(supplementary[0] ?? []), ...direct, ...supplementary.slice(1).flat()]
|
||||||
// Rules use the opposite order so a user-global rule can override a
|
// Rules use the opposite order so a user-global rule can override a
|
||||||
// repository rule. Statement order inside each file stays unchanged.
|
// repository rule. Statement order inside each file stays unchanged.
|
||||||
yield* policy.load(configs.toReversed().flatMap((config) => config.info.policies ?? []))
|
yield* policy.load(configs.toReversed().flatMap((config) => config.info.experimental?.policies ?? []))
|
||||||
|
|
||||||
return Service.of({
|
return Service.of({
|
||||||
directories: Effect.fn("Config.directories")(function* () {
|
directories: Effect.fn("Config.directories")(function* () {
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,10 @@ export class Policy extends Schema.Class<Policy>("ConfigV2.Policy")({
|
||||||
action: PolicyAction,
|
action: PolicyAction,
|
||||||
}) {}
|
}) {}
|
||||||
|
|
||||||
|
export class Experimental extends Schema.Class<Experimental>("ConfigV2.Experimental")({
|
||||||
|
policies: Policy.pipe(Schema.Array, Schema.optional),
|
||||||
|
}) {}
|
||||||
|
|
||||||
export class Info extends Schema.Class<Info>("ConfigV2.Info")({
|
export class Info extends Schema.Class<Info>("ConfigV2.Info")({
|
||||||
$schema: Schema.optional(Schema.String).annotate({
|
$schema: Schema.optional(Schema.String).annotate({
|
||||||
description: "JSON schema reference for configuration validation",
|
description: "JSON schema reference for configuration validation",
|
||||||
|
|
@ -21,7 +25,7 @@ export class Info extends Schema.Class<Info>("ConfigV2.Info")({
|
||||||
shell: Schema.String.pipe(Schema.optional).annotate({
|
shell: Schema.String.pipe(Schema.optional).annotate({
|
||||||
description: "Default shell to use for terminal and shell tool execution",
|
description: "Default shell to use for terminal and shell tool execution",
|
||||||
}),
|
}),
|
||||||
policies: Policy.pipe(Schema.Array, Schema.optional),
|
experimental: Experimental.pipe(Schema.optional),
|
||||||
providers: Schema.Record(Schema.String, ConfigProvider.Info).pipe(Schema.optional),
|
providers: Schema.Record(Schema.String, ConfigProvider.Info).pipe(Schema.optional),
|
||||||
}) {}
|
}) {}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -132,7 +132,7 @@ describe("Config", () => {
|
||||||
const file = path.join(tmp.path, "opencode.json")
|
const file = path.join(tmp.path, "opencode.json")
|
||||||
const contents = JSON.stringify({
|
const contents = JSON.stringify({
|
||||||
shell: "/bin/zsh",
|
shell: "/bin/zsh",
|
||||||
policies: [{ effect: "deny", action: "provider.use", resource: "openai" }],
|
experimental: { policies: [{ effect: "deny", action: "provider.use", resource: "openai" }] },
|
||||||
providers: { local: provider },
|
providers: { local: provider },
|
||||||
})
|
})
|
||||||
yield* Effect.promise(() => fs.writeFile(file, contents))
|
yield* Effect.promise(() => fs.writeFile(file, contents))
|
||||||
|
|
@ -143,7 +143,7 @@ describe("Config", () => {
|
||||||
|
|
||||||
expect(documents[0]?.info.$schema).toBeUndefined()
|
expect(documents[0]?.info.$schema).toBeUndefined()
|
||||||
expect(documents[0]?.info.shell).toBe("/bin/zsh")
|
expect(documents[0]?.info.shell).toBe("/bin/zsh")
|
||||||
expect(documents[0]?.info.policies?.[0]).toEqual({
|
expect(documents[0]?.info.experimental?.policies?.[0]).toEqual({
|
||||||
effect: "deny",
|
effect: "deny",
|
||||||
action: "provider.use",
|
action: "provider.use",
|
||||||
resource: "openai",
|
resource: "openai",
|
||||||
|
|
@ -223,11 +223,11 @@ describe("Config", () => {
|
||||||
await fs.mkdir(global, { recursive: true })
|
await fs.mkdir(global, { recursive: true })
|
||||||
await fs.writeFile(
|
await fs.writeFile(
|
||||||
path.join(global, "opencode.json"),
|
path.join(global, "opencode.json"),
|
||||||
JSON.stringify({ policies: [{ effect: "deny", action: "provider.use", resource: "openai" }] }),
|
JSON.stringify({ experimental: { policies: [{ effect: "deny", action: "provider.use", resource: "openai" }] } }),
|
||||||
)
|
)
|
||||||
await fs.writeFile(
|
await fs.writeFile(
|
||||||
path.join(tmp.path, "opencode.json"),
|
path.join(tmp.path, "opencode.json"),
|
||||||
JSON.stringify({ policies: [{ effect: "allow", action: "provider.use", resource: "openai" }] }),
|
JSON.stringify({ experimental: { policies: [{ effect: "allow", action: "provider.use", resource: "openai" }] } }),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -178,9 +178,6 @@ export const Info = Schema.Struct({
|
||||||
enabled_providers: Schema.optional(Schema.mutable(Schema.Array(Schema.String))).annotate({
|
enabled_providers: Schema.optional(Schema.mutable(Schema.Array(Schema.String))).annotate({
|
||||||
description: "When set, ONLY these providers will be enabled. All other providers will be ignored",
|
description: "When set, ONLY these providers will be enabled. All other providers will be ignored",
|
||||||
}),
|
}),
|
||||||
policies: Schema.optional(Schema.mutable(Schema.Array(ConfigV2.Policy))).annotate({
|
|
||||||
description: "Policy statements applied to supported resources, such as provider access",
|
|
||||||
}),
|
|
||||||
model: Schema.optional(ConfigModelID).annotate({
|
model: Schema.optional(ConfigModelID).annotate({
|
||||||
description: "Model to use in the format of provider/model, eg anthropic/claude-2",
|
description: "Model to use in the format of provider/model, eg anthropic/claude-2",
|
||||||
}),
|
}),
|
||||||
|
|
@ -305,6 +302,9 @@ export const Info = Schema.Struct({
|
||||||
mcp_timeout: Schema.optional(PositiveInt).annotate({
|
mcp_timeout: Schema.optional(PositiveInt).annotate({
|
||||||
description: "Timeout in milliseconds for model context protocol (MCP) requests",
|
description: "Timeout in milliseconds for model context protocol (MCP) requests",
|
||||||
}),
|
}),
|
||||||
|
policies: Schema.optional(Schema.mutable(Schema.Array(ConfigV2.Policy))).annotate({
|
||||||
|
description: "Policy statements applied to supported resources, such as provider access",
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
}).annotate({ identifier: "Config" })
|
}).annotate({ identifier: "Config" })
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,9 @@ const alphaProviderConfig = {
|
||||||
|
|
||||||
const denyAnthropicPolicyConfig = {
|
const denyAnthropicPolicyConfig = {
|
||||||
provider: {},
|
provider: {},
|
||||||
policies: [{ effect: "deny" as const, action: "provider.use" as const, resource: "anthropic" }],
|
experimental: {
|
||||||
|
policies: [{ effect: "deny" as const, action: "provider.use" as const, resource: "anthropic" }],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
it.instance("provider loaded from env variable", () =>
|
it.instance("provider loaded from env variable", () =>
|
||||||
|
|
@ -139,7 +141,7 @@ it.instance(
|
||||||
)
|
)
|
||||||
|
|
||||||
it.instance(
|
it.instance(
|
||||||
"policies deny provider use",
|
"experimental policies deny provider use",
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
yield* setProcessEnv("ANTHROPIC_API_KEY", "test-api-key")
|
yield* setProcessEnv("ANTHROPIC_API_KEY", "test-api-key")
|
||||||
const providers = yield* list
|
const providers = yield* list
|
||||||
|
|
|
||||||
|
|
@ -395,18 +395,20 @@ You can also configure [local models](/docs/models#local). [Learn more](/docs/mo
|
||||||
|
|
||||||
### Policies
|
### Policies
|
||||||
|
|
||||||
Use the `policies` option to allow or deny OpenCode actions on configured resources. Currently, policies can control which providers OpenCode may use.
|
Use the `experimental.policies` option to allow or deny OpenCode actions on configured resources. Currently, policies can control which providers OpenCode may use.
|
||||||
|
|
||||||
```json title="opencode.json"
|
```json title="opencode.json"
|
||||||
{
|
{
|
||||||
"$schema": "https://opencode.ai/config.json",
|
"$schema": "https://opencode.ai/config.json",
|
||||||
"policies": [
|
"experimental": {
|
||||||
{
|
"policies": [
|
||||||
"effect": "deny",
|
{
|
||||||
"action": "provider.use",
|
"effect": "deny",
|
||||||
"resource": "openai"
|
"action": "provider.use",
|
||||||
}
|
"resource": "openai"
|
||||||
]
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ title: Policies
|
||||||
description: Control which configured resources OpenCode may use.
|
description: Control which configured resources OpenCode may use.
|
||||||
---
|
---
|
||||||
|
|
||||||
Policies control whether OpenCode may perform an action on a named resource. They are configured with the `policies` array in `opencode.json`.
|
Policies control whether OpenCode may perform an action on a named resource. This feature is experimental and is configured with the `experimental.policies` array in `opencode.json`.
|
||||||
|
|
||||||
Policies are separate from [permissions](/docs/permissions). Permissions control what tools can do during a session, while policies control whether OpenCode may use a resource such as an LLM provider.
|
Policies are separate from [permissions](/docs/permissions). Permissions control what tools can do during a session, while policies control whether OpenCode may use a resource such as an LLM provider.
|
||||||
|
|
||||||
|
|
@ -22,13 +22,15 @@ For example, deny use of the `openai` provider:
|
||||||
```json title="opencode.json"
|
```json title="opencode.json"
|
||||||
{
|
{
|
||||||
"$schema": "https://opencode.ai/config.json",
|
"$schema": "https://opencode.ai/config.json",
|
||||||
"policies": [
|
"experimental": {
|
||||||
{
|
"policies": [
|
||||||
"effect": "deny",
|
{
|
||||||
"action": "provider.use",
|
"effect": "deny",
|
||||||
"resource": "openai"
|
"action": "provider.use",
|
||||||
}
|
"resource": "openai"
|
||||||
]
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -55,13 +57,15 @@ The `resource` field supports wildcard matching. Use `*` to match zero or more c
|
||||||
```json title="opencode.json"
|
```json title="opencode.json"
|
||||||
{
|
{
|
||||||
"$schema": "https://opencode.ai/config.json",
|
"$schema": "https://opencode.ai/config.json",
|
||||||
"policies": [
|
"experimental": {
|
||||||
{
|
"policies": [
|
||||||
"effect": "deny",
|
{
|
||||||
"action": "provider.use",
|
"effect": "deny",
|
||||||
"resource": "company-*"
|
"action": "provider.use",
|
||||||
}
|
"resource": "company-*"
|
||||||
]
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -78,18 +82,20 @@ For example, allow only Anthropic:
|
||||||
```json title="opencode.json"
|
```json title="opencode.json"
|
||||||
{
|
{
|
||||||
"$schema": "https://opencode.ai/config.json",
|
"$schema": "https://opencode.ai/config.json",
|
||||||
"policies": [
|
"experimental": {
|
||||||
{
|
"policies": [
|
||||||
"effect": "deny",
|
{
|
||||||
"action": "provider.use",
|
"effect": "deny",
|
||||||
"resource": "*"
|
"action": "provider.use",
|
||||||
},
|
"resource": "*"
|
||||||
{
|
},
|
||||||
"effect": "allow",
|
{
|
||||||
"action": "provider.use",
|
"effect": "allow",
|
||||||
"resource": "anthropic"
|
"action": "provider.use",
|
||||||
}
|
"resource": "anthropic"
|
||||||
]
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -107,10 +113,12 @@ To replace `disabled_providers`:
|
||||||
|
|
||||||
```json title="opencode.json"
|
```json title="opencode.json"
|
||||||
{
|
{
|
||||||
"policies": [
|
"experimental": {
|
||||||
{ "effect": "deny", "action": "provider.use", "resource": "openai" },
|
"policies": [
|
||||||
{ "effect": "deny", "action": "provider.use", "resource": "google" }
|
{ "effect": "deny", "action": "provider.use", "resource": "openai" },
|
||||||
]
|
{ "effect": "deny", "action": "provider.use", "resource": "google" }
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -118,10 +126,12 @@ To replace `enabled_providers`, deny all providers first and allow the selected
|
||||||
|
|
||||||
```json title="opencode.json"
|
```json title="opencode.json"
|
||||||
{
|
{
|
||||||
"policies": [
|
"experimental": {
|
||||||
{ "effect": "deny", "action": "provider.use", "resource": "*" },
|
"policies": [
|
||||||
{ "effect": "allow", "action": "provider.use", "resource": "anthropic" },
|
{ "effect": "deny", "action": "provider.use", "resource": "*" },
|
||||||
{ "effect": "allow", "action": "provider.use", "resource": "openai" }
|
{ "effect": "allow", "action": "provider.use", "resource": "anthropic" },
|
||||||
]
|
{ "effect": "allow", "action": "provider.use", "resource": "openai" }
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -82,27 +82,29 @@ Provider catalog customization and model-choice configuration. The new core work
|
||||||
| Field | Current Purpose | Status | Notes |
|
| Field | Current Purpose | Status | Notes |
|
||||||
| -------------------- | ------------------------------------------------- | -------- | --------------------------------------------------------------------------------------- |
|
| -------------------- | ------------------------------------------------- | -------- | --------------------------------------------------------------------------------------- |
|
||||||
| `provider` | Custom provider configuration and model overrides | pending | New core schema currently uses `providers`; decide public key compatibility. |
|
| `provider` | Custom provider configuration and model overrides | pending | New core schema currently uses `providers`; decide public key compatibility. |
|
||||||
| `disabled_providers` | Disable automatically loaded providers | redesign | Replace with `policies: [{ effect: "deny", action: "provider.use", resource: "..." }]`. |
|
| `disabled_providers` | Disable automatically loaded providers | redesign | Replace with `experimental.policies: [{ effect: "deny", action: "provider.use", resource: "..." }]`. |
|
||||||
| `enabled_providers` | Restrict enabled providers to an allowlist | redesign | Replace with ordered `provider.use` allow/deny statements and wildcard resources. |
|
| `enabled_providers` | Restrict enabled providers to an allowlist | redesign | Replace with ordered `provider.use` allow/deny statements and wildcard resources. |
|
||||||
| `model` | Default model selection | pending | |
|
| `model` | Default model selection | pending | |
|
||||||
| `small_model` | Small/utility model selection | pending | |
|
| `small_model` | Small/utility model selection | pending | |
|
||||||
|
|
||||||
Provider selection rules belong in a plural `policies` array rather than provider entries or repeated top-level provider fields. Initial proposed shape:
|
Provider selection rules belong in `experimental.policies` rather than provider entries or repeated top-level provider fields. Initial proposed shape:
|
||||||
|
|
||||||
```jsonc
|
```jsonc
|
||||||
{
|
{
|
||||||
"policies": [
|
"experimental": {
|
||||||
{
|
"policies": [
|
||||||
"effect": "deny",
|
{
|
||||||
"action": "provider.use",
|
"effect": "deny",
|
||||||
"resource": "*",
|
"action": "provider.use",
|
||||||
},
|
"resource": "*",
|
||||||
{
|
},
|
||||||
"effect": "allow",
|
{
|
||||||
"action": "provider.use",
|
"effect": "allow",
|
||||||
"resource": "anthropic",
|
"action": "provider.use",
|
||||||
},
|
"resource": "anthropic",
|
||||||
],
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ resource: provider ID, such as openai or company-ai
|
||||||
Provider configuration and provider policy remain separate:
|
Provider configuration and provider policy remain separate:
|
||||||
|
|
||||||
- `providers` describes endpoints, options, and model overrides.
|
- `providers` describes endpoints, options, and model overrides.
|
||||||
- `policies` determines whether an operation using a provider is allowed.
|
- `experimental.policies` determines whether an operation using a provider is allowed.
|
||||||
|
|
||||||
A provider can be correctly configured and have valid credentials while policy still denies its use.
|
A provider can be correctly configured and have valid credentials while policy still denies its use.
|
||||||
|
|
||||||
|
|
@ -38,13 +38,15 @@ A provider can be correctly configured and have valid credentials while policy s
|
||||||
|
|
||||||
```jsonc
|
```jsonc
|
||||||
{
|
{
|
||||||
"policies": [
|
"experimental": {
|
||||||
{
|
"policies": [
|
||||||
"effect": "deny",
|
{
|
||||||
"action": "provider.use",
|
"effect": "deny",
|
||||||
"resource": "openai",
|
"action": "provider.use",
|
||||||
},
|
"resource": "openai",
|
||||||
],
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -56,7 +58,7 @@ interface PolicyInfo {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
The `Policy` module owns the shared `Policy.Info` interface, `Policy.Effect` type, and evaluator. Domains define their supported typed statement schemas; for example, `Catalog.ProviderPolicy` fixes `action` to `"provider.use"`. The config schema gathers those domain-defined statement schemas into the accepted `policies` union because config files are one place statements can be authored.
|
The `Policy` module owns the shared `Policy.Info` interface, `Policy.Effect` type, and evaluator. Domains define their supported typed statement schemas; for example, `Catalog.ProviderPolicy` fixes `action` to `"provider.use"`. The config schema gathers those domain-defined statement schemas into the accepted `experimental.policies` union because config files are one place statements can be authored while the capability is experimental.
|
||||||
|
|
||||||
## Matching
|
## Matching
|
||||||
|
|
||||||
|
|
@ -103,18 +105,20 @@ To deny all providers except Anthropic:
|
||||||
|
|
||||||
```jsonc
|
```jsonc
|
||||||
{
|
{
|
||||||
"policies": [
|
"experimental": {
|
||||||
{
|
"policies": [
|
||||||
"effect": "deny",
|
{
|
||||||
"action": "provider.use",
|
"effect": "deny",
|
||||||
"resource": "*",
|
"action": "provider.use",
|
||||||
},
|
"resource": "*",
|
||||||
{
|
},
|
||||||
"effect": "allow",
|
{
|
||||||
"action": "provider.use",
|
"effect": "allow",
|
||||||
"resource": "anthropic",
|
"action": "provider.use",
|
||||||
},
|
"resource": "anthropic",
|
||||||
],
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -129,11 +133,13 @@ To allow internal providers except experimental ones:
|
||||||
|
|
||||||
```jsonc
|
```jsonc
|
||||||
{
|
{
|
||||||
"policies": [
|
"experimental": {
|
||||||
{ "effect": "deny", "action": "provider.use", "resource": "*" },
|
"policies": [
|
||||||
{ "effect": "allow", "action": "provider.use", "resource": "company-*" },
|
{ "effect": "deny", "action": "provider.use", "resource": "*" },
|
||||||
{ "effect": "deny", "action": "provider.use", "resource": "company-experimental-*" },
|
{ "effect": "allow", "action": "provider.use", "resource": "company-*" },
|
||||||
],
|
{ "effect": "deny", "action": "provider.use", "resource": "company-experimental-*" },
|
||||||
|
],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -159,7 +165,9 @@ Project config:
|
||||||
|
|
||||||
```jsonc
|
```jsonc
|
||||||
{
|
{
|
||||||
"policies": [{ "effect": "allow", "action": "provider.use", "resource": "openai" }],
|
"experimental": {
|
||||||
|
"policies": [{ "effect": "allow", "action": "provider.use", "resource": "openai" }],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -167,7 +175,9 @@ User-global config:
|
||||||
|
|
||||||
```jsonc
|
```jsonc
|
||||||
{
|
{
|
||||||
"policies": [{ "effect": "deny", "action": "provider.use", "resource": "openai" }],
|
"experimental": {
|
||||||
|
"policies": [{ "effect": "deny", "action": "provider.use", "resource": "openai" }],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -203,10 +213,12 @@ Provider policy is not a full sandbox for executable plugins. A denied provider
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"policies": [
|
"experimental": {
|
||||||
{ "effect": "deny", "action": "provider.use", "resource": "*" },
|
"policies": [
|
||||||
{ "effect": "allow", "action": "provider.use", "resource": "company-ai" },
|
{ "effect": "deny", "action": "provider.use", "resource": "*" },
|
||||||
],
|
{ "effect": "allow", "action": "provider.use", "resource": "company-ai" },
|
||||||
|
],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -247,10 +259,12 @@ Equivalent v2 policy:
|
||||||
|
|
||||||
```jsonc
|
```jsonc
|
||||||
{
|
{
|
||||||
"policies": [
|
"experimental": {
|
||||||
{ "effect": "deny", "action": "provider.use", "resource": "openai" },
|
"policies": [
|
||||||
{ "effect": "deny", "action": "provider.use", "resource": "google" },
|
{ "effect": "deny", "action": "provider.use", "resource": "openai" },
|
||||||
],
|
{ "effect": "deny", "action": "provider.use", "resource": "google" },
|
||||||
|
],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -266,10 +280,12 @@ Equivalent v2 policy:
|
||||||
|
|
||||||
```jsonc
|
```jsonc
|
||||||
{
|
{
|
||||||
"policies": [
|
"experimental": {
|
||||||
{ "effect": "deny", "action": "provider.use", "resource": "*" },
|
"policies": [
|
||||||
{ "effect": "allow", "action": "provider.use", "resource": "anthropic" },
|
{ "effect": "deny", "action": "provider.use", "resource": "*" },
|
||||||
{ "effect": "allow", "action": "provider.use", "resource": "openai" },
|
{ "effect": "allow", "action": "provider.use", "resource": "anthropic" },
|
||||||
],
|
{ "effect": "allow", "action": "provider.use", "resource": "openai" },
|
||||||
|
],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue