6 Commits
v1.3.0 ... main

Author SHA1 Message Date
fc4bc249b6 docs: refocus README on agentic stack
Some checks failed
Watch Mistral OpenAPI Spec / check-spec (push) Has been cancelled
Lead with workflows, conversations, and connectors — the differentiated
surface no other Go SDK covers — and acknowledge that the OpenAI-wire-
compatible /v1/chat/completions endpoint is the easier path for plain
chat use cases.

Reorders sections so the workflow-execute-with-connector-bindings
example is first, conversations second, chat completions third (and
explicitly signposted as 'when that's all you need'). API coverage
table moved below configuration and error handling — it's reference,
not a hook.
2026-04-28 13:36:27 +02:00
8d1c839093 feat: sync to upstream OpenAPI v1.0.0 / Python SDK v2.4.3
Group A — gap fills:
- restore Client.GetWorkflowWorkerInfo (regression from v1.3.0;
  /v1/workflows/workers/whoami is still in the spec and is needed by
  callers running custom workers)
- add Client.GetChatCompletionFields, GetChatCompletionFieldOptions,
  GetChatCompletionFieldOptionsCounts (previously-missing observability
  fields API)

Group B — Python SDK v2.3.0..v2.4.3 sync:
- workflow.EncodedPayloadOption typed enum (offloaded / encrypted /
  encrypted-partial); replaces []string on NetworkEncodedInput.EncodingOptions.
  Wire-compatible refinement; source-incompatible for callers that built the
  slice as []string literals.
- workflow-connector integration: ConnectorSlot, ConnectorBindings,
  ConnectorExtensions, WorkflowExtensions, BuildConnectorExtensions(...)
  helper, ConnectorAuthTaskState, ConnectorAuthStatus constants.
  New Extensions map[string]any field on workflow.ExecutionRequest.
- HITL confirmation constants: conversation.Confirmation enum
  (ConfirmationAllow/Deny) and ConfirmationStatusPending/Allowed/Denied
  alongside the pre-existing ToolCallConfirmation type and
  tool_confirmations field.

No-op verification:
- ChatCompletionChoice.message remains singular per spec v1.0.0;
  Python's 'messages[]' rename was internal SDK shape, not wire format.

Tests: 297 (was 284), all green. go vet clean.
2026-04-28 13:04:04 +02:00
c5bb663bc7 chore: sync openapi spec to upstream v1.0.0; fix watcher
Upstream OpenAPI spec moved from v0.1.104 (7c0bb6af) to v1.0.0 (ff8a7389).
Only delta is removal of OCR confidence-score fields/types, which the SDK
never wrapped — no code changes required.

The watcher previously tried to commit hash/spec updates to main, but the
gitea→github mirror reverts them on every sync, leaving issue #1 with
stale hashes across multiple upstream releases. Watcher now skips the
push and instead refreshes the open tracking issue's body on each run,
posting a comment when upstream moves again while the issue is still open.
2026-04-28 12:37:56 +02:00
fc61bc42ad docs: update upstream reference to OpenAPI spec as primary source 2026-04-10 01:52:16 +02:00
9b7488183d ci: bump actions/checkout to v5 for Node.js 24 compat 2026-04-10 01:48:34 +02:00
78f7c745a7 ci: add OpenAPI spec change watcher workflow
Monitors upstream Mistral OpenAPI spec daily, opens a GitHub issue
with a unified diff when changes are detected. Includes curl failure
handling and explicit workflow permissions.
2026-04-10 01:45:39 +02:00
16 changed files with 20521 additions and 137 deletions

132
.github/workflows/watch-openapi.yml vendored Normal file
View File

@@ -0,0 +1,132 @@
name: Watch Mistral OpenAPI Spec
on:
schedule:
# Runs daily at 06:00 UTC
- cron: "0 6 * * *"
workflow_dispatch: # manual trigger for testing
permissions:
contents: read
issues: write
jobs:
check-spec:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Fetch and compare spec
id: check
run: |
if ! curl --fail -sL https://raw.githubusercontent.com/mistralai/platform-docs-public/main/openapi.yaml -o new-spec.yaml; then
echo "::error::Failed to download OpenAPI spec"
exit 1
fi
NEW_HASH=$(sha256sum new-spec.yaml | cut -d' ' -f1)
if [ -f .openapi-hash ]; then
OLD_HASH=$(cat .openapi-hash)
else
OLD_HASH="none"
fi
echo "old=$OLD_HASH" >> "$GITHUB_OUTPUT"
echo "new=$NEW_HASH" >> "$GITHUB_OUTPUT"
if [ "$NEW_HASH" != "$OLD_HASH" ]; then
echo "changed=true" >> "$GITHUB_OUTPUT"
# Diff against the in-tree baseline (.openapi-spec.yaml).
# Maintainer commits a fresh baseline when addressing the issue.
if [ -f .openapi-spec.yaml ]; then
diff -u .openapi-spec.yaml new-spec.yaml > /tmp/spec-diff.txt || true
else
echo "No baseline .openapi-spec.yaml in repo - commit one to enable diffs." > /tmp/spec-diff.txt
fi
else
echo "changed=false" >> "$GITHUB_OUTPUT"
fi
- name: Open or refresh tracking issue
if: steps.check.outputs.changed == 'true'
uses: actions/github-script@v7
env:
OLD_HASH: ${{ steps.check.outputs.old }}
NEW_HASH: ${{ steps.check.outputs.new }}
with:
script: |
const fs = require('fs');
let diff = fs.readFileSync('/tmp/spec-diff.txt', 'utf8');
// Truncate if too long for issue body (GitHub issue body limit is 65536 chars)
if (diff.length > 50000) {
diff = diff.substring(0, 50000) + '\n\n... (truncated - view full spec for details)';
}
const oldHash = process.env.OLD_HASH;
const newHash = process.env.NEW_HASH;
const body =
`The upstream OpenAPI spec has changed and the SDK is out of sync.\n\n` +
`**Stored hash (.openapi-hash):** \`${oldHash}\`\n` +
`**Current upstream hash:** \`${newHash}\`\n` +
`_Last refreshed: ${new Date().toISOString()}_\n\n` +
`[View upstream spec](https://github.com/mistralai/platform-docs-public/blob/main/openapi.yaml)\n\n` +
`### Diff vs in-tree baseline\n\`\`\`diff\n${diff}\n\`\`\`\n\n` +
`### TODO\n` +
`- [ ] Review spec changes\n` +
`- [ ] Update SDK types if needed\n` +
`- [ ] Run tests\n` +
`- [ ] Bump \`.openapi-hash\` and refresh \`.openapi-spec.yaml\` in same commit\n` +
`- [ ] Close this issue\n\n` +
`<sub>This issue body is rewritten by the watcher each run, so the diff and hashes always reflect the latest upstream state.</sub>`;
const issues = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
labels: 'openapi-update',
state: 'open'
});
if (issues.data.length === 0) {
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: 'Mistral OpenAPI spec has changed',
body,
labels: ['openapi-update']
});
return;
}
// Refresh the existing open issue so the diff/hashes never go stale.
const issue = issues.data[0];
// Detect whether upstream moved *again* since the last refresh by
// parsing the previous "Current upstream hash" out of the issue body.
const marker = /\*\*Current upstream hash:\*\* `([a-f0-9]{64})`/;
const prev = (issue.body || '').match(marker);
const movedAgain = prev && prev[1] !== newHash;
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body
});
if (movedAgain) {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body:
`Upstream spec moved again since this issue was last refreshed.\n\n` +
`Previous upstream hash: \`${prev[1]}\`\n` +
`New upstream hash: \`${newHash}\`\n\n` +
`Issue body has been updated to the latest diff.`
});
}

1
.openapi-hash Normal file
View File

@@ -0,0 +1 @@
ff8a7389b7a4e61145561361537aae37c49f7e2dcf7c4f79f41e80a30b484cc3

19660
.openapi-spec.yaml Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +1,72 @@
## v1.4.0 — 2026-04-28
Spec/SDK alignment pass after upstream OpenAPI moved to v1.0.0 and
Python SDK shipped v2.3.0..v2.4.3. RAG ingestion-pipeline beta surface
(Python v2.4.3) intentionally deferred until the dust settles upstream.
### Added
- **`Client.GetWorkflowWorkerInfo`** — restores the
`GET /v1/workflows/workers/whoami` endpoint that was removed in v1.3.0.
The endpoint is still in the spec and is needed by callers running
custom workers that connect their own scheduler.
(`workflow.WorkerInfo` type.)
- **Observability fields API** — three GETs missing since the
observability surface was first added:
- `Client.GetChatCompletionFields` (`/v1/observability/chat-completion-fields`)
- `Client.GetChatCompletionFieldOptions` (`…/{field}/options?operator=…`)
- `Client.GetChatCompletionFieldOptionsCounts` (`…/{field}/options-counts`)
- new types: `observability.BaseFieldDefinition`, `FieldGroup`,
`ChatCompletionFields`, `ChatCompletionFieldOptions`,
`FieldOptionCountsRequest`, `FieldOptionCounts`, `FieldOptionCountItem`,
plus `FieldType` and `FieldOperator` typed enums.
- **Workflow payload encoding constants** — `workflow.EncodedPayloadOption`
with `EncodedPayloadOffloaded`, `EncodedPayloadEncrypted`,
`EncodedPayloadEncryptedPartial`. Wire-compatible refinement of the
pre-existing `[]string` field on `NetworkEncodedInput`.
(Mirrors Python SDK v2.4.0.)
- **Workflow ↔ connector integration** (Python SDK v2.4.2):
- `workflow.ConnectorSlot`, `ConnectorBindings`, `ConnectorExtensions`,
`WorkflowExtensions` types.
- `workflow.BuildConnectorExtensions(slots …)` helper that produces the
nested map expected at `ExecutionRequest.Extensions["mistralai"]`.
- `workflow.ConnectorAuthTaskState` + `ConnectorAuthStatus` constants
for parsing payloads emitted by the `connector-auth` custom task event.
- New `Extensions map[string]any` field on `workflow.ExecutionRequest`.
- **HITL (human-in-the-loop) confirmation constants** — typed values
alongside the pre-existing `conversation.ToolCallConfirmation` and
`tool_confirmations` field:
- `conversation.Confirmation` with `ConfirmationAllow` / `ConfirmationDeny`
for the reply side.
- `ConfirmationStatusPending` / `ConfirmationStatusAllowed` /
`ConfirmationStatusDenied` for `FunctionCallEvent.ConfirmationStatus`
and `FunctionCallEntry.ConfirmationStatus` (already present as
untyped strings).
### Changed
- `workflow.NetworkEncodedInput.EncodingOptions` is now
`[]EncodedPayloadOption` (string-typed alias). JSON wire format
unchanged; existing call sites that passed `[]string{"offloaded"}`
need to switch to `[]workflow.EncodedPayloadOption{workflow.EncodedPayloadOffloaded}`
or the typed constants directly.
- Tracking upstream Mistral OpenAPI spec **v1.0.0** (was v0.1.104).
Only spec delta in this window was the removal of OCR confidence-score
fields (`OCRPageObject.confidence_scores`,
`OCRRequest.confidence_scores_granularity`,
`OCRTableObject.word_confidence_scores`, plus the `OCRConfidenceScore`
and `OCRPageConfidenceScores` schemas), none of which this SDK exposed.
### Fixed (CI)
- `watch-openapi.yml` no longer attempts to commit `.openapi-hash` /
`.openapi-spec.yaml` to `main`. The push was being silently reverted
by an upstream mirror, leaving the tracking issue stale across
multiple upstream releases. The watcher now refreshes the open
tracking issue's body on each run so the diff and hashes always
reflect the current upstream state, and posts a comment when the
spec moves again while the issue is still open.
## v1.3.0 — 2026-04-03
Upstream sync with Python SDK v2.3.0. Updates workflow registration model

View File

@@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Project
Idiomatic Go SDK for the Mistral AI API. Module path: `github.com/VikingOwl91/mistral-go-sdk`. Requires Go 1.26+. Zero external dependencies (stdlib only). Tracks the upstream [Mistral Python SDK](https://github.com/mistralai/client-python) as reference for API surface and type definitions.
Idiomatic Go SDK for the Mistral AI API. Module path: `github.com/VikingOwl91/mistral-go-sdk`. Requires Go 1.26+. Zero external dependencies (stdlib only). Tracks the [official Mistral OpenAPI spec](https://github.com/mistralai/platform-docs-public/blob/main/openapi.yaml) as primary reference for API surface and type definitions, with the [Mistral Python SDK](https://github.com/mistralai/client-python) as secondary reference for implementation patterns. A daily GitHub Action monitors the OpenAPI spec for changes.
## Repository layout

273
README.md
View File

@@ -1,25 +1,32 @@
# mistral-go-sdk
The most complete Go client for the [Mistral AI API](https://docs.mistral.ai/).
Go SDK for Mistral's agentic stack — Workflows, Conversations, Connectors — with full coverage of the rest of the Mistral AI API.
<!-- Badges -->
[![Go Reference](https://pkg.go.dev/badge/github.com/VikingOwl91/mistral-go-sdk.svg)](https://pkg.go.dev/github.com/VikingOwl91/mistral-go-sdk)
![Go Version](https://img.shields.io/badge/go-1.26-blue)
![License](https://img.shields.io/badge/license-MIT-green)
## Why This SDK?
## What this is for
**Zero dependencies.** The entire SDK — including tests — uses only the Go standard library. No `go.sum`, no transitive dependency tree to audit, no version conflicts, no supply chain risk.
Mistral's `/v1/chat/completions` endpoint is OpenAI-wire-compatible, so if all you need is plain chat or tool calling, pointing any OpenAI Go client at `https://api.mistral.ai/v1` already works. **This SDK exists for the rest of Mistral's surface** — the parts no other Go client covers:
**Full API coverage.** 165 methods across every Mistral endpoint — including Workflows, Connectors, Audio Speech/Voices, Conversations, Agents CRUD, Libraries, OCR, Observability, Fine-tuning, and Batch Jobs. No other Go SDK covers Workflows, Conversations, Connectors, or Observability.
- **Workflows** — durable, long-running executions with signals, queries, updates, and full event streaming.
- **Conversations API** — server-stored multi-turn state with streaming events, agent handoffs, and human-in-the-loop tool confirmations.
- **Connectors / MCP** — manage MCP connectors and bind them to workflow executions.
- **Agents CRUD** — create, version, and alias agents (not just `agents/completions`).
- **Libraries / Documents** — RAG document stores with reprocess, status, and signed-URL retrieval.
- **OCR, Audio (transcription + TTS + voices), Fine-tuning, Batch, Observability, Moderation, Classification.**
**Typed streaming.** A generic pull-based `Stream[T]` iterator — no channels, no goroutines, no leaks. Just `Next()` / `Current()` / `Err()` / `Close()`.
If your use case is "Go program calls Mistral and gets a response back," any OpenAI-compatible client is the easier path. If you need agentic workflows or stateful conversations from Go, this is the only option.
**Forward-compatible.** Unknown types (`UnknownEntry`, `UnknownEvent`, `UnknownMessage`, `UnknownChunk`, `UnknownAgentTool`, workflow `UnknownEvent`) capture raw JSON instead of returning errors. When Mistral ships a new message role or event type, your code keeps running — it doesn't panic.
## Why this SDK
**Hand-written, not generated.** Idiomatic Go with sealed interfaces, discriminated unions, and functional options — not a Speakeasy/OpenAPI auto-gen dump with `any` everywhere.
**Test-driven.** 284 tests with race detection clean. Every endpoint tested against mock servers; integration tests against the real API.
- **Zero dependencies.** Stdlib-only. No `go.sum`, no transitive tree, no supply chain.
- **Hand-written, not generated.** Idiomatic Go with sealed interfaces, discriminated unions, and functional options — not a Speakeasy/OpenAPI dump with `any` everywhere.
- **Forward-compatible types.** `UnknownEntry`, `UnknownEvent`, `UnknownMessage`, `UnknownChunk`, `UnknownAgentTool` capture raw JSON instead of returning errors — when Mistral ships a new event type, your code keeps running.
- **Typed streaming.** Generic pull-based `Stream[T]` iterator — no channels, no goroutines, no leaks. Just `Next()` / `Current()` / `Err()` / `Close()`.
- **Test-driven.** 297 tests, race-clean. Every endpoint has a mock-server unit test; integration tests run against the real API behind a build tag.
## Install
@@ -29,44 +36,58 @@ go get github.com/VikingOwl91/mistral-go-sdk
## Quick Start
### Chat Completion
### Execute a workflow with connector bindings
The most differentiated thing this SDK does. Run a registered workflow, hand it the MCP connectors it needs, and block until completion:
```go
package main
import (
"context"
"encoding/json"
"fmt"
"log"
mistral "github.com/VikingOwl91/mistral-go-sdk"
"github.com/VikingOwl91/mistral-go-sdk/chat"
"github.com/VikingOwl91/mistral-go-sdk/workflow"
)
func main() {
client := mistral.NewClient("sk-your-api-key")
resp, err := client.ChatComplete(context.Background(), &chat.CompletionRequest{
Model: "mistral-small-latest",
Messages: []chat.Message{
&chat.UserMessage{Content: chat.TextContent("What is the capital of France?")},
creds := "work-account"
resp, err := client.ExecuteWorkflowAndWait(context.Background(), "my-workflow", &workflow.ExecutionRequest{
Input: map[string]any{
"topic": "Q3 earnings",
},
Extensions: workflow.BuildConnectorExtensions(
workflow.ConnectorSlot{ConnectorName: "gmail"},
workflow.ConnectorSlot{ConnectorName: "notion", CredentialsName: &creds},
),
})
if err != nil {
log.Fatal(err)
}
fmt.Println(resp.Choices[0].Message.Content)
fmt.Printf("execution %s finished with status %s\n", resp.ExecutionID, resp.Status)
if resp.Status == workflow.ExecutionCompleted {
result, _ := json.MarshalIndent(resp.Result, "", " ")
fmt.Println(string(result))
}
}
```
### Streaming
### Stream a conversation with tool-call confirmation
Conversations keep server-side state across turns. Stream events, accept or deny pending tool calls, and continue:
```go
stream, err := client.ChatCompleteStream(ctx, &chat.CompletionRequest{
Model: "mistral-small-latest",
Messages: []chat.Message{
&chat.UserMessage{Content: chat.TextContent("Tell me a joke.")},
},
import "github.com/VikingOwl91/mistral-go-sdk/conversation"
stream, err := client.StartConversationStream(ctx, &conversation.StartRequest{
AgentID: "ag-your-agent-id",
Inputs: conversation.TextInputs("Summarize today's incident review and email it to the on-call channel."),
})
if err != nil {
log.Fatal(err)
@@ -74,9 +95,20 @@ if err != nil {
defer stream.Close()
for stream.Next() {
chunk := stream.Current()
if len(chunk.Choices) > 0 {
fmt.Print(chunk.Choices[0].Delta.Content)
event := stream.Current()
switch e := event.(type) {
case *conversation.FunctionCallEvent:
if e.ConfirmationStatus != nil && *e.ConfirmationStatus == conversation.ConfirmationStatusPending {
// Hand the user a confirmation prompt; reply on the next AppendConversation.
confirmations := []conversation.ToolCallConfirmation{
{ToolCallID: e.ToolCallID, Confirmation: string(conversation.ConfirmationAllow)},
}
_, _ = client.AppendConversation(ctx, /* conversationID */ "conv-...", &conversation.AppendRequest{
ToolConfirmations: confirmations,
})
}
case *conversation.MessageOutputEntry:
// assistant tokens
}
}
if err := stream.Err(); err != nil {
@@ -84,114 +116,24 @@ if err := stream.Err(); err != nil {
}
```
### Tool Calling
### Chat completion (when that's all you need)
```go
import "github.com/VikingOwl91/mistral-go-sdk/chat"
resp, err := client.ChatComplete(ctx, &chat.CompletionRequest{
Model: "mistral-small-latest",
Messages: []chat.Message{
&chat.UserMessage{Content: chat.TextContent("What's the weather in Paris?")},
&chat.UserMessage{Content: chat.TextContent("What is the capital of France?")},
},
Tools: []chat.Tool{{
Type: "function",
Function: chat.Function{
Name: "get_weather",
Description: "Get weather for a city",
Parameters: map[string]any{
"type": "object",
"properties": map[string]any{
"city": map[string]any{"type": "string"},
},
"required": []string{"city"},
},
},
}},
})
```
### Conversations
```go
import "github.com/VikingOwl91/mistral-go-sdk/conversation"
resp, err := client.StartConversation(ctx, &conversation.StartRequest{
AgentID: "ag-your-agent-id",
Inputs: conversation.TextInputs("Hello, agent!"),
})
// Stream events
stream, err := client.AppendConversationStream(ctx, resp.ConversationID, &conversation.AppendRequest{
Inputs: conversation.TextInputs("Follow-up question"),
})
defer stream.Close()
for stream.Next() {
event := stream.Current()
// handle typed events
if err != nil {
log.Fatal(err)
}
fmt.Println(resp.Choices[0].Message.Content)
```
## API Coverage
165 public methods on `Client`, grouped by domain:
| Domain | Methods |
|--------|---------|
| **Chat** | `ChatComplete`, `ChatCompleteStream` |
| **FIM** | `FIMComplete`, `FIMCompleteStream` |
| **Agents (completions)** | `AgentsComplete`, `AgentsCompleteStream` |
| **Agents (CRUD)** | `CreateAgent`, `ListAgents`, `GetAgent`, `UpdateAgent`, `DeleteAgent`, `UpdateAgentVersion`, `ListAgentVersions`, `GetAgentVersion`, `SetAgentAlias`, `ListAgentAliases`, `DeleteAgentAlias` |
| **Connectors** | `CreateConnector`, `ListConnectors`, `GetConnector`, `UpdateConnector`, `DeleteConnector`, `GetConnectorAuthURL`, `ListConnectorTools`, `CallConnectorTool` |
| **Conversations** | `StartConversation`, `StartConversationStream`, `AppendConversation`, `AppendConversationStream`, `RestartConversation`, `RestartConversationStream`, `GetConversation`, `ListConversations`, `DeleteConversation`, `GetConversationHistory`, `GetConversationMessages` |
| **Models** | `ListModels`, `GetModel`, `DeleteModel` |
| **Files** | `UploadFile`, `ListFiles`, `GetFile`, `DeleteFile`, `GetFileContent`, `GetFileURL` |
| **Embeddings** | `CreateEmbeddings` |
| **Fine-tuning** | `CreateFineTuningJob`, `ListFineTuningJobs`, `GetFineTuningJob`, `CancelFineTuningJob`, `StartFineTuningJob`, `UpdateFineTunedModel`, `ArchiveFineTunedModel`, `UnarchiveFineTunedModel` |
| **Batch** | `CreateBatchJob`, `ListBatchJobs`, `GetBatchJob`, `CancelBatchJob`, `DeleteBatchJob` |
| **OCR** | `OCR` |
| **Audio (transcription)** | `Transcribe`, `TranscribeStream` |
| **Audio (speech)** | `Speech`, `SpeechStream` |
| **Audio (voices)** | `ListVoices`, `CreateVoice`, `GetVoice`, `UpdateVoice`, `DeleteVoice`, `GetVoiceSampleAudio` |
| **Libraries** | `CreateLibrary`, `ListLibraries`, `GetLibrary`, `UpdateLibrary`, `DeleteLibrary`, `UploadDocument`, `ListDocuments`, `GetDocument`, `UpdateDocument`, `DeleteDocument`, `GetDocumentTextContent`, `GetDocumentStatus`, `GetDocumentSignedURL`, `GetDocumentExtractedTextSignedURL`, `ReprocessDocument`, `ListLibrarySharing`, `ShareLibrary`, `UnshareLibrary` |
| **Moderation** | `Moderate`, `ModerateChat` |
| **Classification** | `Classify`, `ClassifyChat` |
| **Observability (campaigns)** | `CreateCampaign`, `ListCampaigns`, `GetCampaign`, `DeleteCampaign`, `GetCampaignStatus`, `ListCampaignEvents` |
| **Observability (events)** | `SearchChatCompletionEvents`, `SearchChatCompletionEventIDs`, `GetChatCompletionEvent`, `GetSimilarChatCompletionEvents`, `JudgeChatCompletionEvent` |
| **Observability (judges)** | `CreateJudge`, `ListJudges`, `GetJudge`, `UpdateJudge`, `DeleteJudge`, `JudgeConversation` |
| **Observability (datasets)** | `CreateDataset`, `ListDatasets`, `GetDataset`, `UpdateDataset`, `DeleteDataset`, `ExportDatasetToJSONL`, `ListDatasetRecords`, `CreateDatasetRecord`, `GetDatasetRecord`, `UpdateDatasetRecordPayload`, `UpdateDatasetRecordProperties`, `DeleteDatasetRecord`, `BulkDeleteDatasetRecords`, `JudgeDatasetRecord`, `ImportDatasetFromCampaign`, `ImportDatasetFromExplorer`, `ImportDatasetFromFile`, `ImportDatasetFromPlayground`, `ImportDatasetFromDataset`, `ListDatasetTasks`, `GetDatasetTask` |
| **Workflows (CRUD)** | `ListWorkflows`, `GetWorkflow`, `UpdateWorkflow`, `ArchiveWorkflow`, `UnarchiveWorkflow`, `ExecuteWorkflow`, `ExecuteWorkflowAndWait` |
| **Workflows (registrations)** | `ListWorkflowRegistrations`, `GetWorkflowRegistration`, `ExecuteWorkflowRegistration` |
| **Workflows (executions)** | `GetWorkflowExecution`, `GetWorkflowExecutionHistory`, `StreamWorkflowExecution`, `SignalWorkflowExecution`, `QueryWorkflowExecution`, `UpdateWorkflowExecution`, `TerminateWorkflowExecution`, `CancelWorkflowExecution`, `ResetWorkflowExecution`, `BatchCancelWorkflowExecutions`, `BatchTerminateWorkflowExecutions` |
| **Workflows (trace)** | `GetWorkflowExecutionTraceOTel`, `GetWorkflowExecutionTraceSummary`, `GetWorkflowExecutionTraceEvents` |
| **Workflows (events)** | `StreamWorkflowEvents`, `ListWorkflowEvents` |
| **Workflows (deployments)** | `ListWorkflowDeployments`, `GetWorkflowDeployment` |
| **Workflows (metrics)** | `GetWorkflowMetrics` |
| **Workflows (runs)** | `ListWorkflowRuns`, `GetWorkflowRun`, `GetWorkflowRunHistory` |
| **Workflows (schedules)** | `ListWorkflowSchedules`, `ScheduleWorkflow`, `UnscheduleWorkflow` |
## Comparison
There is no official Go SDK from Mistral AI (only Python and TypeScript). The main community options:
| Feature | mistral-go-sdk | [Gage-Technologies](https://github.com/Gage-Technologies/mistral-go) | [robertjkeck2](https://github.com/robertjkeck2/mistral-go) | [AuxData-ai](https://github.com/AuxData-ai/mistral-go) |
|---------|:-:|:-:|:-:|:-:|
| Chat / Streaming | Yes | Yes | Yes | Yes |
| FIM | Yes | Yes | No | Yes |
| Embeddings | Yes | Yes | Yes | Yes |
| Tool calling | Yes | No | No | No |
| Agents (completions + CRUD) | Yes | No | No | No |
| Connectors (MCP) | Yes | No | No | No |
| Conversations API | Yes | No | No | No |
| Libraries / Documents | Yes | No | No | No |
| Fine-tuning / Batch | Yes | No | No | No |
| OCR | Yes | No | No | Yes |
| Audio (transcription + TTS + voices) | Yes | No | No | No |
| Workflows API | Yes | No | No | No |
| Observability (beta) | Yes | No | No | No |
| Moderation / Classification | Yes | No | No | No |
| Vision (multimodal) | Yes | No | No | Yes |
| Zero dependencies | Yes | test-only (testify) | test-only (testify) | test-only (testify) |
| Forward-compatible types | Yes | No | No | No |
| Last updated | 2026 | Jun 2024 | Jan 2024 | ~2025 (fork of Gage) |
If this is your whole use case, the OpenAI-wire-compatible endpoint is also a valid path — point any `openai-go` client at `https://api.mistral.ai/v1` and it works. The rest of this SDK is for what the OpenAI shape doesn't cover.
## Configuration
@@ -233,18 +175,85 @@ if err != nil {
}
```
## API Coverage
169 public methods on `Client`, grouped by domain:
| Domain | Methods |
|--------|---------|
| **Chat** | `ChatComplete`, `ChatCompleteStream` |
| **FIM** | `FIMComplete`, `FIMCompleteStream` |
| **Agents (completions)** | `AgentsComplete`, `AgentsCompleteStream` |
| **Agents (CRUD)** | `CreateAgent`, `ListAgents`, `GetAgent`, `UpdateAgent`, `DeleteAgent`, `UpdateAgentVersion`, `ListAgentVersions`, `GetAgentVersion`, `SetAgentAlias`, `ListAgentAliases`, `DeleteAgentAlias` |
| **Connectors** | `CreateConnector`, `ListConnectors`, `GetConnector`, `UpdateConnector`, `DeleteConnector`, `GetConnectorAuthURL`, `ListConnectorTools`, `CallConnectorTool` |
| **Conversations** | `StartConversation`, `StartConversationStream`, `AppendConversation`, `AppendConversationStream`, `RestartConversation`, `RestartConversationStream`, `GetConversation`, `ListConversations`, `DeleteConversation`, `GetConversationHistory`, `GetConversationMessages` |
| **Models** | `ListModels`, `GetModel`, `DeleteModel` |
| **Files** | `UploadFile`, `ListFiles`, `GetFile`, `DeleteFile`, `GetFileContent`, `GetFileURL` |
| **Embeddings** | `CreateEmbeddings` |
| **Fine-tuning** | `CreateFineTuningJob`, `ListFineTuningJobs`, `GetFineTuningJob`, `CancelFineTuningJob`, `StartFineTuningJob`, `UpdateFineTunedModel`, `ArchiveFineTunedModel`, `UnarchiveFineTunedModel` |
| **Batch** | `CreateBatchJob`, `ListBatchJobs`, `GetBatchJob`, `CancelBatchJob`, `DeleteBatchJob` |
| **OCR** | `OCR` |
| **Audio (transcription)** | `Transcribe`, `TranscribeStream` |
| **Audio (speech)** | `Speech`, `SpeechStream` |
| **Audio (voices)** | `ListVoices`, `CreateVoice`, `GetVoice`, `UpdateVoice`, `DeleteVoice`, `GetVoiceSampleAudio` |
| **Libraries** | `CreateLibrary`, `ListLibraries`, `GetLibrary`, `UpdateLibrary`, `DeleteLibrary`, `UploadDocument`, `ListDocuments`, `GetDocument`, `UpdateDocument`, `DeleteDocument`, `GetDocumentTextContent`, `GetDocumentStatus`, `GetDocumentSignedURL`, `GetDocumentExtractedTextSignedURL`, `ReprocessDocument`, `ListLibrarySharing`, `ShareLibrary`, `UnshareLibrary` |
| **Moderation** | `Moderate`, `ModerateChat` |
| **Classification** | `Classify`, `ClassifyChat` |
| **Observability (campaigns)** | `CreateCampaign`, `ListCampaigns`, `GetCampaign`, `DeleteCampaign`, `GetCampaignStatus`, `ListCampaignEvents` |
| **Observability (events)** | `SearchChatCompletionEvents`, `SearchChatCompletionEventIDs`, `GetChatCompletionEvent`, `GetSimilarChatCompletionEvents`, `JudgeChatCompletionEvent` |
| **Observability (fields)** | `GetChatCompletionFields`, `GetChatCompletionFieldOptions`, `GetChatCompletionFieldOptionsCounts` |
| **Observability (judges)** | `CreateJudge`, `ListJudges`, `GetJudge`, `UpdateJudge`, `DeleteJudge`, `JudgeConversation` |
| **Observability (datasets)** | `CreateDataset`, `ListDatasets`, `GetDataset`, `UpdateDataset`, `DeleteDataset`, `ExportDatasetToJSONL`, `ListDatasetRecords`, `CreateDatasetRecord`, `GetDatasetRecord`, `UpdateDatasetRecordPayload`, `UpdateDatasetRecordProperties`, `DeleteDatasetRecord`, `BulkDeleteDatasetRecords`, `JudgeDatasetRecord`, `ImportDatasetFromCampaign`, `ImportDatasetFromExplorer`, `ImportDatasetFromFile`, `ImportDatasetFromPlayground`, `ImportDatasetFromDataset`, `ListDatasetTasks`, `GetDatasetTask` |
| **Workflows (CRUD)** | `ListWorkflows`, `GetWorkflow`, `UpdateWorkflow`, `ArchiveWorkflow`, `UnarchiveWorkflow`, `ExecuteWorkflow`, `ExecuteWorkflowAndWait` |
| **Workflows (registrations)** | `ListWorkflowRegistrations`, `GetWorkflowRegistration`, `ExecuteWorkflowRegistration` |
| **Workflows (executions)** | `GetWorkflowExecution`, `GetWorkflowExecutionHistory`, `StreamWorkflowExecution`, `SignalWorkflowExecution`, `QueryWorkflowExecution`, `UpdateWorkflowExecution`, `TerminateWorkflowExecution`, `CancelWorkflowExecution`, `ResetWorkflowExecution`, `BatchCancelWorkflowExecutions`, `BatchTerminateWorkflowExecutions` |
| **Workflows (trace)** | `GetWorkflowExecutionTraceOTel`, `GetWorkflowExecutionTraceSummary`, `GetWorkflowExecutionTraceEvents` |
| **Workflows (events)** | `StreamWorkflowEvents`, `ListWorkflowEvents` |
| **Workflows (deployments)** | `ListWorkflowDeployments`, `GetWorkflowDeployment` |
| **Workflows (metrics)** | `GetWorkflowMetrics` |
| **Workflows (runs)** | `ListWorkflowRuns`, `GetWorkflowRun`, `GetWorkflowRunHistory` |
| **Workflows (schedules)** | `ListWorkflowSchedules`, `ScheduleWorkflow`, `UnscheduleWorkflow` |
| **Workflows (workers)** | `GetWorkflowWorkerInfo` |
## Comparison
There is no official Go SDK from Mistral AI (only Python and TypeScript). The main community options:
| Feature | mistral-go-sdk | [Gage-Technologies](https://github.com/Gage-Technologies/mistral-go) | [robertjkeck2](https://github.com/robertjkeck2/mistral-go) | [AuxData-ai](https://github.com/AuxData-ai/mistral-go) |
|---------|:-:|:-:|:-:|:-:|
| Chat / Streaming | Yes | Yes | Yes | Yes |
| FIM | Yes | Yes | No | Yes |
| Embeddings | Yes | Yes | Yes | Yes |
| Tool calling | Yes | No | No | No |
| Agents (completions + CRUD) | Yes | No | No | No |
| Connectors (MCP) | Yes | No | No | No |
| Conversations API | Yes | No | No | No |
| Libraries / Documents | Yes | No | No | No |
| Fine-tuning / Batch | Yes | No | No | No |
| OCR | Yes | No | No | Yes |
| Audio (transcription + TTS + voices) | Yes | No | No | No |
| Workflows API | Yes | No | No | No |
| Observability (beta) | Yes | No | No | No |
| Moderation / Classification | Yes | No | No | No |
| Vision (multimodal) | Yes | No | No | Yes |
| Zero dependencies | Yes | test-only (testify) | test-only (testify) | test-only (testify) |
| Forward-compatible types | Yes | No | No | No |
| Last updated | 2026 | Jun 2024 | Jan 2024 | ~2025 (fork of Gage) |
## Upstream Reference
This SDK tracks the [official Mistral Python SDK](https://github.com/mistralai/client-python)
as its upstream reference for API surface and type definitions.
This SDK tracks the [official Mistral OpenAPI spec](https://github.com/mistralai/platform-docs-public/blob/main/openapi.yaml) as its primary reference for API surface and type definitions. A daily GitHub Action monitors the spec for changes and refreshes a tracking issue when updates are detected.
| SDK Version | Upstream Python SDK |
|-------------|---------------------|
| v1.3.0 | v2.3.0 |
| v1.2.1 | v2.2.0 |
| v1.2.0 | v2.2.0 |
| v1.1.0 | v2.1.3 |
| v1.0.0 | v2.0.4 |
The [Mistral Python SDK](https://github.com/mistralai/client-python) is used as a secondary reference for implementation patterns.
| SDK Version | Upstream Python SDK | Upstream OpenAPI |
|-------------|---------------------|------------------|
| v1.4.0 | v2.4.3 (excl. RAG ingestion-pipeline beta) | v1.0.0 |
| v1.3.0 | v2.3.0 | v0.1.104 |
| v1.2.1 | v2.2.0 | — |
| v1.2.0 | v2.2.0 | — |
| v1.1.0 | v2.1.3 | — |
| v1.0.0 | v2.0.4 | — |
## License

View File

@@ -44,10 +44,30 @@ type CompletionArgs struct {
ToolChoice *chat.ToolChoiceMode `json:"tool_choice,omitempty"`
}
// Confirmation is a client decision on a pending tool call.
type Confirmation string
const (
ConfirmationAllow Confirmation = "allow"
ConfirmationDeny Confirmation = "deny"
)
// ConfirmationStatus values appear on FunctionCallEvent.ConfirmationStatus
// and FunctionCallEntry.ConfirmationStatus, reporting where in the
// human-in-the-loop flow a tool call currently sits.
const (
ConfirmationStatusPending = "pending"
ConfirmationStatusAllowed = "allowed"
ConfirmationStatusDenied = "denied"
)
// ToolCallConfirmation confirms or denies a pending tool call.
//
// Send a slice of these on AppendRequest.ToolConfirmations after receiving
// a function call event whose ConfirmationStatus is "pending".
type ToolCallConfirmation struct {
ToolCallID string `json:"tool_call_id"`
Confirmation string `json:"confirmation"` // "allow" or "deny"
Confirmation string `json:"confirmation"` // use ConfirmationAllow / ConfirmationDeny
}
// Inputs represents conversation inputs (text string or entry array).

84
observability/field.go Normal file
View File

@@ -0,0 +1,84 @@
package observability
// FieldType identifies the data type of a chat-completion-event field.
type FieldType string
const (
FieldTypeEnum FieldType = "ENUM"
FieldTypeText FieldType = "TEXT"
FieldTypeInt FieldType = "INT"
FieldTypeFloat FieldType = "FLOAT"
FieldTypeBool FieldType = "BOOL"
FieldTypeTimestamp FieldType = "TIMESTAMP"
FieldTypeArray FieldType = "ARRAY"
)
// FieldOperator is a filter operator supported on observability fields.
type FieldOperator string
const (
FieldOperatorLT FieldOperator = "lt"
FieldOperatorLTE FieldOperator = "lte"
FieldOperatorGT FieldOperator = "gt"
FieldOperatorGTE FieldOperator = "gte"
FieldOperatorStartsWith FieldOperator = "startswith"
FieldOperatorIStartsWith FieldOperator = "istartswith"
FieldOperatorEndsWith FieldOperator = "endswith"
FieldOperatorIEndsWith FieldOperator = "iendswith"
FieldOperatorContains FieldOperator = "contains"
FieldOperatorIContains FieldOperator = "icontains"
FieldOperatorMatches FieldOperator = "matches"
FieldOperatorNotContains FieldOperator = "notcontains"
FieldOperatorINotContain FieldOperator = "inotcontains"
FieldOperatorEq FieldOperator = "eq"
FieldOperatorNeq FieldOperator = "neq"
FieldOperatorIsNull FieldOperator = "isnull"
FieldOperatorIncludes FieldOperator = "includes"
FieldOperatorExcludes FieldOperator = "excludes"
FieldOperatorLenEq FieldOperator = "len_eq"
)
// BaseFieldDefinition describes a searchable chat-completion-event field.
type BaseFieldDefinition struct {
Name string `json:"name"`
Label string `json:"label"`
Type FieldType `json:"type"`
Group *string `json:"group,omitempty"`
SupportedOperators []FieldOperator `json:"supported_operators"`
}
// FieldGroup groups related field definitions for UI display.
type FieldGroup struct {
Name string `json:"name"`
Label string `json:"label"`
}
// ChatCompletionFields is the response of GET /v1/observability/chat-completion-fields.
type ChatCompletionFields struct {
FieldDefinitions []BaseFieldDefinition `json:"field_definitions"`
FieldGroups []FieldGroup `json:"field_groups"`
}
// ChatCompletionFieldOptions is the response of
// GET /v1/observability/chat-completion-fields/{field_name}/options.
//
// Each option may be a string, bool, or null — preserved as raw any.
type ChatCompletionFieldOptions struct {
Options []any `json:"options"`
}
// FieldOptionCountsRequest is the body of POST options-counts.
type FieldOptionCountsRequest struct {
FilterParams *FilterPayload `json:"filter_params,omitempty"`
}
// FieldOptionCountItem pairs a field value with how many events have it.
type FieldOptionCountItem struct {
Value string `json:"value"`
Count int `json:"count"`
}
// FieldOptionCounts is the response of POST options-counts.
type FieldOptionCounts struct {
Counts []FieldOptionCountItem `json:"counts"`
}

46
observability_fields.go Normal file
View File

@@ -0,0 +1,46 @@
package mistral
import (
"context"
"fmt"
"net/url"
"github.com/VikingOwl91/mistral-go-sdk/observability"
)
// GetChatCompletionFields returns the searchable field definitions and groups
// for chat-completion observability events.
func (c *Client) GetChatCompletionFields(ctx context.Context) (*observability.ChatCompletionFields, error) {
var resp observability.ChatCompletionFields
if err := c.doJSON(ctx, "GET", "/v1/observability/chat-completion-fields", nil, &resp); err != nil {
return nil, err
}
return &resp, nil
}
// GetChatCompletionFieldOptions returns the distinct values seen for the given
// field, filtered by the requested operator.
func (c *Client) GetChatCompletionFieldOptions(ctx context.Context, fieldName string, operator observability.FieldOperator) (*observability.ChatCompletionFieldOptions, error) {
q := url.Values{}
q.Set("operator", string(operator))
path := fmt.Sprintf("/v1/observability/chat-completion-fields/%s/options?%s", url.PathEscape(fieldName), q.Encode())
var resp observability.ChatCompletionFieldOptions
if err := c.doJSON(ctx, "GET", path, nil, &resp); err != nil {
return nil, err
}
return &resp, nil
}
// GetChatCompletionFieldOptionsCounts returns per-value event counts for the
// given field, optionally filtered by the supplied filter payload.
func (c *Client) GetChatCompletionFieldOptionsCounts(ctx context.Context, fieldName string, req *observability.FieldOptionCountsRequest) (*observability.FieldOptionCounts, error) {
if req == nil {
req = &observability.FieldOptionCountsRequest{}
}
path := fmt.Sprintf("/v1/observability/chat-completion-fields/%s/options-counts", url.PathEscape(fieldName))
var resp observability.FieldOptionCounts
if err := c.doJSON(ctx, "POST", path, req, &resp); err != nil {
return nil, err
}
return &resp, nil
}

View File

@@ -0,0 +1,100 @@
package mistral
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/VikingOwl91/mistral-go-sdk/observability"
)
func TestGetChatCompletionFields_Success(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" || r.URL.Path != "/v1/observability/chat-completion-fields" {
t.Errorf("unexpected %s %s", r.Method, r.URL.Path)
}
_ = json.NewEncoder(w).Encode(map[string]any{
"field_definitions": []map[string]any{
{
"name": "model",
"label": "Model",
"type": "ENUM",
"supported_operators": []string{"eq", "neq", "includes"},
},
},
"field_groups": []map[string]any{
{"name": "request", "label": "Request"},
},
})
}))
defer server.Close()
client := NewClient("key", WithBaseURL(server.URL))
resp, err := client.GetChatCompletionFields(context.Background())
if err != nil {
t.Fatal(err)
}
if len(resp.FieldDefinitions) != 1 {
t.Fatalf("got %d field definitions", len(resp.FieldDefinitions))
}
def := resp.FieldDefinitions[0]
if def.Name != "model" || def.Type != observability.FieldTypeEnum {
t.Errorf("unexpected field def: %+v", def)
}
if len(def.SupportedOperators) != 3 {
t.Errorf("got %d operators", len(def.SupportedOperators))
}
if len(resp.FieldGroups) != 1 || resp.FieldGroups[0].Name != "request" {
t.Errorf("unexpected groups: %+v", resp.FieldGroups)
}
}
func TestGetChatCompletionFieldOptions_Success(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/v1/observability/chat-completion-fields/model/options" {
t.Errorf("got path %s", r.URL.Path)
}
if got := r.URL.Query().Get("operator"); got != "eq" {
t.Errorf("got operator=%q want eq", got)
}
_ = json.NewEncoder(w).Encode(map[string]any{
"options": []any{"mistral-small-latest", "mistral-large-latest", nil, true},
})
}))
defer server.Close()
client := NewClient("key", WithBaseURL(server.URL))
resp, err := client.GetChatCompletionFieldOptions(context.Background(), "model", observability.FieldOperatorEq)
if err != nil {
t.Fatal(err)
}
if len(resp.Options) != 4 {
t.Fatalf("got %d options", len(resp.Options))
}
}
func TestGetChatCompletionFieldOptionsCounts_Success(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" || r.URL.Path != "/v1/observability/chat-completion-fields/model/options-counts" {
t.Errorf("unexpected %s %s", r.Method, r.URL.Path)
}
_ = json.NewEncoder(w).Encode(map[string]any{
"counts": []map[string]any{
{"value": "mistral-small-latest", "count": 42},
{"value": "mistral-large-latest", "count": 17},
},
})
}))
defer server.Close()
client := NewClient("key", WithBaseURL(server.URL))
resp, err := client.GetChatCompletionFieldOptionsCounts(context.Background(), "model", &observability.FieldOptionCountsRequest{})
if err != nil {
t.Fatal(err)
}
if len(resp.Counts) != 2 || resp.Counts[0].Count != 42 {
t.Errorf("unexpected counts: %+v", resp.Counts)
}
}

61
workflow/connectors.go Normal file
View File

@@ -0,0 +1,61 @@
package workflow
// ConnectorSlot declares a connector dependency for a workflow execution.
//
// Pass a slice of slots to BuildConnectorExtensions to produce the
// nested map expected on ExecutionRequest.Extensions.
type ConnectorSlot struct {
ConnectorName string `json:"connector_name"`
CredentialsName *string `json:"credentials_name,omitempty"`
}
// ConnectorBindings is the bindings list inside ConnectorExtensions.
type ConnectorBindings struct {
Bindings []ConnectorSlot `json:"bindings"`
}
// ConnectorExtensions is the value of the "mistralai" key in workflow extensions.
type ConnectorExtensions struct {
Connectors ConnectorBindings `json:"connectors"`
}
// WorkflowExtensions is the top-level shape of the extensions field
// expected by the workflow execute endpoint when binding connectors.
type WorkflowExtensions struct {
Mistralai ConnectorExtensions `json:"mistralai"`
}
// BuildConnectorExtensions returns the value to set on
// ExecutionRequest.Extensions for the given connector slots.
//
// The result is a map[string]any so callers can merge in additional
// extension keys without colliding with the connector wire shape.
func BuildConnectorExtensions(slots ...ConnectorSlot) map[string]any {
return map[string]any{
"mistralai": ConnectorExtensions{
Connectors: ConnectorBindings{Bindings: slots},
},
}
}
// ConnectorAuthStatus is the state of an OAuth flow emitted by a
// connector-auth custom task event.
type ConnectorAuthStatus string
const (
ConnectorAuthWaitingForAuth ConnectorAuthStatus = "waiting_for_auth"
ConnectorAuthConnected ConnectorAuthStatus = "connected"
ConnectorAuthAccessDenied ConnectorAuthStatus = "access_denied"
ConnectorAuthTimedOut ConnectorAuthStatus = "timed_out"
ConnectorAuthError ConnectorAuthStatus = "error"
)
// ConnectorAuthTaskState is the payload of a custom task event of type
// "connector-auth", emitted while a workflow waits for OAuth completion.
type ConnectorAuthTaskState struct {
ConnectorName string `json:"connector_name"`
ConnectorID string `json:"connector_id"`
Status ConnectorAuthStatus `json:"status"`
AuthURL *string `json:"auth_url,omitempty"`
Message *string `json:"message,omitempty"`
}

View File

@@ -25,6 +25,9 @@ type ExecutionRequest struct {
TimeoutSeconds *float64 `json:"timeout_seconds,omitempty"`
CustomTracingAttributes map[string]string `json:"custom_tracing_attributes,omitempty"`
DeploymentName *string `json:"deployment_name,omitempty"`
// Extensions carries plugin-specific data such as connector bindings.
// Use BuildConnectorExtensions to construct the standard connector shape.
Extensions map[string]any `json:"extensions,omitempty"`
}
// ExecutionResponse is the response from a workflow execution.
@@ -40,11 +43,20 @@ type ExecutionResponse struct {
TotalDurationMs *int `json:"total_duration_ms,omitempty"`
}
// EncodedPayloadOption identifies how a workflow payload was encoded.
type EncodedPayloadOption string
const (
EncodedPayloadOffloaded EncodedPayloadOption = "offloaded"
EncodedPayloadEncrypted EncodedPayloadOption = "encrypted"
EncodedPayloadEncryptedPartial EncodedPayloadOption = "encrypted-partial"
)
// NetworkEncodedInput holds a base64-encoded payload for workflow input.
type NetworkEncodedInput struct {
B64Payload string `json:"b64payload"`
EncodingOptions []string `json:"encoding_options,omitempty"`
Empty bool `json:"empty,omitempty"`
B64Payload string `json:"b64payload"`
EncodingOptions []EncodedPayloadOption `json:"encoding_options,omitempty"`
Empty bool `json:"empty,omitempty"`
}
// SignalInvocationBody is the request body for signaling a workflow execution.

12
workflow/worker.go Normal file
View File

@@ -0,0 +1,12 @@
package workflow
// WorkerInfo describes the worker scheduler the SDK is connected to.
//
// Returned by GET /v1/workflows/workers/whoami. Useful when running custom
// workers that need to know which scheduler / namespace to connect to.
// For managed deployments, prefer Registration.DeploymentID.
type WorkerInfo struct {
SchedulerURL string `json:"scheduler_url"`
Namespace string `json:"namespace"`
TLS bool `json:"tls"`
}

View File

@@ -0,0 +1,106 @@
package mistral
import (
"encoding/json"
"testing"
"github.com/VikingOwl91/mistral-go-sdk/conversation"
"github.com/VikingOwl91/mistral-go-sdk/workflow"
)
func TestNetworkEncodedInput_EncodingOptions(t *testing.T) {
in := workflow.NetworkEncodedInput{
B64Payload: "eyJrIjoidiJ9",
EncodingOptions: []workflow.EncodedPayloadOption{workflow.EncodedPayloadOffloaded, workflow.EncodedPayloadEncrypted},
}
b, err := json.Marshal(in)
if err != nil {
t.Fatal(err)
}
var got map[string]any
if err := json.Unmarshal(b, &got); err != nil {
t.Fatal(err)
}
opts, ok := got["encoding_options"].([]any)
if !ok || len(opts) != 2 {
t.Fatalf("unexpected encoding_options: %v", got["encoding_options"])
}
if opts[0] != "offloaded" || opts[1] != "encrypted" {
t.Errorf("got %v, want [offloaded encrypted]", opts)
}
}
func TestBuildConnectorExtensions_WireShape(t *testing.T) {
creds := "work-account"
ext := workflow.BuildConnectorExtensions(
workflow.ConnectorSlot{ConnectorName: "gmail"},
workflow.ConnectorSlot{ConnectorName: "notion", CredentialsName: &creds},
)
b, err := json.Marshal(ext)
if err != nil {
t.Fatal(err)
}
want := `{"mistralai":{"connectors":{"bindings":[{"connector_name":"gmail"},{"connector_name":"notion","credentials_name":"work-account"}]}}}`
if string(b) != want {
t.Errorf("\nwant %s\ngot %s", want, string(b))
}
}
func TestExecutionRequest_Extensions(t *testing.T) {
req := workflow.ExecutionRequest{
Extensions: workflow.BuildConnectorExtensions(workflow.ConnectorSlot{ConnectorName: "gmail"}),
}
b, err := json.Marshal(req)
if err != nil {
t.Fatal(err)
}
var got map[string]any
if err := json.Unmarshal(b, &got); err != nil {
t.Fatal(err)
}
if _, ok := got["extensions"]; !ok {
t.Fatalf("expected extensions key in marshalled request: %s", string(b))
}
}
func TestConnectorAuthTaskState_Roundtrip(t *testing.T) {
authURL := "https://oauth.example.com/authorize"
in := workflow.ConnectorAuthTaskState{
ConnectorName: "gmail",
ConnectorID: "conn-1",
Status: workflow.ConnectorAuthWaitingForAuth,
AuthURL: &authURL,
}
b, err := json.Marshal(in)
if err != nil {
t.Fatal(err)
}
var out workflow.ConnectorAuthTaskState
if err := json.Unmarshal(b, &out); err != nil {
t.Fatal(err)
}
if out.Status != workflow.ConnectorAuthWaitingForAuth {
t.Errorf("got status %q", out.Status)
}
if out.AuthURL == nil || *out.AuthURL != authURL {
t.Errorf("auth_url roundtrip failed")
}
}
func TestConfirmationConstants_WireValues(t *testing.T) {
// Reply constants.
c := conversation.ToolCallConfirmation{
ToolCallID: "call_1",
Confirmation: string(conversation.ConfirmationAllow),
}
b, _ := json.Marshal(c)
if string(b) != `{"tool_call_id":"call_1","confirmation":"allow"}` {
t.Errorf("got %s", string(b))
}
// Inbound status constants.
if conversation.ConfirmationStatusPending != "pending" ||
conversation.ConfirmationStatusAllowed != "allowed" ||
conversation.ConfirmationStatusDenied != "denied" {
t.Errorf("unexpected confirmation status constants")
}
}

20
workflows_workers.go Normal file
View File

@@ -0,0 +1,20 @@
package mistral
import (
"context"
"github.com/VikingOwl91/mistral-go-sdk/workflow"
)
// GetWorkflowWorkerInfo returns the scheduler URL, namespace, and TLS setting
// the API expects custom workers to connect with.
//
// Most callers using managed deployments do not need this — see
// Registration.DeploymentID. It is exposed for users running custom workers.
func (c *Client) GetWorkflowWorkerInfo(ctx context.Context) (*workflow.WorkerInfo, error) {
var resp workflow.WorkerInfo
if err := c.doJSON(ctx, "GET", "/v1/workflows/workers/whoami", nil, &resp); err != nil {
return nil, err
}
return &resp, nil
}

52
workflows_workers_test.go Normal file
View File

@@ -0,0 +1,52 @@
package mistral
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
)
func TestGetWorkflowWorkerInfo_Success(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" || r.URL.Path != "/v1/workflows/workers/whoami" {
t.Errorf("unexpected %s %s", r.Method, r.URL.Path)
}
_ = json.NewEncoder(w).Encode(map[string]any{
"scheduler_url": "scheduler.example.com:7233",
"namespace": "tenant-2",
"tls": true,
})
}))
defer server.Close()
client := NewClient("key", WithBaseURL(server.URL))
info, err := client.GetWorkflowWorkerInfo(context.Background())
if err != nil {
t.Fatal(err)
}
if info.SchedulerURL != "scheduler.example.com:7233" || info.Namespace != "tenant-2" || !info.TLS {
t.Errorf("unexpected info: %+v", info)
}
}
func TestGetWorkflowWorkerInfo_TLSDefault(t *testing.T) {
// Server omits the tls field; the SDK should default it to false.
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_ = json.NewEncoder(w).Encode(map[string]any{
"scheduler_url": "s",
"namespace": "n",
})
}))
defer server.Close()
client := NewClient("key", WithBaseURL(server.URL))
info, err := client.GetWorkflowWorkerInfo(context.Background())
if err != nil {
t.Fatal(err)
}
if info.TLS {
t.Errorf("expected default tls=false, got true")
}
}