7 Commits

Author SHA1 Message Date
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
c028dfb0ed feat!: sync with Python SDK v2.3.0 — workflow registration model + remove workers
Add CodeDefinition, SignalDefinition, QueryDefinition, UpdateDefinition
types for workflow interface metadata. Update Registration struct with
DeploymentID, Definition, and CompatibleWithChatAssistant fields.
Deprecate TaskQueue in favor of DeploymentID.

BREAKING CHANGE: Remove GetWorkflowWorkerInfo and workflow.WorkerInfo —
the /v1/workflows/workers/whoami endpoint was removed upstream.
2026-04-03 18:53:15 +02:00
214fd02b3b docs: add v1.2.1 to upstream version table 2026-04-03 12:02:19 +02:00
20 changed files with 20595 additions and 36 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,103 @@
## 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
to reflect the managed deployment architecture and removes the deprecated
workers endpoint.
### Added
- **`workflow.CodeDefinition`** — workflow interface metadata type with
input/output schemas, signal/query/update handler definitions,
determinism flag, and execution timeout.
- **`workflow.SignalDefinition`**, **`QueryDefinition`**,
**`UpdateDefinition`** — handler descriptor types.
- **`Registration.Definition`** — code definition field on workflow
registrations.
- **`Registration.DeploymentID`** — replaces the worker/task-queue model
with managed deployment references.
- **`Registration.CompatibleWithChatAssistant`** — flag for chat assistant
compatibility.
### Deprecated
- **`Registration.TaskQueue`** — use `DeploymentID` instead. Will be
removed in a future release.
### Removed (breaking)
- **`GetWorkflowWorkerInfo`** — the `/v1/workflows/workers/whoami` endpoint
was removed upstream.
- **`workflow.WorkerInfo`** — type no longer exists in the API.
## v1.2.1 — 2026-04-03
Move module path to `github.com/VikingOwl91/mistral-go-sdk` for public

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

View File

@@ -11,7 +11,7 @@ The most complete Go client for the [Mistral AI API](https://docs.mistral.ai/).
**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.
**Full API coverage.** 166 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.
**Full API coverage.** 169 methods across every Mistral endpoint — including Workflows (with worker introspection and connector bindings), Connectors, Audio Speech/Voices, Conversations (including human-in-the-loop tool confirmations), Agents CRUD, Libraries, OCR, Observability (events, judges, datasets, campaigns, fields), Fine-tuning, and Batch Jobs. No other Go SDK covers Workflows, Conversations, Connectors, or Observability.
**Typed streaming.** A generic pull-based `Stream[T]` iterator — no channels, no goroutines, no leaks. Just `Next()` / `Current()` / `Err()` / `Close()`.
@@ -19,7 +19,7 @@ The most complete Go client for the [Mistral AI API](https://docs.mistral.ai/).
**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.
**Test-driven.** 297 tests with race detection clean. Every endpoint tested against mock servers; integration tests against the real API.
## Install
@@ -132,7 +132,7 @@ for stream.Next() {
## API Coverage
166 public methods on `Client`, grouped by domain:
169 public methods on `Client`, grouped by domain:
| Domain | Methods |
|--------|---------|
@@ -156,6 +156,7 @@ for stream.Next() {
| **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` |
@@ -236,14 +237,18 @@ if err != nil {
## 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 opens an issue when updates are detected.
| SDK Version | Upstream Python SDK |
|-------------|---------------------|
| 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).

View File

@@ -6,7 +6,7 @@ import (
)
// Version is the SDK version string.
const Version = "1.2.1"
const Version = "1.3.0"
const (
defaultBaseURL = "https://api.mistral.ai"

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"`
}

36
workflow/definition.go Normal file
View File

@@ -0,0 +1,36 @@
package workflow
// CodeDefinition describes a workflow's code-level interface: its input/output
// schemas, signal/query/update handlers, and execution constraints.
type CodeDefinition struct {
InputSchema map[string]any `json:"input_schema"`
OutputSchema map[string]any `json:"output_schema,omitempty"`
Signals []SignalDefinition `json:"signals,omitempty"`
Queries []QueryDefinition `json:"queries,omitempty"`
Updates []UpdateDefinition `json:"updates,omitempty"`
EnforceDeterminism bool `json:"enforce_determinism,omitempty"`
ExecutionTimeout *float64 `json:"execution_timeout,omitempty"`
}
// SignalDefinition describes a signal handler on a workflow.
type SignalDefinition struct {
Name string `json:"name"`
InputSchema map[string]any `json:"input_schema"`
Description *string `json:"description,omitempty"`
}
// QueryDefinition describes a query handler on a workflow.
type QueryDefinition struct {
Name string `json:"name"`
InputSchema map[string]any `json:"input_schema"`
Description *string `json:"description,omitempty"`
OutputSchema map[string]any `json:"output_schema,omitempty"`
}
// UpdateDefinition describes an update handler on a workflow.
type UpdateDefinition struct {
Name string `json:"name"`
InputSchema map[string]any `json:"input_schema"`
Description *string `json:"description,omitempty"`
OutputSchema map[string]any `json:"output_schema,omitempty"`
}

166
workflow/definition_test.go Normal file
View File

@@ -0,0 +1,166 @@
package workflow
import (
"encoding/json"
"testing"
)
func TestCodeDefinition_RoundTrip(t *testing.T) {
raw := `{
"input_schema": {"type": "object", "properties": {"prompt": {"type": "string"}}},
"output_schema": {"type": "object", "properties": {"result": {"type": "string"}}},
"signals": [
{"name": "cancel", "input_schema": {"type": "object"}, "description": "Cancel the workflow"}
],
"queries": [
{"name": "status", "input_schema": {"type": "object"}, "description": "Get status", "output_schema": {"type": "string"}}
],
"updates": [
{"name": "set_priority", "input_schema": {"type": "object", "properties": {"level": {"type": "integer"}}}, "description": "Set priority", "output_schema": null}
],
"enforce_determinism": true,
"execution_timeout": 3600.5
}`
var def CodeDefinition
if err := json.Unmarshal([]byte(raw), &def); err != nil {
t.Fatal(err)
}
if def.InputSchema == nil {
t.Fatal("InputSchema is nil")
}
if def.OutputSchema == nil {
t.Fatal("OutputSchema is nil")
}
if len(def.Signals) != 1 {
t.Fatalf("expected 1 signal, got %d", len(def.Signals))
}
if def.Signals[0].Name != "cancel" {
t.Errorf("signal name = %q, want cancel", def.Signals[0].Name)
}
if def.Signals[0].Description == nil || *def.Signals[0].Description != "Cancel the workflow" {
t.Errorf("signal description wrong")
}
if len(def.Queries) != 1 {
t.Fatalf("expected 1 query, got %d", len(def.Queries))
}
if def.Queries[0].Name != "status" {
t.Errorf("query name = %q, want status", def.Queries[0].Name)
}
if def.Queries[0].OutputSchema == nil {
t.Error("query OutputSchema is nil, expected non-nil")
}
if len(def.Updates) != 1 {
t.Fatalf("expected 1 update, got %d", len(def.Updates))
}
if def.Updates[0].Name != "set_priority" {
t.Errorf("update name = %q, want set_priority", def.Updates[0].Name)
}
if def.EnforceDeterminism != true {
t.Error("EnforceDeterminism should be true")
}
if def.ExecutionTimeout == nil || *def.ExecutionTimeout != 3600.5 {
t.Errorf("ExecutionTimeout = %v, want 3600.5", def.ExecutionTimeout)
}
// Re-marshal and verify round-trip
out, err := json.Marshal(def)
if err != nil {
t.Fatal(err)
}
var def2 CodeDefinition
if err := json.Unmarshal(out, &def2); err != nil {
t.Fatal(err)
}
if len(def2.Signals) != 1 || def2.Signals[0].Name != "cancel" {
t.Error("round-trip failed for signals")
}
if def2.EnforceDeterminism != true {
t.Error("round-trip failed for enforce_determinism")
}
}
func TestCodeDefinition_MinimalFields(t *testing.T) {
raw := `{"input_schema": {"type": "object"}}`
var def CodeDefinition
if err := json.Unmarshal([]byte(raw), &def); err != nil {
t.Fatal(err)
}
if def.InputSchema == nil {
t.Fatal("InputSchema is nil")
}
if def.OutputSchema != nil {
t.Errorf("OutputSchema should be nil, got %v", def.OutputSchema)
}
if def.Signals != nil {
t.Errorf("Signals should be nil, got %v", def.Signals)
}
if def.EnforceDeterminism != false {
t.Error("EnforceDeterminism should default to false")
}
if def.ExecutionTimeout != nil {
t.Errorf("ExecutionTimeout should be nil, got %v", def.ExecutionTimeout)
}
}
func TestRegistration_NewFields(t *testing.T) {
raw := `{
"id": "reg-1",
"workflow_id": "wf-1",
"task_queue": "legacy-queue",
"deployment_id": "dep-abc",
"compatible_with_chat_assistant": true,
"definition": {
"input_schema": {"type": "object"},
"enforce_determinism": false
},
"created_at": "2026-04-01T00:00:00Z",
"updated_at": "2026-04-02T00:00:00Z"
}`
var reg Registration
if err := json.Unmarshal([]byte(raw), &reg); err != nil {
t.Fatal(err)
}
if reg.ID != "reg-1" {
t.Errorf("ID = %q", reg.ID)
}
if reg.DeploymentID == nil || *reg.DeploymentID != "dep-abc" {
t.Errorf("DeploymentID = %v, want dep-abc", reg.DeploymentID)
}
if reg.CompatibleWithChatAssistant != true {
t.Error("CompatibleWithChatAssistant should be true")
}
if reg.Definition == nil {
t.Fatal("Definition is nil")
}
if reg.Definition.InputSchema == nil {
t.Error("Definition.InputSchema is nil")
}
// TaskQueue still works for backward compat
if reg.TaskQueue != "legacy-queue" {
t.Errorf("TaskQueue = %q, want legacy-queue", reg.TaskQueue)
}
}
func TestRegistration_NullDeploymentID(t *testing.T) {
raw := `{
"id": "reg-2",
"workflow_id": "wf-2",
"task_queue": "q",
"definition": {"input_schema": {"type": "object"}}
}`
var reg Registration
if err := json.Unmarshal([]byte(raw), &reg); err != nil {
t.Fatal(err)
}
if reg.DeploymentID != nil {
t.Errorf("DeploymentID should be nil, got %v", reg.DeploymentID)
}
if reg.CompatibleWithChatAssistant != false {
t.Error("CompatibleWithChatAssistant should default to false")
}
}

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.

View File

@@ -2,8 +2,12 @@ package workflow
// Registration represents a workflow registration.
type Registration struct {
ID string `json:"id"`
WorkflowID string `json:"workflow_id"`
ID string `json:"id"`
WorkflowID string `json:"workflow_id"`
Definition *CodeDefinition `json:"definition,omitempty"`
DeploymentID *string `json:"deployment_id,omitempty"`
CompatibleWithChatAssistant bool `json:"compatible_with_chat_assistant,omitempty"`
// Deprecated: use DeploymentID instead. Will be removed in a future release.
TaskQueue string `json:"task_queue"`
Workflow *Workflow `json:"workflow,omitempty"`
CreatedAt string `json:"created_at"`
@@ -35,10 +39,3 @@ type RegistrationGetParams struct {
WithWorkflow *bool
IncludeShared *bool
}
// WorkerInfo holds information about the current worker.
type WorkerInfo struct {
SchedulerURL string `json:"scheduler_url"`
Namespace string `json:"namespace"`
TLS bool `json:"tls"`
}

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")
}
}

View File

@@ -6,7 +6,11 @@ import (
"github.com/VikingOwl91/mistral-go-sdk/workflow"
)
// GetWorkflowWorkerInfo retrieves information about the current worker.
// 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 {

View File

@@ -10,12 +10,12 @@ import (
func TestGetWorkflowWorkerInfo_Success(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/v1/workflows/workers/whoami" {
t.Errorf("got path %s", r.URL.Path)
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": "https://scheduler.mistral.ai",
"namespace": "default",
_ = json.NewEncoder(w).Encode(map[string]any{
"scheduler_url": "scheduler.example.com:7233",
"namespace": "tenant-2",
"tls": true,
})
}))
@@ -26,10 +26,27 @@ func TestGetWorkflowWorkerInfo_Success(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if info.Namespace != "default" {
t.Errorf("got namespace %q", info.Namespace)
}
if !info.TLS {
t.Error("expected tls=true")
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")
}
}