Sessions g1t starts on Anthropic work through the g1t CLI, and their Console links open in the key's own Console workspace. The sandbox had no CLI, so the agent reached g1t only through MCP and hand-written git headers, though the work-on-issue skill it follows is written for the CLI (g1t work context, g1t work claim, g1t pr checks --watch). The kickoff now opens with one setup command: install the CLI from the site (into /usr/local/bin, else ~/.local/bin), have git send G1T_TOKEN to the site in a header (the vault swaps the placeholder there), clone the repository, and write the session id to the clone's .git/g1t/session, where the CLI keeps it, so claims and notes are this session's. The system prompt tells the agent to run it first, to use the CLI as the skill does, and MCP for what the CLI has no command for; agents already on accounts take the new prompt at their next session (config_hash).
The Console link was always .../workspaces/default/..., which lands on "Session not found" when the key belongs to another Console workspace, and a session does not say which workspace it is in. An Anthropic AI provider has an optional Console workspace (AiConfig console_workspace, consoleWorkspace in TypeScript): its wrkspc_ id, or any Console address in that workspace, from which the id is taken. Session links use it, else default. Other providers drop it. The AI page asks for it when adding Anthropic and under the provider's Change. API: config.console_workspace on add/update/list AI providers. Docs: the AI guide's Anthropic Console workspace section, and the sessions guide on the setup and the link. No migration (the provider's config is JSON). Deploy integrations, api and web together, and the docs.
- Checked:
- cargo test -p g1t-integrations --lib (72, new: the Console workspace from an address or an id, Console links by workspace), g1t-contracts and g1t-api (the OpenAPI document regenerated), wasm32 checks of integrations and api, the site's tsc -b --force and tests (934), the docs build.
| 525 | 525 | "base_url": { "type": "string", "description": "The address of a self-hosted endpoint or an Azure OpenAI resource, over https. An Anthropic-compatible one without /v1; an OpenAI-compatible one up to and including the version." }, | |
| 526 | 526 | "auth_header": { "type": "string", "enum": ["x-api-key", "authorization"], "description": "For an endpoint: send the key as x-api-key, or as authorization: Bearer." }, | |
| 527 | 527 | "model": { "type": "string", "description": "The model the team uses on it; for Azure OpenAI, the deployment." }, | |
| 528 | + | "console_workspace": { "type": "string", "description": "For anthropic: the Console workspace the key belongs to (wrkspc_…), or any Console address in it, so links to the sessions it starts open there. Without it, the account's Default workspace." }, | |
| 528 | 529 | }, | |
| 529 | 530 | }) | |
| 530 | 531 | } |
| 47 | 47 | You can name the model your team uses on each provider. It is optional, | |
| 48 | 48 | except for Azure OpenAI, where it is the deployment. | |
| 49 | 49 | ||
| 50 | + | ### Anthropic Console workspace | |
| 51 | + | ||
| 52 | + | Sessions on issues run on the Anthropic provider's account, and the | |
| 53 | + | Anthropic Console shows each one in the Console workspace its key belongs | |
| 54 | + | to. A session does not say which workspace that is, so if the key is not | |
| 55 | + | in the account's Default workspace, tell g1t: paste any Console address | |
| 56 | + | from that workspace (such as | |
| 57 | + | `https://platform.claude.com/workspaces/wrkspc_01AbC…/sessions`), or its | |
| 58 | + | `wrkspc_` id, into the provider's **Console workspace**. Links to its | |
| 59 | + | sessions then open there. Left empty, they open in the Default workspace. | |
| 60 | + | ||
| 50 | 61 | ## Look after a provider | |
| 51 | 62 | ||
| 52 | 63 | - **Test.** g1t checks a key when it is saved, and again whenever you | |
| ⋯ | |||
| 55 | 66 | provider, until it next works. An endpoint that does not list its | |
| 56 | 67 | models is reached, but its key cannot be checked this way. | |
| 57 | 68 | - **Change.** **Change its model, address or key** sets a new model or | |
| 58 | − | address, or replaces the key. The old key is gone once the new one is | |
| 69 | + | address, or replaces the key (for Anthropic, its model, Console | |
| 70 | + | workspace or key). The old key is gone once the new one is | |
| 59 | 71 | saved. | |
| 60 | 72 | - **Remove.** The **×** on a provider removes it and deletes its key. If | |
| 61 | 73 | Ask was using it, Ask is turned off until an owner chooses another. | |
| 276 | 276 | under [**AI**](/guides/ai/)), g1t starts a | |
| 277 | 277 | [Claude Managed Agents](https://platform.claude.com/docs/en/managed-agents/overview) | |
| 278 | 278 | session on that account, with its key, on the model Ask is set to use | |
| 279 | − | (or `claude-opus-5-5`). It runs in a sandbox on Anthropic's side and | |
| 280 | − | reaches g1t through g1t's MCP server and git, with a token made for you | |
| 279 | + | (or `claude-opus-5-5`). It runs in a sandbox on Anthropic's side. Its | |
| 280 | + | first step installs the [g1t CLI](/guides/cli/) from g1t, sets git up to | |
| 281 | + | send the token, clones the repository and ties the clone to the session, | |
| 282 | + | so it works the way the work-on-issue skill describes: `g1t work | |
| 283 | + | context`, `g1t work claim`, `g1t pr create`, `g1t pr checks --watch`. | |
| 284 | + | g1t's MCP server is attached too, for anything the CLI has no command | |
| 285 | + | for. Both reach g1t with a token made for you | |
| 281 | 286 | and that repository only, which lasts the session and is revoked when it | |
| 282 | 287 | ends. The token is kept in a vault on the Anthropic account: the MCP | |
| 283 | 288 | server's credential, and `G1T_TOKEN`, which the sandbox holds only as a | |
| ⋯ | |||
| 293 | 298 | The session that starts attaches to the issue. | |
| 294 | 299 | ||
| 295 | 300 | The answer has the `session` on g1t, its `session_url`, and | |
| 296 | − | `provider_session_url`, where the Anthropic Console shows it (its | |
| 297 | − | `default` workspace; another workspace's sessions are under its own id). | |
| 301 | + | `provider_session_url`, where the Anthropic Console shows it. The Console | |
| 302 | + | keeps sessions in the workspace the key belongs to, and a session does not | |
| 303 | + | say which that is: if the key is not in the account's Default workspace, | |
| 304 | + | set the provider's **Console workspace** under [**AI**](/guides/ai/#anthropic-console-workspace) | |
| 305 | + | so the link opens there. | |
| 298 | 306 | The session is recorded as harness `claude_managed_agents`: its messages, | |
| 299 | 307 | the tools it called and with what (never what they returned), the skills | |
| 300 | 308 | it read, what it touched, and each thread as a subagent with its task and | |
Binary or large file; its contents are not shown.
| 89 | 89 | <Field label="Model" hint="Optional. The model your team uses on it. g1t lists the key's models when it checks the key."> | |
| 90 | 90 | <Input name="model" placeholder={look.model} /> | |
| 91 | 91 | </Field> | |
| 92 | + | {provider === "anthropic" && <ConsoleWorkspaceField />} | |
| 92 | 93 | </> | |
| 93 | 94 | ); | |
| 94 | 95 | } | |
| 96 | + | ||
| 97 | + | /** | |
| 98 | + | * The Console workspace an Anthropic key belongs to: links to the sessions | |
| 99 | + | * it starts open there. Sessions don't say which workspace they are in. | |
| 100 | + | */ | |
| 101 | + | export function ConsoleWorkspaceField({ defaultValue }: { defaultValue?: string }) { | |
| 102 | + | return ( | |
| 103 | + | <Field | |
| 104 | + | label="Console workspace" | |
| 105 | + | hint="Optional. Paste any Console address from the workspace the key belongs to, or its wrkspc_ id, so session links open there. Empty means the Default workspace." | |
| 106 | + | > | |
| 107 | + | <Input | |
| 108 | + | name="consoleWorkspace" | |
| 109 | + | defaultValue={defaultValue ?? ""} | |
| 110 | + | placeholder="https://platform.claude.com/workspaces/wrkspc_…" | |
| 111 | + | className="max-w-sm font-mono text-[0.8125rem]" | |
| 112 | + | autoComplete="off" | |
| 113 | + | /> | |
| 114 | + | </Field> | |
| 115 | + | ); | |
| 116 | + | } |
| 7 | 7 | import type { Route } from "./+types/ai"; | |
| 8 | 8 | import { page } from "../../lib/meta"; | |
| 9 | 9 | import { AiProviderMark } from "../../components/connectors"; | |
| 10 | − | import { ModelCatalog, ModelProviderFields } from "../../components/model-providers"; | |
| 10 | + | import { ConsoleWorkspaceField, ModelCatalog, ModelProviderFields } from "../../components/model-providers"; | |
| 11 | 11 | import { AI_PROVIDER_LOOKS, isEndpoint } from "../../lib/ai-providers"; | |
| 12 | 12 | import { ErrorText, Field, Input, SubmitButton, TimeAgo } from "../../components/ui"; | |
| 13 | 13 | import { Card } from "../../components/ui/card"; | |
| ⋯ | |||
| 80 | 80 | ...connection.config, | |
| 81 | 81 | model: text(form, "model"), | |
| 82 | 82 | ...(form.has("baseUrl") ? { baseUrl: text(form, "baseUrl") } : {}), | |
| 83 | + | ...(form.has("consoleWorkspace") ? { consoleWorkspace: text(form, "consoleWorkspace") } : {}), | |
| 83 | 84 | }, | |
| 84 | 85 | secret: text(form, "secret"), | |
| 85 | 86 | }); | |
| ⋯ | |||
| 94 | 95 | baseUrl: text(form, "baseUrl"), | |
| 95 | 96 | authHeader: text(form, "authHeader"), | |
| 96 | 97 | model: text(form, "model"), | |
| 98 | + | consoleWorkspace: text(form, "consoleWorkspace"), | |
| 97 | 99 | }, | |
| 98 | 100 | secret: text(form, "secret"), | |
| 99 | 101 | }); | |
| ⋯ | |||
| 349 | 351 | <details className="group mt-3" open={updated || undefined}> | |
| 350 | 352 | <summary className="inline-flex cursor-pointer list-none items-center gap-1 text-xs text-muted hover:text-fg"> | |
| 351 | 353 | <ChevronRight size={12} className="transition-transform group-open:rotate-90" /> | |
| 352 | − | Change its model, address or key | |
| 354 | + | {connection.provider === "anthropic" ? "Change its model, Console workspace or key" : "Change its model, address or key"} | |
| 353 | 355 | </summary> | |
| 354 | 356 | <Form method="post" className="mt-3 max-w-2xl space-y-3"> | |
| 355 | 357 | <input type="hidden" name="intent" value="provider" /> | |
| ⋯ | |||
| 376 | 378 | ))} | |
| 377 | 379 | </datalist> | |
| 378 | 380 | )} | |
| 381 | + | {connection.provider === "anthropic" && <ConsoleWorkspaceField defaultValue={connection.config.consoleWorkspace} />} | |
| 379 | 382 | <Field label="Replace the key" hint="Write-only: g1t never shows it again. Leave it empty to keep the key you have."> | |
| 380 | 383 | <Input | |
| 381 | 384 | name="secret" | |
| 165 | 165 | /// The model the team uses on it; for Azure OpenAI, the deployment. | |
| 166 | 166 | #[serde(default, skip_serializing_if = "Option::is_none")] | |
| 167 | 167 | pub model: Option<String>, | |
| 168 | + | /// For Anthropic: the Console workspace the key belongs to, as its | |
| 169 | + | /// Console address names it (`wrkspc_…`), so links to the sessions it | |
| 170 | + | /// starts open there. Without it, the account's Default workspace. | |
| 171 | + | #[serde(default, skip_serializing_if = "Option::is_none")] | |
| 172 | + | pub console_workspace: Option<String>, | |
| 168 | 173 | } | |
| 169 | 174 | ||
| 170 | 175 | /// One of a workspace's AI providers, as any member sees it. Its key is |
| 98 | 98 | authHeader?: string; | |
| 99 | 99 | /** The model the team uses on it; for Azure OpenAI, the deployment. */ | |
| 100 | 100 | model?: string; | |
| 101 | + | /** | |
| 102 | + | * For Anthropic: the Console workspace the key belongs to, as its Console | |
| 103 | + | * address names it (`wrkspc_…`), so links to the sessions it starts open | |
| 104 | + | * there. Without it, the account's Default workspace. | |
| 105 | + | */ | |
| 106 | + | consoleWorkspace?: string; | |
| 101 | 107 | }; | |
| 102 | 108 | ||
| 103 | 109 | /** One of a workspace's AI providers, as any member sees it. */ |
| 104 | 104 | pub fn system_prompt() -> String { | |
| 105 | 105 | "You work on issues in a g1t repository (g1t is a git forge), for the person who started you, with their own Claude. \ | |
| 106 | 106 | Each session's first message says which repository and issue, what kind of work, and how to reach them.\n\n\ | |
| 107 | − | - g1t's MCP server (the `g1t` tools) acts as that person on that repository only: read the issue and its work context (`issue` action `work`), \ | |
| 108 | − | claim it (`issue` action `claim`), see who else is on it (`session` action `list` with the issue), open and update pull requests, read checks and runs.\n\ | |
| 109 | − | - `G1T_TOKEN` is set for git and the g1t CLI. Use git with it as a header, never in a URL: \ | |
| 110 | − | `git -c http.extraHeader=\"Authorization: Bearer $G1T_TOKEN\" clone …`, then `git config http.extraHeader \"Authorization: Bearer $G1T_TOKEN\"` in the clone. \ | |
| 111 | − | Never print it, write it to a file, or put it anywhere else.\n\ | |
| 112 | − | - Follow the work-on-issue skill. Check get_work and the sessions on the issue before you start, and claim only what you take. \ | |
| 107 | + | - Run the setup in your first message before anything else: it installs the g1t CLI, has git send your token to g1t, \ | |
| 108 | + | clones the repository and ties the clone to this g1t session.\n\ | |
| 109 | + | - Use the g1t CLI from the clone, as the work-on-issue skill does: `g1t work context N`, `g1t work claim N --note …`, \ | |
| 110 | + | `g1t pr create --issue N --draft --fill`, `g1t pr checks --watch --fail-fast`, `g1t run logs --failed`. \ | |
| 111 | + | Add `--json` when you need fields; `g1t <command> --help` says the rest.\n\ | |
| 112 | + | - g1t's MCP server (the `g1t` tools) does the same as that person on that repository only. Use it when the CLI has no command for something.\n\ | |
| 113 | + | - `G1T_TOKEN` is set for the CLI and git. In the sandbox it is a placeholder that becomes the token only in a header on the way to g1t, \ | |
| 114 | + | never in a URL. Never print it or send it anywhere else.\n\ | |
| 115 | + | - Follow the work-on-issue skill. Check the work context and the sessions on the issue before you start, and claim only what you take. \ | |
| 113 | 116 | When work splits into independent parts, you may give a part to a copy of yourself; each is recorded on g1t as a subagent and sees the others.\n\ | |
| 114 | 117 | - Put the trailer `G1t-Session: <the session id in your first message>` on every commit, so g1t ties the commits to this session.\n\ | |
| 115 | 118 | - Work on a branch whose name starts with the issue number, and open a draft pull request early. Never push to the default branch.\n\ | |
| ⋯ | |||
| 213 | 216 | body | |
| 214 | 217 | } | |
| 215 | 218 | ||
| 216 | − | /// Where Anthropic's Console shows a session. | |
| 217 | − | pub fn console_url(provider_session_id: &str) -> String { | |
| 218 | − | format!("https://platform.claude.com/workspaces/default/sessions/{provider_session_id}") | |
| 219 | + | /// Where Anthropic's Console shows a session: in the Console workspace the | |
| 220 | + | /// key belongs to. Sessions do not say which that is, so the provider's | |
| 221 | + | /// settings do; without it, the account's Default workspace. | |
| 222 | + | pub fn console_url(console_workspace: Option<&str>, provider_session_id: &str) -> String { | |
| 223 | + | let workspace = console_workspace.filter(|id| !id.is_empty()).unwrap_or("default"); | |
| 224 | + | format!("https://platform.claude.com/workspaces/{workspace}/sessions/{provider_session_id}") | |
| 219 | 225 | } | |
| 220 | 226 | ||
| 221 | 227 | /// What a session is asked to do. | |
| ⋯ | |||
| 261 | 267 | text.push_str(&format!("\nAlso: {}\n", cut(instruction, MAX_INSTRUCTION_CHARS))); | |
| 262 | 268 | } | |
| 263 | 269 | if k.managed { | |
| 264 | − | let host = site.trim_start_matches("https://").trim_start_matches("http://"); | |
| 270 | + | text.push_str(&format!("\nSet up first, as one command:\n\n```sh\n{}\n```\n", setup_command(site, &repo, &k.repo.name, k.session_id))); | |
| 265 | 271 | text.push_str(&format!( | |
| 266 | − | "\nThe repository: `git -c http.extraHeader=\"Authorization: Bearer $G1T_TOKEN\" clone https://{host}/{repo}.git /workspace/{}`.\n", | |
| 272 | + | "\nIf /usr/local/bin cannot be written, install without G1T_INSTALL_DIR and run ~/.local/bin/g1t. Then work in /workspace/{}.\n", | |
| 267 | 273 | k.repo.name | |
| 268 | 274 | )); | |
| 269 | 275 | } | |
| ⋯ | |||
| 274 | 280 | text | |
| 275 | 281 | } | |
| 276 | 282 | ||
| 283 | + | /// What a sandbox runs before it works: the g1t CLI from the site, git | |
| 284 | + | /// sending `G1T_TOKEN` to the site in a header (the vault swaps the | |
| 285 | + | /// placeholder there), the clone, and the clone's g1t session where the | |
| 286 | + | /// CLI keeps it, so claims and notes are this session's. | |
| 287 | + | pub fn setup_command(site: &str, repo: &str, name: &str, session_id: Option<&str>) -> String { | |
| 288 | + | let site = site.trim_end_matches('/'); | |
| 289 | + | let mut steps = vec![ | |
| 290 | + | format!("curl -fsSL {site}/install | G1T_INSTALL_DIR=/usr/local/bin sh"), | |
| 291 | + | format!("git config --global http.{site}/.extraHeader \"Authorization: Bearer $G1T_TOKEN\""), | |
| 292 | + | format!("git clone {site}/{repo}.git /workspace/{name}"), | |
| 293 | + | ]; | |
| 294 | + | if let Some(id) = session_id { | |
| 295 | + | steps.push(format!("mkdir -p /workspace/{name}/.git/g1t && echo {id} > /workspace/{name}/.git/g1t/session")); | |
| 296 | + | } | |
| 297 | + | steps.join(" \\\n && ") | |
| 298 | + | } | |
| 299 | + | ||
| 277 | 300 | /// Quotes `text` for a POSIX shell. | |
| 278 | 301 | fn shell_quote(text: &str) -> String { | |
| 279 | 302 | format!("'{}'", text.replace('\'', "'\"'\"'")) | |
| ⋯ | |||
| 954 | 977 | session_url: Some(format!("{}/{repo_name}/work/sessions/{}", self.site_url.trim_end_matches('/'), session.id)), | |
| 955 | 978 | session: Some(session), | |
| 956 | 979 | provider: Some("anthropic".to_owned()), | |
| 957 | − | provider_session_url: Some(console_url(&provider_session)), | |
| 980 | + | provider_session_url: Some(console_url(row.config().console_workspace.as_deref(), &provider_session)), | |
| 958 | 981 | provider_session_id: Some(provider_session), | |
| 959 | 982 | handoff: false, | |
| 960 | 983 | command: None, | |
| ⋯ | |||
| 1357 | 1380 | // Nothing secret is in what the agent is: its credentials are the vault's. | |
| 1358 | 1381 | assert!(!body.to_string().contains("g1t_")); | |
| 1359 | 1382 | assert!(system_prompt().contains("never in a URL")); | |
| 1383 | + | assert!(system_prompt().contains("g1t pr checks --watch --fail-fast")); | |
| 1360 | 1384 | } | |
| 1361 | 1385 | ||
| 1362 | 1386 | #[test] | |
| 1387 | + | fn console_links_open_in_the_keys_console_workspace() { | |
| 1388 | + | assert_eq!(console_url(Some("wrkspc_01AbC"), "sesn_1"), "https://platform.claude.com/workspaces/wrkspc_01AbC/sessions/sesn_1"); | |
| 1389 | + | assert_eq!(console_url(None, "sesn_1"), "https://platform.claude.com/workspaces/default/sessions/sesn_1"); | |
| 1390 | + | assert_eq!(console_url(Some(""), "sesn_1"), "https://platform.claude.com/workspaces/default/sessions/sesn_1"); | |
| 1391 | + | } | |
| 1392 | + | ||
| 1393 | + | #[test] | |
| 1363 | 1394 | fn the_token_is_in_a_vault_never_in_the_session_or_its_prompt() { | |
| 1364 | 1395 | let hosts = vec!["g1t.sh".to_owned(), "api.g1t.sh".to_owned(), "mcp.g1t.sh".to_owned()]; | |
| 1365 | 1396 | let mcp = mcp_credential("https://mcp.g1t.sh", "g1t_secret"); | |
| ⋯ | |||
| 1391 | 1422 | assert!(prompt.contains("Work on issue #4 in acme/web on g1t (https://g1t.sh/acme/web/issues/4): Greet")); | |
| 1392 | 1423 | assert!(prompt.contains("<issue>\nSay hello.\n</issue>")); | |
| 1393 | 1424 | assert!(prompt.contains("Also: Keep it short.")); | |
| 1394 | − | assert!(prompt.contains("Authorization: Bearer $G1T_TOKEN\" clone https://g1t.sh/acme/web.git /workspace/web")); | |
| 1425 | + | assert!(prompt.contains("curl -fsSL https://g1t.sh/install | G1T_INSTALL_DIR=/usr/local/bin sh")); | |
| 1426 | + | assert!(prompt.contains("git config --global http.https://g1t.sh/.extraHeader \"Authorization: Bearer $G1T_TOKEN\"")); | |
| 1427 | + | assert!(prompt.contains("git clone https://g1t.sh/acme/web.git /workspace/web")); | |
| 1428 | + | assert!(prompt.contains("echo ses_1 > /workspace/web/.git/g1t/session")); | |
| 1395 | 1429 | assert!(prompt.contains("G1t-Session: ses_1")); | |
| 1396 | 1430 | let environment = environment_body("g1t-acme-con_1", &hosts); | |
| 1397 | 1431 | assert_eq!(environment["config"]["networking"]["allowed_hosts"], json!(hosts)); | |
| 98 | 98 | .then(|| fail(FailureCode::Forbidden, "Only an owner of the workspace can manage its AI providers.")) | |
| 99 | 99 | } | |
| 100 | 100 | ||
| 101 | + | /// A Console workspace's id from the id itself or any Console address in | |
| 102 | + | /// that workspace (`…/workspaces/<id>/…`). | |
| 103 | + | fn console_workspace(given: &str) -> Option<String> { | |
| 104 | + | let id = match given.split_once("/workspaces/") { | |
| 105 | + | Some((_, rest)) => rest.split(['/', '?', '#']).next().unwrap_or_default(), | |
| 106 | + | None => given, | |
| 107 | + | }; | |
| 108 | + | let valid = !id.is_empty() && id.len() <= 64 && id.chars().all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-'); | |
| 109 | + | valid.then(|| id.to_owned()) | |
| 110 | + | } | |
| 111 | + | ||
| 101 | 112 | /// Checks an AI provider's settings, tidying them. The message says what | |
| 102 | 113 | /// to fix. | |
| 103 | 114 | fn check_config(provider: AiProvider, config: &mut AiConfig, secret: Option<&str>) -> std::result::Result<(), String> { | |
| ⋯ | |||
| 110 | 121 | for value in [&mut config.model, &mut config.auth_header] { | |
| 111 | 122 | *value = value.as_deref().map(str::trim).filter(|v| !v.is_empty()).map(str::to_owned); | |
| 112 | 123 | } | |
| 124 | + | config.console_workspace = match config.console_workspace.as_deref().map(str::trim).filter(|v| !v.is_empty()) { | |
| 125 | + | Some(given) if provider == AiProvider::Anthropic => Some(console_workspace(given).ok_or_else(|| { | |
| 126 | + | "Give the Console workspace's id, such as wrkspc_01AbC…, or paste an address from that workspace in the Console.".to_owned() | |
| 127 | + | })?), | |
| 128 | + | _ => None, | |
| 129 | + | }; | |
| 113 | 130 | let needs = |present: bool, what: &str| if present { Ok(()) } else { Err(what.to_owned()) }; | |
| 114 | 131 | match provider { | |
| 115 | 132 | AiProvider::AzureOpenai => { | |
| ⋯ | |||
| 708 | 725 | assert!(check_config(AiProvider::AzureOpenai, &mut azure, key).is_ok()); | |
| 709 | 726 | } | |
| 710 | 727 | ||
| 728 | + | #[test] | |
| 729 | + | fn an_anthropic_providers_console_workspace_is_its_id_from_an_address_or_itself() { | |
| 730 | + | let key = Some("sk-test"); | |
| 731 | + | let mut pasted = AiConfig { | |
| 732 | + | console_workspace: Some(" https://platform.claude.com/workspaces/wrkspc_01AbC/sessions/sesn_1?event=e ".into()), | |
| 733 | + | ..AiConfig::default() | |
| 734 | + | }; | |
| 735 | + | assert!(check_config(AiProvider::Anthropic, &mut pasted, key).is_ok()); | |
| 736 | + | assert_eq!(pasted.console_workspace.as_deref(), Some("wrkspc_01AbC")); | |
| 737 | + | let mut bare = AiConfig { console_workspace: Some("wrkspc_01AbC".into()), ..AiConfig::default() }; | |
| 738 | + | assert!(check_config(AiProvider::Anthropic, &mut bare, key).is_ok()); | |
| 739 | + | assert_eq!(bare.console_workspace.as_deref(), Some("wrkspc_01AbC")); | |
| 740 | + | let mut empty = AiConfig { console_workspace: Some(" ".into()), ..AiConfig::default() }; | |
| 741 | + | assert!(check_config(AiProvider::Anthropic, &mut empty, key).is_ok()); | |
| 742 | + | assert_eq!(empty.console_workspace, None); | |
| 743 | + | let mut wrong = AiConfig { console_workspace: Some("not a workspace".into()), ..AiConfig::default() }; | |
| 744 | + | assert!(check_config(AiProvider::Anthropic, &mut wrong, key).is_err()); | |
| 745 | + | // Only Anthropic's Console has workspaces to link to. | |
| 746 | + | let mut other = AiConfig { console_workspace: Some("wrkspc_01AbC".into()), ..AiConfig::default() }; | |
| 747 | + | assert!(check_config(AiProvider::Openai, &mut other, key).is_ok()); | |
| 748 | + | assert_eq!(other.console_workspace, None); | |
| 749 | + | } | |
| 750 | + | ||
| 711 | 751 | fn connection(provider: AiProvider, model: Option<&str>, models: &[&str]) -> AiConnection { | |
| 712 | 752 | AiConnection { | |
| 713 | 753 | id: "con_1".into(), | |
Comments
Sign in to comment.